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
This commit is contained in:
parent
143067f5a7
commit
6453fb4cea
@ -262,8 +262,6 @@ erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 #17-01-2022
|
||||
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_shopify_doctypes
|
||||
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.add_cost_center_in_loans
|
||||
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
|
||||
erpnext.patches.v13_0.create_gst_custom_fields_in_quotation
|
||||
|
@ -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", "")
|
@ -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()
|
13
erpnext/patches/v14_0/delete_datev_doctypes.py
Normal file
13
erpnext/patches/v14_0/delete_datev_doctypes.py
Normal file
@ -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)
|
@ -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");
|
||||
}
|
||||
});
|
@ -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
|
||||
}
|
@ -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
|
@ -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
|
@ -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()
|
@ -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
|
@ -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"
|
@ -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);
|
||||
});
|
||||
}
|
||||
};
|
@ -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": []
|
||||
}
|
@ -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),
|
||||
},
|
||||
],
|
||||
)
|
@ -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))
|
Loading…
x
Reference in New Issue
Block a user