2020-07-29 15:14:23 +00:00
|
|
|
import datetime
|
|
|
|
import zipfile
|
2020-09-11 16:57:58 +00:00
|
|
|
from csv import QUOTE_NONNUMERIC
|
2022-01-27 14:38:05 +00:00
|
|
|
from io import BytesIO
|
2020-09-11 16:57:58 +00:00
|
|
|
|
2020-07-29 15:14:23 +00:00
|
|
|
import frappe
|
|
|
|
import pandas as pd
|
|
|
|
from frappe import _
|
2021-09-02 11:14:59 +00:00
|
|
|
|
2020-07-29 15:14:23 +00:00
|
|
|
from .datev_constants import DataCategory
|
|
|
|
|
|
|
|
|
|
|
|
def get_datev_csv(data, filters, csv_class):
|
2020-09-11 16:57:58 +00:00
|
|
|
"""
|
|
|
|
Fill in missing columns and return a CSV in DATEV Format.
|
2020-07-29 15:14:23 +00:00
|
|
|
|
|
|
|
For automatic processing, DATEV requires the first line of the CSV file to
|
|
|
|
hold meta data such as the length of account numbers oder the category of
|
|
|
|
the data.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
data -- array of dictionaries
|
|
|
|
filters -- dict
|
|
|
|
csv_class -- defines DATA_CATEGORY, FORMAT_NAME and COLUMNS
|
|
|
|
"""
|
|
|
|
empty_df = pd.DataFrame(columns=csv_class.COLUMNS)
|
|
|
|
data_df = pd.DataFrame.from_records(data)
|
|
|
|
result = empty_df.append(data_df, sort=True)
|
|
|
|
|
|
|
|
if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS:
|
|
|
|
result["Belegdatum"] = pd.to_datetime(result["Belegdatum"])
|
|
|
|
|
2021-09-01 13:57:54 +00:00
|
|
|
result["Beleginfo - Inhalt 6"] = pd.to_datetime(result["Beleginfo - Inhalt 6"])
|
|
|
|
result["Beleginfo - Inhalt 6"] = result["Beleginfo - Inhalt 6"].dt.strftime("%d%m%Y")
|
|
|
|
|
|
|
|
result["Fälligkeit"] = pd.to_datetime(result["Fälligkeit"])
|
|
|
|
result["Fälligkeit"] = result["Fälligkeit"].dt.strftime("%d%m%y")
|
|
|
|
|
|
|
|
result.sort_values(by="Belegdatum", inplace=True, kind="stable", ignore_index=True)
|
|
|
|
|
2020-07-29 15:14:23 +00:00
|
|
|
if csv_class.DATA_CATEGORY == DataCategory.ACCOUNT_NAMES:
|
|
|
|
result["Sprach-ID"] = "de-DE"
|
|
|
|
|
|
|
|
data = result.to_csv(
|
|
|
|
# Reason for str(';'): https://github.com/pandas-dev/pandas/issues/6035
|
|
|
|
sep=";",
|
|
|
|
# European decimal seperator
|
|
|
|
decimal=",",
|
|
|
|
# Windows "ANSI" encoding
|
|
|
|
encoding="latin_1",
|
|
|
|
# format date as DDMM
|
|
|
|
date_format="%d%m",
|
|
|
|
# Windows line terminator
|
|
|
|
line_terminator="\r\n",
|
|
|
|
# Do not number rows
|
|
|
|
index=False,
|
|
|
|
# Use all columns defined above
|
|
|
|
columns=csv_class.COLUMNS,
|
|
|
|
# Quote most fields, even currency values with "," separator
|
|
|
|
quoting=QUOTE_NONNUMERIC,
|
|
|
|
)
|
|
|
|
|
2021-05-23 15:43:44 +00:00
|
|
|
data = data.encode("latin_1", errors="replace")
|
2020-07-29 15:14:23 +00:00
|
|
|
|
|
|
|
header = get_header(filters, csv_class)
|
2021-04-07 18:04:38 +00:00
|
|
|
header = ";".join(header).encode("latin_1", errors="replace")
|
2020-07-29 15:14:23 +00:00
|
|
|
|
|
|
|
# 1st Row: Header with meta data
|
|
|
|
# 2nd Row: Data heading (Überschrift der Nutzdaten), included in `data` here.
|
|
|
|
# 3rd - nth Row: Data (Nutzdaten)
|
|
|
|
return header + b"\r\n" + data
|
|
|
|
|
|
|
|
|
|
|
|
def get_header(filters, csv_class):
|
|
|
|
description = filters.get("voucher_type", csv_class.FORMAT_NAME)
|
|
|
|
company = filters.get("company")
|
|
|
|
datev_settings = frappe.get_doc("DATEV Settings", {"client": company})
|
|
|
|
default_currency = frappe.get_value("Company", company, "default_currency")
|
|
|
|
coa = frappe.get_value("Company", company, "chart_of_accounts")
|
|
|
|
coa_short_code = "04" if "SKR04" in coa else ("03" if "SKR03" in coa else "")
|
|
|
|
|
|
|
|
header = [
|
|
|
|
# DATEV format
|
|
|
|
# "DTVF" = created by DATEV software,
|
|
|
|
# "EXTF" = created by other software
|
|
|
|
'"EXTF"',
|
|
|
|
# version of the DATEV format
|
|
|
|
# 141 = 1.41,
|
|
|
|
# 510 = 5.10,
|
|
|
|
# 720 = 7.20
|
|
|
|
"700",
|
|
|
|
csv_class.DATA_CATEGORY,
|
|
|
|
'"%s"' % csv_class.FORMAT_NAME,
|
|
|
|
# Format version (regarding format name)
|
|
|
|
csv_class.FORMAT_VERSION,
|
|
|
|
# Generated on
|
2020-09-11 16:57:58 +00:00
|
|
|
datetime.datetime.now().strftime("%Y%m%d%H%M%S") + "000",
|
2020-07-29 15:14:23 +00:00
|
|
|
# Imported on -- stays empty
|
|
|
|
"",
|
|
|
|
# Origin. Any two symbols, will be replaced by "SV" on import.
|
|
|
|
'"EN"',
|
|
|
|
# I = Exported by
|
|
|
|
'"%s"' % frappe.session.user,
|
|
|
|
# J = Imported by -- stays empty
|
|
|
|
"",
|
|
|
|
# K = Tax consultant number (Beraternummer)
|
|
|
|
datev_settings.get("consultant_number", "0000000"),
|
|
|
|
# L = Tax client number (Mandantennummer)
|
|
|
|
datev_settings.get("client_number", "00000"),
|
|
|
|
# M = Start of the fiscal year (Wirtschaftsjahresbeginn)
|
2020-11-05 15:57:23 +00:00
|
|
|
frappe.utils.formatdate(filters.get("fiscal_year_start"), "yyyyMMdd"),
|
2020-07-29 15:14:23 +00:00
|
|
|
# N = Length of account numbers (Sachkontenlänge)
|
2020-11-15 04:04:05 +00:00
|
|
|
str(filters.get("account_number_length", 4)),
|
2020-07-29 15:14:23 +00:00
|
|
|
# O = Transaction batch start date (YYYYMMDD)
|
|
|
|
frappe.utils.formatdate(filters.get("from_date"), "yyyyMMdd")
|
|
|
|
if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS
|
|
|
|
else "",
|
|
|
|
# P = Transaction batch end date (YYYYMMDD)
|
|
|
|
frappe.utils.formatdate(filters.get("to_date"), "yyyyMMdd")
|
|
|
|
if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS
|
|
|
|
else "",
|
|
|
|
# Q = Description (for example, "Sales Invoice") Max. 30 chars
|
|
|
|
'"{}"'.format(_(description)) if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else "",
|
|
|
|
# R = Diktatkürzel
|
|
|
|
"",
|
|
|
|
# S = Buchungstyp
|
|
|
|
# 1 = Transaction batch (Finanzbuchführung),
|
|
|
|
# 2 = Annual financial statement (Jahresabschluss)
|
|
|
|
"1" if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else "",
|
|
|
|
# T = Rechnungslegungszweck
|
|
|
|
# 0 oder leer = vom Rechnungslegungszweck unabhängig
|
|
|
|
# 50 = Handelsrecht
|
|
|
|
# 30 = Steuerrecht
|
|
|
|
# 64 = IFRS
|
|
|
|
# 40 = Kalkulatorik
|
|
|
|
# 11 = Reserviert
|
|
|
|
# 12 = Reserviert
|
|
|
|
"0" if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else "",
|
|
|
|
# U = Festschreibung
|
|
|
|
# TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1"
|
|
|
|
"0",
|
|
|
|
# V = Default currency, for example, "EUR"
|
|
|
|
'"%s"' % default_currency if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else "",
|
|
|
|
# reserviert
|
|
|
|
"",
|
|
|
|
# Derivatskennzeichen
|
|
|
|
"",
|
|
|
|
# reserviert
|
|
|
|
"",
|
|
|
|
# reserviert
|
|
|
|
"",
|
|
|
|
# SKR
|
|
|
|
'"%s"' % coa_short_code,
|
|
|
|
# Branchen-Lösungs-ID
|
|
|
|
"",
|
|
|
|
# reserviert
|
|
|
|
"",
|
|
|
|
# reserviert
|
|
|
|
"",
|
|
|
|
# Anwendungsinformation (Verarbeitungskennzeichen der abgebenden Anwendung)
|
2022-03-28 13:22:46 +00:00
|
|
|
"",
|
2020-07-29 15:14:23 +00:00
|
|
|
]
|
|
|
|
return header
|
|
|
|
|
|
|
|
|
2020-11-09 14:47:56 +00:00
|
|
|
def zip_and_download(zip_filename, csv_files):
|
2020-09-11 16:57:58 +00:00
|
|
|
"""
|
|
|
|
Put CSV files in a zip archive and send that to the client.
|
2020-07-29 15:14:23 +00:00
|
|
|
|
|
|
|
Params:
|
2020-11-09 14:47:56 +00:00
|
|
|
zip_filename Name of the zip file
|
|
|
|
csv_files list of dicts [{'file_name': 'my_file.csv', 'csv_data': 'comma,separated,values'}]
|
2020-07-29 15:14:23 +00:00
|
|
|
"""
|
|
|
|
zip_buffer = BytesIO()
|
|
|
|
|
2020-11-09 14:47:56 +00:00
|
|
|
zip_file = zipfile.ZipFile(zip_buffer, mode="w", compression=zipfile.ZIP_DEFLATED)
|
|
|
|
for csv_file in csv_files:
|
|
|
|
zip_file.writestr(csv_file.get("file_name"), csv_file.get("csv_data"))
|
|
|
|
|
|
|
|
zip_file.close()
|
2020-07-29 15:14:23 +00:00
|
|
|
|
|
|
|
frappe.response["filecontent"] = zip_buffer.getvalue()
|
2020-11-09 14:47:56 +00:00
|
|
|
frappe.response["filename"] = zip_filename
|
2020-07-29 15:14:23 +00:00
|
|
|
frappe.response["type"] = "binary"
|