Merge pull request #25238 from alyf-de/datev_fixes

feat: Improve DATEV export
This commit is contained in:
Rushabh Mehta 2021-05-04 15:38:57 +05:30 committed by GitHub
commit 5fc4f1e37d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 213 additions and 134 deletions

View File

@ -1,87 +1,39 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2014-08-29 16:02:39.740505",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"actions": [],
"creation": "2014-08-29 16:02:39.740505",
"doctype": "DocType",
"editable_grid": 1,
"field_order": [
"company",
"account"
],
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"fieldname": "account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Account",
"options": "Account"
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:28:03.348246",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Party Account",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_seen": 0
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-04-07 18:13:08.833822",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Party Account",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@ -774,3 +774,5 @@ erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
erpnext.patches.v13_0.update_shipment_status
erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting
erpnext.patches.v13_0.germany_make_custom_fields
erpnext.patches.v13_0.germany_fill_debtor_creditor_number

View File

@ -0,0 +1,31 @@
# Copyright (c) 2019, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
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', '')

View File

@ -0,0 +1,20 @@
# Copyright (c) 2019, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
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()

View File

@ -1,11 +1,24 @@
import os
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')):
@ -16,4 +29,4 @@ def add_custom_roles_for_reports():
dict(role='Accounts User'),
dict(role='Accounts Manager')
]
)).insert()
)).insert()

View File

@ -56,10 +56,10 @@ def get_datev_csv(data, filters, csv_class):
)
if not six.PY2:
data = data.encode('latin_1')
data = data.encode('latin_1', errors='replace')
header = get_header(filters, csv_class)
header = ';'.join(header).encode('latin_1')
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.

View File

@ -3,9 +3,9 @@
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.
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.
all required columns. Used to import the data into the DATEV Software.
"""
from __future__ import unicode_literals
@ -88,6 +88,32 @@ COLUMNS = [
"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
}
]
@ -120,10 +146,8 @@ def validate(filters):
validate_fiscal_year(from_date, to_date, company)
if not frappe.db.exists('DATEV Settings', filters.get('company')):
frappe.log_error(_('Please create {} for Company {}.').format(
'<a href="desk#List/DATEV%20Settings/List">{}</a>'.format(_('DATEV Settings')),
frappe.bold(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
@ -169,7 +193,11 @@ def get_transactions(filters, as_dict=1):
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.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'
FROM `tabGL Entry` gl
@ -177,6 +205,19 @@ def get_transactions(filters, as_dict=1):
left join `tabAccount` acc
on gl.account = acc.name
left join `tabCustomer` cus
on gl.party_type = 'Customer'
and gl.party = cus.name
left join `tabSupplier` sup
on gl.party_type = 'Supplier'
and gl.party = sup.name
left join `tabParty Account` par
on par.parent = gl.party
and par.parenttype = gl.party_type
and par.company = %(company)s
WHERE gl.company = %(company)s
AND DATE(gl.posting_date) >= %(from_date)s
AND DATE(gl.posting_date) <= %(to_date)s
@ -196,40 +237,56 @@ def get_customers(filters):
return frappe.db.sql("""
SELECT
acc.account_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 con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)',
CASE cus.customer_type WHEN 'Individual' THEN con.first_name 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',
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',
con.email_id as 'E-Mail',
coalesce(con.mobile_no, con.phone) as 'Telefon',
adr.email_id as 'E-Mail',
adr.phone as 'Telefon',
adr.fax as 'Fax',
cus.website as 'Internet',
cus.tax_id as 'Steuernummer'
FROM `tabParty Account` par
FROM `tabCustomer` cus
left join `tabAccount` acc
on acc.name = par.account
left join `tabParty Account` par
on par.parent = cus.name
and par.parenttype = 'Customer'
and par.company = %(company)s
left join `tabCustomer` cus
on cus.name = par.parent
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 = cus.customer_primary_address
on adr.name = dyn_adr.parent
and adr.is_primary_address = '1'
left join `tabCountry` country
on country.name = adr.country
left join `tabContact` con
on con.name = cus.customer_primary_contact
WHERE par.company = %(company)s
AND par.parenttype = 'Customer'""", filters, as_dict=1)
WHERE adr.is_primary_address = '1'
""", filters, as_dict=1)
def get_suppliers(filters):
@ -242,35 +299,48 @@ def get_suppliers(filters):
return frappe.db.sql("""
SELECT
acc.account_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 con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)',
CASE sup.supplier_type WHEN 'Individual' THEN con.first_name 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',
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',
con.email_id as 'E-Mail',
coalesce(con.mobile_no, con.phone) as 'Telefon',
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 `tabParty Account` par
FROM `tabSupplier` sup
left join `tabAccount` acc
on acc.name = par.account
left join `tabSupplier` sup
on sup.name = par.parent
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'
@ -278,17 +348,8 @@ def get_suppliers(filters):
left join `tabCountry` country
on country.name = adr.country
left join `tabDynamic Link` dyn_con
on dyn_con.link_name = sup.name
and dyn_con.link_doctype = 'Supplier'
and dyn_con.parenttype = 'Contact'
left join `tabContact` con
on con.name = dyn_con.parent
and con.is_primary_contact = '1'
WHERE par.company = %(company)s
AND par.parenttype = 'Supplier'""", filters, as_dict=1)
WHERE adr.is_primary_address = '1'
""", filters, as_dict=1)
def get_account_names(filters):