diff --git a/erpnext/healthcare/page/patient_history/patient_history.js b/erpnext/healthcare/page/patient_history/patient_history.js index 87fe7edd297..b335ada8716 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.js +++ b/erpnext/healthcare/page/patient_history/patient_history.js @@ -275,7 +275,7 @@ var show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) { datasets.push({name: "Heart Rate / Pulse", values: pulse, chartType:'line'}); datasets.push({name: "Respiratory Rate", values: respiratory_rate, chartType:'line'}); } - new Chart( ".patient_vital_charts", { + new frappe.Chart( ".patient_vital_charts", { data: { labels: labels, datasets: datasets diff --git a/erpnext/regional/doctype/datev_settings/__init__.py b/erpnext/regional/doctype/datev_settings/__init__.py new file mode 100644 index 00000000000..e69de29bb2d 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 00000000000..69747b0b89e --- /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 00000000000..6860ed3fdae --- /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 00000000000..cff5bba58fd --- /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 00000000000..0271329f4d2 --- /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 diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 50aed084aba..ee8735fb1ff 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 @@ -17,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 Company {}.').format(filters.get('company'))) def get_columns(): """Return the list of columns that will be shown in query report.""" @@ -158,13 +163,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) + frappe.get_value("DATEV Settings", filters.get("company"), "consultant_number") or "", + "", + # L = Tax client number (Mandantennummer) + 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) + 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 +400,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 +419,7 @@ def get_datev_csv(data): columns=columns ) + return header + b'\r\n' + data @frappe.whitelist() def download_datev_csv(filters=None): @@ -359,15 +437,9 @@ 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) - 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'