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):