Merge branch 'develop' into datev_fixes
diff --git a/erpnext/accounts/doctype/party_account/party_account.json b/erpnext/accounts/doctype/party_account/party_account.json
index aa32d95..c9f15a6 100644
--- a/erpnext/accounts/doctype/party_account/party_account.json
+++ b/erpnext/accounts/doctype/party_account/party_account.json
@@ -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"
}
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index de9f6e3..23f9fd8 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -774,3 +774,5 @@
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
diff --git a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
new file mode 100644
index 0000000..11e1e9b
--- /dev/null
+++ b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py
@@ -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', '')
diff --git a/erpnext/patches/v13_0/germany_make_custom_fields.py b/erpnext/patches/v13_0/germany_make_custom_fields.py
new file mode 100644
index 0000000..41ab945
--- /dev/null
+++ b/erpnext/patches/v13_0/germany_make_custom_fields.py
@@ -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()
diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py
index ac1f543..c1fa6e4 100644
--- a/erpnext/regional/germany/setup.py
+++ b/erpnext/regional/germany/setup.py
@@ -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 @@
dict(role='Accounts User'),
dict(role='Accounts Manager')
]
- )).insert()
\ No newline at end of file
+ )).insert()
diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py
index f138a80..826d51f 100644
--- a/erpnext/regional/germany/utils/datev/datev_csv.py
+++ b/erpnext/regional/germany/utils/datev/datev_csv.py
@@ -56,10 +56,10 @@
)
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.
diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py
index cbc9478..a5ca7ee 100644
--- a/erpnext/regional/report/datev/datev.py
+++ b/erpnext/regional/report/datev/datev.py
@@ -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 @@
"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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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):