From 59cee369ce5759b9a5f87490228e0bf0aa1a34c2 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Mon, 8 Jul 2019 14:48:54 +0200 Subject: [PATCH 1/7] add header row --- erpnext/regional/report/datev/datev.py | 90 ++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 11 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 50aed084ab..00c5177dea 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -8,6 +8,7 @@ Provide a report and downloadable CSV according to the German DATEV format. all required columns. Used to import the data into the DATEV Software. """ from __future__ import unicode_literals +import datetime import json from six import string_types import frappe @@ -158,13 +159,84 @@ def get_gl_entries(filters, as_dict): return gl_entries -def get_datev_csv(data): +def get_datev_csv(data, filters): """ Fill in missing columns and return a CSV in DATEV Format. + 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 """ + header = [ + # A = DATEV format + # DTVF = created by DATEV software, + # EXTF = created by other software + "EXTF", + # B = version of the DATEV format + # 141 = 1.41, + # 510 = 5.10, + # 720 = 7.20 + "510", + # C = Data category + # 21 = Transaction batch (Buchungsstapel), + # 67 = Buchungstextkonstanten, + # 16 = Debitors/Creditors, + # 20 = Account names (Kontenbeschriftungen) + "21", + # D = Format name + # Buchungsstapel, + # Buchungstextkonstanten, + # Debitoren/Kreditoren, + # Kontenbeschriftungen + "Buchungsstapel", + # E = Format version (regarding format name) + "", + # F = Generated on + datetime.datetime.now().strftime("%Y%m%d"), + # G = Imported on -- stays empty + "", + # H = Origin (SV = other (?), RE = KARE) + "SV", + # I = Exported by + frappe.session.user, + # J = Imported by -- stays empty + "", + # K = Tax consultant number (Beraternummer) + # TODO: frappe.get_value("Company", filters.get("company"), "tax_consultant_number"), + "", + # L = Tax client number (Mandantennummer) + # TODO: frappe.get_value("Company", filters.get("company"), "tax_client_number"), + "", + # M = Start of the fiscal year (Wirtschaftsjahresbeginn) + "", + # N = Length of account numbers (Sachkontenlänge) + "4", + # O = Transaction batch start date (YYYYMMDD) + frappe.utils.formatdate(filters.get('from_date'), "yyyyMMdd"), + # P = Transaction batch end date (YYYYMMDD) + frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd"), + # Q = Description (for example, "January - February 2019 Transactions") + "{} - {} Buchungsstapel".format( + frappe.utils.formatdate(filters.get('from_date'), "MMMM yyyy"), + frappe.utils.formatdate(filters.get('to_date'), "MMMM yyyy") + ), + # R = Diktatkürzel + "", + # S = Buchungstyp + # 1 = Transaction batch (Buchungsstapel), + # 2 = Annual financial statement (Jahresabschluss) + "1", + # T = Rechnungslegungszweck + "", + # U = Festschreibung + "", + # V = Kontoführungs-Währungskennzeichen des Geldkontos + frappe.get_value("Company", filters.get("company"), "default_currency") + ] columns = [ # All possible columns must tbe listed here, because DATEV requires them to # be present in the CSV. @@ -324,9 +396,10 @@ def get_datev_csv(data): data_df = pd.DataFrame.from_records(data) result = empty_df.append(data_df) - result["Belegdatum"] = pd.to_datetime(result["Belegdatum"]) + result['Belegdatum'] = pd.to_datetime(result['Belegdatum']) - return result.to_csv( + header = ';'.join(header).encode('latin_1') + data = result.to_csv( sep=b';', # European decimal seperator decimal=',', @@ -342,6 +415,7 @@ def get_datev_csv(data): columns=columns ) + return header + b'\r\n' + data @frappe.whitelist() def download_datev_csv(filters=None): @@ -362,12 +436,6 @@ def download_datev_csv(filters=None): validate_filters(filters) data = get_gl_entries(filters, as_dict=1) - filename = 'DATEV_Buchungsstapel_{}-{}_bis_{}'.format( - filters.get('company'), - filters.get('from_date'), - filters.get('to_date') - ) - - frappe.response['result'] = get_datev_csv(data) - frappe.response['doctype'] = filename + frappe.response['result'] = get_datev_csv(data, filters) + frappe.response['doctype'] = 'EXTF_Buchungsstapel' frappe.response['type'] = 'csv' From 02522a0df527bba0b4c7dfa17a02f00fe53c4b14 Mon Sep 17 00:00:00 2001 From: alyf-de Date: Wed, 14 Aug 2019 00:06:21 +0200 Subject: [PATCH 2/7] Add DATEV Settings --- .../doctype/datev_settings/__init__.py | 0 .../doctype/datev_settings/datev_settings.js | 8 ++ .../datev_settings/datev_settings.json | 105 ++++++++++++++++++ .../doctype/datev_settings/datev_settings.py | 10 ++ .../datev_settings/test_datev_settings.py | 10 ++ 5 files changed, 133 insertions(+) create mode 100644 erpnext/regional/doctype/datev_settings/__init__.py create mode 100644 erpnext/regional/doctype/datev_settings/datev_settings.js create mode 100644 erpnext/regional/doctype/datev_settings/datev_settings.json create mode 100644 erpnext/regional/doctype/datev_settings/datev_settings.py create mode 100644 erpnext/regional/doctype/datev_settings/test_datev_settings.py diff --git a/erpnext/regional/doctype/datev_settings/__init__.py b/erpnext/regional/doctype/datev_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/datev_settings/datev_settings.js b/erpnext/regional/doctype/datev_settings/datev_settings.js new file mode 100644 index 0000000000..69747b0b89 --- /dev/null +++ b/erpnext/regional/doctype/datev_settings/datev_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('DATEV Settings', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/regional/doctype/datev_settings/datev_settings.json b/erpnext/regional/doctype/datev_settings/datev_settings.json new file mode 100644 index 0000000000..6860ed3fda --- /dev/null +++ b/erpnext/regional/doctype/datev_settings/datev_settings.json @@ -0,0 +1,105 @@ +{ + "autoname": "field:client", + "creation": "2019-08-13 23:56:34.259906", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "client", + "column_break_2", + "client_number", + "section_break_4", + "consultant", + "column_break_6", + "consultant_number" + ], + "fields": [ + { + "fieldname": "client", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Client", + "options": "Company", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "client_number", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Client ID", + "reqd": 1 + }, + { + "fieldname": "consultant", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Consultant", + "options": "Supplier" + }, + { + "fieldname": "consultant_number", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Consultant ID", + "reqd": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + } + ], + "modified": "2019-08-14 00:03:26.616460", + "modified_by": "Administrator", + "module": "Regional", + "name": "DATEV Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/datev_settings/datev_settings.py b/erpnext/regional/doctype/datev_settings/datev_settings.py new file mode 100644 index 0000000000..cff5bba58f --- /dev/null +++ b/erpnext/regional/doctype/datev_settings/datev_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class DATEVSettings(Document): + pass diff --git a/erpnext/regional/doctype/datev_settings/test_datev_settings.py b/erpnext/regional/doctype/datev_settings/test_datev_settings.py new file mode 100644 index 0000000000..0271329f4d --- /dev/null +++ b/erpnext/regional/doctype/datev_settings/test_datev_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestDATEVSettings(unittest.TestCase): + pass From df4edaa0fd9e72a246fb0586a795a1f3a922414e Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Wed, 14 Aug 2019 00:13:31 +0200 Subject: [PATCH 3/7] Use values from DATEV Settings --- erpnext/regional/report/datev/datev.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 00c5177dea..64e9753a67 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -206,10 +206,10 @@ def get_datev_csv(data, filters): # J = Imported by -- stays empty "", # K = Tax consultant number (Beraternummer) - # TODO: frappe.get_value("Company", filters.get("company"), "tax_consultant_number"), + frappe.get_value("DATEV Settings", filters.get("company"), "consultant_number") or "", "", # L = Tax client number (Mandantennummer) - # TODO: frappe.get_value("Company", filters.get("company"), "tax_client_number"), + frappe.get_value("DATEV Settings", filters.get("company"), "client_number") or "", "", # M = Start of the fiscal year (Wirtschaftsjahresbeginn) "", From 5fe44f906dc1f4e30ccb47bbc37f034c8b4b62fe Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Wed, 14 Aug 2019 00:31:00 +0200 Subject: [PATCH 4/7] Add fiscal year --- erpnext/regional/report/datev/datev.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 64e9753a67..bbc7ad2694 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -212,7 +212,7 @@ def get_datev_csv(data, filters): frappe.get_value("DATEV Settings", filters.get("company"), "client_number") or "", "", # M = Start of the fiscal year (Wirtschaftsjahresbeginn) - "", + frappe.utils.formatdate(frappe.defaults.get_user_default("year_start_date"), "yyyyMMdd"), # N = Length of account numbers (Sachkontenlänge) "4", # O = Transaction batch start date (YYYYMMDD) From c29f594fc169d469d606c7eecaa78b37627511e2 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Wed, 14 Aug 2019 00:39:59 +0200 Subject: [PATCH 5/7] Check existance of DATEV Settings --- erpnext/regional/report/datev/datev.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index bbc7ad2694..07abbe23c5 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -18,24 +18,28 @@ import pandas as pd def execute(filters=None): """Entry point for frappe.""" - validate_filters(filters) + validate(filters) result = get_gl_entries(filters, as_dict=0) columns = get_columns() return columns, result -def validate_filters(filters): - """Make sure all mandatory filters are present.""" +def validate(filters): + """Make sure all mandatory filters and settings are present.""" if not filters.get('company'): - frappe.throw(_('{0} is mandatory').format(_('Company'))) + frappe.throw(_('Company is a mandatory filter.')) if not filters.get('from_date'): - frappe.throw(_('{0} is mandatory').format(_('From Date'))) + frappe.throw(_('From Date is a mandatory filter.')) if not filters.get('to_date'): - frappe.throw(_('{0} is mandatory').format(_('To Date'))) + frappe.throw(_('To Date is a mandatory filter.')) + try: + frappe.get_doc('DATEV Settings', filters.get('company')): + except frappe.DoesNotExistError: + frappe.throw(_('Please create DATEV Settings for your company.')) def get_columns(): """Return the list of columns that will be shown in query report.""" @@ -433,7 +437,7 @@ def download_datev_csv(filters=None): if isinstance(filters, string_types): filters = json.loads(filters) - validate_filters(filters) + validate(filters) data = get_gl_entries(filters, as_dict=1) frappe.response['result'] = get_datev_csv(data, filters) From e6013fb02d3b0c59223d3a38e1dba3975a967b4a Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Wed, 14 Aug 2019 01:15:23 +0200 Subject: [PATCH 6/7] improve error messages --- erpnext/regional/report/datev/datev.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 07abbe23c5..4eb7ad30e8 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -28,18 +28,18 @@ def execute(filters=None): def validate(filters): """Make sure all mandatory filters and settings are present.""" if not filters.get('company'): - frappe.throw(_('Company is a mandatory filter.')) + frappe.throw(_('Company is a mandatory filter.')) if not filters.get('from_date'): - frappe.throw(_('From Date is a mandatory filter.')) + frappe.throw(_('From Date is a mandatory filter.')) if not filters.get('to_date'): - frappe.throw(_('To Date is a mandatory filter.')) + frappe.throw(_('To Date is a mandatory filter.')) try: frappe.get_doc('DATEV Settings', filters.get('company')): except frappe.DoesNotExistError: - frappe.throw(_('Please create DATEV Settings for your company.')) + frappe.throw(_('Please create DATEV Settings for Company {}.').format(filters.get('company'))) def get_columns(): """Return the list of columns that will be shown in query report.""" From 17166a5662ead8f29500b7ce7c150e9ae059b67f Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Wed, 14 Aug 2019 01:22:29 +0200 Subject: [PATCH 7/7] fix typo --- erpnext/regional/report/datev/datev.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 4eb7ad30e8..ee8735fb1f 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -37,7 +37,7 @@ def validate(filters): frappe.throw(_('To Date is a mandatory filter.')) try: - frappe.get_doc('DATEV Settings', filters.get('company')): + frappe.get_doc('DATEV Settings', filters.get('company')) except frappe.DoesNotExistError: frappe.throw(_('Please create DATEV Settings for Company {}.').format(filters.get('company')))