refactor!: remove DATEV integration (#30584)
The DATEV integration follows the trend and moves into a separate app.
- the DATEV Integration will be maintained at https://github.com/alyf-de/erpnext_datev (version-14 branch)
- the German Compliance and Localization will be maintained at https://github.com/alyf-de/erpnext_germany
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 6e5ffed..63b6bb7 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -262,8 +262,6 @@
erpnext.patches.v13_0.update_shipment_status
erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting
erpnext.patches.v12_0.add_ewaybill_validity_field
-erpnext.patches.v13_0.germany_make_custom_fields
-erpnext.patches.v13_0.germany_fill_debtor_creditor_number
erpnext.patches.v13_0.set_pos_closing_as_failed
erpnext.patches.v13_0.rename_stop_to_send_birthday_reminders
execute:frappe.rename_doc("Workspace", "Loan Management", "Loans", force=True)
@@ -343,6 +341,7 @@
erpnext.patches.v14_0.delete_hub_doctypes
erpnext.patches.v14_0.delete_hospitality_doctypes # 20-01-2022
erpnext.patches.v14_0.delete_agriculture_doctypes
+erpnext.patches.v14_0.delete_datev_doctypes
erpnext.patches.v14_0.rearrange_company_fields
erpnext.patches.v14_0.update_leave_notification_template
erpnext.patches.v14_0.restore_einvoice_fields
@@ -364,4 +363,4 @@
erpnext.patches.v13_0.set_return_against_in_pos_invoice_references
erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022
erpnext.patches.v13_0.update_expense_claim_status_for_paid_advances
-erpnext.patches.v13_0.create_gst_custom_fields_in_quotation
\ No newline at end of file
+erpnext.patches.v13_0.create_gst_custom_fields_in_quotation
diff --git a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
deleted file mode 100644
index fc3e68a..0000000
--- a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright (c) 2019, Frappe and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import frappe
-
-
-def execute():
- """Move account number into the new custom field debtor_creditor_number.
-
- German companies used to use a dedicated payable/receivable account for
- every party to mimick party accounts in the external accounting software
- "DATEV". This is no longer necessary. The reference ID for DATEV will be
- stored in a new custom field "debtor_creditor_number".
- """
- company_list = frappe.get_all("Company", filters={"country": "Germany"})
-
- for company in company_list:
- party_account_list = frappe.get_all(
- "Party Account",
- filters={"company": company.name},
- fields=["name", "account", "debtor_creditor_number"],
- )
- for party_account in party_account_list:
- if (not party_account.account) or party_account.debtor_creditor_number:
- # account empty or debtor_creditor_number already filled
- continue
-
- account_number = frappe.db.get_value("Account", party_account.account, "account_number")
- if not account_number:
- continue
-
- frappe.db.set_value(
- "Party Account", party_account.name, "debtor_creditor_number", account_number
- )
- frappe.db.set_value("Party Account", party_account.name, "account", "")
diff --git a/erpnext/patches/v13_0/germany_make_custom_fields.py b/erpnext/patches/v13_0/germany_make_custom_fields.py
deleted file mode 100644
index cc35813..0000000
--- a/erpnext/patches/v13_0/germany_make_custom_fields.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright (c) 2019, Frappe and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import frappe
-
-from erpnext.regional.germany.setup import make_custom_fields
-
-
-def execute():
- """Execute the make_custom_fields method for german companies.
-
- It is usually run once at setup of a new company. Since it's new, run it
- once for existing companies as well.
- """
- company_list = frappe.get_all("Company", filters={"country": "Germany"})
- if not company_list:
- return
-
- make_custom_fields()
diff --git a/erpnext/patches/v14_0/delete_datev_doctypes.py b/erpnext/patches/v14_0/delete_datev_doctypes.py
new file mode 100644
index 0000000..a5de91f
--- /dev/null
+++ b/erpnext/patches/v14_0/delete_datev_doctypes.py
@@ -0,0 +1,13 @@
+import frappe
+
+
+def execute():
+ install_apps = frappe.get_installed_apps()
+ if "erpnext_datev_uo" in install_apps or "erpnext_datev" in install_apps:
+ return
+
+ # doctypes
+ frappe.delete_doc("DocType", "DATEV Settings", ignore_missing=True, force=True)
+
+ # reports
+ frappe.delete_doc("Report", "DATEV", ignore_missing=True, force=True)
diff --git a/erpnext/regional/doctype/datev_settings/__init__.py b/erpnext/regional/doctype/datev_settings/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/regional/doctype/datev_settings/__init__.py
+++ /dev/null
diff --git a/erpnext/regional/doctype/datev_settings/datev_settings.js b/erpnext/regional/doctype/datev_settings/datev_settings.js
deleted file mode 100644
index 3c36549..0000000
--- a/erpnext/regional/doctype/datev_settings/datev_settings.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// 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) {
- frm.add_custom_button(__('Show Report'), () => frappe.set_route('query-report', 'DATEV'), "fa fa-table");
- }
-});
diff --git a/erpnext/regional/doctype/datev_settings/datev_settings.json b/erpnext/regional/doctype/datev_settings/datev_settings.json
deleted file mode 100644
index f60de4c..0000000
--- a/erpnext/regional/doctype/datev_settings/datev_settings.json
+++ /dev/null
@@ -1,125 +0,0 @@
-{
- "actions": [],
- "autoname": "field:client",
- "creation": "2019-08-13 23:56:34.259906",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "client",
- "client_number",
- "column_break_2",
- "consultant_number",
- "consultant",
- "section_break_4",
- "account_number_length",
- "column_break_6",
- "temporary_against_account_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",
- "length": 5,
- "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",
- "length": 7,
- "reqd": 1
- },
- {
- "fieldname": "column_break_2",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "section_break_4",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "column_break_6",
- "fieldtype": "Column Break"
- },
- {
- "default": "4",
- "fieldname": "account_number_length",
- "fieldtype": "Int",
- "label": "Account Number Length",
- "reqd": 1
- },
- {
- "allow_in_quick_entry": 1,
- "fieldname": "temporary_against_account_number",
- "fieldtype": "Data",
- "label": "Temporary Against Account Number",
- "reqd": 1
- }
- ],
- "links": [],
- "modified": "2020-11-19 19:00:09.088816",
- "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
deleted file mode 100644
index 686a93e..0000000
--- a/erpnext/regional/doctype/datev_settings/datev_settings.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-# 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
deleted file mode 100644
index ba70eb4..0000000
--- a/erpnext/regional/doctype/datev_settings/test_datev_settings.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-# import frappe
-import unittest
-
-
-class TestDATEVSettings(unittest.TestCase):
- pass
diff --git a/erpnext/regional/germany/__init__.py b/erpnext/regional/germany/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/regional/germany/__init__.py
+++ /dev/null
diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py
deleted file mode 100644
index b8e66c3..0000000
--- a/erpnext/regional/germany/setup.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import frappe
-from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
-
-
-def setup(company=None, patch=True):
- make_custom_fields()
- add_custom_roles_for_reports()
-
-
-def make_custom_fields():
- custom_fields = {
- "Party Account": [
- dict(
- fieldname="debtor_creditor_number",
- label="Debtor/Creditor Number",
- fieldtype="Data",
- insert_after="account",
- translatable=0,
- )
- ]
- }
-
- create_custom_fields(custom_fields)
-
-
-def add_custom_roles_for_reports():
- """Add Access Control to UAE VAT 201."""
- if not frappe.db.get_value("Custom Role", dict(report="DATEV")):
- frappe.get_doc(
- dict(
- doctype="Custom Role",
- report="DATEV",
- roles=[dict(role="Accounts User"), dict(role="Accounts Manager")],
- )
- ).insert()
diff --git a/erpnext/regional/germany/utils/__init__.py b/erpnext/regional/germany/utils/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/regional/germany/utils/__init__.py
+++ /dev/null
diff --git a/erpnext/regional/germany/utils/datev/__init__.py b/erpnext/regional/germany/utils/datev/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/regional/germany/utils/datev/__init__.py
+++ /dev/null
diff --git a/erpnext/regional/germany/utils/datev/datev_constants.py b/erpnext/regional/germany/utils/datev/datev_constants.py
deleted file mode 100644
index 9524481..0000000
--- a/erpnext/regional/germany/utils/datev/datev_constants.py
+++ /dev/null
@@ -1,501 +0,0 @@
-"""Constants used in datev.py."""
-
-TRANSACTION_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",
- "WKZ Umsatz",
- "Kurs",
- "Basis-Umsatz",
- "WKZ Basis-Umsatz",
- # Konto/Gegenkonto
- "Konto",
- "Gegenkonto (ohne BU-Schlüssel)",
- "BU-Schlüssel",
- # Datum
- "Belegdatum",
- # Rechnungs- / Belegnummer
- "Belegfeld 1",
- # z.B. Fälligkeitsdatum Format: TTMMJJ
- "Belegfeld 2",
- # Skonto-Betrag / -Abzug (Der Wert 0 ist unzulässig)
- "Skonto",
- # Beschreibung des Buchungssatzes
- "Buchungstext",
- # Mahn- / Zahl-Sperre (1 = Postensperre)
- "Postensperre",
- "Diverse Adressnummer",
- "Geschäftspartnerbank",
- "Sachverhalt",
- # Keine Mahnzinsen
- "Zinssperre",
- # Link auf den Buchungsbeleg (Programmkürzel + GUID)
- "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",
- # Zuordnung des Geschäftsvorfalls für die Kostenrechnung
- "KOST1 - Kostenstelle",
- "KOST2 - Kostenstelle",
- "KOST-Menge",
- # USt-ID-Nummer (Beispiel: DE133546770)
- "EU-Mitgliedstaat u. USt-IdNr.",
- # Der im EU-Bestimmungsland gültige Steuersatz
- "EU-Steuersatz",
- # I = Ist-Versteuerung,
- # K = keine Umsatzsteuerrechnung
- # P = Pauschalierung (z. B. für Land- und Forstwirtschaft),
- # S = Soll-Versteuerung
- "Abw. Versteuerungsart",
- # Sachverhalte gem. § 13b Abs. 1 Satz 1 Nrn. 1.-5. UStG
- "Sachverhalt L+L",
- # Steuersatz / Funktion zum L+L-Sachverhalt (Beispiel: Wert 190 für 19%)
- "Funktionsergänzung L+L",
- # Bei Verwendung des BU-Schlüssels 49 für „andere Steuersätze“ muss der
- # steuerliche Sachverhalt mitgegeben werden
- "BU 49 Hauptfunktionstyp",
- "BU 49 Hauptfunktionsnummer",
- "BU 49 Funktionsergänzung",
- # Zusatzinformationen, besitzen den Charakter eines Notizzettels und können
- # frei erfasst werden.
- "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",
- # Wirkt sich nur bei Sachverhalt mit SKR 14 Land- und Forstwirtschaft aus,
- # für andere SKR werden die Felder beim Import / Export überlesen bzw.
- # leer exportiert.
- "Stück",
- "Gewicht",
- # 1 = Lastschrift
- # 2 = Mahnung
- # 3 = Zahlung
- "Zahlweise",
- "Forderungsart",
- # JJJJ
- "Veranlagungsjahr",
- # TTMMJJJJ
- "Zugeordnete Fälligkeit",
- # 1 = Einkauf von Waren
- # 2 = Erwerb von Roh-Hilfs- und Betriebsstoffen
- "Skontotyp",
- # Allgemeine Bezeichnung, des Auftrags / Projekts.
- "Auftragsnummer",
- # AA = Angeforderte Anzahlung / Abschlagsrechnung
- # AG = Erhaltene Anzahlung (Geldeingang)
- # AV = Erhaltene Anzahlung (Verbindlichkeit)
- # SR = Schlussrechnung
- # SU = Schlussrechnung (Umbuchung)
- # SG = Schlussrechnung (Geldeingang)
- # SO = Sonstige
- "Buchungstyp",
- "USt-Schlüssel (Anzahlungen)",
- "EU-Mitgliedstaat (Anzahlungen)",
- "Sachverhalt L+L (Anzahlungen)",
- "EU-Steuersatz (Anzahlungen)",
- "Erlöskonto (Anzahlungen)",
- # Wird beim Import durch SV (Stapelverarbeitung) ersetzt.
- "Herkunft-Kz",
- # Wird von DATEV verwendet.
- "Leerfeld",
- # Format TTMMJJJJ
- "KOST-Datum",
- # Vom Zahlungsempfänger individuell vergebenes Kennzeichen eines Mandats
- # (z.B. Rechnungs- oder Kundennummer).
- "SEPA-Mandatsreferenz",
- # 1 = Skontosperre
- # 0 = Keine Skontosperre
- "Skontosperre",
- # Gesellschafter und Sonderbilanzsachverhalt
- "Gesellschaftername",
- # Amtliche Nummer aus der Feststellungserklärung
- "Beteiligtennummer",
- "Identifikationsnummer",
- "Zeichnernummer",
- # Format TTMMJJJJ
- "Postensperre bis",
- # Gesellschafter und Sonderbilanzsachverhalt
- "Bezeichnung SoBil-Sachverhalt",
- "Kennzeichen SoBil-Buchung",
- # 0 = keine Festschreibung
- # 1 = Festschreibung
- "Festschreibung",
- # Format TTMMJJJJ
- "Leistungsdatum",
- # Format TTMMJJJJ
- "Datum Zuord. Steuerperiode",
- # OPOS-Informationen, Format TTMMJJJJ
- "Fälligkeit",
- # G oder 1 = Generalumkehr
- # 0 = keine Generalumkehr
- "Generalumkehr (GU)",
- # Steuersatz für Steuerschlüssel
- "Steuersatz",
- # Beispiel: DE für Deutschland
- "Land",
-]
-
-DEBTOR_CREDITOR_COLUMNS = [
- # All possible columns must tbe listed here, because DATEV requires them to
- # be present in the CSV.
- # Columns "Leerfeld" have been replaced with "Leerfeld #" to not confuse pandas
- # ---
- "Konto",
- "Name (Adressatentyp Unternehmen)",
- "Unternehmensgegenstand",
- "Name (Adressatentyp natürl. Person)",
- "Vorname (Adressatentyp natürl. Person)",
- "Name (Adressatentyp keine Angabe)",
- "Adressatentyp",
- "Kurzbezeichnung",
- "EU-Land",
- "EU-USt-IdNr.",
- "Anrede",
- "Titel/Akad. Grad",
- "Adelstitel",
- "Namensvorsatz",
- "Adressart",
- "Straße",
- "Postfach",
- "Postleitzahl",
- "Ort",
- "Land",
- "Versandzusatz",
- "Adresszusatz",
- "Abweichende Anrede",
- "Abw. Zustellbezeichnung 1",
- "Abw. Zustellbezeichnung 2",
- "Kennz. Korrespondenzadresse",
- "Adresse gültig von",
- "Adresse gültig bis",
- "Telefon",
- "Bemerkung (Telefon)",
- "Telefon Geschäftsleitung",
- "Bemerkung (Telefon GL)",
- "E-Mail",
- "Bemerkung (E-Mail)",
- "Internet",
- "Bemerkung (Internet)",
- "Fax",
- "Bemerkung (Fax)",
- "Sonstige",
- "Bemerkung (Sonstige)",
- "Bankleitzahl 1",
- "Bankbezeichnung 1",
- "Bankkonto-Nummer 1",
- "Länderkennzeichen 1",
- "IBAN 1",
- "Leerfeld 1",
- "SWIFT-Code 1",
- "Abw. Kontoinhaber 1",
- "Kennz. Haupt-Bankverb. 1",
- "Bankverb. 1 Gültig von",
- "Bankverb. 1 Gültig bis",
- "Bankleitzahl 2",
- "Bankbezeichnung 2",
- "Bankkonto-Nummer 2",
- "Länderkennzeichen 2",
- "IBAN 2",
- "Leerfeld 2",
- "SWIFT-Code 2",
- "Abw. Kontoinhaber 2",
- "Kennz. Haupt-Bankverb. 2",
- "Bankverb. 2 gültig von",
- "Bankverb. 2 gültig bis",
- "Bankleitzahl 3",
- "Bankbezeichnung 3",
- "Bankkonto-Nummer 3",
- "Länderkennzeichen 3",
- "IBAN 3",
- "Leerfeld 3",
- "SWIFT-Code 3",
- "Abw. Kontoinhaber 3",
- "Kennz. Haupt-Bankverb. 3",
- "Bankverb. 3 gültig von",
- "Bankverb. 3 gültig bis",
- "Bankleitzahl 4",
- "Bankbezeichnung 4",
- "Bankkonto-Nummer 4",
- "Länderkennzeichen 4",
- "IBAN 4",
- "Leerfeld 4",
- "SWIFT-Code 4",
- "Abw. Kontoinhaber 4",
- "Kennz. Haupt-Bankverb. 4",
- "Bankverb. 4 Gültig von",
- "Bankverb. 4 Gültig bis",
- "Bankleitzahl 5",
- "Bankbezeichnung 5",
- "Bankkonto-Nummer 5",
- "Länderkennzeichen 5",
- "IBAN 5",
- "Leerfeld 5",
- "SWIFT-Code 5",
- "Abw. Kontoinhaber 5",
- "Kennz. Haupt-Bankverb. 5",
- "Bankverb. 5 gültig von",
- "Bankverb. 5 gültig bis",
- "Leerfeld 6",
- "Briefanrede",
- "Grußformel",
- "Kundennummer",
- "Steuernummer",
- "Sprache",
- "Ansprechpartner",
- "Vertreter",
- "Sachbearbeiter",
- "Diverse-Konto",
- "Ausgabeziel",
- "Währungssteuerung",
- "Kreditlimit (Debitor)",
- "Zahlungsbedingung",
- "Fälligkeit in Tagen (Debitor)",
- "Skonto in Prozent (Debitor)",
- "Kreditoren-Ziel 1 (Tage)",
- "Kreditoren-Skonto 1 (%)",
- "Kreditoren-Ziel 2 (Tage)",
- "Kreditoren-Skonto 2 (%)",
- "Kreditoren-Ziel 3 Brutto (Tage)",
- "Kreditoren-Ziel 4 (Tage)",
- "Kreditoren-Skonto 4 (%)",
- "Kreditoren-Ziel 5 (Tage)",
- "Kreditoren-Skonto 5 (%)",
- "Mahnung",
- "Kontoauszug",
- "Mahntext 1",
- "Mahntext 2",
- "Mahntext 3",
- "Kontoauszugstext",
- "Mahnlimit Betrag",
- "Mahnlimit %",
- "Zinsberechnung",
- "Mahnzinssatz 1",
- "Mahnzinssatz 2",
- "Mahnzinssatz 3",
- "Lastschrift",
- "Verfahren",
- "Mandantenbank",
- "Zahlungsträger",
- "Indiv. Feld 1",
- "Indiv. Feld 2",
- "Indiv. Feld 3",
- "Indiv. Feld 4",
- "Indiv. Feld 5",
- "Indiv. Feld 6",
- "Indiv. Feld 7",
- "Indiv. Feld 8",
- "Indiv. Feld 9",
- "Indiv. Feld 10",
- "Indiv. Feld 11",
- "Indiv. Feld 12",
- "Indiv. Feld 13",
- "Indiv. Feld 14",
- "Indiv. Feld 15",
- "Abweichende Anrede (Rechnungsadresse)",
- "Adressart (Rechnungsadresse)",
- "Straße (Rechnungsadresse)",
- "Postfach (Rechnungsadresse)",
- "Postleitzahl (Rechnungsadresse)",
- "Ort (Rechnungsadresse)",
- "Land (Rechnungsadresse)",
- "Versandzusatz (Rechnungsadresse)",
- "Adresszusatz (Rechnungsadresse)",
- "Abw. Zustellbezeichnung 1 (Rechnungsadresse)",
- "Abw. Zustellbezeichnung 2 (Rechnungsadresse)",
- "Adresse Gültig von (Rechnungsadresse)",
- "Adresse Gültig bis (Rechnungsadresse)",
- "Bankleitzahl 6",
- "Bankbezeichnung 6",
- "Bankkonto-Nummer 6",
- "Länderkennzeichen 6",
- "IBAN 6",
- "Leerfeld 7",
- "SWIFT-Code 6",
- "Abw. Kontoinhaber 6",
- "Kennz. Haupt-Bankverb. 6",
- "Bankverb 6 gültig von",
- "Bankverb 6 gültig bis",
- "Bankleitzahl 7",
- "Bankbezeichnung 7",
- "Bankkonto-Nummer 7",
- "Länderkennzeichen 7",
- "IBAN 7",
- "Leerfeld 8",
- "SWIFT-Code 7",
- "Abw. Kontoinhaber 7",
- "Kennz. Haupt-Bankverb. 7",
- "Bankverb 7 gültig von",
- "Bankverb 7 gültig bis",
- "Bankleitzahl 8",
- "Bankbezeichnung 8",
- "Bankkonto-Nummer 8",
- "Länderkennzeichen 8",
- "IBAN 8",
- "Leerfeld 9",
- "SWIFT-Code 8",
- "Abw. Kontoinhaber 8",
- "Kennz. Haupt-Bankverb. 8",
- "Bankverb 8 gültig von",
- "Bankverb 8 gültig bis",
- "Bankleitzahl 9",
- "Bankbezeichnung 9",
- "Bankkonto-Nummer 9",
- "Länderkennzeichen 9",
- "IBAN 9",
- "Leerfeld 10",
- "SWIFT-Code 9",
- "Abw. Kontoinhaber 9",
- "Kennz. Haupt-Bankverb. 9",
- "Bankverb 9 gültig von",
- "Bankverb 9 gültig bis",
- "Bankleitzahl 10",
- "Bankbezeichnung 10",
- "Bankkonto-Nummer 10",
- "Länderkennzeichen 10",
- "IBAN 10",
- "Leerfeld 11",
- "SWIFT-Code 10",
- "Abw. Kontoinhaber 10",
- "Kennz. Haupt-Bankverb. 10",
- "Bankverb 10 gültig von",
- "Bankverb 10 gültig bis",
- "Nummer Fremdsystem",
- "Insolvent",
- "SEPA-Mandatsreferenz 1",
- "SEPA-Mandatsreferenz 2",
- "SEPA-Mandatsreferenz 3",
- "SEPA-Mandatsreferenz 4",
- "SEPA-Mandatsreferenz 5",
- "SEPA-Mandatsreferenz 6",
- "SEPA-Mandatsreferenz 7",
- "SEPA-Mandatsreferenz 8",
- "SEPA-Mandatsreferenz 9",
- "SEPA-Mandatsreferenz 10",
- "Verknüpftes OPOS-Konto",
- "Mahnsperre bis",
- "Lastschriftsperre bis",
- "Zahlungssperre bis",
- "Gebührenberechnung",
- "Mahngebühr 1",
- "Mahngebühr 2",
- "Mahngebühr 3",
- "Pauschalberechnung",
- "Verzugspauschale 1",
- "Verzugspauschale 2",
- "Verzugspauschale 3",
- "Alternativer Suchname",
- "Status",
- "Anschrift manuell geändert (Korrespondenzadresse)",
- "Anschrift individuell (Korrespondenzadresse)",
- "Anschrift manuell geändert (Rechnungsadresse)",
- "Anschrift individuell (Rechnungsadresse)",
- "Fristberechnung bei Debitor",
- "Mahnfrist 1",
- "Mahnfrist 2",
- "Mahnfrist 3",
- "Letzte Frist",
-]
-
-ACCOUNT_NAME_COLUMNS = [
- # Account number
- "Konto",
- # Account name
- "Kontenbeschriftung",
- # Language of the account name
- # "de-DE" or "en-GB"
- "Sprach-ID",
-]
-
-
-class DataCategory:
-
- """Field of the CSV Header."""
-
- DEBTORS_CREDITORS = "16"
- ACCOUNT_NAMES = "20"
- TRANSACTIONS = "21"
- POSTING_TEXT_CONSTANTS = "67"
-
-
-class FormatName:
-
- """Field of the CSV Header, corresponds to DataCategory."""
-
- DEBTORS_CREDITORS = "Debitoren/Kreditoren"
- ACCOUNT_NAMES = "Kontenbeschriftungen"
- TRANSACTIONS = "Buchungsstapel"
- POSTING_TEXT_CONSTANTS = "Buchungstextkonstanten"
-
-
-class Transactions:
- DATA_CATEGORY = DataCategory.TRANSACTIONS
- FORMAT_NAME = FormatName.TRANSACTIONS
- FORMAT_VERSION = "9"
- COLUMNS = TRANSACTION_COLUMNS
-
-
-class DebtorsCreditors:
- DATA_CATEGORY = DataCategory.DEBTORS_CREDITORS
- FORMAT_NAME = FormatName.DEBTORS_CREDITORS
- FORMAT_VERSION = "5"
- COLUMNS = DEBTOR_CREDITOR_COLUMNS
-
-
-class AccountNames:
- DATA_CATEGORY = DataCategory.ACCOUNT_NAMES
- FORMAT_NAME = FormatName.ACCOUNT_NAMES
- FORMAT_VERSION = "2"
- COLUMNS = ACCOUNT_NAME_COLUMNS
diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py
deleted file mode 100644
index d4e9c27..0000000
--- a/erpnext/regional/germany/utils/datev/datev_csv.py
+++ /dev/null
@@ -1,184 +0,0 @@
-import datetime
-import zipfile
-from csv import QUOTE_NONNUMERIC
-from io import BytesIO
-
-import frappe
-import pandas as pd
-from frappe import _
-
-from .datev_constants import DataCategory
-
-
-def get_datev_csv(data, filters, csv_class):
- """
- 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
- 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"])
-
- 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)
-
- 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,
- )
-
- data = data.encode("latin_1", errors="replace")
-
- header = get_header(filters, csv_class)
- header = ";".join(header).encode("latin_1", errors="replace")
-
- # 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
- datetime.datetime.now().strftime("%Y%m%d%H%M%S") + "000",
- # 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)
- frappe.utils.formatdate(filters.get("fiscal_year_start"), "yyyyMMdd"),
- # N = Length of account numbers (Sachkontenlänge)
- str(filters.get("account_number_length", 4)),
- # 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)
- "",
- ]
- return header
-
-
-def zip_and_download(zip_filename, csv_files):
- """
- Put CSV files in a zip archive and send that to the client.
-
- Params:
- zip_filename Name of the zip file
- csv_files list of dicts [{'file_name': 'my_file.csv', 'csv_data': 'comma,separated,values'}]
- """
- zip_buffer = BytesIO()
-
- 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()
-
- frappe.response["filecontent"] = zip_buffer.getvalue()
- frappe.response["filename"] = zip_filename
- frappe.response["type"] = "binary"
diff --git a/erpnext/regional/report/datev/__init__.py b/erpnext/regional/report/datev/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/regional/report/datev/__init__.py
+++ /dev/null
diff --git a/erpnext/regional/report/datev/datev.js b/erpnext/regional/report/datev/datev.js
deleted file mode 100644
index 03c729e..0000000
--- a/erpnext/regional/report/datev/datev.js
+++ /dev/null
@@ -1,56 +0,0 @@
-frappe.query_reports["DATEV"] = {
- "filters": [
- {
- "fieldname": "company",
- "label": __("Company"),
- "fieldtype": "Link",
- "options": "Company",
- "default": frappe.defaults.get_user_default("Company") || frappe.defaults.get_global_default("Company"),
- "reqd": 1
- },
- {
- "fieldname": "from_date",
- "label": __("From Date"),
- "default": moment().subtract(1, 'month').startOf('month').format(),
- "fieldtype": "Date",
- "reqd": 1
- },
- {
- "fieldname": "to_date",
- "label": __("To Date"),
- "default": moment().subtract(1, 'month').endOf('month').format(),
- "fieldtype": "Date",
- "reqd": 1
- },
- {
- "fieldname": "voucher_type",
- "label": __("Voucher Type"),
- "fieldtype": "Select",
- "options": "\nSales Invoice\nPurchase Invoice\nPayment Entry\nExpense Claim\nPayroll Entry\nBank Reconciliation\nAsset\nStock Entry"
- }
- ],
- onload: function(query_report) {
- let company = frappe.query_report.get_filter_value('company');
- frappe.db.exists('DATEV Settings', company).then((settings_exist) => {
- if (!settings_exist) {
- frappe.confirm(__('DATEV Settings for your Company are missing. Would you like to create them now?'),
- () => frappe.new_doc('DATEV Settings', {'company': company})
- );
- }
- });
-
- query_report.page.add_menu_item(__("Download DATEV File"), () => {
- const filters = encodeURIComponent(
- JSON.stringify(
- query_report.get_values()
- )
- );
- window.open(`/api/method/erpnext.regional.report.datev.datev.download_datev_csv?filters=${filters}`);
- });
-
- query_report.page.add_menu_item(__("Change DATEV Settings"), () => {
- let company = frappe.query_report.get_filter_value('company'); // read company from filters again – it might have changed by now.
- frappe.set_route('Form', 'DATEV Settings', company);
- });
- }
-};
diff --git a/erpnext/regional/report/datev/datev.json b/erpnext/regional/report/datev/datev.json
deleted file mode 100644
index 94e3960..0000000
--- a/erpnext/regional/report/datev/datev.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "add_total_row": 0,
- "columns": [],
- "creation": "2019-04-24 08:45:16.650129",
- "disable_prepared_report": 0,
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "filters": [],
- "idx": 0,
- "is_standard": "Yes",
- "modified": "2021-04-06 12:23:00.379517",
- "modified_by": "Administrator",
- "module": "Regional",
- "name": "DATEV",
- "owner": "Administrator",
- "prepared_report": 0,
- "ref_doctype": "GL Entry",
- "report_name": "DATEV",
- "report_type": "Script Report",
- "roles": []
-}
\ No newline at end of file
diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py
deleted file mode 100644
index 2d888a8..0000000
--- a/erpnext/regional/report/datev/datev.py
+++ /dev/null
@@ -1,570 +0,0 @@
-"""
-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.
-"""
-
-import json
-
-import frappe
-from frappe import _
-
-from erpnext.accounts.utils import get_fiscal_year
-from erpnext.regional.germany.utils.datev.datev_constants import (
- AccountNames,
- DebtorsCreditors,
- Transactions,
-)
-from erpnext.regional.germany.utils.datev.datev_csv import get_datev_csv, zip_and_download
-
-COLUMNS = [
- {
- "label": "Umsatz (ohne Soll/Haben-Kz)",
- "fieldname": "Umsatz (ohne Soll/Haben-Kz)",
- "fieldtype": "Currency",
- "width": 100,
- },
- {
- "label": "Soll/Haben-Kennzeichen",
- "fieldname": "Soll/Haben-Kennzeichen",
- "fieldtype": "Data",
- "width": 100,
- },
- {"label": "Konto", "fieldname": "Konto", "fieldtype": "Data", "width": 100},
- {
- "label": "Gegenkonto (ohne BU-Schlüssel)",
- "fieldname": "Gegenkonto (ohne BU-Schlüssel)",
- "fieldtype": "Data",
- "width": 100,
- },
- {"label": "BU-Schlüssel", "fieldname": "BU-Schlüssel", "fieldtype": "Data", "width": 100},
- {"label": "Belegdatum", "fieldname": "Belegdatum", "fieldtype": "Date", "width": 100},
- {"label": "Belegfeld 1", "fieldname": "Belegfeld 1", "fieldtype": "Data", "width": 150},
- {"label": "Buchungstext", "fieldname": "Buchungstext", "fieldtype": "Text", "width": 300},
- {
- "label": "Beleginfo - Art 1",
- "fieldname": "Beleginfo - Art 1",
- "fieldtype": "Link",
- "options": "DocType",
- "width": 100,
- },
- {
- "label": "Beleginfo - Inhalt 1",
- "fieldname": "Beleginfo - Inhalt 1",
- "fieldtype": "Dynamic Link",
- "options": "Beleginfo - Art 1",
- "width": 150,
- },
- {
- "label": "Beleginfo - Art 2",
- "fieldname": "Beleginfo - Art 2",
- "fieldtype": "Link",
- "options": "DocType",
- "width": 100,
- },
- {
- "label": "Beleginfo - Inhalt 2",
- "fieldname": "Beleginfo - Inhalt 2",
- "fieldtype": "Dynamic Link",
- "options": "Beleginfo - Art 2",
- "width": 150,
- },
- {
- "label": "Beleginfo - Art 3",
- "fieldname": "Beleginfo - Art 3",
- "fieldtype": "Link",
- "options": "DocType",
- "width": 100,
- },
- {
- "label": "Beleginfo - Inhalt 3",
- "fieldname": "Beleginfo - Inhalt 3",
- "fieldtype": "Dynamic Link",
- "options": "Beleginfo - Art 3",
- "width": 150,
- },
- {
- "label": "Beleginfo - Art 4",
- "fieldname": "Beleginfo - Art 4",
- "fieldtype": "Data",
- "width": 100,
- },
- {
- "label": "Beleginfo - Inhalt 4",
- "fieldname": "Beleginfo - Inhalt 4",
- "fieldtype": "Data",
- "width": 150,
- },
- {
- "label": "Beleginfo - Art 5",
- "fieldname": "Beleginfo - Art 5",
- "fieldtype": "Data",
- "width": 150,
- },
- {
- "label": "Beleginfo - Inhalt 5",
- "fieldname": "Beleginfo - Inhalt 5",
- "fieldtype": "Data",
- "width": 100,
- },
- {
- "label": "Beleginfo - Art 6",
- "fieldname": "Beleginfo - Art 6",
- "fieldtype": "Data",
- "width": 150,
- },
- {
- "label": "Beleginfo - Inhalt 6",
- "fieldname": "Beleginfo - Inhalt 6",
- "fieldtype": "Date",
- "width": 100,
- },
- {"label": "Fälligkeit", "fieldname": "Fälligkeit", "fieldtype": "Date", "width": 100},
-]
-
-
-def execute(filters=None):
- """Entry point for frappe."""
- data = []
- if filters and validate(filters):
- fn = "temporary_against_account_number"
- filters[fn] = frappe.get_value("DATEV Settings", filters.get("company"), fn)
- data = get_transactions(filters, as_dict=0)
-
- return COLUMNS, data
-
-
-def validate(filters):
- """Make sure all mandatory filters and settings are present."""
- company = filters.get("company")
- if not company:
- frappe.throw(_("<b>Company</b> is a mandatory filter."))
-
- from_date = filters.get("from_date")
- if not from_date:
- frappe.throw(_("<b>From Date</b> is a mandatory filter."))
-
- to_date = filters.get("to_date")
- if not to_date:
- frappe.throw(_("<b>To Date</b> is a mandatory filter."))
-
- validate_fiscal_year(from_date, to_date, company)
-
- if not frappe.db.exists("DATEV Settings", filters.get("company")):
- msg = "Please create DATEV Settings for Company {}".format(filters.get("company"))
- frappe.log_error(msg, title="DATEV Settings missing")
- return False
-
- return True
-
-
-def validate_fiscal_year(from_date, to_date, company):
- from_fiscal_year = get_fiscal_year(date=from_date, company=company)
- to_fiscal_year = get_fiscal_year(date=to_date, company=company)
- if from_fiscal_year != to_fiscal_year:
- frappe.throw(_("Dates {} and {} are not in the same fiscal year.").format(from_date, to_date))
-
-
-def get_transactions(filters, as_dict=1):
- def run(params_method, filters):
- extra_fields, extra_joins, extra_filters = params_method(filters)
- return run_query(filters, extra_fields, extra_joins, extra_filters, as_dict=as_dict)
-
- def sort_by(row):
- # "Belegdatum" is in the fifth column when list format is used
- return row["Belegdatum" if as_dict else 5]
-
- type_map = {
- # specific query methods for some voucher types
- "Payment Entry": get_payment_entry_params,
- "Sales Invoice": get_sales_invoice_params,
- "Purchase Invoice": get_purchase_invoice_params,
- }
-
- only_voucher_type = filters.get("voucher_type")
- transactions = []
-
- for voucher_type, get_voucher_params in type_map.items():
- if only_voucher_type and only_voucher_type != voucher_type:
- continue
-
- transactions.extend(run(params_method=get_voucher_params, filters=filters))
-
- if not only_voucher_type or only_voucher_type not in type_map:
- # generic query method for all other voucher types
- filters["exclude_voucher_types"] = type_map.keys()
- transactions.extend(run(params_method=get_generic_params, filters=filters))
-
- return sorted(transactions, key=sort_by)
-
-
-def get_payment_entry_params(filters):
- extra_fields = """
- , 'Zahlungsreferenz' as 'Beleginfo - Art 5'
- , pe.reference_no as 'Beleginfo - Inhalt 5'
- , 'Buchungstag' as 'Beleginfo - Art 6'
- , pe.reference_date as 'Beleginfo - Inhalt 6'
- , '' as 'Fälligkeit'
- """
-
- extra_joins = """
- LEFT JOIN `tabPayment Entry` pe
- ON gl.voucher_no = pe.name
- """
-
- extra_filters = """
- AND gl.voucher_type = 'Payment Entry'
- """
-
- return extra_fields, extra_joins, extra_filters
-
-
-def get_sales_invoice_params(filters):
- extra_fields = """
- , '' as 'Beleginfo - Art 5'
- , '' as 'Beleginfo - Inhalt 5'
- , '' as 'Beleginfo - Art 6'
- , '' as 'Beleginfo - Inhalt 6'
- , si.due_date as 'Fälligkeit'
- """
-
- extra_joins = """
- LEFT JOIN `tabSales Invoice` si
- ON gl.voucher_no = si.name
- """
-
- extra_filters = """
- AND gl.voucher_type = 'Sales Invoice'
- """
-
- return extra_fields, extra_joins, extra_filters
-
-
-def get_purchase_invoice_params(filters):
- extra_fields = """
- , 'Lieferanten-Rechnungsnummer' as 'Beleginfo - Art 5'
- , pi.bill_no as 'Beleginfo - Inhalt 5'
- , 'Lieferanten-Rechnungsdatum' as 'Beleginfo - Art 6'
- , pi.bill_date as 'Beleginfo - Inhalt 6'
- , pi.due_date as 'Fälligkeit'
- """
-
- extra_joins = """
- LEFT JOIN `tabPurchase Invoice` pi
- ON gl.voucher_no = pi.name
- """
-
- extra_filters = """
- AND gl.voucher_type = 'Purchase Invoice'
- """
-
- return extra_fields, extra_joins, extra_filters
-
-
-def get_generic_params(filters):
- # produce empty fields so all rows will have the same length
- extra_fields = """
- , '' as 'Beleginfo - Art 5'
- , '' as 'Beleginfo - Inhalt 5'
- , '' as 'Beleginfo - Art 6'
- , '' as 'Beleginfo - Inhalt 6'
- , '' as 'Fälligkeit'
- """
- extra_joins = ""
-
- if filters.get("exclude_voucher_types"):
- # exclude voucher types that are queried by a dedicated method
- exclude = "({})".format(
- ", ".join("'{}'".format(key) for key in filters.get("exclude_voucher_types"))
- )
- extra_filters = "AND gl.voucher_type NOT IN {}".format(exclude)
-
- # if voucher type filter is set, allow only this type
- if filters.get("voucher_type"):
- extra_filters += " AND gl.voucher_type = %(voucher_type)s"
-
- return extra_fields, extra_joins, extra_filters
-
-
-def run_query(filters, extra_fields, extra_joins, extra_filters, as_dict=1):
- """
- 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]
- """
- query = """
- 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 */
- acc.account_number as 'Konto',
-
- /* against number or, if empty, party against number */
- %(temporary_against_account_number)s as 'Gegenkonto (ohne BU-Schlüssel)',
-
- '' as 'BU-Schlüssel',
-
- gl.posting_date as 'Belegdatum',
- gl.voucher_no as 'Belegfeld 1',
- REPLACE(LEFT(gl.remarks, 60), '\n', ' ') 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',
- gl.party_type as 'Beleginfo - Art 3',
- gl.party as 'Beleginfo - Inhalt 3',
- case gl.party_type when 'Customer' then 'Debitorennummer' when 'Supplier' then 'Kreditorennummer' else NULL end as 'Beleginfo - Art 4',
- par.debtor_creditor_number as 'Beleginfo - Inhalt 4'
-
- {extra_fields}
-
- FROM `tabGL Entry` gl
-
- /* Kontonummer */
- LEFT JOIN `tabAccount` acc
- ON gl.account = acc.name
-
- LEFT JOIN `tabParty Account` par
- ON par.parent = gl.party
- AND par.parenttype = gl.party_type
- AND par.company = %(company)s
-
- {extra_joins}
-
- WHERE gl.company = %(company)s
- AND DATE(gl.posting_date) >= %(from_date)s
- AND DATE(gl.posting_date) <= %(to_date)s
-
- {extra_filters}
-
- ORDER BY 'Belegdatum', gl.voucher_no""".format(
- extra_fields=extra_fields, extra_joins=extra_joins, extra_filters=extra_filters
- )
-
- gl_entries = frappe.db.sql(query, filters, as_dict=as_dict)
-
- return gl_entries
-
-
-def get_customers(filters):
- """
- Get a list of Customers.
-
- Arguments:
- filters -- dict of filters to be passed to the sql query
- """
- return frappe.db.sql(
- """
- SELECT
-
- par.debtor_creditor_number as 'Konto',
- CASE cus.customer_type
- WHEN 'Company' THEN cus.customer_name
- ELSE null
- END as 'Name (Adressatentyp Unternehmen)',
- CASE cus.customer_type
- WHEN 'Individual' THEN TRIM(SUBSTR(cus.customer_name, LOCATE(' ', cus.customer_name)))
- ELSE null
- END as 'Name (Adressatentyp natürl. Person)',
- CASE cus.customer_type
- WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(cus.customer_name, ' ', 1), ' ', -1)
- ELSE null
- END as 'Vorname (Adressatentyp natürl. Person)',
- CASE cus.customer_type
- WHEN 'Individual' THEN '1'
- WHEN 'Company' THEN '2'
- ELSE '0'
- END as 'Adressatentyp',
- adr.address_line1 as 'Straße',
- adr.pincode as 'Postleitzahl',
- adr.city as 'Ort',
- UPPER(country.code) as 'Land',
- adr.address_line2 as 'Adresszusatz',
- adr.email_id as 'E-Mail',
- adr.phone as 'Telefon',
- adr.fax as 'Fax',
- cus.website as 'Internet',
- cus.tax_id as 'Steuernummer'
-
- FROM `tabCustomer` cus
-
- left join `tabParty Account` par
- on par.parent = cus.name
- and par.parenttype = 'Customer'
- and par.company = %(company)s
-
- left join `tabDynamic Link` dyn_adr
- on dyn_adr.link_name = cus.name
- and dyn_adr.link_doctype = 'Customer'
- and dyn_adr.parenttype = 'Address'
-
- left join `tabAddress` adr
- on adr.name = dyn_adr.parent
- and adr.is_primary_address = '1'
-
- left join `tabCountry` country
- on country.name = adr.country
-
- WHERE adr.is_primary_address = '1'
- """,
- filters,
- as_dict=1,
- )
-
-
-def get_suppliers(filters):
- """
- Get a list of Suppliers.
-
- Arguments:
- filters -- dict of filters to be passed to the sql query
- """
- return frappe.db.sql(
- """
- SELECT
-
- par.debtor_creditor_number as 'Konto',
- CASE sup.supplier_type
- WHEN 'Company' THEN sup.supplier_name
- ELSE null
- END as 'Name (Adressatentyp Unternehmen)',
- CASE sup.supplier_type
- WHEN 'Individual' THEN TRIM(SUBSTR(sup.supplier_name, LOCATE(' ', sup.supplier_name)))
- ELSE null
- END as 'Name (Adressatentyp natürl. Person)',
- CASE sup.supplier_type
- WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(sup.supplier_name, ' ', 1), ' ', -1)
- ELSE null
- END as 'Vorname (Adressatentyp natürl. Person)',
- CASE sup.supplier_type
- WHEN 'Individual' THEN '1'
- WHEN 'Company' THEN '2'
- ELSE '0'
- END as 'Adressatentyp',
- adr.address_line1 as 'Straße',
- adr.pincode as 'Postleitzahl',
- adr.city as 'Ort',
- UPPER(country.code) as 'Land',
- adr.address_line2 as 'Adresszusatz',
- adr.email_id as 'E-Mail',
- adr.phone as 'Telefon',
- adr.fax as 'Fax',
- sup.website as 'Internet',
- sup.tax_id as 'Steuernummer',
- case sup.on_hold when 1 then sup.release_date else null end as 'Zahlungssperre bis'
-
- FROM `tabSupplier` sup
-
- left join `tabParty Account` par
- on par.parent = sup.name
- and par.parenttype = 'Supplier'
- and par.company = %(company)s
-
- left join `tabDynamic Link` dyn_adr
- on dyn_adr.link_name = sup.name
- and dyn_adr.link_doctype = 'Supplier'
- and dyn_adr.parenttype = 'Address'
-
- left join `tabAddress` adr
- on adr.name = dyn_adr.parent
- and adr.is_primary_address = '1'
-
- left join `tabCountry` country
- on country.name = adr.country
-
- WHERE adr.is_primary_address = '1'
- """,
- filters,
- as_dict=1,
- )
-
-
-def get_account_names(filters):
- return frappe.db.sql(
- """
- SELECT
-
- account_number as 'Konto',
- LEFT(account_name, 40) as 'Kontenbeschriftung',
- 'de-DE' as 'Sprach-ID'
-
- FROM `tabAccount`
- WHERE company = %(company)s
- AND is_group = 0
- AND account_number != ''
- """,
- filters,
- as_dict=1,
- )
-
-
-@frappe.whitelist()
-def download_datev_csv(filters):
- """
- 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, str):
- filters = json.loads(filters)
-
- validate(filters)
- company = filters.get("company")
-
- fiscal_year = get_fiscal_year(date=filters.get("from_date"), company=company)
- filters["fiscal_year_start"] = fiscal_year[1]
-
- # set chart of accounts used
- coa = frappe.get_value("Company", company, "chart_of_accounts")
- filters["skr"] = "04" if "SKR04" in coa else ("03" if "SKR03" in coa else "")
-
- datev_settings = frappe.get_doc("DATEV Settings", company)
- filters["account_number_length"] = datev_settings.account_number_length
- filters["temporary_against_account_number"] = datev_settings.temporary_against_account_number
-
- transactions = get_transactions(filters)
- account_names = get_account_names(filters)
- customers = get_customers(filters)
- suppliers = get_suppliers(filters)
-
- zip_name = "{} DATEV.zip".format(frappe.utils.datetime.date.today())
- zip_and_download(
- zip_name,
- [
- {
- "file_name": "EXTF_Buchungsstapel.csv",
- "csv_data": get_datev_csv(transactions, filters, csv_class=Transactions),
- },
- {
- "file_name": "EXTF_Kontenbeschriftungen.csv",
- "csv_data": get_datev_csv(account_names, filters, csv_class=AccountNames),
- },
- {
- "file_name": "EXTF_Kunden.csv",
- "csv_data": get_datev_csv(customers, filters, csv_class=DebtorsCreditors),
- },
- {
- "file_name": "EXTF_Lieferanten.csv",
- "csv_data": get_datev_csv(suppliers, filters, csv_class=DebtorsCreditors),
- },
- ],
- )
diff --git a/erpnext/regional/report/datev/test_datev.py b/erpnext/regional/report/datev/test_datev.py
deleted file mode 100644
index 0df8c06..0000000
--- a/erpnext/regional/report/datev/test_datev.py
+++ /dev/null
@@ -1,252 +0,0 @@
-import zipfile
-from io import BytesIO
-from unittest import TestCase
-
-import frappe
-from frappe.utils import cstr, now_datetime, today
-
-from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
-from erpnext.regional.germany.utils.datev.datev_constants import (
- AccountNames,
- DebtorsCreditors,
- Transactions,
-)
-from erpnext.regional.germany.utils.datev.datev_csv import get_datev_csv, get_header
-from erpnext.regional.report.datev.datev import (
- download_datev_csv,
- get_account_names,
- get_customers,
- get_suppliers,
- get_transactions,
-)
-
-
-def make_company(company_name, abbr):
- if not frappe.db.exists("Company", company_name):
- company = frappe.get_doc(
- {
- "doctype": "Company",
- "company_name": company_name,
- "abbr": abbr,
- "default_currency": "EUR",
- "country": "Germany",
- "create_chart_of_accounts_based_on": "Standard Template",
- "chart_of_accounts": "SKR04 mit Kontonummern",
- }
- )
- company.insert()
- else:
- company = frappe.get_doc("Company", company_name)
-
- # indempotent
- company.create_default_warehouses()
-
- if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}):
- company.create_default_cost_center()
-
- company.save()
- return company
-
-
-def setup_fiscal_year():
- fiscal_year = None
- year = cstr(now_datetime().year)
- if not frappe.db.get_value("Fiscal Year", {"year": year}, "name"):
- try:
- fiscal_year = frappe.get_doc(
- {
- "doctype": "Fiscal Year",
- "year": year,
- "year_start_date": "{0}-01-01".format(year),
- "year_end_date": "{0}-12-31".format(year),
- }
- )
- fiscal_year.insert()
- except frappe.NameError:
- pass
-
- if fiscal_year:
- fiscal_year.set_as_default()
-
-
-def make_customer_with_account(customer_name, company):
- acc_name = frappe.db.get_value(
- "Account", {"account_name": customer_name, "company": company.name}, "name"
- )
-
- if not acc_name:
- acc = frappe.get_doc(
- {
- "doctype": "Account",
- "parent_account": "1 - Forderungen aus Lieferungen und Leistungen - _TG",
- "account_name": customer_name,
- "company": company.name,
- "account_type": "Receivable",
- "account_number": "10001",
- }
- )
- acc.insert()
- acc_name = acc.name
-
- if not frappe.db.exists("Customer", customer_name):
- customer = frappe.get_doc(
- {
- "doctype": "Customer",
- "customer_name": customer_name,
- "customer_type": "Company",
- "accounts": [{"company": company.name, "account": acc_name}],
- }
- )
- customer.insert()
- else:
- customer = frappe.get_doc("Customer", customer_name)
-
- return customer
-
-
-def make_item(item_code, company):
- warehouse_name = frappe.db.get_value(
- "Warehouse", {"warehouse_name": "Stores", "company": company.name}, "name"
- )
-
- if not frappe.db.exists("Item", item_code):
- item = frappe.get_doc(
- {
- "doctype": "Item",
- "item_code": item_code,
- "item_name": item_code,
- "description": item_code,
- "item_group": "All Item Groups",
- "is_stock_item": 0,
- "is_purchase_item": 0,
- "is_customer_provided_item": 0,
- "item_defaults": [{"default_warehouse": warehouse_name, "company": company.name}],
- }
- )
- item.insert()
- else:
- item = frappe.get_doc("Item", item_code)
- return item
-
-
-def make_datev_settings(company):
- if not frappe.db.exists("DATEV Settings", company.name):
- frappe.get_doc(
- {
- "doctype": "DATEV Settings",
- "client": company.name,
- "client_number": "12345",
- "consultant_number": "67890",
- "temporary_against_account_number": "9999",
- }
- ).insert()
-
-
-class TestDatev(TestCase):
- def setUp(self):
- self.company = make_company("_Test GmbH", "_TG")
- self.customer = make_customer_with_account("_Test Kunde GmbH", self.company)
- self.filters = {
- "company": self.company.name,
- "from_date": today(),
- "to_date": today(),
- "temporary_against_account_number": "9999",
- }
-
- make_datev_settings(self.company)
- item = make_item("_Test Item", self.company)
- setup_fiscal_year()
-
- warehouse = frappe.db.get_value(
- "Item Default", {"parent": item.name, "company": self.company.name}, "default_warehouse"
- )
-
- income_account = frappe.db.get_value(
- "Account", {"account_number": "4200", "company": self.company.name}, "name"
- )
-
- tax_account = frappe.db.get_value(
- "Account", {"account_number": "3806", "company": self.company.name}, "name"
- )
-
- si = create_sales_invoice(
- company=self.company.name,
- customer=self.customer.name,
- currency=self.company.default_currency,
- debit_to=self.customer.accounts[0].account,
- income_account="4200 - Erlöse - _TG",
- expense_account="6990 - Herstellungskosten - _TG",
- cost_center=self.company.cost_center,
- warehouse=warehouse,
- item=item.name,
- do_not_save=1,
- )
-
- si.append(
- "taxes",
- {
- "charge_type": "On Net Total",
- "account_head": tax_account,
- "description": "Umsatzsteuer 19 %",
- "rate": 19,
- "cost_center": self.company.cost_center,
- },
- )
-
- si.cost_center = self.company.cost_center
-
- si.save()
- si.submit()
-
- def test_columns(self):
- def is_subset(get_data, allowed_keys):
- """
- Validate that the dict contains only allowed keys.
-
- Params:
- get_data -- Function that returns a list of dicts.
- allowed_keys -- List of allowed keys
- """
- data = get_data(self.filters)
- if data == []:
- # No data and, therefore, no columns is okay
- return True
- actual_set = set(data[0].keys())
- # allowed set must be interpreted as unicode to match the actual set
- allowed_set = set({frappe.as_unicode(key) for key in allowed_keys})
- return actual_set.issubset(allowed_set)
-
- self.assertTrue(is_subset(get_transactions, Transactions.COLUMNS))
- self.assertTrue(is_subset(get_customers, DebtorsCreditors.COLUMNS))
- self.assertTrue(is_subset(get_suppliers, DebtorsCreditors.COLUMNS))
- self.assertTrue(is_subset(get_account_names, AccountNames.COLUMNS))
-
- def test_header(self):
- self.assertTrue(Transactions.DATA_CATEGORY in get_header(self.filters, Transactions))
- self.assertTrue(AccountNames.DATA_CATEGORY in get_header(self.filters, AccountNames))
- self.assertTrue(DebtorsCreditors.DATA_CATEGORY in get_header(self.filters, DebtorsCreditors))
-
- def test_csv(self):
- test_data = [
- {
- "Umsatz (ohne Soll/Haben-Kz)": 100,
- "Soll/Haben-Kennzeichen": "H",
- "Kontonummer": "4200",
- "Gegenkonto (ohne BU-Schlüssel)": "10000",
- "Belegdatum": today(),
- "Buchungstext": "No remark",
- "Beleginfo - Art 1": "Sales Invoice",
- "Beleginfo - Inhalt 1": "SINV-0001",
- }
- ]
- get_datev_csv(data=test_data, filters=self.filters, csv_class=Transactions)
-
- def test_download(self):
- """Assert that the returned file is a ZIP file."""
- download_datev_csv(self.filters)
-
- # zipfile.is_zipfile() expects a file-like object
- zip_buffer = BytesIO()
- zip_buffer.write(frappe.response["filecontent"])
-
- self.assertTrue(zipfile.is_zipfile(zip_buffer))