From 9c923ae418ec3d7d1a7ac91ba3f48d18d63d0965 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Thu, 25 Apr 2019 01:09:56 +0200 Subject: [PATCH 01/21] feat(regional): Report for german tax consultants (DATEV) --- erpnext/regional/report/datev/__init__.py | 0 erpnext/regional/report/datev/datev.js | 26 ++++ erpnext/regional/report/datev/datev.json | 29 +++++ erpnext/regional/report/datev/datev.py | 137 ++++++++++++++++++++++ 4 files changed, 192 insertions(+) create mode 100644 erpnext/regional/report/datev/__init__.py create mode 100644 erpnext/regional/report/datev/datev.js create mode 100644 erpnext/regional/report/datev/datev.json create mode 100644 erpnext/regional/report/datev/datev.py diff --git a/erpnext/regional/report/datev/__init__.py b/erpnext/regional/report/datev/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/report/datev/datev.js b/erpnext/regional/report/datev/datev.js new file mode 100644 index 00000000000..737a84aef24 --- /dev/null +++ b/erpnext/regional/report/datev/datev.js @@ -0,0 +1,26 @@ +frappe.query_reports["DATEV"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname": "fiscal_year", + "label": __("Fiscal Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "reqd": 1 + } + ], + onload: function(query_report) { + query_report.export_report = function() { + const filters = JSON.stringify(query_report.get_values()); + window.open(`/api/method/erpnext.regional.report.datev.datev.download_datev_csv?filters=${filters}`); + }; + } +}; diff --git a/erpnext/regional/report/datev/datev.json b/erpnext/regional/report/datev/datev.json new file mode 100644 index 00000000000..d4f44b6fbb6 --- /dev/null +++ b/erpnext/regional/report/datev/datev.json @@ -0,0 +1,29 @@ +{ + "add_total_row": 0, + "apply_user_permissions": 0, + "creation": "2019-04-24 08:45:16.650129", + "disabled": 0, + "icon": "octicon octicon-repo-pull", + "color": "#96cf41", + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "module": "Regional", + "name": "DATEV", + "owner": "Administrator", + "ref_doctype": "GL Entry", + "report_name": "DATEV", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Auditor" + } + ] +} diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py new file mode 100644 index 00000000000..268de2874d7 --- /dev/null +++ b/erpnext/regional/report/datev/datev.py @@ -0,0 +1,137 @@ +# coding: utf-8 +from __future__ import unicode_literals +import json +from six import string_types +import frappe +from frappe.utils import format_datetime +from frappe import _ + + +def execute(filters=None): + validate_filters(filters) + result = get_gl_entries(filters, as_dict=0) + columns = get_columns() + + return columns, result + + +def validate_filters(filters): + if not filters.get('company'): + frappe.throw(_('{0} is mandatory').format(_('Company'))) + + if not filters.get('fiscal_year'): + frappe.throw(_('{0} is mandatory').format(_('Fiscal Year'))) + + +def get_columns(): + columns = [ + { + "label": "Umsatz (ohne Soll/Haben-Kz)", + "fieldname": "umsatz", + "fieldtype": "Currency", + }, + { + "label": "Soll/Haben-Kennzeichen", + "fieldname": "soll_haben_kennzeichen", + "fieldtype": "Data", + }, + { + "label": "Kontonummer", + "fieldname": "kontonummer", + "fieldtype": "Data", + }, + { + "label": "Gegenkonto (ohne BU-Schlüssel)", + "fieldname": "gegenkonto_nummer", + "fieldtype": "Data", + }, + { + "label": "Belegdatum", + "fieldname": "belegdatum", + "fieldtype": "Date", + } + ] + + return columns + + +def get_gl_entries(filters, as_dict): + gl_entries = frappe.db.sql(""" + select + + case gl.debit when 0 then gl.credit else gl.debit end as Umsatz, + case gl.debit when 0 then 'H' else 'S' end as Kennzeichen, + coalesce(acc.account_number, acc_pa.account_number) as Kontonummer, + coalesce(acc_against.account_number, acc_against_pa.account_number) as Gegenkonto, + gl.posting_date as Belegdatum + + from `tabGL Entry` gl + + /* Statistisches Konto (Debitoren/Kreditoren) */ + left join `tabParty Account` pa + on gl.against = pa.parent + + /* Kontonummer */ + left join `tabAccount` acc + on gl.account = acc.name + + /* Gegenkonto-Nummer */ + left join `tabAccount` acc_against + on gl.against = acc_against.name + + /* Statistische Kontonummer */ + left join `tabAccount` acc_pa + on pa.account = acc_pa.name + + /* Statistische Gegenkonto-Nummer */ + left join `tabAccount` acc_against_pa + on pa.account = acc_against_pa.name + + where gl.company=%(company)s and gl.fiscal_year=%(fiscal_year)s + order by 'Belegdatum:Date', voucher_no""", filters, as_dict=as_dict) + + return gl_entries + + +def get_datev_csv(data): + title_row = [ + "Umsatz (ohne Soll/Haben-Kz)", + "Soll/Haben-Kennzeichen", + "Kontonummer", + "Gegenkonto (ohne BU-Schlüssel)", + "Belegdatum" + ] + + result = ['"' + '";"'.join(title_row) + '"'] + result += [ + ';'.join( + [ + '{:.2f}'.format(d.get('Umsatz')).replace('.', ','), + '"{}"'.format(d.get('Kennzeichen')), + d.get('Kontonummer'), + # Can be empty, if there are no debtor / creditor accounts + d.get('Gegenkonto') or '', + format_datetime(d.get('Belegdatum'), 'ddMMyyyy') + ] + ) for d in data + ] + + return b'\r\n'.join(result).encode(encoding='latin_1') + + +@frappe.whitelist() +def download_datev_csv(filters=None): + if isinstance(filters, string_types): + filters = json.loads(filters) + + validate_filters(filters) + data = get_gl_entries(filters, as_dict=1) + + filename = 'DATEV_Buchungsstapel_{}-{}'.format( + filters.get('company'), + filters.get('fiscal_year') + ) + + frappe.response['result'] = get_datev_csv(data) + frappe.response['doctype'] = filename + frappe.response['type'] = 'csv' From e5f7af9e9f1703b53159f046526470a2439f49f0 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Thu, 9 May 2019 03:54:49 +0200 Subject: [PATCH 02/21] Use pandas, more columns, filter by date --- erpnext/regional/report/datev/datev.js | 14 ++- erpnext/regional/report/datev/datev.py | 149 ++++++++++++++++++++----- 2 files changed, 128 insertions(+), 35 deletions(-) diff --git a/erpnext/regional/report/datev/datev.js b/erpnext/regional/report/datev/datev.js index 737a84aef24..a556199a046 100644 --- a/erpnext/regional/report/datev/datev.js +++ b/erpnext/regional/report/datev/datev.js @@ -9,11 +9,15 @@ frappe.query_reports["DATEV"] = { "reqd": 1 }, { - "fieldname": "fiscal_year", - "label": __("Fiscal Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": frappe.defaults.get_user_default("fiscal_year"), + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "reqd": 1 + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", "reqd": 1 } ], diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 268de2874d7..5209f34528e 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -5,6 +5,7 @@ from six import string_types import frappe from frappe.utils import format_datetime from frappe import _ +import pandas as pd def execute(filters=None): @@ -19,35 +20,38 @@ def validate_filters(filters): if not filters.get('company'): frappe.throw(_('{0} is mandatory').format(_('Company'))) - if not filters.get('fiscal_year'): - frappe.throw(_('{0} is mandatory').format(_('Fiscal Year'))) + if not filters.get('from_date'): + frappe.throw(_('{0} is mandatory').format(_('From Date'))) + + if not filters.get('to_date'): + frappe.throw(_('{0} is mandatory').format(_('To Date'))) def get_columns(): columns = [ { "label": "Umsatz (ohne Soll/Haben-Kz)", - "fieldname": "umsatz", + "fieldname": "Umsatz (ohne Soll/Haben-Kz)", "fieldtype": "Currency", }, { "label": "Soll/Haben-Kennzeichen", - "fieldname": "soll_haben_kennzeichen", + "fieldname": "Soll/Haben-Kennzeichen", "fieldtype": "Data", }, { "label": "Kontonummer", - "fieldname": "kontonummer", + "fieldname": "Kontonummer", "fieldtype": "Data", }, { "label": "Gegenkonto (ohne BU-Schlüssel)", - "fieldname": "gegenkonto_nummer", + "fieldname": "Gegenkonto (ohne BU-Schlüssel)", "fieldtype": "Data", }, { "label": "Belegdatum", - "fieldname": "belegdatum", + "fieldname": "Belegdatum", "fieldtype": "Date", } ] @@ -59,11 +63,11 @@ def get_gl_entries(filters, as_dict): gl_entries = frappe.db.sql(""" select - case gl.debit when 0 then gl.credit else gl.debit end as Umsatz, - case gl.debit when 0 then 'H' else 'S' end as Kennzeichen, - coalesce(acc.account_number, acc_pa.account_number) as Kontonummer, - coalesce(acc_against.account_number, acc_against_pa.account_number) as Gegenkonto, - gl.posting_date as Belegdatum + case gl.debit when 0 then gl.credit else gl.debit end as 'Umsatz (ohne Soll/Haben-Kz)', + case gl.debit when 0 then 'H' else 'S' end as 'Soll/Haben-Kennzeichen', + coalesce(acc.account_number, acc_pa.account_number) as 'Kontonummer', + coalesce(acc_against.account_number, acc_against_pa.account_number) as 'Gegenkonto (ohne BU-Schlüssel)', + gl.posting_date as 'Belegdatum' from `tabGL Entry` gl @@ -87,36 +91,121 @@ def get_gl_entries(filters, as_dict): left join `tabAccount` acc_against_pa on pa.account = acc_against_pa.name - where gl.company=%(company)s and gl.fiscal_year=%(fiscal_year)s - order by 'Belegdatum:Date', voucher_no""", filters, as_dict=as_dict) + where gl.company = %(company)s + and DATE(gl.posting_date) >= %(from_date)s + and DATE(gl.posting_date) <= %(to_date)s + order by 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict) return gl_entries def get_datev_csv(data): - title_row = [ + columns = [ + # Umsatz "Umsatz (ohne Soll/Haben-Kz)", "Soll/Haben-Kennzeichen", + "WKZ Umsatz", + "Kurs", + "Basis-Umsatz", + "WKZ Basis-Umsatz", + # Konto/Gegenkonto "Kontonummer", "Gegenkonto (ohne BU-Schlüssel)", - "Belegdatum" + "BU-Schlüssel", + # Datum + "Belegdatum", + # Belegfelder + "Belegfeld 1", + "Belegfeld 2", + # Weitere Felder + "Skonto", + "Buchungstext", + # OPOS-Informationen + "Postensperre", + "Diverse Adressnummer", + "Geschäftspartnerbank", + "Sachverhalt", + "Zinssperre", + # Digitaler Beleg + "Beleglink", + # Kostenrechnung + "Kost 1 - Kostenstelle", + "Kost 2 - Kostenstelle", + "Kost-Menge", + # Steuerrechnung + "EU-Land u. UStID", + "EU-Steuersatz", + "Abw. Versteuerungsart", + # L+L Sachverhalt + "Sachverhalt L+L", + "FunktionsergänzungL+L", + # Mengenfelder LuF + "Stück", + "Gewicht", + # Forderungsart + "Zahlweise", + "Forderungsart", + "Veranlagungsjahr", + "Zugeordnete Fälligkeit", + # Weitere Felder + "Skontotyp", + # Anzahlungen + "Auftragsnummer", + "Buchungstyp", + "USt-Schlüssel (Anzahlungen)", + "EU-Land (Anzahlungen)", + "Sachverhalt L+L (Anzahlungen)", + "EU-Steuersatz (Anzahlungen)", + "Erlöskonto (Anzahlungen)", + # Stapelinformationen + "Herkunft-Kz", + # Technische Identifikation + "Buchungs GUID", + # Kostenrechnung + "Kost-Datum", + # OPOS-Informationen + "SEPA-Mandatsreferenz", + "Skontosperre", + # Gesellschafter und Sonderbilanzsachverhalt + "Gesellschaftername", + "Beteiligtennummer", + "Identifikationsnummer", + "Zeichnernummer", + # OPOS-Informationen + "Postensperre bis", + # Gesellschafter und Sonderbilanzsachverhalt + "Bezeichnung SoBil-Sachverhalt", + "Kennzeichen SoBil-Buchung", + # Stapelinformationen + "Festschreibung", + # Datum + "Leistungsdatum", + "Datum Zuord. Steuerperiode", + # OPOS-Informationen + "Fälligkeit", + # Konto/Gegenkonto + "Generalumkehr (GU)", + # Steuersatz für Steuerschlüssel + "Steuersatz", + "Land" ] - result = ['"' + '";"'.join(title_row) + '"'] - result += [ - ';'.join( - [ - '{:.2f}'.format(d.get('Umsatz')).replace('.', ','), - '"{}"'.format(d.get('Kennzeichen')), - d.get('Kontonummer'), - # Can be empty, if there are no debtor / creditor accounts - d.get('Gegenkonto') or '', - format_datetime(d.get('Belegdatum'), 'ddMMyyyy') - ] - ) for d in data - ] + empty_df = pd.DataFrame(columns=columns) - return b'\r\n'.join(result).encode(encoding='latin_1') + data_df = pd.DataFrame.from_records(data) + data_df["Belegdatum"] = pd.to_datetime(data_df["Belegdatum"]) + + result = empty_df.append(data_df) + + return result.to_csv( + sep=b';', + decimal=',', + encoding='latin_1', + date_format='%d%m', + line_terminator=b'\r\n', + index=False, + columns=columns + ) @frappe.whitelist() From 4b2901704cf4fa613c7b050fb0c18f32b001b0c8 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Thu, 9 May 2019 04:03:23 +0200 Subject: [PATCH 03/21] Add download button --- erpnext/regional/report/datev/datev.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/datev/datev.js b/erpnext/regional/report/datev/datev.js index a556199a046..781862c2a6a 100644 --- a/erpnext/regional/report/datev/datev.js +++ b/erpnext/regional/report/datev/datev.js @@ -22,9 +22,9 @@ frappe.query_reports["DATEV"] = { } ], onload: function(query_report) { - query_report.export_report = function() { + query_report.page.add_inner_button("Download DATEV Export", () => { const filters = JSON.stringify(query_report.get_values()); window.open(`/api/method/erpnext.regional.report.datev.datev.download_datev_csv?filters=${filters}`); - }; + }); } }; From 9eef60754da9c27527a21bbd627b0e1bbe56fc2d Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Sun, 12 May 2019 02:24:39 +0200 Subject: [PATCH 04/21] remove unused import --- erpnext/regional/report/datev/datev.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 5209f34528e..e6d1cbfbf38 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals import json from six import string_types import frappe -from frappe.utils import format_datetime from frappe import _ import pandas as pd From 2701f944911d98b2a6e829392814f98928d10980 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Tue, 21 May 2019 21:58:35 +0200 Subject: [PATCH 05/21] add remarks and voucher info --- erpnext/regional/report/datev/datev.py | 37 +++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index e6d1cbfbf38..4a6e0ef27e1 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -52,6 +52,31 @@ def get_columns(): "label": "Belegdatum", "fieldname": "Belegdatum", "fieldtype": "Date", + }, + { + "label": "Buchungstext", + "fieldname": "Buchungstext", + "fieldtype": "Text", + }, + { + "label": "Beleginfo - Art 1", + "fieldname": "Beleginfo - Art 1", + "fieldtype": "Data", + }, + { + "label": "Beleginfo - Inhalt 1", + "fieldname": "Beleginfo - Inhalt 1", + "fieldtype": "Data", + }, + { + "label": "Beleginfo - Art 2", + "fieldname": "Beleginfo - Art 2", + "fieldtype": "Data", + }, + { + "label": "Beleginfo - Inhalt 2", + "fieldname": "Beleginfo - Inhalt 2", + "fieldtype": "Data", } ] @@ -66,7 +91,12 @@ def get_gl_entries(filters, as_dict): case gl.debit when 0 then 'H' else 'S' end as 'Soll/Haben-Kennzeichen', coalesce(acc.account_number, acc_pa.account_number) as 'Kontonummer', coalesce(acc_against.account_number, acc_against_pa.account_number) as 'Gegenkonto (ohne BU-Schlüssel)', - gl.posting_date as 'Belegdatum' + gl.posting_date as 'Belegdatum', + gl.remarks as 'Buchungstext', + gl.voucher_type as 'Beleginfo - Art 1', + gl.voucher_no as 'Beleginfo - Inhalt 1', + gl.against_voucher_type as 'Beleginfo - Art 2', + gl.against_voucher as 'Beleginfo - Inhalt 2' from `tabGL Entry` gl @@ -127,6 +157,11 @@ def get_datev_csv(data): "Zinssperre", # Digitaler Beleg "Beleglink", + # Beleginfo + "Beleginfo - Art 1", + "Beleginfo - Inhalt 1", + "Beleginfo - Art 2", + "Beleginfo - Inhalt 2", # Kostenrechnung "Kost 1 - Kostenstelle", "Kost 2 - Kostenstelle", From 50de88417c1ce6dd08fee257a5144c043aa160b4 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Tue, 21 May 2019 21:59:04 +0200 Subject: [PATCH 06/21] fix filename --- erpnext/regional/report/datev/datev.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 4a6e0ef27e1..2012fa21bff 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -250,9 +250,10 @@ def download_datev_csv(filters=None): validate_filters(filters) data = get_gl_entries(filters, as_dict=1) - filename = 'DATEV_Buchungsstapel_{}-{}'.format( + filename = 'DATEV_Buchungsstapel_{}-{}_bis_{}'.format( filters.get('company'), - filters.get('fiscal_year') + filters.get('from_date'), + filters.get('to_date') ) frappe.response['result'] = get_datev_csv(data) From ce9239af83b519b7aad810b407acac587f022b02 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Tue, 21 May 2019 22:49:59 +0200 Subject: [PATCH 07/21] fix error when downloading empty report --- erpnext/regional/report/datev/datev.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 2012fa21bff..8b05ad0878c 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -225,11 +225,10 @@ def get_datev_csv(data): ] empty_df = pd.DataFrame(columns=columns) - data_df = pd.DataFrame.from_records(data) - data_df["Belegdatum"] = pd.to_datetime(data_df["Belegdatum"]) result = empty_df.append(data_df) + result["Belegdatum"] = pd.to_datetime(result["Belegdatum"]) return result.to_csv( sep=b';', From 31f505528705f7b1a40749cacb273a7341340ec6 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Wed, 22 May 2019 00:03:16 +0200 Subject: [PATCH 08/21] fix sql for multi-company case --- erpnext/regional/report/datev/datev.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 8b05ad0878c..84bd52926b8 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -103,6 +103,7 @@ def get_gl_entries(filters, as_dict): /* Statistisches Konto (Debitoren/Kreditoren) */ left join `tabParty Account` pa on gl.against = pa.parent + and gl.company = pa.company /* Kontonummer */ left join `tabAccount` acc From 9638f0ef2b1234bf84207b2c892cb49ffde2a7a0 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Mon, 27 May 2019 14:42:52 +0200 Subject: [PATCH 09/21] Add columns to CSV, add comments and docstrings --- erpnext/regional/report/datev/datev.py | 122 ++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 84bd52926b8..6ca1fe54c85 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -1,4 +1,12 @@ # coding: utf-8 +""" +Provide a report and downloadable CSV according to the German DATEV format. + +- Query report showing only the columns that contain data, formatted nicely for + dispay to the user. +- CSV download functionality `download_datev_csv` that provides a CSV file with + all required columns. Used to import the data into the DATEV Software. +""" from __future__ import unicode_literals import json from six import string_types @@ -8,6 +16,7 @@ import pandas as pd def execute(filters=None): + """Entry point for frappe.""" validate_filters(filters) result = get_gl_entries(filters, as_dict=0) columns = get_columns() @@ -16,6 +25,7 @@ def execute(filters=None): def validate_filters(filters): + """Make sure all mandatory filters are present.""" if not filters.get('company'): frappe.throw(_('{0} is mandatory').format(_('Company'))) @@ -27,6 +37,7 @@ def validate_filters(filters): def get_columns(): + """Return the list of columns that will be shown in query report.""" columns = [ { "label": "Umsatz (ohne Soll/Haben-Kz)", @@ -84,13 +95,31 @@ def get_columns(): def get_gl_entries(filters, as_dict): + """ + Get a list of accounting entries. + + Select GL Entries joined with Account and Party Account in order to get the + account numbers. Returns a list of accounting entries. + + Arguments: + filters -- dict of filters to be passed to the sql query + as_dict -- return as list of dicts [0,1] + """ gl_entries = frappe.db.sql(""" select + /* either debit or credit amount; always positive */ case gl.debit when 0 then gl.credit else gl.debit end as 'Umsatz (ohne Soll/Haben-Kz)', + + /* 'H' when credit, 'S' when debit */ case gl.debit when 0 then 'H' else 'S' end as 'Soll/Haben-Kennzeichen', + + /* account number or, if empty, party account number */ coalesce(acc.account_number, acc_pa.account_number) as 'Kontonummer', + + /* against number or, if empty, party against number */ coalesce(acc_against.account_number, acc_against_pa.account_number) as 'Gegenkonto (ohne BU-Schlüssel)', + gl.posting_date as 'Belegdatum', gl.remarks as 'Buchungstext', gl.voucher_type as 'Beleginfo - Art 1', @@ -130,7 +159,16 @@ def get_gl_entries(filters, as_dict): def get_datev_csv(data): + """ + Fill in missing columns and return a CSV in DATEV Format. + + Arguments: + data -- array of dictionaries + """ columns = [ + # All possible columns must tbe listed here, because DATEV requires them to + # be present in the CSV. + # --- # Umsatz "Umsatz (ohne Soll/Haben-Kz)", "Soll/Haben-Kennzeichen", @@ -159,10 +197,22 @@ def get_datev_csv(data): # Digitaler Beleg "Beleglink", # Beleginfo - "Beleginfo - Art 1", - "Beleginfo - Inhalt 1", - "Beleginfo - Art 2", - "Beleginfo - Inhalt 2", + "Beleginfo – Art 1", + "Beleginfo – Inhalt 1", + "Beleginfo – Art 2", + "Beleginfo – Inhalt 2", + "Beleginfo – Art 3", + "Beleginfo – Inhalt 3", + "Beleginfo – Art 4", + "Beleginfo – Inhalt 4", + "Beleginfo – Art 5", + "Beleginfo – Inhalt 5", + "Beleginfo – Art 6", + "Beleginfo – Inhalt 6", + "Beleginfo – Art 7", + "Beleginfo – Inhalt 7", + "Beleginfo – Art 8", + "Beleginfo – Inhalt 8", # Kostenrechnung "Kost 1 - Kostenstelle", "Kost 2 - Kostenstelle", @@ -173,7 +223,52 @@ def get_datev_csv(data): "Abw. Versteuerungsart", # L+L Sachverhalt "Sachverhalt L+L", - "FunktionsergänzungL+L", + "Funktionsergänzung L+L", + # Funktion Steuerschlüssel 49 + "BU 49 Hauptfunktionstyp", + "BU 49 Hauptfunktionsnummer", + "BU 49 Funktionsergänzung", + # Zusatzinformationen + "Zusatzinformation – Art 1", + "Zusatzinformation – Inhalt 1", + "Zusatzinformation – Art 2", + "Zusatzinformation – Inhalt 2", + "Zusatzinformation – Art 3", + "Zusatzinformation – Inhalt 3", + "Zusatzinformation – Art 4", + "Zusatzinformation – Inhalt 4", + "Zusatzinformation – Art 5", + "Zusatzinformation – Inhalt 5", + "Zusatzinformation – Art 6", + "Zusatzinformation – Inhalt 6", + "Zusatzinformation – Art 7", + "Zusatzinformation – Inhalt 7", + "Zusatzinformation – Art 8", + "Zusatzinformation – Inhalt 8", + "Zusatzinformation – Art 9", + "Zusatzinformation – Inhalt 9", + "Zusatzinformation – Art 10", + "Zusatzinformation – Inhalt 10", + "Zusatzinformation – Art 11", + "Zusatzinformation – Inhalt 11", + "Zusatzinformation – Art 12", + "Zusatzinformation – Inhalt 12", + "Zusatzinformation – Art 13", + "Zusatzinformation – Inhalt 13", + "Zusatzinformation – Art 14", + "Zusatzinformation – Inhalt 14", + "Zusatzinformation – Art 15", + "Zusatzinformation – Inhalt 15", + "Zusatzinformation – Art 16", + "Zusatzinformation – Inhalt 16", + "Zusatzinformation – Art 17", + "Zusatzinformation – Inhalt 17", + "Zusatzinformation – Art 18", + "Zusatzinformation – Inhalt 18", + "Zusatzinformation – Art 19", + "Zusatzinformation – Inhalt 19", + "Zusatzinformation – Art 20", + "Zusatzinformation – Inhalt 20", # Mengenfelder LuF "Stück", "Gewicht", @@ -233,17 +328,34 @@ def get_datev_csv(data): return result.to_csv( sep=b';', + # European decimal seperator decimal=',', + # Windows "ANSI" encoding encoding='latin_1', + # format date as DDMM date_format='%d%m', + # Windows line terminator line_terminator=b'\r\n', + # Do not number rows index=False, + # Use all columns defined above columns=columns ) @frappe.whitelist() def download_datev_csv(filters=None): + """ + Provide accounting entries for download in DATEV format. + + Validate the filters, get the data, produce the CSV file and provide it for + download. Can be called like this: + + GET /api/method/erpnext.regional.report.datev.datev.download_datev_csv + + Arguments / Params: + filters -- dict of filters to be passed to the sql query + """ if isinstance(filters, string_types): filters = json.loads(filters) From b7b5bcbd855a8b23eda9929f8c3ab66215fe15ab Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Mon, 27 May 2019 15:05:08 +0200 Subject: [PATCH 10/21] fix: em dash cannot be encoded in latin-1 --- erpnext/regional/report/datev/datev.py | 112 ++++++++++++------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 6ca1fe54c85..50aed084aba 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -197,22 +197,22 @@ def get_datev_csv(data): # Digitaler Beleg "Beleglink", # Beleginfo - "Beleginfo – Art 1", - "Beleginfo – Inhalt 1", - "Beleginfo – Art 2", - "Beleginfo – Inhalt 2", - "Beleginfo – Art 3", - "Beleginfo – Inhalt 3", - "Beleginfo – Art 4", - "Beleginfo – Inhalt 4", - "Beleginfo – Art 5", - "Beleginfo – Inhalt 5", - "Beleginfo – Art 6", - "Beleginfo – Inhalt 6", - "Beleginfo – Art 7", - "Beleginfo – Inhalt 7", - "Beleginfo – Art 8", - "Beleginfo – Inhalt 8", + "Beleginfo - Art 1", + "Beleginfo - Inhalt 1", + "Beleginfo - Art 2", + "Beleginfo - Inhalt 2", + "Beleginfo - Art 3", + "Beleginfo - Inhalt 3", + "Beleginfo - Art 4", + "Beleginfo - Inhalt 4", + "Beleginfo - Art 5", + "Beleginfo - Inhalt 5", + "Beleginfo - Art 6", + "Beleginfo - Inhalt 6", + "Beleginfo - Art 7", + "Beleginfo - Inhalt 7", + "Beleginfo - Art 8", + "Beleginfo - Inhalt 8", # Kostenrechnung "Kost 1 - Kostenstelle", "Kost 2 - Kostenstelle", @@ -229,46 +229,46 @@ def get_datev_csv(data): "BU 49 Hauptfunktionsnummer", "BU 49 Funktionsergänzung", # Zusatzinformationen - "Zusatzinformation – Art 1", - "Zusatzinformation – Inhalt 1", - "Zusatzinformation – Art 2", - "Zusatzinformation – Inhalt 2", - "Zusatzinformation – Art 3", - "Zusatzinformation – Inhalt 3", - "Zusatzinformation – Art 4", - "Zusatzinformation – Inhalt 4", - "Zusatzinformation – Art 5", - "Zusatzinformation – Inhalt 5", - "Zusatzinformation – Art 6", - "Zusatzinformation – Inhalt 6", - "Zusatzinformation – Art 7", - "Zusatzinformation – Inhalt 7", - "Zusatzinformation – Art 8", - "Zusatzinformation – Inhalt 8", - "Zusatzinformation – Art 9", - "Zusatzinformation – Inhalt 9", - "Zusatzinformation – Art 10", - "Zusatzinformation – Inhalt 10", - "Zusatzinformation – Art 11", - "Zusatzinformation – Inhalt 11", - "Zusatzinformation – Art 12", - "Zusatzinformation – Inhalt 12", - "Zusatzinformation – Art 13", - "Zusatzinformation – Inhalt 13", - "Zusatzinformation – Art 14", - "Zusatzinformation – Inhalt 14", - "Zusatzinformation – Art 15", - "Zusatzinformation – Inhalt 15", - "Zusatzinformation – Art 16", - "Zusatzinformation – Inhalt 16", - "Zusatzinformation – Art 17", - "Zusatzinformation – Inhalt 17", - "Zusatzinformation – Art 18", - "Zusatzinformation – Inhalt 18", - "Zusatzinformation – Art 19", - "Zusatzinformation – Inhalt 19", - "Zusatzinformation – Art 20", - "Zusatzinformation – Inhalt 20", + "Zusatzinformation - Art 1", + "Zusatzinformation - Inhalt 1", + "Zusatzinformation - Art 2", + "Zusatzinformation - Inhalt 2", + "Zusatzinformation - Art 3", + "Zusatzinformation - Inhalt 3", + "Zusatzinformation - Art 4", + "Zusatzinformation - Inhalt 4", + "Zusatzinformation - Art 5", + "Zusatzinformation - Inhalt 5", + "Zusatzinformation - Art 6", + "Zusatzinformation - Inhalt 6", + "Zusatzinformation - Art 7", + "Zusatzinformation - Inhalt 7", + "Zusatzinformation - Art 8", + "Zusatzinformation - Inhalt 8", + "Zusatzinformation - Art 9", + "Zusatzinformation - Inhalt 9", + "Zusatzinformation - Art 10", + "Zusatzinformation - Inhalt 10", + "Zusatzinformation - Art 11", + "Zusatzinformation - Inhalt 11", + "Zusatzinformation - Art 12", + "Zusatzinformation - Inhalt 12", + "Zusatzinformation - Art 13", + "Zusatzinformation - Inhalt 13", + "Zusatzinformation - Art 14", + "Zusatzinformation - Inhalt 14", + "Zusatzinformation - Art 15", + "Zusatzinformation - Inhalt 15", + "Zusatzinformation - Art 16", + "Zusatzinformation - Inhalt 16", + "Zusatzinformation - Art 17", + "Zusatzinformation - Inhalt 17", + "Zusatzinformation - Art 18", + "Zusatzinformation - Inhalt 18", + "Zusatzinformation - Art 19", + "Zusatzinformation - Inhalt 19", + "Zusatzinformation - Art 20", + "Zusatzinformation - Inhalt 20", # Mengenfelder LuF "Stück", "Gewicht", From 1c1a5958bc4ed65ceeb746e43177de285cb2bed4 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Mon, 27 May 2019 15:05:32 +0200 Subject: [PATCH 11/21] more pleasant icon color --- erpnext/regional/report/datev/datev.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/datev/datev.json b/erpnext/regional/report/datev/datev.json index d4f44b6fbb6..80a866cbf5c 100644 --- a/erpnext/regional/report/datev/datev.json +++ b/erpnext/regional/report/datev/datev.json @@ -4,7 +4,7 @@ "creation": "2019-04-24 08:45:16.650129", "disabled": 0, "icon": "octicon octicon-repo-pull", - "color": "#96cf41", + "color": "#4CB944", "docstatus": 0, "doctype": "Report", "idx": 0, From 0261472ecd305ac405dd7604913cf2b359e7bbbd Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Mon, 27 May 2019 16:01:05 +0200 Subject: [PATCH 12/21] add default filters --- erpnext/regional/report/datev/datev.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/report/datev/datev.js b/erpnext/regional/report/datev/datev.js index 781862c2a6a..1e000b673e6 100644 --- a/erpnext/regional/report/datev/datev.js +++ b/erpnext/regional/report/datev/datev.js @@ -5,18 +5,20 @@ frappe.query_reports["DATEV"] = { "label": __("Company"), "fieldtype": "Link", "options": "Company", - "default": frappe.defaults.get_user_default("Company"), + "default": frappe.defaults.get_user_default("Company") || frappe.defaults.get_global_default("Company"), "reqd": 1 }, { "fieldname": "from_date", "label": __("From Date"), + "default": frappe.datetime.month_start(), "fieldtype": "Date", "reqd": 1 }, { "fieldname": "to_date", "label": __("To Date"), + "default": frappe.datetime.now_date(), "fieldtype": "Date", "reqd": 1 } From 1d11ddc23524201b81d8b815a83612b6a621ff13 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 10 Jun 2019 17:33:36 +0530 Subject: [PATCH 13/21] fix: Available qty not shown in item batch selector for batch --- .../js/utils/serial_no_batch_selector.js | 65 ++++++++++--------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index df50884ce71..d9c84f5c463 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -233,30 +233,32 @@ erpnext.SerialNoBatchSelector = Class.extend({ get_batch_fields: function() { var me = this; return [ - {fieldtype:'Section Break', label: __('Batches')}, - {fieldname: 'batches', fieldtype: 'Table', + { fieldtype: 'Section Break', label: __('Batches') }, + { + fieldname: 'batches', fieldtype: 'Table', fields: [ { - fieldtype:'Link', - fieldname:'batch_no', - options: 'Batch', - label: __('Select Batch'), - in_list_view:1, - get_query: function() { + 'fieldtype': 'Link', + 'read_only': 0, + 'fieldname': 'batch_no', + 'options': 'Batch', + 'label': __('Select Batch'), + 'in_list_view': 1, + get_query: function () { return { - filters: {item: me.item_code }, - query: 'erpnext.controllers.queries.get_batch_numbers' - }; + filters: { item: me.item_code }, + query: 'erpnext.controllers.queries.get_batch_numbers' + }; }, - onchange: function(e) { + change: function (e) { let val = this.get_value(); - if(val.length === 0) { + if (val.length === 0) { this.grid_row.on_grid_fields_dict .available_qty.set_value(0); return; } let selected_batches = this.grid.grid_rows.map((row) => { - if(row === this.grid_row) { + if (row === this.grid_row) { return ""; } @@ -264,12 +266,12 @@ erpnext.SerialNoBatchSelector = Class.extend({ return row.on_grid_fields_dict.batch_no.get_value(); } }); - if(selected_batches.includes(val)) { + if (selected_batches.includes(val)) { this.set_value(""); frappe.throw(__(`Batch ${val} already selected.`)); return; } - if(me.warehouse_details.name) { + if (me.warehouse_details.name) { frappe.call({ method: 'erpnext.stock.doctype.batch.batch.get_batch_qty', args: { @@ -292,31 +294,32 @@ erpnext.SerialNoBatchSelector = Class.extend({ } }, { - fieldtype:'Float', - read_only:1, - fieldname:'available_qty', - label: __('Available'), - in_list_view:1, - default: 0, - onchange: function() { + 'fieldtype': 'Float', + 'read_only': 1, + 'fieldname': 'available_qty', + 'label': __('Available'), + 'in_list_view': 1, + 'default': 0, + change: function () { this.grid_row.on_grid_fields_dict.selected_qty.set_value('0'); } }, { - fieldtype:'Float', - fieldname:'selected_qty', - label: __('Qty'), - in_list_view:1, + 'fieldtype': 'Float', + 'read_only': 0, + 'fieldname': 'selected_qty', + 'label': __('Qty'), + 'in_list_view': 1, 'default': 0, - onchange: function(e) { + change: function (e) { var batch_no = this.grid_row.on_grid_fields_dict.batch_no.get_value(); var available_qty = this.grid_row.on_grid_fields_dict.available_qty.get_value(); var selected_qty = this.grid_row.on_grid_fields_dict.selected_qty.get_value(); - if(batch_no.length === 0 && parseInt(selected_qty)!==0) { + if (batch_no.length === 0 && parseInt(selected_qty) !== 0) { frappe.throw(__("Please select a batch")); } - if(me.warehouse_details.type === 'Source Warehouse' && + if (me.warehouse_details.type === 'Source Warehouse' && parseFloat(available_qty) < parseFloat(selected_qty)) { this.set_value('0'); @@ -332,7 +335,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ ], in_place_edit: true, data: this.data, - get_data: function() { + get_data: function () { return this.data; }, } From 2de80fdc3ecc1ed5a4ba782056783bad9be28ffa Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 10 Jun 2019 17:51:47 +0530 Subject: [PATCH 14/21] fix: Codacy --- erpnext/public/js/utils/serial_no_batch_selector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index d9c84f5c463..0202bbb872a 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -250,7 +250,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ query: 'erpnext.controllers.queries.get_batch_numbers' }; }, - change: function (e) { + change: function () { let val = this.get_value(); if (val.length === 0) { this.grid_row.on_grid_fields_dict @@ -311,7 +311,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ 'label': __('Qty'), 'in_list_view': 1, 'default': 0, - change: function (e) { + change: function () { var batch_no = this.grid_row.on_grid_fields_dict.batch_no.get_value(); var available_qty = this.grid_row.on_grid_fields_dict.available_qty.get_value(); var selected_qty = this.grid_row.on_grid_fields_dict.selected_qty.get_value(); From 860b6bac75bad1fffd09ef296426fe1f123abbe1 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 13 Jun 2019 14:28:46 +0530 Subject: [PATCH 15/21] fix: get bom item when company is not passed --- erpnext/stock/doctype/material_request/material_request.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index fe1319103a8..a0fc3f34e20 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -186,6 +186,7 @@ frappe.ui.form.on('Material Request', { var values = d.get_values(); if(!values) return; values["company"] = frm.doc.company; + if(!frm.doc.company) frappe.throw(__("Company field is required")); frappe.call({ method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items", args: values, From 49c6c909f8e23f2fbe98bd6df71a617149f5c507 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 13 Jun 2019 15:42:13 +0530 Subject: [PATCH 16/21] fix: taxes are not overriding after changing the taxes template --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index a836a1813e6..5393f5d37fb 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -371,6 +371,10 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte me.frm.pos_print_format = r.message.print_format; } me.frm.script_manager.trigger("update_stock"); + if(me.frm.doc.taxes_and_charges) { + me.frm.script_manager.trigger("taxes_and_charges"); + } + frappe.model.set_default_values(me.frm.doc); me.set_dynamic_labels(); me.calculate_taxes_and_totals(); From 2fc6d1d210b0e07b98a2dff446d84f168b793602 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 13 Jun 2019 16:09:30 +0530 Subject: [PATCH 17/21] fix: column sequence and width --- erpnext/projects/report/billing_summary.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/projects/report/billing_summary.py b/erpnext/projects/report/billing_summary.py index 929a13f6683..76379f1de2e 100644 --- a/erpnext/projects/report/billing_summary.py +++ b/erpnext/projects/report/billing_summary.py @@ -30,23 +30,23 @@ def get_columns(): "options": "Timesheet", "width": 150 }, - { - "label": _("Billable Hours"), - "fieldtype": "Float", - "fieldname": "total_billable_hours", - "width": 50 - }, { "label": _("Working Hours"), "fieldtype": "Float", "fieldname": "total_hours", - "width": 50 + "width": 150 + }, + { + "label": _("Billable Hours"), + "fieldtype": "Float", + "fieldname": "total_billable_hours", + "width": 150 }, { "label": _("Billing Amount"), "fieldtype": "Currency", "fieldname": "amount", - "width": 100 + "width": 150 } ] From a59aaf48f2476a681181ea8a8666c9c8321827b1 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 13 Jun 2019 19:43:47 +0530 Subject: [PATCH 18/21] fix: salary slip amount calculation based on formula --- erpnext/hr/doctype/salary_slip/salary_slip.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index ffd786836a5..d3b960d29eb 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -445,6 +445,8 @@ class SalarySlip(TransactionBase): if not overwrite and component_row.default_amount: amount += component_row.default_amount + else: + component_row.default_amount = amount component_row.amount = amount component_row.deduct_full_tax_on_selected_payroll_date = struct_row.deduct_full_tax_on_selected_payroll_date From f7af9b87457564506c89564165c0cce137ae3fca Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 14 Jun 2019 11:22:23 +0530 Subject: [PATCH 19/21] fix: trial balance opening balance not showing in the debit side for the liability account (#17786) --- .../report/trial_balance/trial_balance.py | 57 +++++++++---------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 6b18c5d8731..b5f0186d4d2 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -180,20 +180,28 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, filters, if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense": d["opening_debit"] -= d["opening_credit"] - d["opening_credit"] = 0.0 - total_row["opening_debit"] += d["opening_debit"] + d["closing_debit"] -= d["closing_credit"] + + # For opening + check_opening_closing_has_negative_value(d, "opening_debit", "opening_credit") + + # For closing + check_opening_closing_has_negative_value(d, "closing_debit", "closing_credit") + if d["root_type"] == "Liability" or d["root_type"] == "Income": d["opening_credit"] -= d["opening_debit"] - d["opening_debit"] = 0.0 - total_row["opening_credit"] += d["opening_credit"] - if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense": - d["closing_debit"] -= d["closing_credit"] - d["closing_credit"] = 0.0 - total_row["closing_debit"] += d["closing_debit"] - if d["root_type"] == "Liability" or d["root_type"] == "Income": d["closing_credit"] -= d["closing_debit"] - d["closing_debit"] = 0.0 - total_row["closing_credit"] += d["closing_credit"] + + # For opening + check_opening_closing_has_negative_value(d, "opening_credit", "opening_debit") + + # For closing + check_opening_closing_has_negative_value(d, "closing_credit", "closing_debit") + + total_row["opening_debit"] += d["opening_debit"] + total_row["closing_debit"] += d["closing_debit"] + total_row["opening_credit"] += d["opening_credit"] + total_row["closing_credit"] += d["closing_credit"] return total_row @@ -219,8 +227,6 @@ def prepare_data(accounts, filters, total_row, parent_children_map, company_curr if d.account_number else d.account_name) } - prepare_opening_and_closing(d) - for key in value_fields: row[key] = flt(d.get(key, 0.0), 3) @@ -295,22 +301,11 @@ def get_columns(): } ] -def prepare_opening_and_closing(d): - d["closing_debit"] = d["opening_debit"] + d["debit"] - d["closing_credit"] = d["opening_credit"] + d["credit"] +def check_opening_closing_has_negative_value(d, dr_or_cr, switch_to_column): + # If opening debit has negetive value then move it to opening credit and vice versa. - if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense": - d["opening_debit"] -= d["opening_credit"] - d["opening_credit"] = 0.0 - - if d["root_type"] == "Liability" or d["root_type"] == "Income": - d["opening_credit"] -= d["opening_debit"] - d["opening_debit"] = 0.0 - - if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense": - d["closing_debit"] -= d["closing_credit"] - d["closing_credit"] = 0.0 - - if d["root_type"] == "Liability" or d["root_type"] == "Income": - d["closing_credit"] -= d["closing_debit"] - d["closing_debit"] = 0.0 + if d[dr_or_cr] < 0: + d[switch_to_column] = abs(d[dr_or_cr]) + d[dr_or_cr] = 0.0 + else: + d[switch_to_column] = 0.0 From 1450a7b40822d487c6940dde465f60c97fa4b74c Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Fri, 14 Jun 2019 11:29:29 +0530 Subject: [PATCH 20/21] fix column width (#17936) --- .../budget_variance_report.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index b292bd33b9b..0f802d88cc4 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -45,8 +45,8 @@ def execute(filters=None): if(filters.get("show_cumulative")): last_total = period_data[0] - period_data[1] - - period_data[2] = period_data[0] - period_data[1] + + period_data[2] = period_data[0] - period_data[1] row += period_data totals[2] = totals[0] - totals[1] if filters["period"] != "Yearly" : @@ -60,7 +60,7 @@ def validate_filters(filters): frappe.throw(_("Filter based on Cost Center is only applicable if Budget Against is selected as Cost Center")) def get_columns(filters): - columns = [_(filters.get("budget_against")) + ":Link/%s:80"%(filters.get("budget_against")), _("Account") + ":Link/Account:80"] + columns = [_(filters.get("budget_against")) + ":Link/%s:150"%(filters.get("budget_against")), _("Account") + ":Link/Account:150"] group_months = False if filters["period"] == "Monthly" else True @@ -71,7 +71,7 @@ def get_columns(filters): if filters["period"] == "Yearly": labels = [_("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), _("Varaiance ") + " " + str(year[0])] for label in labels: - columns.append(label+":Float:80") + columns.append(label+":Float:150") else: for label in [_("Budget") + " (%s)" + " " + str(year[0]), _("Actual") + " (%s)" + " " + str(year[0]), _("Variance") + " (%s)" + " " + str(year[0])]: if group_months: @@ -79,20 +79,20 @@ def get_columns(filters): else: label = label % formatdate(from_date, format_string="MMM") - columns.append(label+":Float:80") + columns.append(label+":Float:150") if filters["period"] != "Yearly" : - return columns + [_("Total Budget") + ":Float:80", _("Total Actual") + ":Float:80", - _("Total Variance") + ":Float:80"] + return columns + [_("Total Budget") + ":Float:150", _("Total Actual") + ":Float:150", + _("Total Variance") + ":Float:150"] else: return columns - + def get_cost_centers(filters): cond = "and 1=1" if filters.get("budget_against") == "Cost Center": cond = "order by lft" - return frappe.db.sql_list("""select name from `tab{tab}` where company=%s + return frappe.db.sql_list("""select name from `tab{tab}` where company=%s {cond}""".format(tab=filters.get("budget_against"), cond=cond), filters.get("company")) #Get cost center & target details @@ -109,7 +109,7 @@ def get_cost_center_target_details(filters): """.format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond), (filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company), as_dict=True) - + #Get target distribution details of accounts of cost center def get_target_distribution_details(filters): @@ -118,7 +118,7 @@ def get_target_distribution_details(filters): from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md where mdp.parent=md.name and md.fiscal_year between %s and %s order by md.fiscal_year""",(filters.from_fiscal_year, filters.to_fiscal_year), as_dict=1): target_details.setdefault(d.name, {}).setdefault(d.month, flt(d.percentage_allocation)) - + return target_details #Get actual details from gl entry @@ -129,7 +129,7 @@ def get_actual_details(name, filters): if filters.get("budget_against") == "Cost Center": cc_lft, cc_rgt = frappe.db.get_value("Cost Center", name, ["lft", "rgt"]) cond = "lft>='{lft}' and rgt<='{rgt}'".format(lft = cc_lft, rgt=cc_rgt) - + ac_details = frappe.db.sql("""select gl.account, gl.debit, gl.credit,gl.fiscal_year, MONTHNAME(gl.posting_date) as month_name, b.{budget_against} as budget_against from `tabGL Entry` gl, `tabBudget Account` ba, `tabBudget` b @@ -159,7 +159,7 @@ def get_cost_center_account_month_map(filters): for ccd in cost_center_target_details: actual_details = get_actual_details(ccd.budget_against, filters) - + for month_id in range(1, 13): month = datetime.date(2013, month_id, 1).strftime('%B') cam_map.setdefault(ccd.budget_against, {}).setdefault(ccd.account, {}).setdefault(ccd.fiscal_year,{})\ @@ -172,7 +172,7 @@ def get_cost_center_account_month_map(filters): if ccd.monthly_distribution else 100.0/12 tav_dict.target = flt(ccd.budget_amount) * month_percentage / 100 - + for ad in actual_details.get(ccd.account, []): if ad.month_name == month: tav_dict.actual += flt(ad.debit) - flt(ad.credit) From dc24fe60be702ad2e0b95ef6867a871179e0a0fe Mon Sep 17 00:00:00 2001 From: Khadija Tul Kubra Zaki Date: Fri, 14 Jun 2019 11:29:45 +0500 Subject: [PATCH 21/21] fix: leave without pay spelling in salary register (#17938) * leave without pay spelling in salary register * fixed typo --- erpnext/hr/report/salary_register/salary_register.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/report/salary_register/salary_register.py b/erpnext/hr/report/salary_register/salary_register.py index 3326ac7a6c8..9c45a628a3f 100644 --- a/erpnext/hr/report/salary_register/salary_register.py +++ b/erpnext/hr/report/salary_register/salary_register.py @@ -19,12 +19,12 @@ def execute(filters=None): data = [] for ss in salary_slips: row = [ss.name, ss.employee, ss.employee_name, ss.branch, ss.department, ss.designation, - ss.company, ss.start_date, ss.end_date, ss.leave_withut_pay, ss.payment_days] + ss.company, ss.start_date, ss.end_date, ss.leave_without_pay, ss.payment_days] if not ss.branch == None:columns[3] = columns[3].replace('-1','120') if not ss.department == None: columns[4] = columns[4].replace('-1','120') if not ss.designation == None: columns[5] = columns[5].replace('-1','120') - if not ss.leave_withut_pay == None: columns[9] = columns[9].replace('-1','130') + if not ss.leave_without_pay == None: columns[9] = columns[9].replace('-1','130') for e in earning_types: @@ -117,4 +117,4 @@ def get_ss_ded_map(salary_slips): ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, []) ss_ded_map[d.parent][d.salary_component] = flt(d.amount) - return ss_ded_map \ No newline at end of file + return ss_ded_map