From 09c7598a6782e53d90978b41f8e22e27dfc866d9 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:03:33 +0200 Subject: [PATCH 01/72] fix: make account field non-mandatory --- .../doctype/party_account/party_account.json | 114 +++++------------- 1 file changed, 33 insertions(+), 81 deletions(-) diff --git a/erpnext/accounts/doctype/party_account/party_account.json b/erpnext/accounts/doctype/party_account/party_account.json index aa32d953738..c9f15a6a470 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 From 6b2e4f2b5d7555e14091f0908588ac5ebc360e64 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:03:59 +0200 Subject: [PATCH 02/72] feat: add custom field debtor_creditor_number to Party Account --- erpnext/regional/germany/setup.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py index d6047e863ce..6258c05459f 100644 --- a/erpnext/regional/germany/setup.py +++ b/erpnext/regional/germany/setup.py @@ -1,6 +1,18 @@ import os import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def setup(company=None, patch=True): - pass + make_custom_fields() + + +def make_custom_fields(): + custom_fields = { + 'Party Account': [ + dict(fieldname='debtor_creditor_number', label='Debtor/Creditor Number', + fieldtype='Data', insert_after='account') + ] + } + + create_custom_fields(custom_fields) From b39608a02e389426d7da405a9d5467ec05514834 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:04:38 +0200 Subject: [PATCH 03/72] fix: handle encoding errors replace unknown characters by '?' --- erpnext/regional/germany/utils/datev/datev_csv.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py index f138a807bca..826d51f7128 100644 --- a/erpnext/regional/germany/utils/datev/datev_csv.py +++ b/erpnext/regional/germany/utils/datev/datev_csv.py @@ -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. From 4fe2d35b2e4ee548728f2592fb1cb322a2df2506 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:05:41 +0200 Subject: [PATCH 04/72] feat: more infos for transactions --- erpnext/regional/report/datev/datev.py | 45 +++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index cbc94789874..e1b0c54b2bf 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -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 } ] @@ -169,7 +195,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 +207,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 From 94f293940c947873c5c78ca20c16c1fb2bad0e3e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:06:16 +0200 Subject: [PATCH 05/72] fix: better party export --- erpnext/regional/report/datev/datev.py | 106 +++++++++++++++---------- 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index e1b0c54b2bf..42e4f96d1fa 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -239,40 +239,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): @@ -285,35 +301,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' @@ -321,17 +350,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): From 368a6541e9471f53b88e00d585820f7a1a7ba1dd Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 16:26:56 +0200 Subject: [PATCH 06/72] fix: Debtor/Creditor Number is not translatable --- erpnext/regional/germany/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/germany/setup.py b/erpnext/regional/germany/setup.py index 6258c05459f..83e4f975196 100644 --- a/erpnext/regional/germany/setup.py +++ b/erpnext/regional/germany/setup.py @@ -11,7 +11,7 @@ def make_custom_fields(): custom_fields = { 'Party Account': [ dict(fieldname='debtor_creditor_number', label='Debtor/Creditor Number', - fieldtype='Data', insert_after='account') + fieldtype='Data', insert_after='account', translatable=0) ] } From 03425071e7392673a9dfd22a4fb83294a2b19c0c Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 16:31:00 +0200 Subject: [PATCH 07/72] fix: patch to add custom fields --- erpnext/patches.txt | 1 + .../patches/v13_0/germany_make_custom_fields.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 erpnext/patches/v13_0/germany_make_custom_fields.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 16863142bc1..df004abc48d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -764,3 +764,4 @@ erpnext.patches.v13_0.setup_uae_vat_fields execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') erpnext.patches.v13_0.rename_discharge_date_in_ip_record erpnext.patches.v12_0.purchase_receipt_status +erpnext.patches.v13_0.germany_make_custom_fields 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 00000000000..cf1fe219dc0 --- /dev/null +++ b/erpnext/patches/v13_0/germany_make_custom_fields.py @@ -0,0 +1,14 @@ +# 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(): + company_list = frappe.get_all('Company', filters = {'country': 'Germany'}) + if not company_list: + return + + make_custom_fields() From ed36fb2073de13459e09993ace93116322858a92 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 16:57:20 +0200 Subject: [PATCH 08/72] docs: doctring for patch --- erpnext/patches/v13_0/germany_make_custom_fields.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/patches/v13_0/germany_make_custom_fields.py b/erpnext/patches/v13_0/germany_make_custom_fields.py index cf1fe219dc0..41ab945eb12 100644 --- a/erpnext/patches/v13_0/germany_make_custom_fields.py +++ b/erpnext/patches/v13_0/germany_make_custom_fields.py @@ -6,7 +6,13 @@ 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 From c6e13ac218e3a95ccb3df25dfe7ebcd40c2ffdb2 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 16:57:55 +0200 Subject: [PATCH 09/72] fix: patch to fill Debtor/Creditor Number --- erpnext/patches.txt | 1 + .../germany_fill_debtor_creditor_number.py | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index df004abc48d..a03bd007355 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -765,3 +765,4 @@ execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') erpnext.patches.v13_0.rename_discharge_date_in_ip_record erpnext.patches.v12_0.purchase_receipt_status 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 00000000000..8482730ec80 --- /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 +from erpnext.regional.germany.setup import make_custom_fields + +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', '') From 3a12f1f1ae0a76321eda86c32c16ba3a224d177c Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 17:53:48 +0200 Subject: [PATCH 10/72] fix: prettier error log --- erpnext/regional/report/datev/datev.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 42e4f96d1fa..c81568bfa64 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -146,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( - '{}'.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 From 0a45fc8c58927c3f0810d0124b4e3bdf580234a9 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 18:25:17 +0200 Subject: [PATCH 11/72] fix: remove unused import --- erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py index 8482730ec80..11e1e9b3b94 100644 --- a/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py +++ b/erpnext/patches/v13_0/germany_fill_debtor_creditor_number.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from erpnext.regional.germany.setup import make_custom_fields + def execute(): """Move account number into the new custom field debtor_creditor_number. From e66cf0aa444d33eaebf72314e4a77590793100f7 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 8 Apr 2021 18:26:45 +0200 Subject: [PATCH 12/72] fix: hanging indent --- erpnext/regional/report/datev/datev.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index c81568bfa64..1a215031c1a 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -102,8 +102,8 @@ COLUMNS = [ "fieldtype": "Dynamic Link", "options": "Beleginfo - Art 3", "width": 150 - } - ,{ + }, + { "label": "Beleginfo - Art 4", "fieldname": "Beleginfo - Art 4", "fieldtype": "Data", From 5b9d3f15a243511a4fedcd12eb25fbd248399aac Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 27 Apr 2021 12:34:10 +0200 Subject: [PATCH 13/72] docs: replace whitespace indent in docstring with tabs --- erpnext/regional/report/datev/datev.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 1a215031c1a..a5ca7eee5d4 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 From 35d4829383e278809bc64784b5d8a558db2ac61f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 1 May 2021 13:07:26 +0530 Subject: [PATCH 14/72] fix: serial no changed after saving stock reconciliation --- .../report/project_profitability/project_profitability.py | 5 +++-- .../doctype/stock_reconciliation/stock_reconciliation.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py index 5ad2d852326..9139d84facc 100644 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ b/erpnext/projects/report/project_profitability/project_profitability.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import flt def execute(filters=None): columns, data = [], [] @@ -52,8 +53,8 @@ def get_rows(filters): def calculate_cost_and_profit(data): for row in data: - row.fractional_cost = row.base_gross_pay * row.utilization - row.profit = row.base_grand_total - row.base_gross_pay * row.utilization + row.fractional_cost = flt(row.base_gross_pay) * flt(row.utilization) + row.profit = flt(row.base_grand_total) - flt(row.base_gross_pay) * flt(row.utilization) return data def get_conditions(filters): diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 1396f19d3f6..e4cdcb41163 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -72,7 +72,7 @@ class StockReconciliation(StockController): if item_dict.get("serial_nos"): item.current_serial_no = item_dict.get("serial_nos") - if self.purpose == "Stock Reconciliation": + if self.purpose == "Stock Reconciliation" and not item.serial_no:: item.serial_no = item.current_serial_no item.current_qty = item_dict.get("qty") From 6a5a380c07893f45f72f57ff3ac135b48f207ed0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 2 May 2021 18:02:28 +0530 Subject: [PATCH 15/72] fix: total stock summary report not working --- erpnext/stock/report/total_stock_summary/total_stock_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.py b/erpnext/stock/report/total_stock_summary/total_stock_summary.py index ed523939232..59c253c425b 100644 --- a/erpnext/stock/report/total_stock_summary/total_stock_summary.py +++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.py @@ -51,7 +51,7 @@ def get_total_stock(filters): INNER JOIN `tabWarehouse` warehouse ON warehouse.name = ledger.warehouse WHERE - actual_qty != 0 %s""" % (columns, conditions)) + ledger.actual_qty != 0 %s""" % (columns, conditions)) def validate_filters(filters): if filters.get("group_by") == 'Company' and \ From 9cc7c294e78b2580351b7e033bb6506143d3c263 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 3 May 2021 10:25:53 +0530 Subject: [PATCH 16/72] Update stock_reconciliation.py --- .../stock/doctype/stock_reconciliation/stock_reconciliation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index e4cdcb41163..2029b0708a6 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -72,7 +72,7 @@ class StockReconciliation(StockController): if item_dict.get("serial_nos"): item.current_serial_no = item_dict.get("serial_nos") - if self.purpose == "Stock Reconciliation" and not item.serial_no:: + if self.purpose == "Stock Reconciliation" and not item.serial_no: item.serial_no = item.current_serial_no item.current_qty = item_dict.get("qty") From e36f3030422babc8aed6207c9534716bfb5ae921 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 3 May 2021 19:49:22 +0530 Subject: [PATCH 17/72] fix: use percent string templates for db.sql calls --- erpnext/stock/get_item_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 3fc1df76bc3..98d08c0a185 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -935,8 +935,8 @@ def get_bin_details(item_code, warehouse, company=None): def get_company_total_stock(item_code, company): return frappe.db.sql("""SELECT sum(actual_qty) from (`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name) - WHERE `tabWarehouse`.company = '{0}' and `tabBin`.item_code = '{1}'""" - .format(company, item_code))[0][0] + WHERE `tabWarehouse`.company = %s and `tabBin`.item_code = %s""", + (company, item_code))[0][0] @frappe.whitelist() def get_serial_no_details(item_code, warehouse, stock_qty, serial_no): From 308905b1bee572c0161c26e0f89bf8a2a86c0c1f Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 3 May 2021 23:34:34 +0530 Subject: [PATCH 18/72] fix: semgrep, refactor default mutable dict --- erpnext/stock/get_item_details.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 98d08c0a185..3832415db62 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -470,7 +470,9 @@ def get_item_tax_template(args, item, out): item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out) item_group = item_group_doc.parent_item_group -def _get_item_tax_template(args, taxes, out={}, for_validate=False): +def _get_item_tax_template(args, taxes, out=None, for_validate=False): + if out is None: + out = {} taxes_with_validity = [] taxes_with_no_validity = [] From 076020643d55252c881cdc20ff1e169af97fb897 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 4 May 2021 12:26:49 +0530 Subject: [PATCH 19/72] fix: empty payment term column in accounts receivable report (#25556) --- .../report/accounts_receivable/accounts_receivable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 444b40ed798..db605f7285a 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -364,7 +364,7 @@ class ReceivablePayableReport(object): payment_terms_details = frappe.db.sql(""" select si.name, si.party_account_currency, si.currency, si.conversion_rate, - ps.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount + ps.due_date, ps.payment_term, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount from `tab{0}` si, `tabPayment Schedule` ps where si.name = ps.parent and @@ -394,7 +394,7 @@ class ReceivablePayableReport(object): "due_date": d.due_date, "invoiced": invoiced, "invoice_grand_total": row.invoiced, - "payment_term": d.description, + "payment_term": d.description or d.payment_term, "paid": d.paid_amount + d.discounted_amount, "credit_note": 0.0, "outstanding": invoiced - d.paid_amount - d.discounted_amount From 384f4b5b7e6d23436046d0f84888c5687cdca7f7 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 4 May 2021 12:33:49 +0530 Subject: [PATCH 21/72] fix: can't open general ledger from consolidated financial report (#25542) --- .../consolidated_financial_statement.js | 224 +++++++++--------- .../consolidated_financial_statement.py | 5 +- 2 files changed, 120 insertions(+), 109 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js index 09479221fbb..1363b53746a 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js @@ -2,118 +2,128 @@ // For license information, please see license.txt /* eslint-disable */ -frappe.query_reports["Consolidated Financial Statement"] = { - "filters": [ - { - "fieldname":"company", - "label": __("Company"), - "fieldtype": "Link", - "options": "Company", - "default": frappe.defaults.get_user_default("Company"), - "reqd": 1 - }, - { - "fieldname":"filter_based_on", - "label": __("Filter Based On"), - "fieldtype": "Select", - "options": ["Fiscal Year", "Date Range"], - "default": ["Fiscal Year"], - "reqd": 1, - on_change: function() { - let filter_based_on = frappe.query_report.get_filter_value('filter_based_on'); - frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range'); - frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range'); - frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year'); - frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year'); +frappe.require("assets/erpnext/js/financial_statements.js", function() { + frappe.query_reports["Consolidated Financial Statement"] = { + "filters": [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname":"filter_based_on", + "label": __("Filter Based On"), + "fieldtype": "Select", + "options": ["Fiscal Year", "Date Range"], + "default": ["Fiscal Year"], + "reqd": 1, + on_change: function() { + let filter_based_on = frappe.query_report.get_filter_value('filter_based_on'); + frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range'); + frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range'); + frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year'); + frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year'); - frappe.query_report.refresh(); + frappe.query_report.refresh(); + } + }, + { + "fieldname":"period_start_date", + "label": __("Start Date"), + "fieldtype": "Date", + "hidden": 1, + "reqd": 1 + }, + { + "fieldname":"period_end_date", + "label": __("End Date"), + "fieldtype": "Date", + "hidden": 1, + "reqd": 1 + }, + { + "fieldname":"from_fiscal_year", + "label": __("Start Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "reqd": 1 + }, + { + "fieldname":"to_fiscal_year", + "label": __("End Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "reqd": 1 + }, + { + "fieldname":"finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book" + }, + { + "fieldname":"report", + "label": __("Report"), + "fieldtype": "Select", + "options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"], + "default": "Balance Sheet", + "reqd": 1 + }, + { + "fieldname": "presentation_currency", + "label": __("Currency"), + "fieldtype": "Select", + "options": erpnext.get_presentation_currency_list(), + "default": frappe.defaults.get_user_default("Currency") + }, + { + "fieldname":"accumulated_in_group_company", + "label": __("Accumulated Values in Group Company"), + "fieldtype": "Check", + "default": 0 + }, + { + "fieldname": "include_default_book_entries", + "label": __("Include Default Book Entries"), + "fieldtype": "Check", + "default": 1 + } + ], + "formatter": function(value, row, column, data, default_formatter) { + if (data && column.fieldname=="account") { + value = data.account_name || value; + + column.link_onclick = + "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; + column.is_tree = true; } - }, - { - "fieldname":"period_start_date", - "label": __("Start Date"), - "fieldtype": "Date", - "hidden": 1, - "reqd": 1 - }, - { - "fieldname":"period_end_date", - "label": __("End Date"), - "fieldtype": "Date", - "hidden": 1, - "reqd": 1 - }, - { - "fieldname":"from_fiscal_year", - "label": __("Start Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1 - }, - { - "fieldname":"to_fiscal_year", - "label": __("End Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1 - }, - { - "fieldname":"finance_book", - "label": __("Finance Book"), - "fieldtype": "Link", - "options": "Finance Book" - }, - { - "fieldname":"report", - "label": __("Report"), - "fieldtype": "Select", - "options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"], - "default": "Balance Sheet", - "reqd": 1 - }, - { - "fieldname": "presentation_currency", - "label": __("Currency"), - "fieldtype": "Select", - "options": erpnext.get_presentation_currency_list(), - "default": frappe.defaults.get_user_default("Currency") - }, - { - "fieldname":"accumulated_in_group_company", - "label": __("Accumulated Values in Group Company"), - "fieldtype": "Check", - "default": 0 - }, - { - "fieldname": "include_default_book_entries", - "label": __("Include Default Book Entries"), - "fieldtype": "Check", - "default": 1 - } - ], - "formatter": function(value, row, column, data, default_formatter) { - value = default_formatter(value, row, column, data); - if (!data.parent_account) { - value = $(`${value}`); + value = default_formatter(value, row, column, data); - var $value = $(value).css("font-weight", "bold"); + if (!data.parent_account) { + value = $(`${value}`); - value = $value.wrap("

").parent().html(); - } - return value; - }, - onload: function() { - let fiscal_year = frappe.defaults.get_user_default("fiscal_year") + var $value = $(value).css("font-weight", "bold"); - frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { - var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); - frappe.query_report.set_filter_value({ - period_start_date: fy.year_start_date, - period_end_date: fy.year_end_date + value = $value.wrap("

").parent().html(); + } + return value; + }, + onload: function() { + let fiscal_year = frappe.defaults.get_user_default("fiscal_year") + + frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) { + var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); + frappe.query_report.set_filter_value({ + period_start_date: fy.year_start_date, + period_end_date: fy.year_end_date + }); }); - }); + } } -} +}); \ No newline at end of file diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 0c4a4224407..094f5db89b9 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -329,8 +329,9 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com has_value = False total = 0 row = frappe._dict({ - "account_name": _(d.account_name), - "account": _(d.account_name), + "account_name": ('%s - %s' %(_(d.account_number), _(d.account_name)) + if d.account_number else _(d.account_name)), + "account": _(d.name), "parent_account": _(d.parent_account), "indent": flt(d.indent), "year_start_date": start_date, From ba8dc1ffbd8d02cb6d01e3b10c0c21a9956bcca3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 4 May 2021 15:03:10 +0530 Subject: [PATCH 22/72] fix: stock balance and batchwise balance history report showing different closing stock --- .../batch_wise_balance_history/batch_wise_balance_history.py | 2 +- erpnext/stock/report/stock_balance/stock_balance.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 087c12ed2df..01927c2d10f 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -70,7 +70,7 @@ def get_stock_ledger_entries(filters): return frappe.db.sql(""" select item_code, batch_no, warehouse, posting_date, sum(actual_qty) as actual_qty from `tabStock Ledger Entry` - where docstatus < 2 and ifnull(batch_no, '') != '' %s + where is_cancelled = 0 and docstatus < 2 and ifnull(batch_no, '') != '' %s group by voucher_no, batch_no, item_code, warehouse order by item_code, warehouse""" % conditions, as_dict=1) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 6dfede45906..bbd73e91129 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -165,7 +165,7 @@ def get_stock_ledger_entries(filters, items): select sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate, sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference, - sle.item_code as name, sle.voucher_no, sle.stock_value + sle.item_code as name, sle.voucher_no, sle.stock_value, sle.batch_no from `tabStock Ledger Entry` sle force index (posting_sort_index) where sle.docstatus < 2 %s %s @@ -193,7 +193,7 @@ def get_item_warehouse_map(filters, sle): qty_dict = iwb_map[(d.company, d.item_code, d.warehouse)] - if d.voucher_type == "Stock Reconciliation": + if d.voucher_type == "Stock Reconciliation" and not d.batch_no: qty_diff = flt(d.qty_after_transaction) - flt(qty_dict.bal_qty) else: qty_diff = flt(d.actual_qty) From eebc6e9277b3d461ae4da4a92fa9cc45c27eea55 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 4 May 2021 17:05:12 +0530 Subject: [PATCH 23/72] refactor: Show item's full name on hover over item in POS (#25554) Co-authored-by: Saqib --- .../page/point_of_sale/pos_item_selector.js | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 709fe577477..9384ae5542f 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -81,13 +81,24 @@ erpnext.PointOfSale.ItemSelector = class { const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item; const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange"; + let qty_to_display = actual_qty; + + if (Math.round(qty_to_display) > 999) { + qty_to_display = Math.round(qty_to_display)/1000; + qty_to_display = qty_to_display.toFixed(1) + 'K'; + } + function get_item_image_html() { if (!me.hide_images && item_image) { - return `
+ return `
+ ${qty_to_display}
+
${frappe.get_abbr(item.item_name)}
`; } else { - return `
${frappe.get_abbr(item.item_name)}
`; + return `
+ ${qty_to_display}
+
${frappe.get_abbr(item.item_name)}
`; } } @@ -95,13 +106,12 @@ erpnext.PointOfSale.ItemSelector = class { `
+ title="${item.item_name}"> ${get_item_image_html()}
- ${frappe.ellipsis(item.item_name, 18)}
${format_currency(item.price_list_rate, item.currency, 0) || 0}
@@ -316,4 +326,4 @@ erpnext.PointOfSale.ItemSelector = class { toggle_component(show) { show ? this.$component.css('display', 'flex') : this.$component.css('display', 'none'); } -}; \ No newline at end of file +}; From 18ad15ed160ce209cea7b6da7cc6fc07fb3ee152 Mon Sep 17 00:00:00 2001 From: Asharam Seervi Date: Tue, 4 May 2021 19:28:07 +0530 Subject: [PATCH 24/72] fix: designation insufficient permission on lead doctype. (#25331) --- erpnext/hr/doctype/designation/designation.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/designation/designation.json b/erpnext/hr/doctype/designation/designation.json index 4c3888be4aa..bab6b90d1aa 100644 --- a/erpnext/hr/doctype/designation/designation.json +++ b/erpnext/hr/doctype/designation/designation.json @@ -182,6 +182,10 @@ "share": 1, "submit": 0, "write": 1 + }, + { + "read": 1, + "role": "Sales User" } ], "quick_entry": 1, @@ -191,4 +195,4 @@ "track_changes": 0, "track_seen": 0, "track_views": 0 -} \ No newline at end of file +} From 04923d6a65637e7e6c50db826b46e92bbb491c2e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 4 May 2021 21:01:12 +0530 Subject: [PATCH 25/72] fix: function call to update payment schedule labels --- erpnext/public/js/controllers/transaction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index f91b432a394..d218a5ee5f0 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1384,7 +1384,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], this.frm.doc.currency, "payment_schedule"); - + var schedule_grid = this.frm.fields_dict["payment_schedule"].grid; $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) { if (frappe.meta.get_docfield(schedule_grid.doctype, fname)) @@ -2034,7 +2034,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(r.message && !r.exc) { me.frm.set_value("payment_schedule", r.message); const company_currency = me.get_company_currency(); - this.update_payment_schedule_grid_labels(company_currency); + me.update_payment_schedule_grid_labels(company_currency); } } }) From 2a20a03c28c32944b5a5726241906010d42456af Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Wed, 5 May 2021 11:59:15 +0530 Subject: [PATCH 26/72] fix: check for None in item.schedule_date before setting --- erpnext/controllers/buying_controller.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index b686dc026c6..3f2d3390c05 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -838,9 +838,10 @@ class BuyingController(StockController): if not self.get("items"): return - earliest_schedule_date = min([d.schedule_date for d in self.get("items")]) - if earliest_schedule_date: - self.schedule_date = earliest_schedule_date + if any(d.schedule_date for d in self.get("items")): + # Select earliest schedule_date. + self.schedule_date = min(d.schedule_date for d in self.get("items") + if d.schedule_date is not None) if self.schedule_date: for d in self.get('items'): From 1bb7bb74adb4b07bd2efc6e2fb641ed84a262fae Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 5 May 2021 12:19:57 +0530 Subject: [PATCH 27/72] fix: Check if payment schedule exits before updating label --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index d218a5ee5f0..10c802c6f07 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1379,7 +1379,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ update_payment_schedule_grid_labels: function(company_currency) { const me = this; - if (this.frm.fields_dict["payment_schedule"]) { + if (this.frm.doc.payment_schedule.length > 0) { this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"], company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], From 136eb30081b7b74d3b500acf6731696b4692da8e Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Wed, 5 May 2021 12:26:29 +0530 Subject: [PATCH 28/72] fix: use get_serial_nos for splitting --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4461f29fe37..4de877353aa 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1111,7 +1111,7 @@ class SalesInvoice(SellingController): if not item.serial_no: continue - for serial_no in item.serial_no.split("\n"): + for serial_no in get_serial_nos(item.serial_no): if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code: frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice) From ffea9d4126c9ab544ac7a659874b9f41254404ec Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 5 May 2021 12:28:40 +0530 Subject: [PATCH 29/72] fix: Check if payment schedule exists --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 10c802c6f07..0af8da77a0c 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1379,7 +1379,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ update_payment_schedule_grid_labels: function(company_currency) { const me = this; - if (this.frm.doc.payment_schedule.length > 0) { + if (this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length > 0) { this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"], company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], From 85b675a554cfcdf78cfa5255e0a12e06e7e02e44 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 5 May 2021 20:57:31 +0530 Subject: [PATCH 30/72] fix: Invoices not fetch during payment reconciliation --- .../doctype/payment_reconciliation/payment_reconciliation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index cf6ec18f3b8..6635128f9ef 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -114,7 +114,7 @@ class PaymentReconciliation(Document): 'party_type': self.party_type, 'voucher_type': voucher_type, 'account': self.receivable_payable_account - }, as_dict=1, debug=1) + }, as_dict=1) def add_payment_entries(self, entries): self.set('payments', []) From bb3e5d00f44869132d869fbead5046ba1d3ddfc6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 24 Apr 2021 17:28:33 +0530 Subject: [PATCH 31/72] fix: allow to receive same serial numbers multiple times --- .../purchase_receipt/test_purchase_receipt.py | 83 +++++++++++++------ erpnext/stock/doctype/serial_no/serial_no.py | 18 +--- .../stock/report/serial_no_ledger/__init__.py | 0 .../serial_no_ledger/serial_no_ledger.js | 52 ++++++++++++ .../serial_no_ledger/serial_no_ledger.json | 33 ++++++++ .../serial_no_ledger/serial_no_ledger.py | 53 ++++++++++++ erpnext/stock/stock_ledger.py | 53 ++++++++++-- 7 files changed, 247 insertions(+), 45 deletions(-) create mode 100644 erpnext/stock/report/serial_no_ledger/__init__.py create mode 100644 erpnext/stock/report/serial_no_ledger/serial_no_ledger.js create mode 100644 erpnext/stock/report/serial_no_ledger/serial_no_ledger.json create mode 100644 erpnext/stock/report/serial_no_ledger/serial_no_ledger.py diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 16eea24f847..f9b2c1d7876 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -13,8 +13,9 @@ from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import make_item from six import iteritems +from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse - +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos class TestPurchaseReceipt(unittest.TestCase): def setUp(self): @@ -144,6 +145,62 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertFalse(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name})) self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no})) + def test_duplicate_serial_nos(self): + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + item = frappe.db.exists("Item", {'item_name': 'Test Serialized Item 123'}) + if not item: + item = create_item("Test Serialized Item 123") + item.has_serial_no = 1 + item.serial_no_series = "TSI123-.####" + item.save() + else: + item = frappe.get_doc("Item", {'item_name': 'Test Serialized Item 123'}) + + # First make purchase receipt + pr = make_purchase_receipt(item_code=item.name, qty=2, rate=500) + pr.load_from_db() + + serial_nos = frappe.db.get_value('Stock Ledger Entry', + {'voucher_type': 'Purchase Receipt', 'voucher_no': pr.name, 'item_code': item.name}, 'serial_no') + + serial_nos = get_serial_nos(serial_nos) + + self.assertEquals(get_serial_nos(pr.items[0].serial_no), serial_nos) + + # Then tried to receive same serial nos in difference company + pr_different_company = make_purchase_receipt(item_code=item.name, qty=2, rate=500, + serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True, + warehouse = 'Stores - _TC1') + + self.assertRaises(SerialNoDuplicateError, pr_different_company.submit) + + # Then made delivery note to remove the serial nos from stock + dn = create_delivery_note(item_code=item.name, qty=2, rate = 1500, serial_no='\n'.join(serial_nos)) + dn.load_from_db() + self.assertEquals(get_serial_nos(dn.items[0].serial_no), serial_nos) + + posting_date = add_days(today(), -3) + + # Try to receive same serial nos again in the same company with backdated. + pr1 = make_purchase_receipt(item_code=item.name, qty=2, rate=500, + posting_date=posting_date, serial_no='\n'.join(serial_nos), do_not_submit=True) + + self.assertRaises(SerialNoExistsInFutureTransaction, pr1.submit) + + # Try to receive same serial nos with different company with backdated. + pr2 = make_purchase_receipt(item_code=item.name, qty=2, rate=500, + posting_date=posting_date, serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True, + warehouse = 'Stores - _TC1') + + self.assertRaises(SerialNoExistsInFutureTransaction, pr2.submit) + + # Receive the same serial nos after the delivery note posting date and time + make_purchase_receipt(item_code=item.name, qty=2, rate=500, serial_no='\n'.join(serial_nos)) + + # Raise the error for backdated deliver note entry cancel + self.assertRaises(SerialNoExistsInFutureTransaction, dn.cancel) + def test_purchase_receipt_gl_entry(self): pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", @@ -562,30 +619,6 @@ class TestPurchaseReceipt(unittest.TestCase): new_pr_doc.cancel() - def test_not_accept_duplicate_serial_no(self): - from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note - - item_code = frappe.db.get_value('Item', {'has_serial_no': 1, 'is_fixed_asset': 0, "has_batch_no": 0}) - if not item_code: - item = make_item("Test Serial Item 1", dict(has_serial_no=1, has_batch_no=0)) - item_code = item.name - - serial_no = random_string(5) - pr1 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no) - dn = create_delivery_note(item_code=item_code, qty=1, serial_no=serial_no) - - pr2 = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no, do_not_submit=True) - self.assertRaises(SerialNoDuplicateError, pr2.submit) - - se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, - serial_no=serial_no, basic_rate=100, do_not_submit=True) - se.submit() - - se.cancel() - dn.cancel() - pr1.cancel() - def test_auto_asset_creation(self): asset_item = "Test Asset Item" diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index c02dd2e518d..5ecc9f81405 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -243,7 +243,7 @@ def validate_serial_no(sle, item_det): if frappe.db.exists("Serial No", serial_no): sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order", "delivery_document_no", "delivery_document_type", "warehouse", "purchase_document_type", - "purchase_document_no", "company"], as_dict=1) + "purchase_document_no", "company", "status"], as_dict=1) if sr.item_code!=sle.item_code: if not allow_serial_nos_with_different_item(serial_no, sle): @@ -266,6 +266,9 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Serial No {0} does not belong to Warehouse {1}").format(serial_no, sle.warehouse), SerialNoWarehouseError) + if not sr.purchase_document_no: + frappe.throw(_("Serial No {0} not in stock").format(serial_no), SerialNoNotExistsError) + if sle.voucher_type in ("Delivery Note", "Sales Invoice"): if sr.batch_no and sr.batch_no != sle.batch_no: @@ -382,19 +385,6 @@ def has_serial_no_exists(sn, sle): if sn.company != sle.company: return False - status = False - if sn.purchase_document_no: - if (sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and - sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]): - status = True - - # If status is receipt then system will allow to in-ward the delivered serial no - if (status and sle.voucher_type == "Stock Entry" and frappe.db.get_value("Stock Entry", - sle.voucher_no, "purpose") in ("Material Receipt", "Material Transfer")): - status = False - - return status - def allow_serial_nos_with_different_item(sle_serial_no, sle): """ Allows same serial nos for raw materials and finished goods diff --git a/erpnext/stock/report/serial_no_ledger/__init__.py b/erpnext/stock/report/serial_no_ledger/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js new file mode 100644 index 00000000000..616312e3118 --- /dev/null +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.js @@ -0,0 +1,52 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Serial No Ledger"] = { + "filters": [ + { + 'label': __('Item Code'), + 'fieldtype': 'Link', + 'fieldname': 'item_code', + 'reqd': 1, + 'options': 'Item', + get_query: function() { + return { + filters: { + 'has_serial_no': 1 + } + } + } + }, + { + 'label': __('Serial No'), + 'fieldtype': 'Link', + 'fieldname': 'serial_no', + 'options': 'Serial No', + 'reqd': 1 + }, + { + 'label': __('Warehouse'), + 'fieldtype': 'Link', + 'fieldname': 'warehouse', + 'options': 'Warehouse', + get_query: function() { + let company = frappe.query_report.get_filter_value('company'); + + if (company) { + return { + filters: { + 'company': company + } + } + } + } + }, + { + 'label': __('As On Date'), + 'fieldtype': 'Date', + 'fieldname': 'posting_date', + 'default': frappe.datetime.get_today() + }, + ] +}; diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json new file mode 100644 index 00000000000..e20e74c78b7 --- /dev/null +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.json @@ -0,0 +1,33 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-04-20 13:32:41.523219", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "modified": "2021-04-20 13:33:19.015829", + "modified_by": "Administrator", + "module": "Stock", + "name": "Serial No Ledger", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Stock Ledger Entry", + "report_name": "Serial No Ledger", + "report_type": "Script Report", + "roles": [ + { + "role": "Stock User" + }, + { + "role": "Purchase User" + }, + { + "role": "Sales User" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py new file mode 100644 index 00000000000..c3339fd341e --- /dev/null +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py @@ -0,0 +1,53 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe import _ +from erpnext.stock.stock_ledger import get_stock_ledger_entries +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + +def execute(filters=None): + columns = get_columns(filters) + data = get_data(filters) + return columns, data + +def get_columns(filters): + columns = [{ + 'label': _('Posting Date'), + 'fieldtype': 'Date', + 'fieldname': 'posting_date' + }, { + 'label': _('Posting Time'), + 'fieldtype': 'Time', + 'fieldname': 'posting_time' + }, { + 'label': _('Voucher Type'), + 'fieldtype': 'Link', + 'fieldname': 'voucher_type', + 'options': 'DocType', + 'width': 220 + }, { + 'label': _('Voucher No'), + 'fieldtype': 'Dynamic Link', + 'fieldname': 'voucher_no', + 'options': 'voucher_type', + 'width': 220 + }, { + 'label': _('Company'), + 'fieldtype': 'Link', + 'fieldname': 'company', + 'options': 'Company', + 'width': 220 + }, { + 'label': _('Warehouse'), + 'fieldtype': 'Link', + 'fieldname': 'warehouse', + 'options': 'Warehouse', + 'width': 220 + }] + + return columns + +def get_data(filters): + return get_stock_ledger_entries(filters, '<=', order="asc") or [] + diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index bbfcb7ad7d1..9729987d2d3 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -2,9 +2,11 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe, erpnext +import frappe +import erpnext +import copy from frappe import _ -from frappe.utils import cint, flt, cstr, now, now_datetime +from frappe.utils import cint, flt, cstr, now, get_link_to_form from frappe.model.meta import get_field_precision from erpnext.stock.utils import get_valuation_method, get_incoming_outgoing_rate_for_cancel from erpnext.stock.utils import get_bin @@ -13,6 +15,8 @@ from six import iteritems # future reposting class NegativeStockError(frappe.ValidationError): pass +class SerialNoExistsInFutureTransaction(frappe.ValidationError): + pass _exceptions = frappe.local('stockledger_exceptions') # _exceptions = [] @@ -27,6 +31,9 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no')) for sle in sl_entries: + if sle.serial_no: + validate_serial_no(sle) + if cancel: sle['actual_qty'] = -flt(sle.get('actual_qty')) @@ -46,6 +53,30 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc args = sle_doc.as_dict() update_bin(args, allow_negative_stock, via_landed_cost_voucher) +def validate_serial_no(sle): + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + for sn in get_serial_nos(sle.serial_no): + args = copy.deepcopy(sle) + args.serial_no = sn + args.warehouse = '' + + vouchers = [] + for row in get_stock_ledger_entries(args, '>'): + voucher_type = frappe.bold(row.voucher_type) + voucher_no = frappe.bold(get_link_to_form(row.voucher_type, row.voucher_no)) + vouchers.append(f'{voucher_type} {voucher_no}') + + if vouchers: + serial_no = frappe.bold(sn) + msg = (f'''The serial no {serial_no} has been used in the future transactions so you need to cancel them first. + The list of the transactions are as below.''' + '

  • ') + + msg += '
  • '.join(vouchers) + msg += '
' + + title = 'Cannot Submit' if not sle.get('is_cancelled') else 'Cannot Cancel' + frappe.throw(_(msg), title=_(title), exc=SerialNoExistsInFutureTransaction) + def validate_cancellation(args): if args[0].get("is_cancelled"): repost_entry = frappe.db.get_value("Repost Item Valuation", { @@ -718,7 +749,17 @@ def get_stock_ledger_entries(previous_sle, operator=None, conditions += " and " + previous_sle.get("warehouse_condition") if check_serial_no and previous_sle.get("serial_no"): - conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no")))) + # conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no")))) + serial_no = previous_sle.get("serial_no") + conditions += (""" and + ( + serial_no = {0} + or serial_no like {1} + or serial_no like {2} + or serial_no like {3} + ) + """).format(frappe.db.escape(serial_no), frappe.db.escape('{}\n%'.format(serial_no)), + frappe.db.escape('%\n{}'.format(serial_no)), frappe.db.escape('%\n{}\n%'.format(serial_no))) if not previous_sle.get("posting_date"): previous_sle["posting_date"] = "1900-01-01" @@ -793,12 +834,12 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, if not allow_zero_rate and not valuation_rate and raise_error_if_no_rate \ and cint(erpnext.is_perpetual_inventory_enabled(company)): frappe.local.message_log = [] - form_link = frappe.utils.get_link_to_form("Item", item_code) + form_link = get_link_to_form("Item", item_code) message = _("Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}.").format(form_link, voucher_type, voucher_no) - message += "

" + _(" Here are the options to proceed:") + message += "

" + _("Here are the options to proceed:") solutions = "
  • " + _("If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table.").format(voucher_type) + "
  • " - solutions += "
  • " + _("If not, you can Cancel / Submit this entry ") + _("{0}").format(frappe.bold("after")) + _(" performing either one below:") + "
  • " + solutions += "
  • " + _("If not, you can Cancel / Submit this entry") + " {0} ".format(frappe.bold("after")) + _("performing either one below:") + "
  • " sub_solutions = "
    • " + _("Create an incoming stock transaction for the Item.") + "
    • " sub_solutions += "
    • " + _("Mention Valuation Rate in the Item master.") + "
    " msg = message + solutions + sub_solutions + "" From d502f763197b9d3891c4c2e498c132a87d35ee19 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 6 May 2021 16:10:55 +0530 Subject: [PATCH 32/72] feat(e-invoicing): e-way bill validity field (#25555) --- erpnext/patches.txt | 1 + .../patches/v12_0/add_ewaybill_validity_field.py | 16 ++++++++++++++++ erpnext/regional/india/e_invoice/utils.py | 12 ++++++++---- erpnext/regional/india/setup.py | 3 +++ 4 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 erpnext/patches/v12_0/add_ewaybill_validity_field.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 23f9fd8ecbe..7faaf261587 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -774,5 +774,6 @@ 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.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number diff --git a/erpnext/patches/v12_0/add_ewaybill_validity_field.py b/erpnext/patches/v12_0/add_ewaybill_validity_field.py new file mode 100644 index 00000000000..87d98f1a563 --- /dev/null +++ b/erpnext/patches/v12_0/add_ewaybill_validity_field.py @@ -0,0 +1,16 @@ +from __future__ import unicode_literals +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1, + depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill') + ] + } + create_custom_fields(custom_fields, update=True) \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 699441be7e6..b4e7a8889ef 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -71,13 +71,14 @@ def validate_einvoice_fields(doc): def raise_document_name_too_long_error(): title = _('Document ID Too Long') - msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice, ') - msg += _('document id {} exceed 16 letters. ').format(bold(_('should not'))) + msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice') + msg += ', ' + msg += _('document id {} exceed 16 letters.').format(bold(_('should not'))) msg += '

    ' - msg += _('You must {} your {} in order to have document id of {} length 16. ').format( + msg += _('You must {} your {} in order to have document id of {} length 16.').format( bold(_('modify')), bold(_('naming series')), bold(_('maximum')) ) - msg += _('Please account for ammended documents too. ') + msg += _('Please account for ammended documents too.') frappe.throw(msg, title=title) def read_json(name): @@ -847,6 +848,7 @@ class GSPConnector(): res = self.make_request('post', self.generate_ewaybill_url, headers, data) if res.get('success'): self.invoice.ewaybill = res.get('result').get('EwbNo') + self.invoice.eway_bill_validity = res.get('result').get('EwbValidTill') self.invoice.eway_bill_cancelled = 0 self.invoice.update(args) self.invoice.flags.updater_reference = { @@ -944,6 +946,7 @@ class GSPConnector(): self.invoice.irn = res.get('Irn') self.invoice.ewaybill = res.get('EwbNo') + self.invoice.eway_bill_validity = res.get('EwbValidTill') self.invoice.ack_no = res.get('AckNo') self.invoice.ack_date = res.get('AckDt') self.invoice.signed_einvoice = dec_signed_invoice @@ -960,6 +963,7 @@ class GSPConnector(): 'label': _('IRN Generated') } self.update_invoice() + def attach_qrcode_image(self): qrcode = self.invoice.signed_qr_code doctype = self.invoice.doctype diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 9ded8dab5bc..b12e152b14e 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -422,6 +422,9 @@ def make_custom_fields(update=True): dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1, + depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'), + dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), From f43a86d90ffb4029dcaee6768c6974f4e90e3026 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 6 May 2021 16:40:06 +0530 Subject: [PATCH 33/72] perf: significant reduction in time taken to save a delivery note (#25475) --- erpnext/selling/doctype/customer/customer.py | 32 +++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 49ca9423e8d..51d86ff0bf8 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -490,7 +490,7 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F outstanding_based_on_gle = flt(outstanding_based_on_gle[0][0]) if outstanding_based_on_gle else 0 # Outstanding based on Sales Order - outstanding_based_on_so = 0.0 + outstanding_based_on_so = 0 # if credit limit check is bypassed at sales order level, # we should not consider outstanding Sales Orders, when customer credit balance report is run @@ -501,9 +501,11 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F where customer=%s and docstatus = 1 and company=%s and per_billed < 100 and status != 'Closed'""", (customer, company)) - outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0.0 + outstanding_based_on_so = flt(outstanding_based_on_so[0][0]) if outstanding_based_on_so else 0 # Outstanding based on Delivery Note, which are not created against Sales Order + outstanding_based_on_dn = 0 + unmarked_delivery_note_items = frappe.db.sql("""select dn_item.name, dn_item.amount, dn.base_net_total, dn.base_grand_total from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item @@ -515,15 +517,29 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F and ifnull(dn_item.against_sales_invoice, '') = '' """, (customer, company), as_dict=True) - outstanding_based_on_dn = 0.0 + if not unmarked_delivery_note_items: + return outstanding_based_on_gle + outstanding_based_on_so + + si_amounts = frappe.db.sql(""" + SELECT + dn_detail, sum(amount) from `tabSales Invoice Item` + WHERE + docstatus = 1 + and dn_detail in ({}) + GROUP BY dn_detail""".format(", ".join( + frappe.db.escape(dn_item.name) + for dn_item in unmarked_delivery_note_items + )) + ) + + si_amounts = {si_item[0]: si_item[1] for si_item in si_amounts} for dn_item in unmarked_delivery_note_items: - si_amount = frappe.db.sql("""select sum(amount) - from `tabSales Invoice Item` - where dn_detail = %s and docstatus = 1""", dn_item.name)[0][0] + dn_amount = flt(dn_item.amount) + si_amount = flt(si_amounts.get(dn_item.name)) - if flt(dn_item.amount) > flt(si_amount) and dn_item.base_net_total: - outstanding_based_on_dn += ((flt(dn_item.amount) - flt(si_amount)) \ + if dn_amount > si_amount and dn_item.base_net_total: + outstanding_based_on_dn += ((dn_amount - si_amount) / dn_item.base_net_total) * dn_item.base_grand_total return outstanding_based_on_gle + outstanding_based_on_so + outstanding_based_on_dn From 900a8fb21a9a22d4f683912035777c33341730d6 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 6 May 2021 17:02:47 +0530 Subject: [PATCH 34/72] feat(pos): ability to retry on pos closing failure (#25595) * feat(pos): ability to retry on pos closing failure * fix: sider issues * fix: sider issues * fix: mark all queued closing entry as failed * feat: add headline message --- .../pos_closing_entry/pos_closing_entry.js | 104 ++++++++++-------- .../pos_closing_entry/pos_closing_entry.json | 21 +++- .../pos_closing_entry/pos_closing_entry.py | 4 + .../pos_closing_entry_list.js | 1 + .../pos_invoice_merge_log.py | 87 +++++++++++---- erpnext/controllers/status_updater.py | 1 + erpnext/patches.txt | 1 + .../v13_0/set_pos_closing_as_failed.py | 7 ++ 8 files changed, 158 insertions(+), 68 deletions(-) create mode 100644 erpnext/patches/v13_0/set_pos_closing_as_failed.py diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index 9ea616f8e77..aa0c53e228b 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -22,7 +22,43 @@ frappe.ui.form.on('POS Closing Entry', { }); if (frm.doc.docstatus === 0 && !frm.doc.amended_from) frm.set_value("period_end_date", frappe.datetime.now_datetime()); - if (frm.doc.docstatus === 1) set_html_data(frm); + + frappe.realtime.on('closing_process_complete', async function(data) { + await frm.reload_doc(); + if (frm.doc.status == 'Failed' && frm.doc.error_message && data.user == frappe.session.user) { + frappe.msgprint({ + title: __('POS Closing Failed'), + message: frm.doc.error_message, + indicator: 'orange', + clear: true + }); + } + }); + + set_html_data(frm); + }, + + refresh: function(frm) { + if (frm.doc.docstatus == 1 && frm.doc.status == 'Failed') { + const issue = 'issue'; + frm.dashboard.set_headline( + __('POS Closing failed while running in a background process. You can resolve the {0} and retry the process again.', [issue])); + + $('#jump_to_error').on('click', (e) => { + e.preventDefault(); + frappe.utils.scroll_to( + cur_frm.get_field("error_message").$wrapper, + true, + 30 + ); + }); + + frm.add_custom_button(__('Retry'), function () { + frm.call('retry', {}, () => { + frm.reload_doc(); + }); + }); + } }, pos_opening_entry(frm) { @@ -61,44 +97,24 @@ frappe.ui.form.on('POS Closing Entry', { refresh_fields(frm); set_html_data(frm); } - }) + }); + }, + + before_save: function(frm) { + for (let row of frm.doc.pos_transactions) { + frappe.db.get_doc("POS Invoice", row.pos_invoice).then(doc => { + cur_frm.doc.grand_total -= flt(doc.grand_total); + cur_frm.doc.net_total -= flt(doc.net_total); + cur_frm.doc.total_quantity -= flt(doc.total_qty); + refresh_payments(doc, cur_frm, 1); + refresh_taxes(doc, cur_frm, 1); + refresh_fields(cur_frm); + set_html_data(cur_frm); + }); + } } }); -cur_frm.cscript.before_pos_transactions_remove = function(doc, cdt, cdn) { - const removed_row = locals[cdt][cdn]; - - if (!removed_row.pos_invoice) return; - - frappe.db.get_doc("POS Invoice", removed_row.pos_invoice).then(doc => { - cur_frm.doc.grand_total -= flt(doc.grand_total); - cur_frm.doc.net_total -= flt(doc.net_total); - cur_frm.doc.total_quantity -= flt(doc.total_qty); - refresh_payments(doc, cur_frm, 1); - refresh_taxes(doc, cur_frm, 1); - refresh_fields(cur_frm); - set_html_data(cur_frm); - }); -} - -frappe.ui.form.on('POS Invoice Reference', { - pos_invoice(frm, cdt, cdn) { - const added_row = locals[cdt][cdn]; - - if (!added_row.pos_invoice) return; - - frappe.db.get_doc("POS Invoice", added_row.pos_invoice).then(doc => { - frm.doc.grand_total += flt(doc.grand_total); - frm.doc.net_total += flt(doc.net_total); - frm.doc.total_quantity += flt(doc.total_qty); - refresh_payments(doc, frm); - refresh_taxes(doc, frm); - refresh_fields(frm); - set_html_data(frm); - }); - } -}) - frappe.ui.form.on('POS Closing Entry Detail', { closing_amount: (frm, cdt, cdn) => { const row = locals[cdt][cdn]; @@ -177,11 +193,13 @@ function refresh_fields(frm) { } function set_html_data(frm) { - frappe.call({ - method: "get_payment_reconciliation_details", - doc: frm.doc, - callback: (r) => { - frm.get_field("payment_reconciliation_details").$wrapper.html(r.message); - } - }) + if (frm.doc.docstatus === 1 && frm.doc.status == 'Submitted') { + frappe.call({ + method: "get_payment_reconciliation_details", + doc: frm.doc, + callback: (r) => { + frm.get_field("payment_reconciliation_details").$wrapper.html(r.message); + } + }); + } } diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json index a9b91e02a9d..4d6e4a2ba07 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json @@ -30,6 +30,8 @@ "total_quantity", "column_break_16", "taxes", + "failure_description_section", + "error_message", "section_break_14", "amended_from" ], @@ -195,7 +197,7 @@ "fieldtype": "Select", "hidden": 1, "label": "Status", - "options": "Draft\nSubmitted\nQueued\nCancelled", + "options": "Draft\nSubmitted\nQueued\nFailed\nCancelled", "print_hide": 1, "read_only": 1 }, @@ -203,6 +205,21 @@ "fieldname": "period_details_section", "fieldtype": "Section Break", "label": "Period Details" + }, + { + "collapsible": 1, + "collapsible_depends_on": "error_message", + "depends_on": "error_message", + "fieldname": "failure_description_section", + "fieldtype": "Section Break", + "label": "Failure Description" + }, + { + "depends_on": "error_message", + "fieldname": "error_message", + "fieldtype": "Small Text", + "label": "Error", + "read_only": 1 } ], "is_submittable": 1, @@ -212,7 +229,7 @@ "link_fieldname": "pos_closing_entry" } ], - "modified": "2021-02-01 13:47:20.722104", + "modified": "2021-05-05 16:59:49.723261", "modified_by": "Administrator", "module": "Accounts", "name": "POS Closing Entry", diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py index 1065168a50c..82528728ddc 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py @@ -60,6 +60,10 @@ class POSClosingEntry(StatusUpdater): def on_cancel(self): unconsolidate_pos_invoices(closing_entry=self) + @frappe.whitelist() + def retry(self): + consolidate_pos_invoices(closing_entry=self) + def update_opening_entry(self, for_cancel=False): opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry) opening_entry.pos_closing_entry = self.name if not for_cancel else None diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js index 20fd610899e..cffeb4d5351 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js @@ -8,6 +8,7 @@ frappe.listview_settings['POS Closing Entry'] = { "Draft": "red", "Submitted": "blue", "Queued": "orange", + "Failed": "red", "Cancelled": "red" }; diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 4d5472df4b4..bc7874305c0 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -13,8 +13,7 @@ from frappe.model.mapper import map_doc, map_child_doc from frappe.utils.scheduler import is_scheduler_inactive from frappe.core.page.background_jobs.background_jobs import get_info import json - -from six import iteritems +import six class POSInvoiceMergeLog(Document): def validate(self): @@ -239,7 +238,7 @@ def consolidate_pos_invoices(pos_invoices=None, closing_entry=None): invoices = pos_invoices or (closing_entry and closing_entry.get('pos_transactions')) or get_all_unconsolidated_invoices() invoice_by_customer = get_invoice_customer_map(invoices) - if len(invoices) >= 1 and closing_entry: + if len(invoices) >= 10 and closing_entry: closing_entry.set_status(update=True, status='Queued') enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry) else: @@ -252,36 +251,68 @@ def unconsolidate_pos_invoices(closing_entry): pluck='name' ) - if len(merge_logs) >= 1: + if len(merge_logs) >= 10: closing_entry.set_status(update=True, status='Queued') enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry) else: cancel_merge_logs(merge_logs, closing_entry) def create_merge_logs(invoice_by_customer, closing_entry=None): - for customer, invoices in iteritems(invoice_by_customer): - merge_log = frappe.new_doc('POS Invoice Merge Log') - merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate() - merge_log.customer = customer - merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None + try: + for customer, invoices in six.iteritems(invoice_by_customer): + merge_log = frappe.new_doc('POS Invoice Merge Log') + merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate() + merge_log.customer = customer + merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None - merge_log.set('pos_invoices', invoices) - merge_log.save(ignore_permissions=True) - merge_log.submit() + merge_log.set('pos_invoices', invoices) + merge_log.save(ignore_permissions=True) + merge_log.submit() - if closing_entry: - closing_entry.set_status(update=True, status='Submitted') - closing_entry.update_opening_entry() + if closing_entry: + closing_entry.set_status(update=True, status='Submitted') + closing_entry.db_set('error_message', '') + closing_entry.update_opening_entry() + + except Exception: + frappe.db.rollback() + message_log = frappe.message_log.pop() + error_message = safe_load_json(message_log) + + if closing_entry: + closing_entry.set_status(update=True, status='Failed') + closing_entry.db_set('error_message', error_message) + raise + + finally: + frappe.db.commit() + frappe.publish_realtime('closing_process_complete', {'user': frappe.session.user}) def cancel_merge_logs(merge_logs, closing_entry=None): - for log in merge_logs: - merge_log = frappe.get_doc('POS Invoice Merge Log', log) - merge_log.flags.ignore_permissions = True - merge_log.cancel() + try: + for log in merge_logs: + merge_log = frappe.get_doc('POS Invoice Merge Log', log) + merge_log.flags.ignore_permissions = True + merge_log.cancel() - if closing_entry: - closing_entry.set_status(update=True, status='Cancelled') - closing_entry.update_opening_entry(for_cancel=True) + if closing_entry: + closing_entry.set_status(update=True, status='Cancelled') + closing_entry.db_set('error_message', '') + closing_entry.update_opening_entry(for_cancel=True) + + except Exception: + frappe.db.rollback() + message_log = frappe.message_log.pop() + error_message = safe_load_json(message_log) + + if closing_entry: + closing_entry.set_status(update=True, status='Submitted') + closing_entry.db_set('error_message', error_message) + raise + + finally: + frappe.db.commit() + frappe.publish_realtime('closing_process_complete', {'user': frappe.session.user}) def enqueue_job(job, **kwargs): check_scheduler_status() @@ -314,4 +345,14 @@ def check_scheduler_status(): def job_already_enqueued(job_name): enqueued_jobs = [d.get("job_name") for d in get_info()] if job_name in enqueued_jobs: - return True \ No newline at end of file + return True + +def safe_load_json(message): + JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError + + try: + json_message = json.loads(message).get('message') + except JSONDecodeError: + json_message = message + + return json_message \ No newline at end of file diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 5276da97200..4bb6138e5d7 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -98,6 +98,7 @@ status_map = { ["Draft", None], ["Submitted", "eval:self.docstatus == 1"], ["Queued", "eval:self.status == 'Queued'"], + ["Failed", "eval:self.status == 'Failed'"], ["Cancelled", "eval:self.docstatus == 2"], ] } diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 7faaf261587..82d223cada0 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -777,3 +777,4 @@ 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 diff --git a/erpnext/patches/v13_0/set_pos_closing_as_failed.py b/erpnext/patches/v13_0/set_pos_closing_as_failed.py new file mode 100644 index 00000000000..1c576db1c7e --- /dev/null +++ b/erpnext/patches/v13_0/set_pos_closing_as_failed.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('accounts', 'doctype', 'pos_closing_entry') + + frappe.db.sql("update `tabPOS Closing Entry` set `status` = 'Failed' where `status` = 'Queued'") \ No newline at end of file From 695becdd0569aac2bb57e0c87b9d4234b3ca9647 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 6 May 2021 18:03:32 +0530 Subject: [PATCH 35/72] fix: added validation in stock entry to check duplicate serial nos --- .../stock/doctype/stock_entry/stock_entry.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 48cfa51041d..2f76bc7d56a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -76,6 +76,7 @@ class StockEntry(StockController): self.validate_difference_account() self.set_job_card_data() self.set_purpose_for_stock_entry() + self.validate_duplicate_serial_no() if not self.from_bom: self.fg_completed_qty = 0.0 @@ -587,6 +588,22 @@ class StockEntry(StockController): self.purpose = frappe.get_cached_value('Stock Entry Type', self.stock_entry_type, 'purpose') + def validate_duplicate_serial_no(self): + warehouse_wise_serial_nos = {} + + # In case of repack the source and target serial nos could be same + for warehouse in ['s_warehouse', 't_warehouse']: + serial_nos = [] + for row in self.items: + if not (row.serial_no and row.get(warehouse)): continue + + for sn in get_serial_nos(row.serial_no): + if sn in serial_nos: + frappe.throw(_('The serial no {0} has added multiple times in the stock entry {1}') + .format(frappe.bold(sn), self.name)) + + serial_nos.append(sn) + def validate_purchase_order(self): """Throw exception if more raw material is transferred against Purchase Order than in the raw materials supplied table""" From 134eaa5786745fc9930a795b74f0461e353363ac Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 6 May 2021 19:13:54 +0530 Subject: [PATCH 36/72] perf: Performance enhancement on setup wizard (#25605) * perf: Performance enhancement on setup wizard * fix: create departments without updating nsm --- erpnext/accounts/doctype/account/account.py | 2 +- .../chart_of_accounts/chart_of_accounts.py | 4 +- .../accounts_settings/accounts_settings.py | 4 +- .../education_settings/education_settings.py | 4 +- erpnext/hr/doctype/department/department.py | 3 +- .../payroll_settings/payroll_settings.py | 4 +- .../selling_settings/selling_settings.py | 4 +- .../global_defaults/global_defaults.py | 12 +- .../doctype/naming_series/naming_series.py | 16 +-- .../operations/install_fixtures.py | 134 ++++++++++-------- erpnext/setup/setup_wizard/setup_wizard.py | 9 -- .../doctype/stock_settings/stock_settings.py | 10 +- 12 files changed, 105 insertions(+), 101 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 06068238213..1be2fbf5c81 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -13,7 +13,7 @@ class BalanceMismatchError(frappe.ValidationError): pass class Account(NestedSet): nsm_parent_field = 'parent_account' def on_update(self): - if frappe.local.flags.ignore_on_update: + if frappe.local.flags.ignore_update_nsm: return else: super(Account, self).on_update() diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py index 0e3b24cda3d..927adc7086c 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py @@ -57,10 +57,10 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch # Rebuild NestedSet HSM tree for Account Doctype # after all accounts are already inserted. - frappe.local.flags.ignore_on_update = True + frappe.local.flags.ignore_update_nsm = True _import_accounts(chart, None, None, root_account=True) rebuild_tree("Account", "parent_account") - frappe.local.flags.ignore_on_update = False + frappe.local.flags.ignore_update_nsm = False def add_suffix_if_duplicate(account_name, account_number, accounts): if account_number: diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 5593466fc2b..4d3388090dc 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -30,5 +30,5 @@ class AccountsSettings(Document): def enable_payment_schedule_in_print(self): show_in_print = cint(self.show_payment_schedule_in_print) for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"): - make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check") - make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check") + make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check", validate_fields_for_doctype=False) diff --git a/erpnext/education/doctype/education_settings/education_settings.py b/erpnext/education/doctype/education_settings/education_settings.py index a85d3e70f34..658380ea429 100644 --- a/erpnext/education/doctype/education_settings/education_settings.py +++ b/erpnext/education/doctype/education_settings/education_settings.py @@ -31,9 +31,9 @@ class EducationSettings(Document): def validate(self): from frappe.custom.doctype.property_setter.property_setter import make_property_setter if self.get('instructor_created_by')=='Naming Series': - make_property_setter('Instructor', "naming_series", "hidden", 0, "Check") + make_property_setter('Instructor', "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False) else: - make_property_setter('Instructor', "naming_series", "hidden", 1, "Check") + make_property_setter('Instructor', "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False) def update_website_context(context): context["lms_enabled"] = frappe.get_doc("Education Settings").enable_lms \ No newline at end of file diff --git a/erpnext/hr/doctype/department/department.py b/erpnext/hr/doctype/department/department.py index 2cef5092767..539a360269f 100644 --- a/erpnext/hr/doctype/department/department.py +++ b/erpnext/hr/doctype/department/department.py @@ -31,7 +31,8 @@ class Department(NestedSet): return new def on_update(self): - NestedSet.on_update(self) + if not frappe.local.flags.ignore_update_nsm: + super(Department, self).on_update() def on_trash(self): super(Department, self).on_trash() diff --git a/erpnext/payroll/doctype/payroll_settings/payroll_settings.py b/erpnext/payroll/doctype/payroll_settings/payroll_settings.py index 5efa41db1f7..459b7eacb43 100644 --- a/erpnext/payroll/doctype/payroll_settings/payroll_settings.py +++ b/erpnext/payroll/doctype/payroll_settings/payroll_settings.py @@ -28,5 +28,5 @@ class PayrollSettings(Document): def toggle_rounded_total(self): self.disable_rounded_total = cint(self.disable_rounded_total) - make_property_setter("Salary Slip", "rounded_total", "hidden", self.disable_rounded_total, "Check") - make_property_setter("Salary Slip", "rounded_total", "print_hide", self.disable_rounded_total, "Check") + make_property_setter("Salary Slip", "rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) + make_property_setter("Salary Slip", "rounded_total", "print_hide", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py index d2978838763..b219e7ecce0 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.py +++ b/erpnext/selling/doctype/selling_settings/selling_settings.py @@ -30,8 +30,8 @@ class SellingSettings(Document): # Make property setters to hide tax_id fields for doctype in ("Sales Order", "Sales Invoice", "Delivery Note"): - make_property_setter(doctype, "tax_id", "hidden", self.hide_tax_id, "Check") - make_property_setter(doctype, "tax_id", "print_hide", self.hide_tax_id, "Check") + make_property_setter(doctype, "tax_id", "hidden", self.hide_tax_id, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "tax_id", "print_hide", self.hide_tax_id, "Check", validate_fields_for_doctype=False) def set_default_customer_group_and_territory(self): if not self.customer_group: diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py index 76a84508291..e5872171815 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.py +++ b/erpnext/setup/doctype/global_defaults/global_defaults.py @@ -60,11 +60,11 @@ class GlobalDefaults(Document): # Make property setters to hide rounded total fields for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note", "Supplier Quotation", "Purchase Order", "Purchase Invoice"): - make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check") - make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check") + make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check", validate_fields_for_doctype=False) - make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check") - make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check") + make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check", validate_fields_for_doctype=False) def toggle_in_words(self): self.disable_in_words = cint(self.disable_in_words) @@ -72,5 +72,5 @@ class GlobalDefaults(Document): # Make property setters to hide in words fields for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note", "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"): - make_property_setter(doctype, "in_words", "hidden", self.disable_in_words, "Check") - make_property_setter(doctype, "in_words", "print_hide", self.disable_in_words, "Check") + make_property_setter(doctype, "in_words", "hidden", self.disable_in_words, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "in_words", "print_hide", self.disable_in_words, "Check", validate_fields_for_doctype=False) diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py index 373b0a58c98..c1f9433b411 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.py +++ b/erpnext/setup/doctype/naming_series/naming_series.py @@ -183,8 +183,8 @@ class NamingSeries(Document): def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True): from frappe.custom.doctype.property_setter.property_setter import make_property_setter if naming_series: - make_property_setter(doctype, "naming_series", "hidden", 0, "Check") - make_property_setter(doctype, "naming_series", "reqd", 1, "Check") + make_property_setter(doctype, "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "naming_series", "reqd", 1, "Check", validate_fields_for_doctype=False) # set values for mandatory try: @@ -195,15 +195,15 @@ def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True pass if hide_name_field: - make_property_setter(doctype, fieldname, "reqd", 0, "Check") - make_property_setter(doctype, fieldname, "hidden", 1, "Check") + make_property_setter(doctype, fieldname, "reqd", 0, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, fieldname, "hidden", 1, "Check", validate_fields_for_doctype=False) else: - make_property_setter(doctype, "naming_series", "reqd", 0, "Check") - make_property_setter(doctype, "naming_series", "hidden", 1, "Check") + make_property_setter(doctype, "naming_series", "reqd", 0, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False) if hide_name_field: - make_property_setter(doctype, fieldname, "hidden", 0, "Check") - make_property_setter(doctype, fieldname, "reqd", 1, "Check") + make_property_setter(doctype, fieldname, "hidden", 0, "Check", validate_fields_for_doctype=False) + make_property_setter(doctype, fieldname, "reqd", 1, "Check", validate_fields_for_doctype=False) # set values for mandatory frappe.db.sql("""update `tab{doctype}` set `{fieldname}`=`name` where diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 5053c6a5124..5c725d332de 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -12,6 +12,7 @@ from frappe.desk.doctype.global_search_settings.global_search_settings import up from erpnext.accounts.doctype.account.account import RootNotEditable from erpnext.regional.address_template.setup import set_up_address_templates +from frappe.utils.nestedset import rebuild_tree default_lead_sources = ["Existing Customer", "Reference", "Advertisement", "Cold Calling", "Exhibition", "Supplier Reference", "Mass Mailing", @@ -280,13 +281,15 @@ def install(country=None): set_more_defaults() update_global_search_doctypes() - # path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country)) - # if os.path.exists(path.encode("utf-8")): - # frappe.get_attr("erpnext.regional.{0}.setup.setup_company_independent_fixtures".format(frappe.scrub(country)))() - - def set_more_defaults(): # Do more setup stuff that can be done here with no dependencies + update_selling_defaults() + update_buying_defaults() + update_hr_defaults() + add_uom_data() + update_item_variant_settings() + +def update_selling_defaults(): selling_settings = frappe.get_doc("Selling Settings") selling_settings.set_default_customer_group_and_territory() selling_settings.cust_master_name = "Customer Name" @@ -296,13 +299,7 @@ def set_more_defaults(): selling_settings.sales_update_frequency = "Each Transaction" selling_settings.save() - add_uom_data() - - # set no copy fields of an item doctype to item variant settings - doc = frappe.get_doc('Item Variant Settings') - doc.set_default_fields() - doc.save() - +def update_buying_defaults(): buying_settings = frappe.get_doc("Buying Settings") buying_settings.supp_master_name = "Supplier Name" buying_settings.po_required = "No" @@ -311,12 +308,19 @@ def set_more_defaults(): buying_settings.allow_multiple_items = 1 buying_settings.save() +def update_hr_defaults(): hr_settings = frappe.get_doc("HR Settings") hr_settings.emp_created_by = "Naming Series" hr_settings.leave_approval_notification_template = _("Leave Approval Notification") hr_settings.leave_status_notification_template = _("Leave Status Notification") hr_settings.save() +def update_item_variant_settings(): + # set no copy fields of an item doctype to item variant settings + doc = frappe.get_doc('Item Variant Settings') + doc.set_default_fields() + doc.save() + def add_uom_data(): # add UOMs uoms = json.loads(open(frappe.get_app_path("erpnext", "setup", "setup_wizard", "data", "uom_data.json")).read()) @@ -327,7 +331,7 @@ def add_uom_data(): "uom_name": _(d.get("uom_name")), "name": _(d.get("uom_name")), "must_be_whole_number": d.get("must_be_whole_number") - }).insert(ignore_permissions=True) + }).db_insert() # bootstrap uom conversion factors uom_conversions = json.loads(open(frappe.get_app_path("erpnext", "setup", "setup_wizard", "data", "uom_conversion_data.json")).read()) @@ -336,7 +340,7 @@ def add_uom_data(): frappe.get_doc({ "doctype": "UOM Category", "category_name": _(d.get("category")) - }).insert(ignore_permissions=True) + }).db_insert() if not frappe.db.exists("UOM Conversion Factor", {"from_uom": _(d.get("from_uom")), "to_uom": _(d.get("to_uom"))}): uom_conversion = frappe.get_doc({ @@ -369,8 +373,8 @@ def add_sale_stages(): {"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")}, {"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")} ] - - make_records(records) + for sales_stage in records: + frappe.get_doc(sales_stage).db_insert() def install_company(args): records = [ @@ -418,7 +422,14 @@ def install_post_company_fixtures(args=None): {'doctype': 'Department', 'department_name': _('Legal'), 'parent_department': _('All Departments'), 'company': args.company_name}, ] - make_records(records) + # Make root department with NSM updation + make_records(records[:1]) + + frappe.local.flags.ignore_update_nsm = True + make_records(records[1:]) + frappe.local.flags.ignore_update_nsm = False + + rebuild_tree("Department", "parent_department") def install_defaults(args=None): @@ -432,7 +443,15 @@ def install_defaults(args=None): # enable default currency frappe.db.set_value("Currency", args.get("currency"), "enabled", 1) + frappe.db.set_value("Stock Settings", None, "email_footer_address", args.get("company_name")) + set_global_defaults(args) + set_active_domains(args) + update_stock_settings() + update_shopping_cart_settings(args) + create_bank_account(args) + +def set_global_defaults(args): global_defaults = frappe.get_doc("Global Defaults", "Global Defaults") current_fiscal_year = frappe.get_all("Fiscal Year")[0] @@ -445,13 +464,10 @@ def install_defaults(args=None): global_defaults.save() - system_settings = frappe.get_doc("System Settings") - system_settings.email_footer_address = args.get("company_name") - system_settings.save() - - domain_settings = frappe.get_single('Domain Settings') - domain_settings.set_active_domains(args.get('domains')) +def set_active_domains(args): + frappe.get_single('Domain Settings').set_active_domains(args.get('domains')) +def update_stock_settings(): stock_settings = frappe.get_doc("Stock Settings") stock_settings.item_naming_by = "Item Code" stock_settings.valuation_method = "FIFO" @@ -463,48 +479,44 @@ def install_defaults(args=None): stock_settings.set_qty_in_transactions_based_on_serial_no_input = 1 stock_settings.save() - if args.bank_account: - company_name = args.company_name - bank_account_group = frappe.db.get_value("Account", - {"account_type": "Bank", "is_group": 1, "root_type": "Asset", - "company": company_name}) - if bank_account_group: - bank_account = frappe.get_doc({ - "doctype": "Account", - 'account_name': args.bank_account, - 'parent_account': bank_account_group, - 'is_group':0, - 'company': company_name, - "account_type": "Bank", - }) - try: - doc = bank_account.insert() +def create_bank_account(args): + if not args.bank_account: + return - frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False) + company_name = args.company_name + bank_account_group = frappe.db.get_value("Account", + {"account_type": "Bank", "is_group": 1, "root_type": "Asset", + "company": company_name}) + if bank_account_group: + bank_account = frappe.get_doc({ + "doctype": "Account", + 'account_name': args.bank_account, + 'parent_account': bank_account_group, + 'is_group':0, + 'company': company_name, + "account_type": "Bank", + }) + try: + doc = bank_account.insert() - except RootNotEditable: - frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account)) - except frappe.DuplicateEntryError: - # bank account same as a CoA entry - pass + frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False) - # Now, with fixtures out of the way, onto concrete stuff - records = [ - - # Shopping cart: needs price lists - { - "doctype": "Shopping Cart Settings", - "enabled": 1, - 'company': args.company_name, - # uh oh - 'price_list': frappe.db.get_value("Price List", {"selling": 1}), - 'default_customer_group': _("Individual"), - 'quotation_series': "QTN-", - }, - ] - - make_records(records) + except RootNotEditable: + frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account)) + except frappe.DuplicateEntryError: + # bank account same as a CoA entry + pass +def update_shopping_cart_settings(args): + shopping_cart = frappe.get_doc("Shopping Cart Settings") + shopping_cart.update({ + "enabled": 1, + 'company': args.company_name, + 'price_list': frappe.db.get_value("Price List", {"selling": 1}), + 'default_customer_group': _("Individual"), + 'quotation_series': "QTN-", + }) + shopping_cart.update_single(shopping_cart.get_valid_dict()) def get_fy_details(fy_start_date, fy_end_date): start_year = getdate(fy_start_date).year diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py index e74d837ef5c..f63d2695aa3 100644 --- a/erpnext/setup/setup_wizard/setup_wizard.py +++ b/erpnext/setup/setup_wizard/setup_wizard.py @@ -51,11 +51,6 @@ def get_setup_stages(args=None): 'status': _('Setting defaults'), 'fail_msg': 'Failed to set defaults', 'tasks': [ - { - 'fn': setup_post_company_fixtures, - 'args': args, - 'fail_msg': _("Failed to setup post company fixtures") - }, { 'fn': setup_defaults, 'args': args, @@ -94,9 +89,6 @@ def stage_fixtures(args): def setup_company(args): fixtures.install_company(args) -def setup_post_company_fixtures(args): - fixtures.install_post_company_fixtures(args) - def setup_defaults(args): fixtures.install_defaults(frappe._dict(args)) @@ -129,7 +121,6 @@ def login_as_first_user(args): def setup_complete(args=None): stage_fixtures(args) setup_company(args) - setup_post_company_fixtures(args) setup_defaults(args) stage_four(args) fin(args) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index 3b9608b8056..2dd7c6f35b8 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -30,7 +30,7 @@ class StockSettings(Document): # show/hide barcode field for name in ["barcode", "barcodes", "scan_barcode"]: frappe.make_property_setter({'fieldname': name, 'property': 'hidden', - 'value': 0 if self.show_barcode_field else 1}) + 'value': 0 if self.show_barcode_field else 1}, validate_fields_for_doctype=False) self.validate_warehouses() self.cant_change_valuation_method() @@ -67,10 +67,10 @@ class StockSettings(Document): self.toggle_warehouse_field_for_inter_warehouse_transfer() def toggle_warehouse_field_for_inter_warehouse_transfer(self): - make_property_setter("Sales Invoice Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check") - make_property_setter("Delivery Note Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check") - make_property_setter("Purchase Invoice Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check") - make_property_setter("Purchase Receipt Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check") + make_property_setter("Sales Invoice Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check", validate_fields_for_doctype=False) + make_property_setter("Delivery Note Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check", validate_fields_for_doctype=False) + make_property_setter("Purchase Invoice Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check", validate_fields_for_doctype=False) + make_property_setter("Purchase Receipt Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check", validate_fields_for_doctype=False) def clean_all_descriptions(): From 4ecae62194d00f455dd757ce45bb80148d750977 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 6 May 2021 19:42:01 +0530 Subject: [PATCH 37/72] fix: added is_stock_item filter (#25530) --- erpnext/manufacturing/doctype/bom/bom.js | 5 ++++- erpnext/manufacturing/doctype/bom/bom.py | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index fbfd801a114..a09a5e34300 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -29,7 +29,10 @@ frappe.ui.form.on("BOM", { frm.set_query("item", function() { return { - query: "erpnext.manufacturing.doctype.bom.bom.item_query" + query: "erpnext.manufacturing.doctype.bom.bom.item_query", + filters: { + "is_stock_item": 1 + } }; }); diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 979f7ca3128..d1f63854c71 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -973,6 +973,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): if not has_variants: query_filters["has_variants"] = 0 + if filters and filters.get("is_stock_item"): + query_filters["is_stock_item"] = 1 + return frappe.get_all("Item", fields = fields, filters=query_filters, or_filters = or_cond_filters, order_by=order_by, From 0e0de6baa14683b10b18e43d1b2dab03c9a94f37 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Fri, 7 May 2021 11:40:02 +0530 Subject: [PATCH 38/72] fix: prevent spurious defaults for items when making prec from dnote (#25559) * fix: prevent spurious defaults for items when making prec from dnote * refactor: make concise, use dict comp --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4de877353aa..bb74a02606f 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1755,15 +1755,10 @@ def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, wa item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item)) def get_delivery_note_details(internal_reference): - so_item_map = {} - si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'], filters={'parent': internal_reference}) - for d in si_item_details: - so_item_map.setdefault(d.name, d.so_detail) - - return so_item_map + return {d.name: d.so_detail for d in si_item_details if d.so_detail} def get_sales_invoice_details(internal_reference): dn_item_map = {} From f132ed4335996a7848a386d017ed6cfbc93c8197 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Fri, 7 May 2021 12:11:09 +0530 Subject: [PATCH 39/72] fix: update item level cost center from POS (#25609) --- erpnext/stock/get_item_details.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 3832415db62..d1dcdc21c87 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -79,7 +79,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru get_price_list_rate(args, item, out) if args.customer and cint(args.is_pos): - out.update(get_pos_profile_item_details(args.company, args)) + out.update(get_pos_profile_item_details(args.company, args, update_data=True)) if (args.get("doctype") == "Material Request" and args.get("material_request_type") == "Material Transfer"): @@ -935,8 +935,8 @@ def get_bin_details(item_code, warehouse, company=None): return bin_details def get_company_total_stock(item_code, company): - return frappe.db.sql("""SELECT sum(actual_qty) from - (`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name) + return frappe.db.sql("""SELECT sum(actual_qty) from + (`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name) WHERE `tabWarehouse`.company = %s and `tabBin`.item_code = %s""", (company, item_code))[0][0] From 996f7e53a19e2ab741f9709f09f774d04498cc81 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 7 May 2021 12:14:14 +0530 Subject: [PATCH 40/72] fix: update shopify api version (#25600) --- .../erpnext_integrations/connectors/shopify_connection.py | 6 +++--- .../doctype/shopify_settings/shopify_settings.py | 4 ++-- .../doctype/shopify_settings/sync_product.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py index f0a05ed192f..5d5b2e19ce3 100644 --- a/erpnext/erpnext_integrations/connectors/shopify_connection.py +++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py @@ -335,13 +335,13 @@ def get_url(shopify_settings): if not last_order_id: if shopify_settings.sync_based_on == 'Date': - url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&created_at_min={0}&since_id=0".format( + url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&created_at_min={0}&since_id=0".format( get_datetime(shopify_settings.from_date)), shopify_settings) else: - url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format( + url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format( shopify_settings.from_order_id), shopify_settings) else: - url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings) + url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings) return url diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py index cbdf90681d3..7634fd0caf4 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py @@ -30,7 +30,7 @@ class ShopifySettings(Document): webhooks = ["orders/create", "orders/paid", "orders/fulfilled"] # url = get_shopify_url('admin/webhooks.json', self) created_webhooks = [d.method for d in self.webhooks] - url = get_shopify_url('admin/api/2020-04/webhooks.json', self) + url = get_shopify_url('admin/api/2021-04/webhooks.json', self) for method in webhooks: session = get_request_session() try: @@ -56,7 +56,7 @@ class ShopifySettings(Document): deleted_webhooks = [] for d in self.webhooks: - url = get_shopify_url('admin/api/2020-04/webhooks/{0}.json'.format(d.webhook_id), self) + url = get_shopify_url('admin/api/2021-04/webhooks/{0}.json'.format(d.webhook_id), self) try: res = session.delete(url, headers=get_header(self)) res.raise_for_status() diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py index f9f0bb3cecc..16efb6caee1 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py @@ -8,7 +8,7 @@ from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings impo shopify_variants_attr_list = ["option1", "option2", "option3"] def sync_item_from_shopify(shopify_settings, item): - url = get_shopify_url("admin/api/2020-04/products/{0}.json".format(item.get("product_id")), shopify_settings) + url = get_shopify_url("admin/api/2021-04/products/{0}.json".format(item.get("product_id")), shopify_settings) session = get_request_session() try: From f648d2d7c4a23061f4b0a03afa060d0d061eea95 Mon Sep 17 00:00:00 2001 From: Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com> Date: Fri, 7 May 2021 12:15:19 +0530 Subject: [PATCH 41/72] fix: added tax_types list (#25587) --- .../v12_0/move_item_tax_to_item_tax_template.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py index 06331d7ff78..a6471eb53cd 100644 --- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py +++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py @@ -44,9 +44,11 @@ def execute(): # make current item's tax map item_tax_map = {} for d in old_item_taxes[item_code]: - item_tax_map[d.tax_type] = d.tax_rate + if d.tax_type not in item_tax_map: + item_tax_map[d.tax_type] = d.tax_rate - item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code) + tax_types = [] + item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code, tax_types=tax_types) # update the item tax table frappe.db.sql("delete from `tabItem Tax` where parent=%s and parenttype='Item'", item_code) @@ -68,7 +70,7 @@ def execute(): and item_tax_template is NULL""".format(dt), as_dict=1): item_tax_map = json.loads(d.item_tax_rate) item_tax_template_name = get_item_tax_template(item_tax_templates, - item_tax_map, d.item_code, d.parenttype, d.parent) + item_tax_map, d.item_code, d.parenttype, d.parent, tax_types=tax_types) frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name) frappe.db.auto_commit_on_many_writes = False @@ -78,7 +80,7 @@ def execute(): settings.determine_address_tax_category_from = "Billing Address" settings.save() -def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None): +def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttype=None, parent=None, tax_types=None): # search for previously created item tax template by comparing tax maps for template, item_tax_template_map in iteritems(item_tax_templates): if item_tax_map == item_tax_template_map: @@ -126,7 +128,9 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttyp account_type = frappe.get_cached_value("Account", tax_type, "account_type") if tax_type and account_type in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'): - item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate}) + if tax_type not in tax_types: + item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate}) + tax_types.append(tax_type) item_tax_templates.setdefault(item_tax_template.title, {}) item_tax_templates[item_tax_template.title][tax_type] = tax_rate if item_tax_template.get("taxes"): From 00e00e4e903ac9f6e37520903b0e7ca97e1c388b Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 7 May 2021 12:16:44 +0530 Subject: [PATCH 42/72] fix: Report summary showing inflated values when values are accumulated in Group Company (#25577) * fix: Report summary showing inflated values when values are accumullated in Group Company * fix: Remove extra space * fix: Translate strings * fix: Remove unintended changes --- .../report/balance_sheet/balance_sheet.py | 7 ++++++- erpnext/accounts/report/cash_flow/cash_flow.py | 17 ++++++++++++----- .../consolidated_financial_statement.py | 6 +++--- erpnext/accounts/report/financial_statements.py | 14 +++++++++++--- .../profit_and_loss_statement.py | 11 ++++++++--- 5 files changed, 40 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index 1729abce9ef..287b8a7484f 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -5,7 +5,8 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import flt, cint -from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data) +from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data, + get_filtered_list_for_consolidated_report) def execute(filters=None): period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, @@ -132,6 +133,10 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit if filters.get('accumulated_values'): period_list = [period_list[-1]] + # from consolidated financial statement + if filters.get('accumulated_in_group_company'): + period_list = get_filtered_list_for_consolidated_report(period_list) + for period in period_list: key = period if consolidated else period.key if asset: diff --git a/erpnext/accounts/report/cash_flow/cash_flow.py b/erpnext/accounts/report/cash_flow/cash_flow.py index cf0946beaba..3577457c980 100644 --- a/erpnext/accounts/report/cash_flow/cash_flow.py +++ b/erpnext/accounts/report/cash_flow/cash_flow.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cint, cstr -from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data) +from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data, get_filtered_list_for_consolidated_report) from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import get_net_profit_loss from erpnext.accounts.utils import get_fiscal_year from six import iteritems @@ -67,9 +67,9 @@ def execute(filters=None): section_data.append(account_data) add_total_row_account(data, section_data, cash_flow_account['section_footer'], - period_list, company_currency, summary_data) + period_list, company_currency, summary_data, filters) - add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data) + add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data, filters) columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company) chart = get_chart_data(columns, data) @@ -162,18 +162,26 @@ def get_start_date(period, accumulated_values, company): return start_date -def add_total_row_account(out, data, label, period_list, currency, summary_data, consolidated = False): +def add_total_row_account(out, data, label, period_list, currency, summary_data, filters, consolidated=False): total_row = { "account_name": "'" + _("{0}").format(label) + "'", "account": "'" + _("{0}").format(label) + "'", "currency": currency } + + summary_data[label] = 0 + + # from consolidated financial statement + if filters.get('accumulated_in_group_company'): + period_list = get_filtered_list_for_consolidated_report(filters, period_list) + for row in data: if row.get("parent_account"): for period in period_list: key = period if consolidated else period['key'] total_row.setdefault(key, 0.0) total_row[key] += row.get(key, 0.0) + summary_data[label] += row.get(key) total_row.setdefault("total", 0.0) total_row["total"] += row["total"] @@ -181,7 +189,6 @@ def add_total_row_account(out, data, label, period_list, currency, summary_data, out.append(total_row) out.append({}) - summary_data[label] = total_row["total"] def get_report_summary(summary_data, currency): report_summary = [] diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 094f5db89b9..7793af737f9 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -94,7 +94,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters): chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss) - report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, True) + report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, filters, True) return data, None, chart, report_summary @@ -149,9 +149,9 @@ def get_cash_flow_data(fiscal_year, companies, filters): section_data.append(account_data) add_total_row_account(data, section_data, cash_flow_account['section_footer'], - companies, company_currency, summary_data, True) + companies, company_currency, summary_data, filters, True) - add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, True) + add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, filters, True) report_summary = get_cash_flow_summary(summary_data, company_currency) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 14efa1f8fc7..d20ddbde5c6 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -119,10 +119,10 @@ def validate_fiscal_year(fiscal_year, from_fiscal_year, to_fiscal_year): def validate_dates(from_date, to_date): if not from_date or not to_date: - frappe.throw("From Date and To Date are mandatory") + frappe.throw(_("From Date and To Date are mandatory")) if to_date < from_date: - frappe.throw("To Date cannot be less than From Date") + frappe.throw(_("To Date cannot be less than From Date")) def get_months(start_date, end_date): diff = (12 * end_date.year + end_date.month) - (12 * start_date.year + start_date.month) @@ -522,4 +522,12 @@ def get_columns(periodicity, period_list, accumulated_values=1, company=None): "width": 150 }) - return columns \ No newline at end of file + return columns + +def get_filtered_list_for_consolidated_report(filters, period_list): + filtered_summary_list = [] + for period in period_list: + if period == filters.get('company'): + filtered_summary_list.append(period) + + return filtered_summary_list diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py index fe261b30b45..5d04824b571 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py @@ -5,7 +5,8 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import flt -from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data) +from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data, + get_filtered_list_for_consolidated_report) def execute(filters=None): period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, @@ -33,13 +34,17 @@ def execute(filters=None): chart = get_chart_data(filters, columns, income, expense, net_profit_loss) currency = filters.presentation_currency or frappe.get_cached_value('Company', filters.company, "default_currency") - report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency) + report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters) return columns, data, None, chart, report_summary -def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, consolidated=False): +def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, filters, consolidated=False): net_income, net_expense, net_profit = 0.0, 0.0, 0.0 + # from consolidated financial statement + if filters.get('accumulated_in_group_company'): + period_list = get_filtered_list_for_consolidated_report(filters, period_list) + for period in period_list: key = period if consolidated else period.key if income: From 735fbdc350d0449720db36e684c85ca56161442f Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Fri, 7 May 2021 12:26:32 +0530 Subject: [PATCH 43/72] fix: Updating Standard Notification's channel field (#25564) --- .../notification_for_new_fiscal_year.json | 1 + erpnext/hr/notification/training_feedback/training_feedback.json | 1 + .../payroll/notification/retention_bonus/retention_bonus.json | 1 + 3 files changed, 3 insertions(+) diff --git a/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json b/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json index bd7a1265170..4c7faf4f65b 100644 --- a/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json +++ b/erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json @@ -1,5 +1,6 @@ { "attach_print": 0, + "channel": "Email", "condition": "doc.auto_created", "creation": "2018-04-25 14:19:05.440361", "days_in_advance": 0, diff --git a/erpnext/hr/notification/training_feedback/training_feedback.json b/erpnext/hr/notification/training_feedback/training_feedback.json index 2cc064f34a5..92b68a98a90 100644 --- a/erpnext/hr/notification/training_feedback/training_feedback.json +++ b/erpnext/hr/notification/training_feedback/training_feedback.json @@ -1,5 +1,6 @@ { "attach_print": 0, + "channel": "Email", "creation": "2017-08-11 03:17:11.769210", "days_in_advance": 0, "docstatus": 0, diff --git a/erpnext/payroll/notification/retention_bonus/retention_bonus.json b/erpnext/payroll/notification/retention_bonus/retention_bonus.json index 50db0338c4a..37381fa9428 100644 --- a/erpnext/payroll/notification/retention_bonus/retention_bonus.json +++ b/erpnext/payroll/notification/retention_bonus/retention_bonus.json @@ -1,5 +1,6 @@ { "attach_print": 0, + "channel": "Email", "condition": "doc.docstatus==1", "creation": "2018-05-15 18:52:36.362838", "date_changed": "bonus_payment_date", From 7f79d463f69237189f9393c820e1bae58054493a Mon Sep 17 00:00:00 2001 From: Umair Sayed Date: Fri, 7 May 2021 12:28:57 +0530 Subject: [PATCH 44/72] fix: Stock and Accounts Settings form refactor (#25534) * stock and accounts settings page * fix: Stock and accounts settings page cleanup Co-authored-by: Umair Sayed --- .../accounts_settings/accounts_settings.json | 61 ++++++++++------ .../stock_settings/stock_settings.json | 70 ++++++++++++------- 2 files changed, 86 insertions(+), 45 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index e1276e7da3d..781f94e203a 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -7,26 +7,30 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "auto_accounting_for_stock", - "acc_frozen_upto", - "frozen_accounts_modifier", - "determine_address_tax_category_from", + "accounts_transactions_settings_section", "over_billing_allowance", "role_allowed_to_over_bill", - "column_break_4", - "credit_controller", - "check_supplier_invoice_uniqueness", "make_payment_via_journal_entry", + "column_break_11", + "check_supplier_invoice_uniqueness", "unlink_payment_on_cancellation_of_invoice", - "unlink_advance_payment_on_cancelation_of_order", - "book_asset_depreciation_entry_automatically", - "add_taxes_from_item_tax_template", "automatically_fetch_payment_terms", "delete_linked_ledger_entries", + "book_asset_depreciation_entry_automatically", + "unlink_advance_payment_on_cancelation_of_order", + "tax_settings_section", + "determine_address_tax_category_from", + "column_break_19", + "add_taxes_from_item_tax_template", + "period_closing_settings_section", + "acc_frozen_upto", + "frozen_accounts_modifier", + "column_break_4", + "credit_controller", "deferred_accounting_settings_section", - "automatically_process_deferred_accounting_entry", "book_deferred_entries_based_on", "column_break_18", + "automatically_process_deferred_accounting_entry", "book_deferred_entries_via_journal_entry", "submit_journal_entries", "print_settings", @@ -40,15 +44,6 @@ "use_custom_cash_flow" ], "fields": [ - { - "default": "1", - "description": "If enabled, the system will post accounting entries for inventory automatically", - "fieldname": "auto_accounting_for_stock", - "fieldtype": "Check", - "hidden": 1, - "in_list_view": 1, - "label": "Make Accounting Entry For Every Stock Movement" - }, { "description": "Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below", "fieldname": "acc_frozen_upto", @@ -94,6 +89,7 @@ "default": "0", "fieldname": "make_payment_via_journal_entry", "fieldtype": "Check", + "hidden": 1, "label": "Make Payment via Journal Entry" }, { @@ -234,6 +230,29 @@ "fieldtype": "Link", "label": "Role Allowed to Over Bill ", "options": "Role" + }, + { + "fieldname": "period_closing_settings_section", + "fieldtype": "Section Break", + "label": "Period Closing Settings" + }, + { + "fieldname": "accounts_transactions_settings_section", + "fieldtype": "Section Break", + "label": "Transactions Settings" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "tax_settings_section", + "fieldtype": "Section Break", + "label": "Tax Settings" + }, + { + "fieldname": "column_break_19", + "fieldtype": "Column Break" } ], "icon": "icon-cog", @@ -241,7 +260,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-03-11 18:52:05.601996", + "modified": "2021-04-30 15:25:10.381008", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index f18eabc84bb..cf5d98d0923 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -5,40 +5,44 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ + "item_defaults_section", "item_naming_by", "item_group", "stock_uom", "default_warehouse", - "sample_retention_warehouse", "column_break_4", "valuation_method", + "sample_retention_warehouse", + "use_naming_series", + "naming_series_prefix", + "section_break_9", "over_delivery_receipt_allowance", "role_allowed_to_over_deliver_receive", - "action_if_quality_inspection_is_not_submitted", - "show_barcode_field", - "clean_description_html", - "disable_serial_no_and_batch_selector", - "section_break_7", + "column_break_12", "auto_insert_price_list_rate_if_missing", "allow_negative_stock", - "column_break_10", + "show_barcode_field", + "clean_description_html", + "action_if_quality_inspection_is_not_submitted", + "section_break_7", "automatically_set_serial_nos_based_on_fifo", "set_qty_in_transactions_based_on_serial_no_input", + "column_break_10", + "disable_serial_no_and_batch_selector", "auto_material_request", "auto_indent", + "column_break_27", "reorder_email_notify", "inter_warehouse_transfer_settings_section", "allow_from_dn", + "column_break_31", "allow_from_pr", "control_historical_stock_transactions_section", - "role_allowed_to_create_edit_back_dated_transactions", - "column_break_26", "stock_frozen_upto", "stock_frozen_upto_days", - "stock_auth_role", - "batch_id_sb", - "use_naming_series", - "naming_series_prefix" + "column_break_26", + "role_allowed_to_create_edit_back_dated_transactions", + "stock_auth_role" ], "fields": [ { @@ -102,23 +106,24 @@ "default": "1", "fieldname": "show_barcode_field", "fieldtype": "Check", - "label": "Show Barcode Field" + "label": "Show Barcode Field in Stock Transactions" }, { "default": "1", "fieldname": "clean_description_html", "fieldtype": "Check", - "label": "Convert Item Description to Clean HTML" + "label": "Convert Item Description to Clean HTML in Transactions" }, { "fieldname": "section_break_7", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Serialised and Batch Setting" }, { "default": "0", "fieldname": "auto_insert_price_list_rate_if_missing", "fieldtype": "Check", - "label": "Auto Insert Price List Rate If Missing" + "label": "Auto Insert Item Price If Missing" }, { "default": "0", @@ -179,16 +184,11 @@ "label": "Role Allowed to Edit Frozen Stock", "options": "Role" }, - { - "fieldname": "batch_id_sb", - "fieldtype": "Section Break", - "label": "Batch Identification" - }, { "default": "0", "fieldname": "use_naming_series", "fieldtype": "Check", - "label": "Use Naming Series" + "label": "Have Default Naming Series for Batch ID?" }, { "default": "BATCH-", @@ -242,6 +242,28 @@ "fieldtype": "Link", "label": "Role Allowed to Over Deliver/Receive", "options": "Role" + }, + { + "fieldname": "item_defaults_section", + "fieldtype": "Section Break", + "label": "Item Defaults" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Stock Transactions Settings" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_27", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_31", + "fieldtype": "Column Break" } ], "icon": "icon-cog", @@ -249,7 +271,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-03-11 18:48:14.513055", + "modified": "2021-04-30 17:27:42.709231", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", From 062d30146f967a28672a76f9f8d286c0d28470ca Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 7 May 2021 13:31:14 +0530 Subject: [PATCH 45/72] fix: Include search fields in Project Link field query (#25505) * fix: Include search fields in Project Link field query * fix: add project_name to Project search fields --- erpnext/controllers/queries.py | 10 +++++++--- erpnext/projects/doctype/project/project.json | 6 +++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index bc1ac5ea069..b31724fa487 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -292,11 +292,14 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): cond = """(`tabProject`.customer = %s or ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer"))) - fields = get_fields("Project", ["name"]) + fields = get_fields("Project", ["name", "project_name"]) + searchfields = frappe.get_meta("Project").get_search_fields() + searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) return frappe.db.sql("""select {fields} from `tabProject` - where `tabProject`.status not in ("Completed", "Cancelled") - and {cond} `tabProject`.name like %(txt)s {match_cond} + where + `tabProject`.status not in ("Completed", "Cancelled") + and {cond} {match_cond} {scond} order by if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), idx desc, @@ -304,6 +307,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): limit {start}, {page_len}""".format( fields=", ".join(['`tabProject`.{0}'.format(f) for f in fields]), cond=cond, + scond=searchfields, match_cond=get_match_cond(doctype), start=start, page_len=page_len), { diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 3cdfcb212f5..2570df70261 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -458,7 +458,7 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 4, - "modified": "2020-09-02 11:54:01.223620", + "modified": "2021-04-28 16:36:11.654632", "modified_by": "Administrator", "module": "Projects", "name": "Project", @@ -495,11 +495,11 @@ } ], "quick_entry": 1, - "search_fields": "customer, status, priority, is_active", + "search_fields": "project_name,customer, status, priority, is_active", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", "timeline_field": "customer", "title_field": "project_name", "track_seen": 1 -} +} \ No newline at end of file From 5618ce3852e0dbad1460d31dae834cf19d61a197 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Fri, 7 May 2021 13:35:09 +0530 Subject: [PATCH 46/72] fix(Material Request): Add 'Partially Received' to Status drop-down list (#24857) Co-authored-by: Ganga Manoj Co-authored-by: Nabin Hait --- erpnext/stock/doctype/material_request/material_request.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index 8d7b238c17f..4e2d9e61704 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -181,7 +181,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nOrdered\nIssued\nTransferred\nReceived", + "options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nPartially Received\nOrdered\nIssued\nTransferred\nReceived", "print_hide": 1, "print_width": "100px", "read_only": 1, From 27cf19a19f09169a0f5fc08d537555471cc466e2 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 7 May 2021 13:37:42 +0530 Subject: [PATCH 47/72] feat(pos): show POS reserved stock in stock projected qty report (#25593) * feat(pos): consider POS reserved stock in stock projected qty report * chore: remove unwanted string formats --- .../doctype/pos_invoice/pos_invoice.py | 22 +++++++++++-------- .../report/pos_register/pos_register.py | 13 +++++------ .../stock_projected_qty.js | 9 +++++++- .../stock_projected_qty.py | 13 ++++++++--- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 1e6a3d1b3be..473db565fa5 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -461,7 +461,17 @@ def get_stock_availability(item_code, warehouse): order by posting_date desc, posting_time desc limit 1""", (item_code, warehouse), as_dict=1) - pos_sales_qty = frappe.db.sql("""select sum(p_item.qty) as qty + pos_sales_qty = get_pos_reserved_qty(item_code, warehouse) + + sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0 + + if sle_qty and pos_sales_qty: + return sle_qty - pos_sales_qty + else: + return sle_qty + +def get_pos_reserved_qty(item_code, warehouse): + reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item where p.name = p_item.parent and p.consolidated_invoice is NULL @@ -470,14 +480,8 @@ def get_stock_availability(item_code, warehouse): and p_item.item_code = %s and p_item.warehouse = %s """, (item_code, warehouse), as_dict=1) - - sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0 - pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0 - - if sle_qty and pos_sales_qty: - return sle_qty - pos_sales_qty - else: - return sle_qty + + return reserved_qty[0].qty or 0 if reserved_qty else 0 @frappe.whitelist() def make_sales_return(source_name, target_doc=None): diff --git a/erpnext/accounts/report/pos_register/pos_register.py b/erpnext/accounts/report/pos_register/pos_register.py index 52f7fe238e8..cfbd7fd0c8b 100644 --- a/erpnext/accounts/report/pos_register/pos_register.py +++ b/erpnext/accounts/report/pos_register/pos_register.py @@ -116,22 +116,19 @@ def validate_filters(filters): frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method")) def get_conditions(filters): - conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s".format( - company=filters.get("company"), - from_date=filters.get("from_date"), - to_date=filters.get("to_date")) + conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s" if filters.get("pos_profile"): - conditions += " AND pos_profile = %(pos_profile)s".format(pos_profile=filters.get("pos_profile")) + conditions += " AND pos_profile = %(pos_profile)s" if filters.get("owner"): - conditions += " AND owner = %(owner)s".format(owner=filters.get("owner")) + conditions += " AND owner = %(owner)s" if filters.get("customer"): - conditions += " AND customer = %(customer)s".format(customer=filters.get("customer")) + conditions += " AND customer = %(customer)s" if filters.get("is_return"): - conditions += " AND is_return = %(is_return)s".format(is_return=filters.get("is_return")) + conditions += " AND is_return = %(is_return)s" if filters.get("mode_of_payment"): conditions += """ diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js index babc6dc9602..cb109f8050d 100644 --- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.js @@ -14,7 +14,14 @@ frappe.query_reports["Stock Projected Qty"] = { "fieldname":"warehouse", "label": __("Warehouse"), "fieldtype": "Link", - "options": "Warehouse" + "options": "Warehouse", + "get_query": () => { + return { + filters: { + company: frappe.query_report.get_filter_value('company') + } + } + } }, { "fieldname":"item_code", diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py index 1183e41d041..808d2791709 100644 --- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py @@ -6,6 +6,7 @@ import frappe from frappe import _ from frappe.utils import flt, today from erpnext.stock.utils import update_included_uom_in_report, is_reposting_item_valuation_in_progress +from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_pos_reserved_qty def execute(filters=None): is_reposting_item_valuation_in_progress() @@ -49,9 +50,13 @@ def execute(filters=None): if (re_order_level or re_order_qty) and re_order_level > bin.projected_qty: shortage_qty = re_order_level - flt(bin.projected_qty) + reserved_qty_for_pos = get_pos_reserved_qty(bin.item_code, bin.warehouse) + if reserved_qty_for_pos: + bin.projected_qty -= reserved_qty_for_pos + data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse, item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty, - bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract, + bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract, reserved_qty_for_pos, bin.projected_qty, re_order_level, re_order_qty, shortage_qty]) if include_uom: @@ -74,9 +79,11 @@ def get_columns(): {"label": _("Requested Qty"), "fieldname": "indented_qty", "fieldtype": "Float", "width": 110, "convertible": "qty"}, {"label": _("Ordered Qty"), "fieldname": "ordered_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, {"label": _("Reserved Qty"), "fieldname": "reserved_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, - {"label": _("Reserved Qty for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float", + {"label": _("Reserved for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float", "width": 100, "convertible": "qty"}, - {"label": _("Reserved for sub contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float", + {"label": _("Reserved for Sub Contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float", + "width": 100, "convertible": "qty"}, + {"label": _("Reserved for POS Transactions"), "fieldname": "reserved_qty_for_pos", "fieldtype": "Float", "width": 100, "convertible": "qty"}, {"label": _("Projected Qty"), "fieldname": "projected_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, {"label": _("Reorder Level"), "fieldname": "re_order_level", "fieldtype": "Float", "width": 100, "convertible": "qty"}, From da7fefe29d82886a90263064b309b1fa0a9b02d9 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Fri, 7 May 2021 20:26:50 +0530 Subject: [PATCH 48/72] fix: timesheet filter date exclusive issue (#25626) --- erpnext/projects/doctype/timesheet/timesheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index ed02f79c2dd..8d99b48b595 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -209,7 +209,7 @@ def get_projectwise_timesheet_data(project, parent=None, from_time=None, to_time if parent: condition = "AND parent = %(parent)s" if from_time and to_time: - condition += "AND from_time BETWEEN %(from_time)s AND %(to_time)s" + condition += "AND CAST(from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s" return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt from `tabTimesheet Detail` where parenttype = 'Timesheet' and docstatus=1 and project = %(project)s {0} and billable = 1 From e28165ea871720a68ebdc00cfb7b97d6bf775d73 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 7 May 2021 20:27:51 +0530 Subject: [PATCH 49/72] fix: force https for shopify webhook registration (#25630) --- .../doctype/shopify_settings/shopify_settings.py | 2 +- erpnext/erpnext_integrations/utils.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py index 7634fd0caf4..381c5e5dec4 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py @@ -37,7 +37,7 @@ class ShopifySettings(Document): res = session.post(url, data=json.dumps({ "webhook": { "topic": method, - "address": get_webhook_address(connector_name='shopify_connection', method='store_request_data'), + "address": get_webhook_address(connector_name='shopify_connection', method='store_request_data', force_https=True), "format": "json" } }), headers=get_header(self)) diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index 362f6cf88ee..3840e781b4c 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -28,7 +28,7 @@ def validate_webhooks_request(doctype, hmac_key, secret_key='secret'): return innerfn -def get_webhook_address(connector_name, method, exclude_uri=False): +def get_webhook_address(connector_name, method, exclude_uri=False, force_https=False): endpoint = "erpnext.erpnext_integrations.connectors.{0}.{1}".format(connector_name, method) if exclude_uri: @@ -39,7 +39,11 @@ def get_webhook_address(connector_name, method, exclude_uri=False): except RuntimeError: url = "http://localhost:8000" - server_url = '{uri.scheme}://{uri.netloc}/api/method/{endpoint}'.format(uri=urlparse(url), endpoint=endpoint) + url_data = urlparse(url) + scheme = "https" if force_https else url_data.scheme + netloc = url_data.netloc + + server_url = f"{scheme}://{netloc}/api/method/{endpoint}" return server_url From 90e671905a9a4e8b496a84a8315ceba25e10d9ed Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 7 May 2021 20:28:51 +0530 Subject: [PATCH 50/72] chore: replace assertEquals with alias assertEqual (#25613) * chore: replace assertEquals with alias assertEqual assertEquals has been deprecated. ref: https://docs.python.org/3/library/unittest.html#deprecated-aliases * chore: sider fixes --- .../accounts/doctype/dunning/test_dunning.py | 6 +- .../doctype/gl_entry/test_gl_entry.py | 2 +- .../payment_order/test_payment_order.py | 8 +- .../doctype/pricing_rule/test_pricing_rule.py | 36 +++---- erpnext/assets/doctype/asset/test_asset.py | 4 +- .../purchase_order/test_purchase_order.py | 42 ++++---- .../mpesa_settings/test_mpesa_settings.py | 26 ++--- .../test_clinical_procedure.py | 2 +- .../doctype/lab_test/test_lab_test.py | 4 +- .../test_patient_appointment.py | 8 +- .../doctype/therapy_plan/test_therapy_plan.py | 12 +-- .../doctype/therapy_type/test_therapy_type.py | 2 +- .../test_compensatory_leave_request.py | 16 ++-- .../expense_claim/test_expense_claim.py | 12 +-- .../hr/doctype/job_offer/test_job_offer.py | 4 +- .../leave_allocation/test_leave_allocation.py | 14 +-- .../test_leave_application.py | 78 +++++++-------- .../leave_encashment/test_leave_encashment.py | 8 +- .../loan_management/doctype/loan/test_loan.py | 96 +++++++++---------- .../test_loan_disbursement.py | 6 +- .../test_loan_interest_accrual.py | 6 +- erpnext/manufacturing/doctype/bom/test_bom.py | 2 +- .../bom_update_tool/test_bom_update_tool.py | 6 +- .../doctype/work_order/test_work_order.py | 10 +- .../doctype/donation/test_donation.py | 2 +- .../portal/doctype/homepage/test_homepage.py | 2 +- .../homepage_section/test_homepage_section.py | 4 +- .../test_tax_exemption_80g_certificate.py | 12 +-- .../doctype/quotation/test_quotation.py | 2 +- .../doctype/sales_order/test_sales_order.py | 4 +- .../delivery_note/test_delivery_note.py | 6 +- .../purchase_receipt/test_purchase_receipt.py | 10 +- erpnext/support/doctype/issue/test_issue.py | 28 +++--- 33 files changed, 240 insertions(+), 240 deletions(-) diff --git a/erpnext/accounts/doctype/dunning/test_dunning.py b/erpnext/accounts/doctype/dunning/test_dunning.py index cb18309e3c9..c5ce514cdd2 100644 --- a/erpnext/accounts/doctype/dunning/test_dunning.py +++ b/erpnext/accounts/doctype/dunning/test_dunning.py @@ -42,9 +42,9 @@ class TestDunning(unittest.TestCase): ['Sales - _TC', 0.0, 20.44] ]) for gle in gl_entries: - self.assertEquals(expected_values[gle.account][0], gle.account) - self.assertEquals(expected_values[gle.account][1], gle.debit) - self.assertEquals(expected_values[gle.account][2], gle.credit) + self.assertEqual(expected_values[gle.account][0], gle.account) + self.assertEqual(expected_values[gle.account][1], gle.debit) + self.assertEqual(expected_values[gle.account][2], gle.credit) def test_payment_entry(self): dunning = create_dunning() diff --git a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py index b4a547b21ba..4167ca70df2 100644 --- a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py @@ -54,4 +54,4 @@ class TestGLEntry(unittest.TestCase): self.assertTrue(all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries))) new_naming_series_current_value = frappe.db.sql("SELECT current from tabSeries where name = %s", naming_series)[0][0] - self.assertEquals(old_naming_series_current_value + 2, new_naming_series_current_value) + self.assertEqual(old_naming_series_current_value + 2, new_naming_series_current_value) diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.py b/erpnext/accounts/doctype/payment_order/test_payment_order.py index 1c23e2a0ec2..5fdde07faa4 100644 --- a/erpnext/accounts/doctype/payment_order/test_payment_order.py +++ b/erpnext/accounts/doctype/payment_order/test_payment_order.py @@ -31,10 +31,10 @@ class TestPaymentOrder(unittest.TestCase): doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry") reference_doc = doc.get("references")[0] - self.assertEquals(reference_doc.reference_name, payment_entry.name) - self.assertEquals(reference_doc.reference_doctype, "Payment Entry") - self.assertEquals(reference_doc.supplier, "_Test Supplier") - self.assertEquals(reference_doc.amount, 250) + self.assertEqual(reference_doc.reference_name, payment_entry.name) + self.assertEqual(reference_doc.reference_doctype, "Payment Entry") + self.assertEqual(reference_doc.supplier, "_Test Supplier") + self.assertEqual(reference_doc.amount, 250) def create_payment_order_against_payment_entry(ref_doc, order_type): payment_order = frappe.get_doc(dict( diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index ef9aad562df..ffe8be1162f 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -99,7 +99,7 @@ class TestPricingRule(unittest.TestCase): args.item_code = "_Test Item 2" details = get_item_details(args) - self.assertEquals(details.get("discount_percentage"), 15) + self.assertEqual(details.get("discount_percentage"), 15) def test_pricing_rule_for_margin(self): from erpnext.stock.get_item_details import get_item_details @@ -145,8 +145,8 @@ class TestPricingRule(unittest.TestCase): "name": None }) details = get_item_details(args) - self.assertEquals(details.get("margin_type"), "Percentage") - self.assertEquals(details.get("margin_rate_or_amount"), 10) + self.assertEqual(details.get("margin_type"), "Percentage") + self.assertEqual(details.get("margin_rate_or_amount"), 10) def test_mixed_conditions_for_item_group(self): for item in ["Mixed Cond Item 1", "Mixed Cond Item 2"]: @@ -192,7 +192,7 @@ class TestPricingRule(unittest.TestCase): "name": None }) details = get_item_details(args) - self.assertEquals(details.get("discount_percentage"), 10) + self.assertEqual(details.get("discount_percentage"), 10) def test_pricing_rule_for_variants(self): from erpnext.stock.get_item_details import get_item_details @@ -322,11 +322,11 @@ class TestPricingRule(unittest.TestCase): si.insert(ignore_permissions=True) item = si.items[0] - self.assertEquals(item.margin_rate_or_amount, 10) - self.assertEquals(item.rate_with_margin, 1100) + self.assertEqual(item.margin_rate_or_amount, 10) + self.assertEqual(item.rate_with_margin, 1100) self.assertEqual(item.discount_percentage, 10) - self.assertEquals(item.discount_amount, 110) - self.assertEquals(item.rate, 990) + self.assertEqual(item.discount_amount, 110) + self.assertEqual(item.rate, 990) def test_pricing_rule_with_margin_and_discount_amount(self): frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule') @@ -338,10 +338,10 @@ class TestPricingRule(unittest.TestCase): si.insert(ignore_permissions=True) item = si.items[0] - self.assertEquals(item.margin_rate_or_amount, 10) - self.assertEquals(item.rate_with_margin, 1100) - self.assertEquals(item.discount_amount, 110) - self.assertEquals(item.rate, 990) + self.assertEqual(item.margin_rate_or_amount, 10) + self.assertEqual(item.rate_with_margin, 1100) + self.assertEqual(item.discount_amount, 110) + self.assertEqual(item.rate, 990) def test_pricing_rule_for_product_discount_on_same_item(self): frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule') @@ -458,21 +458,21 @@ class TestPricingRule(unittest.TestCase): si.items[0].price_list_rate = 1000 si.submit() item = si.items[0] - self.assertEquals(item.rate, 100) + self.assertEqual(item.rate, 100) # Correct Customer and Incorrect is_return value si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1) si.items[0].price_list_rate = 1000 si.submit() item = si.items[0] - self.assertEquals(item.rate, 100) + self.assertEqual(item.rate, 100) # Correct Customer and correct is_return value si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0) si.items[0].price_list_rate = 1000 si.submit() item = si.items[0] - self.assertEquals(item.rate, 900) + self.assertEqual(item.rate, 900) def test_multiple_pricing_rules(self): make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1, @@ -545,11 +545,11 @@ class TestPricingRule(unittest.TestCase): apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10) si = create_sales_invoice(qty=5, do_not_submit=True) - self.assertEquals(len(si.items), 2) - self.assertEquals(si.items[1].rate, 10) + self.assertEqual(len(si.items), 2) + self.assertEqual(si.items[1].rate, 10) si1 = create_sales_invoice(qty=2, do_not_submit=True) - self.assertEquals(len(si1.items), 1) + self.assertEqual(len(si1.items), 1) for doc in [si, si1]: doc.delete() diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index a0d76031fc4..40a8f85d8d6 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -78,7 +78,7 @@ class TestAsset(unittest.TestCase): }) doc.set_missing_values() - self.assertEquals(doc.items[0].is_fixed_asset, 1) + self.assertEqual(doc.items[0].is_fixed_asset, 1) def test_schedule_for_straight_line_method(self): pr = make_purchase_receipt(item_code="Macbook Pro", @@ -565,7 +565,7 @@ class TestAsset(unittest.TestCase): doc = make_invoice(pr.name) - self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account) + self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account) def test_asset_cwip_toggling_cases(self): cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting") diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 42f4472f29e..aaa98f2f1f4 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -187,7 +187,7 @@ class TestPurchaseOrder(unittest.TestCase): update_child_qty_rate('Purchase Order', trans_item, po.name) po.reload() - self.assertEquals(len(po.get('items')), 2) + self.assertEqual(len(po.get('items')), 2) self.assertEqual(po.status, 'To Receive and Bill') # ordered qty should increase on row addition self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7) @@ -234,7 +234,7 @@ class TestPurchaseOrder(unittest.TestCase): update_child_qty_rate('Purchase Order', trans_item, po.name) po.reload() - self.assertEquals(len(po.get('items')), 1) + self.assertEqual(len(po.get('items')), 1) self.assertEqual(po.status, 'To Receive and Bill') # ordered qty should decrease (back to initial) on row deletion @@ -448,13 +448,13 @@ class TestPurchaseOrder(unittest.TestCase): pi.load_from_db() - self.assertEquals(pi.per_received, 100.00) - self.assertEquals(pi.items[0].qty, pi.items[0].received_qty) + self.assertEqual(pi.per_received, 100.00) + self.assertEqual(pi.items[0].qty, pi.items[0].received_qty) po.load_from_db() - self.assertEquals(po.per_received, 100.00) - self.assertEquals(po.per_billed, 100.00) + self.assertEqual(po.per_received, 100.00) + self.assertEqual(po.per_billed, 100.00) pr.cancel() @@ -674,8 +674,8 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname=["reserved_qty_for_sub_contract", "projected_qty"], as_dict=1) - self.assertEquals(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) - self.assertEquals(bin2.projected_qty, bin1.projected_qty - 10) + self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) + self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10) # Create stock transfer rm_item = [{"item_code":"_Test FG Item","rm_item_code":"_Test Item","item_name":"_Test Item", @@ -690,7 +690,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + self.assertEqual(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) # close PO po.update_status("Closed") @@ -698,7 +698,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + self.assertEqual(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) # Re-open PO po.update_status("Submitted") @@ -706,7 +706,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + self.assertEqual(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) make_stock_entry(target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=40, basic_rate=100) @@ -723,7 +723,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + self.assertEqual(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) # Cancel PR pr.cancel() @@ -731,7 +731,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + self.assertEqual(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) # Make Purchase Invoice pi = make_pi_from_po(po.name) @@ -743,7 +743,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + self.assertEqual(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) # Cancel PR pi.cancel() @@ -751,7 +751,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + self.assertEqual(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) # Cancel Stock Entry se.cancel() @@ -759,7 +759,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) + self.assertEqual(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) # Cancel PO po.reload() @@ -768,7 +768,7 @@ class TestPurchaseOrder(unittest.TestCase): filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, fieldname="reserved_qty_for_sub_contract", as_dict=1) - self.assertEquals(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + self.assertEqual(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) def test_exploded_items_in_subcontracted(self): item_code = "_Test Subcontracted FG Item 1" @@ -782,7 +782,7 @@ class TestPurchaseOrder(unittest.TestCase): exploded_items = sorted([d.item_code for d in bom.exploded_items if not d.get('sourced_by_supplier')]) supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) - self.assertEquals(exploded_items, supplied_items) + self.assertEqual(exploded_items, supplied_items) po1 = create_purchase_order(item_code=item_code, qty=1, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0) @@ -790,7 +790,7 @@ class TestPurchaseOrder(unittest.TestCase): supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items]) bom_items = sorted([d.item_code for d in bom.items if not d.get('sourced_by_supplier')]) - self.assertEquals(supplied_items1, bom_items) + self.assertEqual(supplied_items1, bom_items) def test_backflush_based_on_stock_entry(self): item_code = "_Test Subcontracted FG Item 1" @@ -840,8 +840,8 @@ class TestPurchaseOrder(unittest.TestCase): transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name]) issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')]) - self.assertEquals(transferred_items, issued_items) - self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000) + self.assertEqual(transferred_items, issued_items) + self.assertEqual(pr.get('items')[0].rm_supp_cost, 2000) transferred_rm_map = frappe._dict() diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index 29487962f69..d370fbcda70 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -19,7 +19,7 @@ class TestMpesaSettings(unittest.TestCase): mode_of_payment = frappe.get_doc("Mode of Payment", "Mpesa-_Test") self.assertTrue(frappe.db.exists("Payment Gateway Account", {'payment_gateway': "Mpesa-_Test"})) self.assertTrue(mode_of_payment.name) - self.assertEquals(mode_of_payment.type, "Phone") + self.assertEqual(mode_of_payment.type, "Phone") def test_processing_of_account_balance(self): mpesa_doc = create_mpesa_settings(payment_gateway_name="_Account Balance") @@ -31,11 +31,11 @@ class TestMpesaSettings(unittest.TestCase): # test integration request creation and successful update of the status on receiving callback response self.assertTrue(integration_request) - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(integration_request.status, "Completed") # test formatting of account balance received as string to json with appropriate currency symbol mpesa_doc.reload() - self.assertEquals(mpesa_doc.account_balance, dumps({ + self.assertEqual(mpesa_doc.account_balance, dumps({ "Working Account": { "current_balance": "Sh 481,000.00", "available_balance": "Sh 481,000.00", @@ -60,7 +60,7 @@ class TestMpesaSettings(unittest.TestCase): pr = pos_invoice.create_payment_request() # test payment request creation - self.assertEquals(pr.payment_gateway, "Mpesa-Payment") + self.assertEqual(pr.payment_gateway, "Mpesa-Payment") # submitting payment request creates integration requests with random id integration_req_ids = frappe.get_all("Integration Request", filters={ @@ -75,12 +75,12 @@ class TestMpesaSettings(unittest.TestCase): # test integration request creation and successful update of the status on receiving callback response self.assertTrue(integration_request) - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(integration_request.status, "Completed") pos_invoice.reload() integration_request.reload() - self.assertEquals(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R") - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R") + self.assertEqual(integration_request.status, "Completed") frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") integration_request.delete() @@ -104,7 +104,7 @@ class TestMpesaSettings(unittest.TestCase): pr = pos_invoice.create_payment_request() # test payment request creation - self.assertEquals(pr.payment_gateway, "Mpesa-Payment") + self.assertEqual(pr.payment_gateway, "Mpesa-Payment") # submitting payment request creates integration requests with random id integration_req_ids = frappe.get_all("Integration Request", filters={ @@ -126,12 +126,12 @@ class TestMpesaSettings(unittest.TestCase): verify_transaction(**callback_response) # test completion of integration request integration_request = frappe.get_doc("Integration Request", integration_req_ids[i]) - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(integration_request.status, "Completed") integration_requests.append(integration_request) # check receipt number once all the integration requests are completed pos_invoice.reload() - self.assertEquals(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers)) + self.assertEqual(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers)) frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") [d.delete() for d in integration_requests] @@ -155,7 +155,7 @@ class TestMpesaSettings(unittest.TestCase): pr = pos_invoice.create_payment_request() # test payment request creation - self.assertEquals(pr.payment_gateway, "Mpesa-Payment") + self.assertEqual(pr.payment_gateway, "Mpesa-Payment") # submitting payment request creates integration requests with random id integration_req_ids = frappe.get_all("Integration Request", filters={ @@ -175,7 +175,7 @@ class TestMpesaSettings(unittest.TestCase): verify_transaction(**callback_response) # test completion of integration request integration_request = frappe.get_doc("Integration Request", integration_req_ids[0]) - self.assertEquals(integration_request.status, "Completed") + self.assertEqual(integration_request.status, "Completed") # now one request is completed # second integration request fails @@ -187,7 +187,7 @@ class TestMpesaSettings(unittest.TestCase): 'name': ['not in', integration_req_ids] }, pluck="name") - self.assertEquals(len(new_integration_req_ids), 1) + self.assertEqual(len(new_integration_req_ids), 1) frappe.db.set_value("Customer", "_Test Customer", "default_currency", "") frappe.db.sql("delete from `tabIntegration Request` where integration_request_service = 'Mpesa'") diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py index fb72073a07f..03e96a4b3be 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py @@ -17,7 +17,7 @@ class TestClinicalProcedure(unittest.TestCase): procedure_template.disabled = 1 procedure_template.save() - self.assertEquals(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1) + self.assertEqual(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1) def test_consumables(self): patient, medical_department, practitioner = create_healthcare_docs() diff --git a/erpnext/healthcare/doctype/lab_test/test_lab_test.py b/erpnext/healthcare/doctype/lab_test/test_lab_test.py index 79ab8a4d7f2..c9f0029ed80 100644 --- a/erpnext/healthcare/doctype/lab_test/test_lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/test_lab_test.py @@ -18,7 +18,7 @@ class TestLabTest(unittest.TestCase): lab_template.disabled = 1 lab_template.save() - self.assertEquals(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1) + self.assertEqual(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1) lab_template.reload() @@ -57,7 +57,7 @@ class TestLabTest(unittest.TestCase): # sample collection should not be created lab_test.reload() - self.assertEquals(lab_test.sample, None) + self.assertEqual(lab_test.sample, None) def test_create_lab_tests_from_sales_invoice(self): sales_invoice = create_sales_invoice() diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 2bb8a53c454..5f2dc480a1b 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -20,13 +20,13 @@ class TestPatientAppointment(unittest.TestCase): patient, medical_department, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0) appointment = create_appointment(patient, practitioner, nowdate()) - self.assertEquals(appointment.status, 'Open') + self.assertEqual(appointment.status, 'Open') appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2)) - self.assertEquals(appointment.status, 'Scheduled') + self.assertEqual(appointment.status, 'Scheduled') encounter = create_encounter(appointment) - self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') + self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') encounter.cancel() - self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') + self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') def test_start_encounter(self): patient, medical_department, practitioner = create_healthcare_docs() diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py index 7fb159d6b50..d079bedb420 100644 --- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py @@ -18,24 +18,24 @@ class TestTherapyPlan(unittest.TestCase): def test_status(self): plan = create_therapy_plan() - self.assertEquals(plan.status, 'Not Started') + self.assertEqual(plan.status, 'Not Started') session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company') frappe.get_doc(session).submit() - self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress') + self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress') session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company') frappe.get_doc(session).submit() - self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed') + self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed') patient, medical_department, practitioner = create_healthcare_docs() appointment = create_appointment(patient, practitioner, nowdate()) session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name) session = frappe.get_doc(session) session.submit() - self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') + self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') session.cancel() - self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') + self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') def test_therapy_plan_from_template(self): patient = create_patient() @@ -49,7 +49,7 @@ class TestTherapyPlan(unittest.TestCase): si.save() therapy_plan_template_amt = frappe.db.get_value('Therapy Plan Template', template, 'total_amount') - self.assertEquals(si.items[0].amount, therapy_plan_template_amt) + self.assertEqual(si.items[0].amount, therapy_plan_template_amt) def create_therapy_plan(template=None): diff --git a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py index 03a1be8a4e7..21f63699753 100644 --- a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py +++ b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py @@ -13,7 +13,7 @@ class TestTherapyType(unittest.TestCase): therapy_type.disabled = 1 therapy_type.save() - self.assertEquals(frappe.db.get_value('Item', therapy_type.item, 'disabled'), 1) + self.assertEqual(frappe.db.get_value('Item', therapy_type.item, 'disabled'), 1) def create_therapy_type(): exercise = create_exercise_type() diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py index 74ce30108fd..3b99c57051a 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py @@ -68,19 +68,19 @@ class TestCompensatoryLeaveRequest(unittest.TestCase): filters = dict(transaction_name=compensatory_leave_request.leave_allocation) leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters) - self.assertEquals(len(leave_ledger_entry), 1) - self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, 1) + self.assertEqual(len(leave_ledger_entry), 1) + self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, 1) # check reverse leave ledger entry on cancellation compensatory_leave_request.cancel() leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters, order_by = 'creation desc') - self.assertEquals(len(leave_ledger_entry), 2) - self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, -1) + self.assertEqual(len(leave_ledger_entry), 2) + self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, -1) def get_compensatory_leave_request(employee, leave_date=today()): prev_comp_leave_req = frappe.db.get_value('Compensatory Leave Request', diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 3f22ca21412..578eccf787d 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -88,9 +88,9 @@ class TestExpenseClaim(unittest.TestCase): ]) for gle in gl_entries: - self.assertEquals(expected_values[gle.account][0], gle.account) - self.assertEquals(expected_values[gle.account][1], gle.debit) - self.assertEquals(expected_values[gle.account][2], gle.credit) + self.assertEqual(expected_values[gle.account][0], gle.account) + self.assertEqual(expected_values[gle.account][1], gle.debit) + self.assertEqual(expected_values[gle.account][2], gle.credit) def test_rejected_expense_claim(self): payable_account = get_payable_account(company_name) @@ -104,11 +104,11 @@ class TestExpenseClaim(unittest.TestCase): }) expense_claim.submit() - self.assertEquals(expense_claim.status, 'Rejected') - self.assertEquals(expense_claim.total_sanctioned_amount, 0.0) + self.assertEqual(expense_claim.status, 'Rejected') + self.assertEqual(expense_claim.total_sanctioned_amount, 0.0) gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name}) - self.assertEquals(len(gl_entry), 0) + self.assertEqual(len(gl_entry), 0) def test_expense_approver_perms(self): user = "test_approver_perm_emp@example.com" diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py index 690a692ddca..b3e1dc8d87b 100644 --- a/erpnext/hr/doctype/job_offer/test_job_offer.py +++ b/erpnext/hr/doctype/job_offer/test_job_offer.py @@ -35,13 +35,13 @@ class TestJobOffer(unittest.TestCase): job_offer = create_job_offer(job_applicant=job_applicant.name) job_offer.submit() job_applicant.reload() - self.assertEquals(job_applicant.status, "Accepted") + self.assertEqual(job_applicant.status, "Accepted") # status update after rejection job_offer.status = "Rejected" job_offer.submit() job_applicant.reload() - self.assertEquals(job_applicant.status, "Rejected") + self.assertEqual(job_applicant.status, "Rejected") def create_job_offer(**args): args = frappe._dict(args) diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index 0b71036c860..6e7ae87d08c 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -96,7 +96,7 @@ class TestLeaveAllocation(unittest.TestCase): carry_forward=1) leave_allocation_1.submit() - self.assertEquals(leave_allocation_1.unused_leaves, 10) + self.assertEqual(leave_allocation_1.unused_leaves, 10) leave_allocation_1.cancel() @@ -108,7 +108,7 @@ class TestLeaveAllocation(unittest.TestCase): new_leaves_allocated=25) leave_allocation_2.submit() - self.assertEquals(leave_allocation_2.unused_leaves, 5) + self.assertEqual(leave_allocation_2.unused_leaves, 5) def test_carry_forward_leaves_expiry(self): frappe.db.sql("delete from `tabLeave Allocation`") @@ -145,7 +145,7 @@ class TestLeaveAllocation(unittest.TestCase): to_date=add_months(nowdate(), 12)) leave_allocation_1.submit() - self.assertEquals(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated) + self.assertEqual(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated) def test_creation_of_leave_ledger_entry_on_submit(self): frappe.db.sql("delete from `tabLeave Allocation`") @@ -155,10 +155,10 @@ class TestLeaveAllocation(unittest.TestCase): leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_allocation.name)) - self.assertEquals(len(leave_ledger_entry), 1) - self.assertEquals(leave_ledger_entry[0].employee, leave_allocation.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, leave_allocation.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated) + self.assertEqual(len(leave_ledger_entry), 1) + self.assertEqual(leave_ledger_entry[0].employee, leave_allocation.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, leave_allocation.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated) # check if leave ledger entry is deleted on cancellation leave_allocation.cancel() diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index b54c9712c89..a4a96b813ee 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -16,36 +16,36 @@ from erpnext.hr.doctype.employee.test_employee import make_employee test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"] _test_records = [ - { - "company": "_Test Company", - "doctype": "Leave Application", - "employee": "_T-Employee-00001", - "from_date": "2013-05-01", - "description": "_Test Reason", - "leave_type": "_Test Leave Type", - "posting_date": "2013-01-02", - "to_date": "2013-05-05" - }, - { - "company": "_Test Company", - "doctype": "Leave Application", - "employee": "_T-Employee-00002", - "from_date": "2013-05-01", - "description": "_Test Reason", - "leave_type": "_Test Leave Type", - "posting_date": "2013-01-02", - "to_date": "2013-05-05" - }, - { - "company": "_Test Company", - "doctype": "Leave Application", - "employee": "_T-Employee-00001", - "from_date": "2013-01-15", - "description": "_Test Reason", - "leave_type": "_Test Leave Type LWP", - "posting_date": "2013-01-02", - "to_date": "2013-01-15" - } + { + "company": "_Test Company", + "doctype": "Leave Application", + "employee": "_T-Employee-00001", + "from_date": "2013-05-01", + "description": "_Test Reason", + "leave_type": "_Test Leave Type", + "posting_date": "2013-01-02", + "to_date": "2013-05-05" + }, + { + "company": "_Test Company", + "doctype": "Leave Application", + "employee": "_T-Employee-00002", + "from_date": "2013-05-01", + "description": "_Test Reason", + "leave_type": "_Test Leave Type", + "posting_date": "2013-01-02", + "to_date": "2013-05-05" + }, + { + "company": "_Test Company", + "doctype": "Leave Application", + "employee": "_T-Employee-00001", + "from_date": "2013-01-15", + "description": "_Test Reason", + "leave_type": "_Test Leave Type LWP", + "posting_date": "2013-01-02", + "to_date": "2013-01-15" + } ] @@ -516,9 +516,9 @@ class TestLeaveApplication(unittest.TestCase): leave_application.submit() leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name)) - self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1) + self.assertEqual(leave_ledger_entry[0].employee, leave_application.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, leave_application.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1) # check if leave ledger entry is deleted on cancellation leave_application.cancel() @@ -549,11 +549,11 @@ class TestLeaveApplication(unittest.TestCase): leave_ledger_entry = frappe.get_all('Leave Ledger Entry', '*', filters=dict(transaction_name=leave_application.name)) - self.assertEquals(len(leave_ledger_entry), 2) - self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, -9) - self.assertEquals(leave_ledger_entry[1].leaves, -2) + self.assertEqual(len(leave_ledger_entry), 2) + self.assertEqual(leave_ledger_entry[0].employee, leave_application.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, leave_application.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, -9) + self.assertEqual(leave_ledger_entry[1].leaves, -2) def test_leave_application_creation_after_expiry(self): # test leave balance for carry forwarded allocation @@ -566,7 +566,7 @@ class TestLeaveApplication(unittest.TestCase): create_carry_forwarded_allocation(employee, leave_type) - self.assertEquals(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0) + self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0) def test_leave_approver_perms(self): employee = get_employee() diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py index aafc9642d46..e0ffa5dd41a 100644 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py @@ -88,10 +88,10 @@ class TestLeaveEncashment(unittest.TestCase): leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_encashment.name)) - self.assertEquals(len(leave_ledger_entry), 1) - self.assertEquals(leave_ledger_entry[0].employee, leave_encashment.employee) - self.assertEquals(leave_ledger_entry[0].leave_type, leave_encashment.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1) + self.assertEqual(len(leave_ledger_entry), 1) + self.assertEqual(leave_ledger_entry[0].employee, leave_encashment.employee) + self.assertEqual(leave_ledger_entry[0].leave_type, leave_encashment.leave_type) + self.assertEqual(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1) # check if leave ledger entry is deleted on cancellation diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index fae6f860b6d..fa4707ce2b4 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -55,9 +55,9 @@ class TestLoan(unittest.TestCase): def test_loan(self): loan = frappe.get_doc("Loan", {"applicant":self.applicant1}) - self.assertEquals(loan.monthly_repayment_amount, 15052) - self.assertEquals(flt(loan.total_interest_payable, 0), 21034) - self.assertEquals(flt(loan.total_payment, 0), 301034) + self.assertEqual(loan.monthly_repayment_amount, 15052) + self.assertEqual(flt(loan.total_interest_payable, 0), 21034) + self.assertEqual(flt(loan.total_payment, 0), 301034) schedule = loan.repayment_schedule @@ -72,9 +72,9 @@ class TestLoan(unittest.TestCase): loan.monthly_repayment_amount = 14000 loan.save() - self.assertEquals(len(loan.repayment_schedule), 22) - self.assertEquals(flt(loan.total_interest_payable, 0), 22712) - self.assertEquals(flt(loan.total_payment, 0), 302712) + self.assertEqual(len(loan.repayment_schedule), 22) + self.assertEqual(flt(loan.total_interest_payable, 0), 22712) + self.assertEqual(flt(loan.total_payment, 0), 302712) def test_loan_with_security(self): @@ -89,7 +89,7 @@ class TestLoan(unittest.TestCase): loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application) - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) def test_loan_disbursement(self): pledge = [{ @@ -102,7 +102,7 @@ class TestLoan(unittest.TestCase): create_pledge(loan_application) loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application) - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) loan.submit() @@ -120,8 +120,8 @@ class TestLoan(unittest.TestCase): filters = {'voucher_type': 'Loan Disbursement', 'voucher_no': loan_disbursement_entry2.name} ) - self.assertEquals(loan.status, "Disbursed") - self.assertEquals(loan.disbursed_amount, 1000000) + self.assertEqual(loan.status, "Disbursed") + self.assertEqual(loan.disbursed_amount, 1000000) self.assertTrue(gl_entries1) self.assertTrue(gl_entries2) @@ -137,7 +137,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -156,15 +156,15 @@ class TestLoan(unittest.TestCase): repayment_entry.submit() penalty_amount = (accrued_interest_amount * 5 * 25) / 100 - self.assertEquals(flt(repayment_entry.penalty_amount,0), flt(penalty_amount, 0)) + self.assertEqual(flt(repayment_entry.penalty_amount,0), flt(penalty_amount, 0)) amounts = frappe.db.get_all('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount']) loan.load_from_db() total_interest_paid = amounts[0]['paid_interest_amount'] + amounts[1]['paid_interest_amount'] - self.assertEquals(amounts[1]['paid_interest_amount'], repayment_entry.interest_payable) - self.assertEquals(flt(loan.total_principal_paid, 0), flt(repayment_entry.amount_paid - + self.assertEqual(amounts[1]['paid_interest_amount'], repayment_entry.interest_payable) + self.assertEqual(flt(loan.total_principal_paid, 0), flt(repayment_entry.amount_paid - penalty_amount - total_interest_paid, 0)) def test_loan_closure(self): @@ -179,7 +179,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -204,12 +204,12 @@ class TestLoan(unittest.TestCase): amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)']) - self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) - self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) + self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0)) + self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0) request_loan_closure(loan.name) loan.load_from_db() - self.assertEquals(loan.status, "Loan Closure Requested") + self.assertEqual(loan.status, "Loan Closure Requested") def test_loan_repayment_for_term_loan(self): pledges = [{ @@ -241,8 +241,8 @@ class TestLoan(unittest.TestCase): amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', 'paid_principal_amount']) - self.assertEquals(amounts[0], 11250.00) - self.assertEquals(amounts[1], 78303.00) + self.assertEqual(amounts[0], 11250.00) + self.assertEqual(amounts[1], 78303.00) def test_security_shortfall(self): pledges = [{ @@ -268,17 +268,17 @@ class TestLoan(unittest.TestCase): loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name}) self.assertTrue(loan_security_shortfall) - self.assertEquals(loan_security_shortfall.loan_amount, 1000000.00) - self.assertEquals(loan_security_shortfall.security_value, 800000.00) - self.assertEquals(loan_security_shortfall.shortfall_amount, 600000.00) + self.assertEqual(loan_security_shortfall.loan_amount, 1000000.00) + self.assertEqual(loan_security_shortfall.security_value, 800000.00) + self.assertEqual(loan_security_shortfall.shortfall_amount, 600000.00) frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250 where loan_security='Test Security 2'""") create_process_loan_security_shortfall() loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name}) - self.assertEquals(loan_security_shortfall.status, "Completed") - self.assertEquals(loan_security_shortfall.shortfall_amount, 0) + self.assertEqual(loan_security_shortfall.status, "Completed") + self.assertEqual(loan_security_shortfall.shortfall_amount, 0) def test_loan_security_unpledge(self): pledge = [{ @@ -292,7 +292,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -312,7 +312,7 @@ class TestLoan(unittest.TestCase): request_loan_closure(loan.name) loan.load_from_db() - self.assertEquals(loan.status, "Loan Closure Requested") + self.assertEqual(loan.status, "Loan Closure Requested") unpledge_request = unpledge_security(loan=loan.name, save=1) unpledge_request.submit() @@ -323,11 +323,11 @@ class TestLoan(unittest.TestCase): pledged_qty = get_pledged_security_qty(loan.name) self.assertEqual(loan.status, 'Closed') - self.assertEquals(sum(pledged_qty.values()), 0) + self.assertEqual(sum(pledged_qty.values()), 0) amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 5)) self.assertEqual(amounts['pending_principal_amount'], 0) - self.assertEquals(amounts['payable_principal_amount'], 0.0) + self.assertEqual(amounts['payable_principal_amount'], 0.0) self.assertEqual(amounts['interest_amount'], 0) def test_partial_loan_security_unpledge(self): @@ -346,7 +346,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -379,7 +379,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) unpledge_map = {'Test Security 1': 4000} unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1) @@ -450,7 +450,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -475,7 +475,7 @@ class TestLoan(unittest.TestCase): request_loan_closure(loan.name) loan.load_from_db() - self.assertEquals(loan.status, "Loan Closure Requested") + self.assertEqual(loan.status, "Loan Closure Requested") amounts = calculate_amounts(loan.name, add_days(last_date, 5)) self.assertEqual(amounts['pending_principal_amount'], 0.0) @@ -492,7 +492,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -533,8 +533,8 @@ class TestLoan(unittest.TestCase): calculated_penalty_amount = frappe.db.get_value('Loan Interest Accrual', {'process_loan_interest_accrual': process, 'loan': loan.name}, 'penalty_amount') - self.assertEquals(loan.loan_amount, 1000000) - self.assertEquals(calculated_penalty_amount, penalty_amount) + self.assertEqual(loan.loan_amount, 1000000) + self.assertEqual(calculated_penalty_amount, penalty_amount) def test_penalty_repayment(self): loan, dummy = create_loan_scenario_for_penalty(self) @@ -547,13 +547,13 @@ class TestLoan(unittest.TestCase): repayment_entry.submit() amounts = calculate_amounts(loan.name, '2019-11-30 00:00:01') - self.assertEquals(amounts['penalty_amount'], second_penalty) + self.assertEqual(amounts['penalty_amount'], second_penalty) repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2019-11-30 00:00:01', second_penalty) repayment_entry.submit() amounts = calculate_amounts(loan.name, '2019-11-30 00:00:02') - self.assertEquals(amounts['penalty_amount'], 0) + self.assertEqual(amounts['penalty_amount'], 0) def test_loan_write_off_limit(self): pledge = [{ @@ -567,7 +567,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -589,15 +589,15 @@ class TestLoan(unittest.TestCase): amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)']) - self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) - self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) + self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0)) + self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0) amounts = calculate_amounts(loan.name, add_days(last_date, 5)) - self.assertEquals(flt(amounts['pending_principal_amount'], 0), 50) + self.assertEqual(flt(amounts['pending_principal_amount'], 0), 50) request_loan_closure(loan.name) loan.load_from_db() - self.assertEquals(loan.status, "Loan Closure Requested") + self.assertEqual(loan.status, "Loan Closure Requested") def test_loan_amount_write_off(self): pledge = [{ @@ -611,7 +611,7 @@ class TestLoan(unittest.TestCase): loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -633,17 +633,17 @@ class TestLoan(unittest.TestCase): amount = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['sum(paid_interest_amount)']) - self.assertEquals(flt(amount, 0),flt(accrued_interest_amount, 0)) - self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) + self.assertEqual(flt(amount, 0),flt(accrued_interest_amount, 0)) + self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0) amounts = calculate_amounts(loan.name, add_days(last_date, 5)) - self.assertEquals(flt(amounts['pending_principal_amount'], 0), 100) + self.assertEqual(flt(amounts['pending_principal_amount'], 0), 100) we = make_loan_write_off(loan.name, amount=amounts['pending_principal_amount']) we.submit() amounts = calculate_amounts(loan.name, add_days(last_date, 5)) - self.assertEquals(flt(amounts['pending_principal_amount'], 0), 0) + self.assertEqual(flt(amounts['pending_principal_amount'], 0), 0) def create_loan_scenario_for_penalty(doc): pledge = [{ diff --git a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py index a8753877a6a..da56710c679 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py @@ -87,7 +87,7 @@ class TestLoanDisbursement(unittest.TestCase): loan = create_demand_loan(self.applicant, "Demand Loan", loan_application, posting_date='2019-10-01') loan.submit() - self.assertEquals(loan.loan_amount, 1000000) + self.assertEqual(loan.loan_amount, 1000000) first_date = '2019-10-01' last_date = '2019-10-30' @@ -114,5 +114,5 @@ class TestLoanDisbursement(unittest.TestCase): per_day_interest = get_per_day_interest(1500000, 13.5, '2019-10-30') interest = per_day_interest * 15 - self.assertEquals(amounts['pending_principal_amount'], 1500000) - self.assertEquals(amounts['interest_amount'], flt(interest + previous_interest, 2)) + self.assertEqual(amounts['pending_principal_amount'], 1500000) + self.assertEqual(amounts['interest_amount'], flt(interest + previous_interest, 2)) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py index 85e008ac293..eb626f3eee0 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py @@ -52,7 +52,7 @@ class TestLoanInterestAccrual(unittest.TestCase): process_loan_interest_accrual_for_demand_loans(posting_date=last_date) loan_interest_accural = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name}) - self.assertEquals(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0)) + self.assertEqual(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0)) def test_accumulated_amounts(self): pledge = [{ @@ -76,7 +76,7 @@ class TestLoanInterestAccrual(unittest.TestCase): process_loan_interest_accrual_for_demand_loans(posting_date=last_date) loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name}) - self.assertEquals(flt(loan_interest_accrual.interest_amount, 0), flt(accrued_interest_amount, 0)) + self.assertEqual(flt(loan_interest_accrual.interest_amount, 0), flt(accrued_interest_amount, 0)) next_start_date = '2019-10-31' next_end_date = '2019-11-29' @@ -90,4 +90,4 @@ class TestLoanInterestAccrual(unittest.TestCase): loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name, 'process_loan_interest_accrual': process}) - self.assertEquals(flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount) + self.assertEqual(flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 7108338dab4..e1cca9e3ef4 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -223,7 +223,7 @@ class TestBOM(unittest.TestCase): is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") bom_items = sorted([d.item_code for d in bom.items if d.sourced_by_supplier != 1]) supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) - self.assertEquals(bom_items, supplied_items) + self.assertEqual(bom_items, supplied_items) def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py index ac9a409bcbe..80d1cdfc8f2 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py @@ -45,16 +45,16 @@ class TestBOMUpdateTool(unittest.TestCase): else: doc = frappe.get_doc("BOM", bom_no) - self.assertEquals(doc.total_cost, 200) + self.assertEqual(doc.total_cost, 200) frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 200) update_cost() doc.load_from_db() - self.assertEquals(doc.total_cost, 300) + self.assertEqual(doc.total_cost, 300) frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 100) update_cost() doc.load_from_db() - self.assertEquals(doc.total_cost, 200) + self.assertEqual(doc.total_cost, 200) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 6b1fafe5f4c..cb1ee92196f 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -473,7 +473,7 @@ class TestWorkOrder(unittest.TestCase): def test_cost_center_for_manufacture(self): wo_order = make_wo_order_test_record() ste = make_stock_entry(wo_order.name, "Material Transfer for Manufacture", wo_order.qty) - self.assertEquals(ste.get("items")[0].get("cost_center"), "_Test Cost Center - _TC") + self.assertEqual(ste.get("items")[0].get("cost_center"), "_Test Cost Center - _TC") def test_operation_time_with_batch_size(self): fg_item = "Test Batch Size Item For BOM" @@ -539,11 +539,11 @@ class TestWorkOrder(unittest.TestCase): ste_cancel_list.append(ste1) ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2)) - self.assertEquals(ste3.fg_completed_qty, 2) + self.assertEqual(ste3.fg_completed_qty, 2) expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4} for row in ste3.items: - self.assertEquals(row.qty, expected_qty.get(row.item_code)) + self.assertEqual(row.qty, expected_qty.get(row.item_code)) ste_cancel_list.reverse() for ste_doc in ste_cancel_list: ste_doc.cancel() @@ -577,7 +577,7 @@ class TestWorkOrder(unittest.TestCase): ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2)) for ste_row in ste3.items: if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse: - self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) + self.assertEqual(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) ste3.submit() ste_cancel_list.append(ste3) @@ -585,7 +585,7 @@ class TestWorkOrder(unittest.TestCase): ste2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2)) for ste_row in ste2.items: if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse: - self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) + self.assertEqual(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) ste_cancel_list.reverse() for ste_doc in ste_cancel_list: ste_doc.cancel() diff --git a/erpnext/non_profit/doctype/donation/test_donation.py b/erpnext/non_profit/doctype/donation/test_donation.py index c6a534dac34..bbe9bf5228d 100644 --- a/erpnext/non_profit/doctype/donation/test_donation.py +++ b/erpnext/non_profit/doctype/donation/test_donation.py @@ -39,7 +39,7 @@ class TestDonation(unittest.TestCase): donation.on_payment_authorized() donation.reload() - self.assertEquals(donation.paid, 1) + self.assertEqual(donation.paid, 1) self.assertTrue(frappe.db.exists('Payment Entry', {'reference_no': donation.name})) diff --git a/erpnext/portal/doctype/homepage/test_homepage.py b/erpnext/portal/doctype/homepage/test_homepage.py index bf5c4025a0b..b717491a821 100644 --- a/erpnext/portal/doctype/homepage/test_homepage.py +++ b/erpnext/portal/doctype/homepage/test_homepage.py @@ -13,7 +13,7 @@ class TestHomepage(unittest.TestCase): set_request(method='GET', path='home') response = render() - self.assertEquals(response.status_code, 200) + self.assertEqual(response.status_code, 200) html = frappe.safe_decode(response.get_data()) self.assertTrue('
    Date: Fri, 7 May 2021 20:30:04 +0530 Subject: [PATCH 51/72] feat!: add pick batch button (#25413) * feat!: add pick batch button BREAKING CHANGE: replaces setup_serial_no with setup_serial_or_batch_no. * refactor: use setup_serial_or_batch_no instead of setup_serial_no * refactor: use setup_serial_or_batch_no instead of setup_serial_no * refactor: use setup_serial_or_batch_no instead of setup_serial_no * style: add sider review changes * refactor: make consice, extract function * refactor: camel to snake casing --- .../doctype/sales_invoice/sales_invoice.js | 4 +- erpnext/public/js/utils.js | 48 ++++++++++--------- .../doctype/delivery_note/delivery_note.js | 4 +- .../stock/doctype/stock_entry/stock_entry.js | 2 +- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 8a42d9e13c1..7c73ad6c90e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -356,11 +356,11 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte }, items_on_form_rendered: function() { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, packed_items_on_form_rendered: function(doc, grid_row) { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, make_sales_return: function() { diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 19c90730908..472746ab84c 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -48,31 +48,24 @@ $.extend(erpnext, { return cint(frappe.boot.sysdefaults.allow_stale); }, - setup_serial_no: function() { - var grid_row = cur_frm.open_grid_row(); - if(!grid_row || !grid_row.grid_form.fields_dict.serial_no || - grid_row.grid_form.fields_dict.serial_no.get_status()!=="Write") return; + setup_serial_or_batch_no: function() { + let grid_row = cur_frm.open_grid_row(); + if (!grid_row || !grid_row.grid_form.fields_dict.serial_no || + grid_row.grid_form.fields_dict.serial_no.get_status() !== "Write") return; - var $btn = $('') - .appendTo($("
    ") - .css({"margin-bottom": "10px", "margin-top": "10px"}) - .appendTo(grid_row.grid_form.fields_dict.serial_no.$wrapper)); + frappe.model.get_value('Item', {'name': grid_row.doc.item_code}, + ['has_serial_no', 'has_batch_no'], ({has_serial_no, has_batch_no}) => { + Object.assign(grid_row.doc, {has_serial_no, has_batch_no}); - var me = this; - $btn.on("click", function() { - let callback = ''; - let on_close = ''; - - frappe.model.get_value('Item', {'name':grid_row.doc.item_code}, 'has_serial_no', - (data) => { - if(data) { - grid_row.doc.has_serial_no = data.has_serial_no; - me.show_serial_batch_selector(grid_row.frm, grid_row.doc, - callback, on_close, true); - } + if (has_serial_no) { + attach_selector_button(__("Add Serial No"), + grid_row.grid_form.fields_dict.serial_no.$wrapper, this, grid_row); + } else if (has_batch_no) { + attach_selector_button(__("Pick Batch No"), + grid_row.grid_form.fields_dict.batch_no.$wrapper, this, grid_row); } - ); - }); + } + ); }, route_to_adjustment_jv: (args) => { @@ -743,3 +736,14 @@ $(document).on('app_ready', function() { }); } }); + +function attach_selector_button(inner_text, append_loction, context, grid_row) { + let $btn_div = $("
    ").css({"margin-bottom": "10px", "margin-top": "10px"}) + .appendTo(append_loction); + let $btn = $(``) + .appendTo($btn_div); + + $btn.on("click", function() { + context.show_serial_batch_selector(grid_row.frm, grid_row.doc, "", "", true); + }); +} diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 334bdeac9d3..7875b9cd87f 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -273,11 +273,11 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( }, items_on_form_rendered: function(doc, grid_row) { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, packed_items_on_form_rendered: function(doc, grid_row) { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, close_delivery_note: function(doc){ diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index ef7d54ac968..772c8df96e1 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -996,7 +996,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({ }, items_on_form_rendered: function(doc, grid_row) { - erpnext.setup_serial_no(); + erpnext.setup_serial_or_batch_no(); }, toggle_related_fields: function(doc) { From aa9e1720913527aef4193a053606d5cf38a4f1cf Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Sat, 8 May 2021 17:15:33 +0530 Subject: [PATCH 52/72] feat: Add Create Expense Claim button in Delivery Trip (#25526) * feat(Delivery Trip): Add employee_code field * feat(Expense Claim): Add Delivery Trip Number field * feat(Delivery Trip): Add Create Expense Claim button * feat(Delivery Trip): Make Create Expense Claim button show up after save * fix(Delivery Trip): Fix Sider issues * fix(Delivery Trip): Display button after submit * fix(Delivery Trip & Expense Claim): Rename new fields * fix(Delivery Trip): Add button in refresh * fix(Delivery Trip): Remove redundant line * fix(Expense Claim): Display delivery_trip only if non-empty * fix(Delivery Trip): Add test for Create Expense Claim * fix(Delivery Trip): Fix Sider Issue * fix(Delivery Trip): Only display Create Expense Claim if the driver is an employee * fix(Delivery Trip): Fix test * fix(Delivery Trip): Fix make_expense_claim() * fix: sider Co-authored-by: Saqib --- erpnext/hr/doctype/expense_claim/expense_claim.json | 10 +++++++++- .../stock/doctype/delivery_trip/delivery_trip.js | 9 +++++++++ .../stock/doctype/delivery_trip/delivery_trip.json | 11 ++++++++++- .../stock/doctype/delivery_trip/delivery_trip.py | 13 +++++++++++++ .../doctype/delivery_trip/test_delivery_trip.py | 6 +++++- 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index e3e6e80616a..a268c15c70b 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -14,6 +14,7 @@ "column_break_5", "expense_approver", "approval_status", + "delivery_trip", "is_paid", "expense_details", "expenses", @@ -365,13 +366,20 @@ "label": "Total Taxes and Charges", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "depends_on": "eval: doc.delivery_trip", + "fieldname": "delivery_trip", + "fieldtype": "Link", + "label": "Delivery Trip", + "options": "Delivery Trip" } ], "icon": "fa fa-money", "idx": 1, "is_submittable": 1, "links": [], - "modified": "2020-09-18 17:26:09.703215", + "modified": "2021-05-04 05:35:12.040199", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.js b/erpnext/stock/doctype/delivery_trip/delivery_trip.js index a6fbb66aa2b..68cba2993c6 100755 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.js +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.js @@ -41,6 +41,15 @@ frappe.ui.form.on('Delivery Trip', { }, refresh: function (frm) { + if (frm.doc.docstatus == 1 && frm.doc.employee) { + frm.add_custom_button(__('Expense Claim'), function() { + frappe.model.open_mapped_doc({ + method: 'erpnext.stock.doctype.delivery_trip.delivery_trip.make_expense_claim', + frm: cur_frm, + }); + }, __("Create")); + } + if (frm.doc.docstatus == 1 && frm.doc.delivery_stops.length > 0) { frm.add_custom_button(__("Notify Customers via Email"), function () { frm.trigger('notify_customers'); diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.json b/erpnext/stock/doctype/delivery_trip/delivery_trip.json index 879901f6a8d..11b71c20761 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.json +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.json @@ -21,6 +21,7 @@ "column_break_4", "vehicle", "departure_time", + "employee", "delivery_service_stops", "delivery_stops", "calculate_arrival_time", @@ -176,11 +177,19 @@ "fieldtype": "Data", "label": "Driver Email", "read_only": 1 + }, + { + "fetch_from": "driver.employee", + "fieldname": "employee", + "fieldtype": "Link", + "label": "Employee", + "options": "Employee", + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-01-26 22:37:14.824021", + "modified": "2021-04-30 21:21:36.610142", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Trip", diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py index de85bc3922c..81e730126ec 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py @@ -11,6 +11,7 @@ from frappe import _ from frappe.contacts.doctype.address.address import get_address_display from frappe.model.document import Document from frappe.utils import cint, get_datetime, get_link_to_form +from frappe.model.mapper import get_mapped_doc class DeliveryTrip(Document): @@ -394,3 +395,15 @@ def get_driver_email(driver): employee = frappe.db.get_value("Driver", driver, "employee") email = frappe.db.get_value("Employee", employee, "prefered_email") return {"email": email} + +@frappe.whitelist() +def make_expense_claim(source_name, target_doc=None): + doc = get_mapped_doc("Delivery Trip", source_name, + {"Delivery Trip": { + "doctype": "Expense Claim", + "field_map": { + "name" : "delivery_trip" + } + }}, target_doc) + + return doc \ No newline at end of file diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py index eeea6da7a42..1e716031751 100644 --- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py @@ -7,7 +7,7 @@ import unittest import erpnext import frappe -from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers +from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers, make_expense_claim from erpnext.tests.utils import create_test_contact_and_address from frappe.utils import add_days, flt, now_datetime, nowdate @@ -28,6 +28,10 @@ class TestDeliveryTrip(unittest.TestCase): frappe.db.sql("delete from `tabEmail Template`") frappe.db.sql("delete from `tabDelivery Trip`") + def test_expense_claim_fields_are_fetched_properly(self): + expense_claim = make_expense_claim(self.delivery_trip.name) + self.assertEqual(self.delivery_trip.name, expense_claim.delivery_trip) + def test_delivery_trip_notify_customers(self): notify_customers(delivery_trip=self.delivery_trip.name) self.delivery_trip.load_from_db() From 9226cd3932e3a087c7e474c43b7b8d0535221c3c Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 10 May 2021 12:36:56 +0530 Subject: [PATCH 53/72] feat(india): reduced rate of depreciation as per IT Act (#25648) * feat(india): reduced rate of depreciation as per IT Act * refactor: check date difference instead of month difference * feat: add test for regional feature --- erpnext/assets/doctype/asset/asset.py | 51 ++++++++++------------ erpnext/assets/doctype/asset/test_asset.py | 39 +++++++++++++++++ erpnext/hooks.py | 3 +- erpnext/regional/india/utils.py | 21 +++++++++ 4 files changed, 84 insertions(+), 30 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 9aff1440d6a..8799275fc4e 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -195,8 +195,7 @@ class Asset(AccountsController): # If depreciation is already completed (for double declining balance) if skip_row: continue - depreciation_amount = self.get_depreciation_amount(value_after_depreciation, - d.total_number_of_depreciations, d) + depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d) if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: schedule_date = add_months(d.depreciation_start_date, @@ -208,7 +207,7 @@ class Asset(AccountsController): # For first row if has_pro_rata and n==0: - depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount, + depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, self.available_for_use_date, d.depreciation_start_date) # For first depr schedule date will be the start date @@ -220,7 +219,7 @@ class Asset(AccountsController): to_date = add_months(self.available_for_use_date, n * cint(d.frequency_of_depreciation)) - depreciation_amount, days, months = get_pro_rata_amt(d, + depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, schedule_date, to_date) monthly_schedule_date = add_months(schedule_date, 1) @@ -365,24 +364,6 @@ class Asset(AccountsController): def get_value_after_depreciation(self, idx): return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation) - def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row): - precision = self.precision("gross_purchase_amount") - - if row.depreciation_method in ("Straight Line", "Manual"): - depreciation_left = (cint(row.total_number_of_depreciations) - cint(self.number_of_depreciations_booked)) - - if not depreciation_left: - frappe.msgprint(_("All the depreciations has been booked")) - depreciation_amount = flt(row.expected_value_after_useful_life) - return depreciation_amount - - depreciation_amount = (flt(row.value_after_depreciation) - - flt(row.expected_value_after_useful_life)) / depreciation_left - else: - depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision) - - return depreciation_amount - def validate_expected_value_after_useful_life(self): for row in self.get('finance_books'): accumulated_depreciation_after_full_schedule = [d.accumulated_depreciation_amount @@ -575,6 +556,13 @@ class Asset(AccountsController): return 100 * (1 - flt(depreciation_rate, float_precision)) + def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date): + days = date_diff(to_date, from_date) + months = month_diff(to_date, from_date) + total_days = get_total_days(to_date, row.frequency_of_depreciation) + + return (depreciation_amount * flt(days)) / flt(total_days), days, months + def update_maintenance_status(): assets = frappe.get_all( "Asset", filters={"docstatus": 1, "maintenance_required": 1} @@ -758,15 +746,20 @@ def make_asset_movement(assets, purpose=None): def is_cwip_accounting_enabled(asset_category): return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting")) -def get_pro_rata_amt(row, depreciation_amount, from_date, to_date): - days = date_diff(to_date, from_date) - months = month_diff(to_date, from_date) - total_days = get_total_days(to_date, row.frequency_of_depreciation) - - return (depreciation_amount * flt(days)) / flt(total_days), days, months - def get_total_days(date, frequency): period_start_date = add_months(date, cint(frequency) * -1) return date_diff(date, period_start_date) + +@erpnext.allow_regional +def get_depreciation_amount(asset, depreciable_value, row): + depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) + + if row.depreciation_method in ("Straight Line", "Manual"): + depreciation_amount = (flt(row.value_after_depreciation) - + flt(row.expected_value_after_useful_life)) / depreciation_left + else: + depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100)) + + return depreciation_amount \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 40a8f85d8d6..30a270c2043 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -635,6 +635,45 @@ class TestAsset(unittest.TestCase): frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc) frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc) + def test_discounted_wdv_depreciation_rate_for_indian_region(self): + # set indian company + company_flag = frappe.flags.company + frappe.flags.company = "_Test Company" + + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=8000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-06-12' + asset.purchase_date = '2030-01-01' + asset.append("finance_books", { + "expected_value_after_useful_life": 1000, + "depreciation_method": "Written Down Value", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save(ignore_permissions=True) + + self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + expected_schedules = [ + ["2030-12-31", 1106.85, 1106.85], + ["2031-12-31", 3446.58, 4553.43], + ["2032-12-31", 1723.29, 6276.72], + ["2033-06-12", 723.28, 7000.00] + ] + + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + # reset indian company + frappe.flags.company = company_flag + def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): create_asset_category() diff --git a/erpnext/hooks.py b/erpnext/hooks.py index bb6cd8bdc2b..9d1ce9bbbfb 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -426,7 +426,8 @@ regional_overrides = { 'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption', 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period', 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries', - 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields' + 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields', + 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount' }, 'United Arab Emirates': { 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data', diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 6338056698f..052d7bdedf8 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -879,3 +879,24 @@ def update_taxable_values(doc, method): if total_charges != additional_taxes: diff = additional_taxes - total_charges doc.get('items')[item_count - 1].taxable_value += diff + +def get_depreciation_amount(asset, depreciable_value, row): + depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) + + if row.depreciation_method in ("Straight Line", "Manual"): + depreciation_amount = (flt(row.value_after_depreciation) - + flt(row.expected_value_after_useful_life)) / depreciation_left + else: + rate_of_depreciation = row.rate_of_depreciation + # if its the first depreciation + if depreciable_value == asset.gross_purchase_amount: + # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 + diff = date_diff(asset.available_for_use_date, row.depreciation_start_date) + if diff <= 180: + rate_of_depreciation = rate_of_depreciation / 2 + frappe.msgprint( + _('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.')) + + depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100)) + + return depreciation_amount \ No newline at end of file From 6e179c3092c5f31f43ed61610a654e8d61487993 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 10 May 2021 13:24:26 +0530 Subject: [PATCH 54/72] fix: sync shopify customer addresses (#25481) --- .../doctype/shopify_settings/sync_customer.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py index 7866fdea31a..2af57f4c891 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py @@ -32,10 +32,12 @@ def create_customer(shopify_customer, shopify_settings): raise e def create_customer_address(customer, shopify_customer): - if not shopify_customer.get("addresses"): - return + addresses = shopify_customer.get("addresses", []) - for i, address in enumerate(shopify_customer.get("addresses")): + if not addresses and "default_address" in shopify_customer: + addresses.append(shopify_customer["default_address"]) + + for i, address in enumerate(addresses): address_title, address_type = get_address_title_and_type(customer.customer_name, i) try : frappe.get_doc({ From f2eb8dd1d5b6a156f2d6df9a4eb4d41ca497738b Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Mon, 10 May 2021 14:02:58 +0530 Subject: [PATCH 55/72] feat: Transaction Deletion Record (#25354) Co-authored-by: Saqib --- erpnext/controllers/status_updater.py | 4 + erpnext/setup/doctype/company/company.js | 4 +- erpnext/setup/doctype/company/company.py | 11 +- .../company/delete_company_transactions.py | 117 -------------- erpnext/setup/doctype/company/test_company.py | 9 -- .../transaction_deletion_record/__init__.py | 0 .../test_transaction_deletion_record.py | 68 ++++++++ .../transaction_deletion_record.js | 40 +++++ .../transaction_deletion_record.json | 79 ++++++++++ .../transaction_deletion_record.py | 147 ++++++++++++++++++ .../transaction_deletion_record_list.js | 12 ++ .../__init__.py | 0 .../transaction_deletion_record_item.json | 39 +++++ .../transaction_deletion_record_item.py | 10 ++ 14 files changed, 411 insertions(+), 129 deletions(-) delete mode 100644 erpnext/setup/doctype/company/delete_company_transactions.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record/__init__.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js create mode 100644 erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json create mode 100644 erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js create mode 100644 erpnext/setup/doctype/transaction_deletion_record_item/__init__.py create mode 100644 erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json create mode 100644 erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 4bb6138e5d7..ed3aee5c1a1 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -100,6 +100,10 @@ status_map = { ["Queued", "eval:self.status == 'Queued'"], ["Failed", "eval:self.status == 'Failed'"], ["Cancelled", "eval:self.docstatus == 2"], + ], + "Transaction Deletion Record": [ + ["Draft", None], + ["Completed", "eval:self.docstatus == 1"], ] } diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index c2b5e4f9a90..9957aad019f 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -169,9 +169,9 @@ frappe.ui.form.on("Company", { return; } frappe.call({ - method: "erpnext.setup.doctype.company.delete_company_transactions.delete_company_transactions", + method: "erpnext.setup.doctype.company.company.create_transaction_deletion_request", args: { - company_name: data.company_name + company: data.company_name }, freeze: true, callback: function(r, rt) { diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 64e027dd28b..077538d479c 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -613,4 +613,13 @@ def get_default_company_address(name, sort_key='is_primary_address', existing_ad if out: return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0] else: - return None \ No newline at end of file + return None + +@frappe.whitelist() +def create_transaction_deletion_request(company): + tdr = frappe.get_doc({ + 'doctype': 'Transaction Deletion Record', + 'company': company + }) + tdr.insert() + tdr.submit() diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py deleted file mode 100644 index 8367a257ea4..00000000000 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -from frappe.utils import cint -from frappe import _ -from frappe.desk.notifications import clear_notifications - -import functools - -@frappe.whitelist() -def delete_company_transactions(company_name): - frappe.only_for("System Manager") - doc = frappe.get_doc("Company", company_name) - - if frappe.session.user != doc.owner and frappe.session.user != 'Administrator': - frappe.throw(_("Transactions can only be deleted by the creator of the Company"), - frappe.PermissionError) - - delete_bins(company_name) - delete_lead_addresses(company_name) - - for doctype in frappe.db.sql_list("""select parent from - tabDocField where fieldtype='Link' and options='Company'"""): - if doctype not in ("Account", "Cost Center", "Warehouse", "Budget", - "Party Account", "Employee", "Sales Taxes and Charges Template", - "Purchase Taxes and Charges Template", "POS Profile", "BOM", - "Company", "Bank Account", "Item Tax Template", "Mode Of Payment", "Mode of Payment Account", - "Item Default", "Customer", "Supplier", "GST Account"): - delete_for_doctype(doctype, company_name) - - # reset company values - doc.total_monthly_sales = 0 - doc.sales_monthly_history = None - doc.save() - # Clear notification counts - clear_notifications() - -def delete_for_doctype(doctype, company_name): - meta = frappe.get_meta(doctype) - company_fieldname = meta.get("fields", {"fieldtype": "Link", - "options": "Company"})[0].fieldname - - if not meta.issingle: - if not meta.istable: - # delete communication - delete_communications(doctype, company_name, company_fieldname) - - # delete children - for df in meta.get_table_fields(): - frappe.db.sql("""delete from `tab{0}` where parent in - (select name from `tab{1}` where `{2}`=%s)""".format(df.options, - doctype, company_fieldname), company_name) - - #delete version log - frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in - (select name from `tab{0}` where `{1}`=%s)""".format(doctype, - company_fieldname), (doctype, company_name)) - - # delete parent - frappe.db.sql("""delete from `tab{0}` - where {1}= %s """.format(doctype, company_fieldname), company_name) - - # reset series - naming_series = meta.get_field("naming_series") - if naming_series and naming_series.options: - prefixes = sorted(naming_series.options.split("\n"), - key=functools.cmp_to_key(lambda a, b: len(b) - len(a))) - - for prefix in prefixes: - if prefix: - last = frappe.db.sql("""select max(name) from `tab{0}` - where name like %s""".format(doctype), prefix + "%") - if last and last[0][0]: - last = cint(last[0][0].replace(prefix, "")) - else: - last = 0 - - frappe.db.sql("""update tabSeries set current = %s - where name=%s""", (last, prefix)) - -def delete_bins(company_name): - frappe.db.sql("""delete from tabBin where warehouse in - (select name from tabWarehouse where company=%s)""", company_name) - -def delete_lead_addresses(company_name): - """Delete addresses to which leads are linked""" - leads = frappe.get_all("Lead", filters={"company": company_name}) - leads = [ "'%s'"%row.get("name") for row in leads ] - addresses = [] - if leads: - addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name - in ({leads})""".format(leads=",".join(leads))) - - if addresses: - addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] - - frappe.db.sql("""delete from tabAddress where name in ({addresses}) and - name not in (select distinct dl1.parent from `tabDynamic Link` dl1 - inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent - and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses))) - - frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead' - and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads))) - - frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) - -def delete_communications(doctype, company_name, company_fieldname): - reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name}) - reference_doc_names = [r.name for r in reference_docs] - - communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]}) - communication_names = [c.name for c in communications] - - frappe.delete_doc("Communication", communication_names, ignore_permissions=True) diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index 29f6c3731d7..e1c803a038b 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -86,15 +86,6 @@ class TestCompany(unittest.TestCase): self.delete_mode_of_payment(template) frappe.delete_doc("Company", template) - def test_delete_communication(self): - from erpnext.setup.doctype.company.delete_company_transactions import delete_communications - company = create_child_company() - lead = create_test_lead_in_company(company) - communication = create_company_communication("Lead", lead) - delete_communications("Lead", "Test Company", "company") - self.assertFalse(frappe.db.exists("Communcation", communication)) - self.assertFalse(frappe.db.exists({"doctype":"Comunication Link", "link_name": communication})) - def delete_mode_of_payment(self, company): frappe.db.sql(""" delete from `tabMode of Payment Account` where company =%s """, (company)) diff --git a/erpnext/setup/doctype/transaction_deletion_record/__init__.py b/erpnext/setup/doctype/transaction_deletion_record/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py new file mode 100644 index 00000000000..bbe68369ffd --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestTransactionDeletionRecord(unittest.TestCase): + def setUp(self): + create_company('Dunder Mifflin Paper Co') + + def tearDown(self): + frappe.db.rollback() + + def test_doctypes_contain_company_field(self): + tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co') + for doctype in tdr.doctypes: + contains_company = False + doctype_fields = frappe.get_meta(doctype.doctype_name).as_dict()['fields'] + for doctype_field in doctype_fields: + if doctype_field['fieldtype'] == 'Link' and doctype_field['options'] == 'Company': + contains_company = True + break + self.assertTrue(contains_company) + + def test_no_of_docs_is_correct(self): + for i in range(5): + create_task('Dunder Mifflin Paper Co') + tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co') + for doctype in tdr.doctypes: + if doctype.doctype_name == 'Task': + self.assertEqual(doctype.no_of_docs, 5) + + def test_deletion_is_successful(self): + create_task('Dunder Mifflin Paper Co') + create_transaction_deletion_request('Dunder Mifflin Paper Co') + tasks_containing_company = frappe.get_all('Task', + filters = { + 'company' : 'Dunder Mifflin Paper Co' + }) + self.assertEqual(tasks_containing_company, []) + +def create_company(company_name): + company = frappe.get_doc({ + 'doctype': 'Company', + 'company_name': company_name, + 'default_currency': 'INR' + }) + company.insert(ignore_if_duplicate = True) + +def create_transaction_deletion_request(company): + tdr = frappe.get_doc({ + 'doctype': 'Transaction Deletion Record', + 'company': company + }) + tdr.insert() + tdr.submit() + return tdr + + +def create_task(company): + task = frappe.get_doc({ + 'doctype': 'Task', + 'company': company, + 'subject': 'Delete' + }) + task.insert() diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js new file mode 100644 index 00000000000..20caa15ee41 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js @@ -0,0 +1,40 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Transaction Deletion Record', { + onload: function(frm) { + if (frm.doc.docstatus == 0) { + let doctypes_to_be_ignored_array; + frappe.call({ + method: 'erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.get_doctypes_to_be_ignored', + callback: function(r) { + doctypes_to_be_ignored_array = r.message; + populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm); + frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false); + frm.refresh_field('doctypes_to_be_ignored'); + } + }); + } + + frm.get_field('doctypes_to_be_ignored').grid.cannot_add_rows = true; + frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false); + frm.refresh_field('doctypes_to_be_ignored'); + }, + + refresh: function(frm) { + frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false); + frm.refresh_field('doctypes_to_be_ignored'); + } + +}); + +function populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm) { + if (!(frm.doc.doctypes_to_be_ignored)) { + var i; + for (i = 0; i < doctypes_to_be_ignored_array.length; i++) { + frm.add_child('doctypes_to_be_ignored', { + doctype_name: doctypes_to_be_ignored_array[i] + }); + } + } +} diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json new file mode 100644 index 00000000000..9313f955167 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -0,0 +1,79 @@ +{ + "actions": [], + "autoname": "TDL.####", + "creation": "2021-04-06 20:17:18.404716", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "doctypes", + "doctypes_to_be_ignored", + "amended_from", + "status" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "doctypes", + "fieldtype": "Table", + "label": "Summary", + "options": "Transaction Deletion Record Item", + "read_only": 1 + }, + { + "fieldname": "doctypes_to_be_ignored", + "fieldtype": "Table", + "label": "Excluded DocTypes", + "options": "Transaction Deletion Record Item" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Transaction Deletion Record", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "hidden": 1, + "label": "Status", + "options": "Draft\nCompleted" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-05-08 23:13:48.049879", + "modified_by": "Administrator", + "module": "Setup", + "name": "Transaction Deletion Record", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py new file mode 100644 index 00000000000..38f8de7a660 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.utils import cint +import frappe +from frappe.model.document import Document +from frappe import _ +from frappe.desk.notifications import clear_notifications + +class TransactionDeletionRecord(Document): + def validate(self): + frappe.only_for('System Manager') + company_obj = frappe.get_doc('Company', self.company) + if frappe.session.user != company_obj.owner and frappe.session.user != 'Administrator': + frappe.throw(_('Transactions can only be deleted by the creator of the Company or the Administrator.'), + frappe.PermissionError) + doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() + for doctype in self.doctypes_to_be_ignored: + if doctype.doctype_name not in doctypes_to_be_ignored_list: + frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "), title=_("Not Allowed")) + + def before_submit(self): + if not self.doctypes_to_be_ignored: + self.populate_doctypes_to_be_ignored_table() + + self.delete_bins() + self.delete_lead_addresses() + + company_obj = frappe.get_doc('Company', self.company) + # reset company values + company_obj.total_monthly_sales = 0 + company_obj.sales_monthly_history = None + company_obj.save() + # Clear notification counts + clear_notifications() + + singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name') + tables = frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name') + doctypes_to_be_ignored_list = singles + for doctype in self.doctypes_to_be_ignored: + doctypes_to_be_ignored_list.append(doctype.doctype_name) + + docfields = frappe.get_all('DocField', + filters = { + 'fieldtype': 'Link', + 'options': 'Company', + 'parent': ['not in', doctypes_to_be_ignored_list]}, + fields=['parent', 'fieldname']) + + for docfield in docfields: + if docfield['parent'] != self.doctype: + no_of_docs = frappe.db.count(docfield['parent'], { + docfield['fieldname'] : self.company + }) + + if no_of_docs > 0: + self.delete_version_log(docfield['parent'], docfield['fieldname']) + self.delete_communications(docfield['parent'], docfield['fieldname']) + + # populate DocTypes table + if docfield['parent'] not in tables: + self.append('doctypes', { + 'doctype_name' : docfield['parent'], + 'no_of_docs' : no_of_docs + }) + + # delete the docs linked with the specified company + frappe.db.delete(docfield['parent'], { + docfield['fieldname'] : self.company + }) + + naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname') + if naming_series: + if '#' in naming_series: + self.update_naming_series(naming_series, docfield['parent']) + + def populate_doctypes_to_be_ignored_table(self): + doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() + for doctype in doctypes_to_be_ignored_list: + self.append('doctypes_to_be_ignored', { + 'doctype_name' : doctype + }) + + def update_naming_series(self, naming_series, doctype_name): + if '.' in naming_series: + prefix, hashes = naming_series.rsplit('.', 1) + else: + prefix, hashes = naming_series.rsplit('{', 1) + last = frappe.db.sql("""select max(name) from `tab{0}` + where name like %s""".format(doctype_name), prefix + '%') + if last and last[0][0]: + last = cint(last[0][0].replace(prefix, '')) + else: + last = 0 + + frappe.db.sql("""update tabSeries set current = %s where name=%s""", (last, prefix)) + + def delete_version_log(self, doctype, company_fieldname): + frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in + (select name from `tab{0}` where `{1}`=%s)""".format(doctype, + company_fieldname), (doctype, self.company)) + + def delete_communications(self, doctype, company_fieldname): + reference_docs = frappe.get_all(doctype, filters={company_fieldname:self.company}) + reference_doc_names = [r.name for r in reference_docs] + + communications = frappe.get_all('Communication', filters={'reference_doctype':doctype,'reference_name':['in', reference_doc_names]}) + communication_names = [c.name for c in communications] + + frappe.delete_doc('Communication', communication_names, ignore_permissions=True) + + def delete_bins(self): + frappe.db.sql("""delete from tabBin where warehouse in + (select name from tabWarehouse where company=%s)""", self.company) + + def delete_lead_addresses(self): + """Delete addresses to which leads are linked""" + leads = frappe.get_all('Lead', filters={'company': self.company}) + leads = ["'%s'" % row.get("name") for row in leads] + addresses = [] + if leads: + addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name + in ({leads})""".format(leads=",".join(leads))) + + if addresses: + addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] + + frappe.db.sql("""delete from tabAddress where name in ({addresses}) and + name not in (select distinct dl1.parent from `tabDynamic Link` dl1 + inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent + and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses))) + + frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead' + and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads))) + + frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) + +@frappe.whitelist() +def get_doctypes_to_be_ignored(): + doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget', + 'Party Account', 'Employee', 'Sales Taxes and Charges Template', + 'Purchase Taxes and Charges Template', 'POS Profile', 'BOM', + 'Company', 'Bank Account', 'Item Tax Template', 'Mode of Payment', + 'Item Default', 'Customer', 'Supplier', 'GST Account'] + return doctypes_to_be_ignored_list diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js new file mode 100644 index 00000000000..d7175ddac43 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js @@ -0,0 +1,12 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.listview_settings['Transaction Deletion Record'] = { + get_indicator: function(doc) { + if (doc.docstatus == 0) { + return [__("Draft"), "red"]; + } else { + return [__("Completed"), "green"]; + } + } +}; \ No newline at end of file diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/__init__.py b/erpnext/setup/doctype/transaction_deletion_record_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json new file mode 100644 index 00000000000..be0be945c4e --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json @@ -0,0 +1,39 @@ +{ + "actions": [], + "creation": "2021-04-07 07:34:00.124124", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "doctype_name", + "no_of_docs" + ], + "fields": [ + { + "fieldname": "doctype_name", + "fieldtype": "Link", + "in_list_view": 1, + "label": "DocType", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "no_of_docs", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Number of Docs" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-05-08 23:10:46.166744", + "modified_by": "Administrator", + "module": "Setup", + "name": "Transaction Deletion Record Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py new file mode 100644 index 00000000000..2176cb10deb --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class TransactionDeletionRecordItem(Document): + pass From 1a48eb49cf912223913698383fce7568c52d510b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 10 May 2021 14:37:10 +0530 Subject: [PATCH 56/72] fix: Client script breaking while settings tax labels --- erpnext/public/js/controllers/transaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index f91b432a394..43eea1357a5 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1351,7 +1351,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); } - if(this.frm.fields_dict["taxes"]) { + if(this.frm.doc.taxes && this.frm.doc.taxes.length > 0) { this.frm.set_currency_labels(["tax_amount", "total", "tax_amount_after_discount"], this.frm.doc.currency, "taxes"); this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes"); From 13dfb9734cb8a32a88b425177b0803fc7a838505 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 10 May 2021 15:38:32 +0530 Subject: [PATCH 57/72] fix: Lable for transaction child tables --- erpnext/public/js/controllers/transaction.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 43eea1357a5..a3f4de48b87 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1329,7 +1329,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.toggle_item_grid_columns(company_currency); - if(this.frm.fields_dict["operations"]) { + if(this.frm.doc.operations && this.frm.doc.operations.length > 0) { this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations"); this.frm.set_currency_labels(["base_operating_cost", "base_hour_rate"], company_currency, "operations"); @@ -1340,7 +1340,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); } - if(this.frm.fields_dict["scrap_items"]) { + if(this.frm.doc.scrap_items && this.frm.doc.scrap_items.length > 0) { this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "scrap_items"); this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "scrap_items"); @@ -1357,7 +1357,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes"); } - if(this.frm.fields_dict["advances"]) { + if(this.frm.doc.advances && this.frm.doc.advances.length > 0) { this.frm.set_currency_labels(["advance_amount", "allocated_amount"], this.frm.doc.party_account_currency, "advances"); } @@ -1384,7 +1384,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ company_currency, "payment_schedule"); this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"], this.frm.doc.currency, "payment_schedule"); - + var schedule_grid = this.frm.fields_dict["payment_schedule"].grid; $.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) { if (frappe.meta.get_docfield(schedule_grid.doctype, fname)) From 55d47a2baaeb9d10c991ecfc048fba5ff853869b Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 10 May 2021 15:59:37 +0530 Subject: [PATCH 58/72] fix(pos): UI fixes related to overflowing payment section (#25652) * fix: additional fields overflowing in payment section * fix: pos profile filter in pos opening dialog * fix: item quantity pill --- erpnext/public/scss/point-of-sale.scss | 29 ++++++++++++++++++- .../page/point_of_sale/pos_controller.js | 2 +- .../page/point_of_sale/pos_item_selector.js | 10 ++++--- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss index 0bb8e68b698..9bdaa8d1eeb 100644 --- a/erpnext/public/scss/point-of-sale.scss +++ b/erpnext/public/scss/point-of-sale.scss @@ -129,11 +129,20 @@ @extend .pointer-no-select; border-radius: var(--border-radius-md); box-shadow: var(--shadow-base); + position: relative; &:hover { transform: scale(1.02, 1.02); } + .item-qty-pill { + position: absolute; + display: flex; + margin: var(--margin-sm); + justify-content: flex-end; + right: 0px; + } + .item-display { display: flex; align-items: center; @@ -766,9 +775,10 @@ > .payment-modes { display: flex; padding-bottom: var(--padding-sm); - margin-bottom: var(--margin-xs); + margin-bottom: var(--margin-sm); overflow-x: scroll; overflow-y: hidden; + flex-shrink: 0; > .payment-mode-wrapper { min-width: 40%; @@ -825,9 +835,24 @@ > .fields-numpad-container { display: flex; flex: 1; + height: 100%; + position: relative; + justify-content: flex-end; > .fields-section { flex: 1; + position: absolute; + display: flex; + flex-direction: column; + width: 50%; + height: 100%; + top: 0; + left: 0; + padding-bottom: var(--margin-md); + + .invoice-fields { + overflow-y: scroll; + } } > .number-pad { @@ -835,6 +860,7 @@ display: flex; justify-content: flex-end; align-items: flex-end; + max-width: 50%; .numpad-container { display: grid; @@ -861,6 +887,7 @@ margin-bottom: var(--margin-sm); justify-content: center; flex-direction: column; + flex-shrink: 0; > .totals { display: flex; diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 8adf5bf7473..8e0a1e1c185 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -58,7 +58,7 @@ erpnext.PointOfSale.Controller = class { } const pos_profile_query = { query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query', - filters: { company: frappe.defaults.get_default('company') } + filters: { company: dialog.fields_dict.company.get_value() } } const dialog = new frappe.ui.Dialog({ title: __('Create POS Opening Entry'), diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js index 9384ae5542f..b8a82a9edab 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_selector.js +++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js @@ -90,14 +90,16 @@ erpnext.PointOfSale.ItemSelector = class { function get_item_image_html() { if (!me.hide_images && item_image) { - return `
    - ${qty_to_display}
    + return `
    + ${qty_to_display} +
    ${frappe.get_abbr(item.item_name)}
    `; } else { - return `
    - ${qty_to_display}
    + return `
    + ${qty_to_display} +
    ${frappe.get_abbr(item.item_name)}
    `; } } From 9f0823a164e43c740f4aac0e9cb93559b5b06d13 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 10 May 2021 16:07:41 +0530 Subject: [PATCH 59/72] fix: Linting issues --- erpnext/public/js/controllers/transaction.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a3f4de48b87..7cfd939e956 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1329,7 +1329,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.toggle_item_grid_columns(company_currency); - if(this.frm.doc.operations && this.frm.doc.operations.length > 0) { + if (this.frm.doc.operations && this.frm.doc.operations.length > 0) { this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations"); this.frm.set_currency_labels(["base_operating_cost", "base_hour_rate"], company_currency, "operations"); @@ -1340,7 +1340,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); } - if(this.frm.doc.scrap_items && this.frm.doc.scrap_items.length > 0) { + if (this.frm.doc.scrap_items && this.frm.doc.scrap_items.length > 0) { this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "scrap_items"); this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "scrap_items"); @@ -1351,13 +1351,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); } - if(this.frm.doc.taxes && this.frm.doc.taxes.length > 0) { + if (this.frm.doc.taxes && this.frm.doc.taxes.length > 0) { this.frm.set_currency_labels(["tax_amount", "total", "tax_amount_after_discount"], this.frm.doc.currency, "taxes"); this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes"); } - if(this.frm.doc.advances && this.frm.doc.advances.length > 0) { + if (this.frm.doc.advances && this.frm.doc.advances.length > 0) { this.frm.set_currency_labels(["advance_amount", "allocated_amount"], this.frm.doc.party_account_currency, "advances"); } From d2520680bc2f64d5a4692e63c70b129b2b46a8b7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 10 May 2021 21:17:06 +0530 Subject: [PATCH 60/72] fix: Error on applying TDS without party (#25632) * fix: Error on applying TDS without party * fix: Add placeholder value --- .../tax_withholding_category/tax_withholding_category.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 09db7fee2b1..5c1cbaa4aaa 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -21,7 +21,10 @@ def get_party_details(inv): else: party_type = 'Supplier' party = inv.supplier - + + if not party: + frappe.throw(_("Please select {0} first").format(party_type)) + return party_type, party def get_party_tax_withholding_details(inv, tax_withholding_category=None): @@ -324,7 +327,7 @@ def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, post net_total, ldc.certificate_limit ): tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details) - + return tds_amount def get_debit_note_amount(suppliers, fiscal_year_details, company=None): From dd1822ef58dc24f32d5bf62d634c886a50d7a386 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Wed, 12 May 2021 13:01:53 +0530 Subject: [PATCH 61/72] fix: change links in workspace (#25673) --- .../workspace/accounting/accounting.json | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index 9ffa481c1cb..df68318052f 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -15,6 +15,7 @@ "hide_custom": 0, "icon": "accounting", "idx": 0, + "is_default": 0, "is_standard": 1, "label": "Accounting", "links": [ @@ -625,9 +626,9 @@ "dependencies": "", "hidden": 0, "is_query_report": 0, - "label": "Bank Reconciliation", - "link_to": "bank-reconciliation", - "link_type": "Page", + "label": "Bank Reconciliation Tool", + "link_to": "Bank Reconciliation Tool", + "link_type": "DocType", "onboard": 0, "type": "Link" }, @@ -641,26 +642,6 @@ "onboard": 0, "type": "Link" }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Bank Statement Transaction Entry", - "link_to": "Bank Statement Transaction Entry", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Bank Statement Settings", - "link_to": "Bank Statement Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, @@ -1071,7 +1052,7 @@ "type": "Link" } ], - "modified": "2021-03-04 00:38:35.349024", + "modified": "2021-05-12 11:48:01.905144", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", From aaca8335f080dca5078d90212bd810ced5f56d84 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 May 2021 16:36:25 +0530 Subject: [PATCH 62/72] fix: updated modified time to pull new fields --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 24e67febca5..d3d3ffa17fa 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1380,7 +1380,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-03-30 22:45:58.334107", + "modified": "2021-04-30 22:45:58.334107", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", From 6578c045ca3d7cb40481caf0b4239420d5ab5cbb Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Wed, 12 May 2021 17:41:50 +0530 Subject: [PATCH 63/72] fix: Dialog variable assignment after definition in POS (#25680) --- erpnext/selling/page/point_of_sale/pos_controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 8e0a1e1c185..4f4f1b2240b 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -56,10 +56,6 @@ erpnext.PointOfSale.Controller = class { dialog.fields_dict.balance_details.grid.refresh(); }); } - const pos_profile_query = { - query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query', - filters: { company: dialog.fields_dict.company.get_value() } - } const dialog = new frappe.ui.Dialog({ title: __('Create POS Opening Entry'), static: true, @@ -105,6 +101,10 @@ erpnext.PointOfSale.Controller = class { primary_action_label: __('Submit') }); dialog.show(); + const pos_profile_query = { + query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query', + filters: { company: dialog.fields_dict.company.get_value() } + }; } async prepare_app_defaults(data) { From fe68a0ff80c8d807a5886d46f2d51ad33e6949f1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 12 May 2021 19:42:04 +0530 Subject: [PATCH 64/72] fix: Woocommerce order sync issue --- .../connectors/woocommerce_connection.py | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py index 6dedaa8c530..a505ee09d28 100644 --- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py +++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import frappe, base64, hashlib, hmac, json +from frappe.utils import cstr from frappe import _ def verify_request(): @@ -146,22 +147,19 @@ def rename_address(address, customer): def link_items(items_list, woocommerce_settings, sys_lang): for item_data in items_list: - item_woo_com_id = item_data.get("product_id") + item_woo_com_id = cstr(item_data.get("product_id")) - if frappe.get_value("Item", {"woocommerce_id": item_woo_com_id}): - #Edit Item - item = frappe.get_doc("Item", {"woocommerce_id": item_woo_com_id}) - else: + if not frappe.db.get_value("Item", {"woocommerce_id": item_woo_com_id}, 'name'): #Create Item item = frappe.new_doc("Item") + item.item_code = _("woocommerce - {0}", sys_lang).format(item_woo_com_id) + item.stock_uom = woocommerce_settings.uom or _("Nos", sys_lang) + item.item_group = _("WooCommerce Products", sys_lang) - item.item_name = item_data.get("name") - item.item_code = _("woocommerce - {0}", sys_lang).format(item_data.get("product_id")) - item.woocommerce_id = item_data.get("product_id") - item.item_group = _("WooCommerce Products", sys_lang) - item.stock_uom = woocommerce_settings.uom or _("Nos", sys_lang) - item.flags.ignore_mandatory = True - item.save() + item.item_name = item_data.get("name") + item.woocommerce_id = item_woo_com_id + item.flags.ignore_mandatory = True + item.save() def create_sales_order(order, woocommerce_settings, customer_name, sys_lang): new_sales_order = frappe.new_doc("Sales Order") @@ -194,12 +192,12 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l for item in order.get("line_items"): woocomm_item_id = item.get("product_id") - found_item = frappe.get_doc("Item", {"woocommerce_id": woocomm_item_id}) + found_item = frappe.get_doc("Item", {"woocommerce_id": cstr(woocomm_item_id)}) ordered_items_tax = item.get("total_tax") - new_sales_order.append("items",{ - "item_code": found_item.item_code, + new_sales_order.append("items", { + "item_code": found_item.name, "item_name": found_item.item_name, "description": found_item.item_name, "delivery_date": new_sales_order.delivery_date, @@ -207,7 +205,7 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l "qty": item.get("quantity"), "rate": item.get("price"), "warehouse": woocommerce_settings.warehouse or default_warehouse - }) + }) add_tax_details(new_sales_order, ordered_items_tax, "Ordered Item tax", woocommerce_settings.tax_account) From 8e748f8451db11e08cc0a920e1d0eea7c75e6a56 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 13 May 2021 14:59:28 +0530 Subject: [PATCH 65/72] fix: Parameter for get_filtered_list_for_consolidated_report in consolidated balance sheet --- erpnext/accounts/report/balance_sheet/balance_sheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index 287b8a7484f..26bb44f4f7b 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -135,7 +135,7 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit # from consolidated financial statement if filters.get('accumulated_in_group_company'): - period_list = get_filtered_list_for_consolidated_report(period_list) + period_list = get_filtered_list_for_consolidated_report(filters, period_list) for period in period_list: key = period if consolidated else period.key From 7c6de1a8ac2ec818a796fe54268d0650d71ffb9b Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 13 May 2021 17:28:49 +0530 Subject: [PATCH 66/72] fix: bank statement import via google sheet (#25677) * fix: change links in workspace * fix: google sheet bank statement import * chore: quotes * fix: capitalization * fix: typo * chore: add translation --- .../bank_statement_import.js | 1 + .../bank_statement_import.json | 6 +++--- .../bank_statement_import.py | 18 ++++++++++++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js index 3dbd6053441..016f29a7b51 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -239,6 +239,7 @@ frappe.ui.form.on("Bank Statement Import", { "withdrawal", "description", "reference_number", + "bank_account" ], }, }); diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json index 5e913cc2aac..7ffff02850c 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json @@ -146,7 +146,7 @@ }, { "depends_on": "eval:!doc.__islocal && !doc.import_file\n", - "description": "Must be a publicly accessible Google Sheets URL", + "description": "Must be a publicly accessible Google Sheets URL and adding Bank Account column is necessary for importing via Google Sheets", "fieldname": "google_sheets_url", "fieldtype": "Data", "label": "Import from Google Sheets" @@ -202,7 +202,7 @@ ], "hide_toolbar": 1, "links": [], - "modified": "2021-02-10 19:29:59.027325", + "modified": "2021-05-12 14:17:37.777246", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Statement Import", @@ -224,4 +224,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index 9f41b13f4b6..5f110e2727c 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -47,6 +47,13 @@ class BankStatementImport(DataImport): def start_import(self): + preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template( + self.import_file, self.google_sheets_url + ) + + if 'Bank Account' not in json.dumps(preview): + frappe.throw(_("Please add the Bank Account column")) + from frappe.core.page.background_jobs.background_jobs import get_info from frappe.utils.scheduler import is_scheduler_inactive @@ -67,6 +74,7 @@ class BankStatementImport(DataImport): data_import=self.name, bank_account=self.bank_account, import_file_path=self.import_file, + google_sheets_url=self.google_sheets_url, bank=self.bank, template_options=self.template_options, now=frappe.conf.developer_mode or frappe.flags.in_test, @@ -90,18 +98,20 @@ def download_errored_template(data_import_name): data_import = frappe.get_doc("Bank Statement Import", data_import_name) data_import.export_errored_rows() -def start_import(data_import, bank_account, import_file_path, bank, template_options): +def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options): """This method runs in background job""" update_mapping_db(bank, template_options) data_import = frappe.get_doc("Bank Statement Import", data_import) + file = import_file_path if import_file_path else google_sheets_url - import_file = ImportFile("Bank Transaction", file = import_file_path, import_type="Insert New Records") + import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records") data = import_file.raw_data - add_bank_account(data, bank_account) - write_files(import_file, data) + if import_file_path: + add_bank_account(data, bank_account) + write_files(import_file, data) try: i = Importer(data_import.reference_doctype, data_import=data_import) From a0a88a710ebd989eae8c2918a286961c79e4f65d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 13 May 2021 17:42:06 +0530 Subject: [PATCH 67/72] fix: change today to now to get data for reposting --- .../doctype/repost_item_valuation/repost_item_valuation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 3f837805695..63c71891e44 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe.model.document import Document -from frappe.utils import cint, get_link_to_form, add_to_date, today +from frappe.utils import cint, get_link_to_form, add_to_date, now, today from erpnext.stock.stock_ledger import repost_future_sle from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced from frappe.utils.user import get_users_with_role @@ -127,7 +127,7 @@ def repost_entries(): check_if_stock_and_account_balance_synced(today(), d.name) def get_repost_item_valuation_entries(): - date = add_to_date(today(), hours=-3) + date = add_to_date(now(), hours=-3) return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` WHERE status != 'Completed' and creation <= %s and docstatus = 1 From e9f6c8cdb19b93fb5c9a96e5f0e920032059e1ed Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 14 May 2021 12:34:13 +0530 Subject: [PATCH 68/72] fix: validation message of quality inspection in purchase receipt (#25667) --- erpnext/controllers/stock_controller.py | 3 +-- .../doctype/quality_inspection/test_quality_inspection.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index b14c2745159..41ca404d9b8 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -379,8 +379,7 @@ class StockController(AccountsController): link = frappe.utils.get_link_to_form('Quality Inspection', d.quality_inspection) frappe.throw(_("Quality Inspection: {0} is not submitted for the item: {1} in row {2}").format(link, d.item_code, d.idx), QualityInspectionNotSubmittedError) - qa_failed = any([r.status=="Rejected" for r in qa_doc.readings]) - if qa_failed: + if qa_doc.status != 'Accepted': frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}") .format(d.idx, d.item_code), QualityInspectionRejectedError) elif qa_required : diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index a7dfc9ee288..56b046a92e1 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -27,10 +27,11 @@ class TestQualityInspection(unittest.TestCase): dn.reload() self.assertRaises(QualityInspectionRejectedError, dn.submit) - frappe.db.set_value("Quality Inspection Reading", {"parent": qa.name}, "status", "Accepted") + frappe.db.set_value("Quality Inspection", qa.name, "status", "Accepted") dn.reload() dn.submit() + qa.reload() qa.cancel() dn.reload() dn.cancel() From ad0b8fdd1e63c01c0819e129940fae5b83924560 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 17 May 2021 10:49:21 +0530 Subject: [PATCH 69/72] chore: Added change log for v13.3.0 --- erpnext/change_log/v13/v13_3_0.md | 73 +++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 erpnext/change_log/v13/v13_3_0.md diff --git a/erpnext/change_log/v13/v13_3_0.md b/erpnext/change_log/v13/v13_3_0.md new file mode 100644 index 00000000000..016dbb01f4d --- /dev/null +++ b/erpnext/change_log/v13/v13_3_0.md @@ -0,0 +1,73 @@ +# Version 13.3.0 Release Notes + +### Features & Enhancements + +- Purchase receipt creation from purchase invoice ([#25126](https://github.com/frappe/erpnext/pull/25126)) +- New Document Transaction Deletion ([#25354](https://github.com/frappe/erpnext/pull/25354)) +- Employee Referral ([#24997](https://github.com/frappe/erpnext/pull/24997)) +- Add Create Expense Claim button in Delivery Trip ([#25526](https://github.com/frappe/erpnext/pull/25526)) +- Reduced rate of asset depreciation as per IT Act ([#25648](https://github.com/frappe/erpnext/pull/25648)) +- Improve DATEV export ([#25238](https://github.com/frappe/erpnext/pull/25238)) +- Add pick batch button ([#25413](https://github.com/frappe/erpnext/pull/25413)) +- Enable custom field search on POS ([#25421](https://github.com/frappe/erpnext/pull/25421)) +- New check field in subscriptions for (not) submitting invoices ([#25394](https://github.com/frappe/erpnext/pull/25394)) +- Show POS reserved stock in stock projected qty report ([#25593](https://github.com/frappe/erpnext/pull/25593)) +- e-way bill validity field ([#25555](https://github.com/frappe/erpnext/pull/25555)) +- Significant reduction in time taken to save sales documents ([#25475](https://github.com/frappe/erpnext/pull/25475)) + +### Fixes + +- Bank statement import via google sheet ([#25677](https://github.com/frappe/erpnext/pull/25677)) +- Invoices not getting fetched during payment reconciliation ([#25598](https://github.com/frappe/erpnext/pull/25598)) +- Error on applying TDS without party ([#25632](https://github.com/frappe/erpnext/pull/25632)) +- Allow to cancel loan with cancelled repayment entry ([#25507](https://github.com/frappe/erpnext/pull/25507)) +- Can't open general ledger from consolidated financial report ([#25542](https://github.com/frappe/erpnext/pull/25542)) +- Add 'Partially Received' to Status drop-down list in Material Request ([#24857](https://github.com/frappe/erpnext/pull/24857)) +- Updated item filters for material request ([#25531](https://github.com/frappe/erpnext/pull/25531)) +- Added validation in stock entry to check duplicate serial nos ([#25611](https://github.com/frappe/erpnext/pull/25611)) +- Update shopify api version ([#25600](https://github.com/frappe/erpnext/pull/25600)) +- Dialog variable assignment after definition in POS ([#25680](https://github.com/frappe/erpnext/pull/25680)) +- Added tax_types list ([#25587](https://github.com/frappe/erpnext/pull/25587)) +- Include search fields in Project Link field query ([#25505](https://github.com/frappe/erpnext/pull/25505)) +- Item stock levels displaying inconsistently ([#25506](https://github.com/frappe/erpnext/pull/25506)) +- Change today to now to get data for reposting ([#25703](https://github.com/frappe/erpnext/pull/25703)) +- Parameter for get_filtered_list_for_consolidated_report in consolidated balance sheet ([#25700](https://github.com/frappe/erpnext/pull/25700)) +- Minor fixes in loan ([#25546](https://github.com/frappe/erpnext/pull/25546)) +- Fieldname when updating docfield property ([#25516](https://github.com/frappe/erpnext/pull/25516)) +- Use get_serial_nos for splitting ([#25590](https://github.com/frappe/erpnext/pull/25590)) +- Show item's full name on hover over item in POS ([#25554](https://github.com/frappe/erpnext/pull/25554)) +- Stock ledger entry created against draft stock entry ([#25540](https://github.com/frappe/erpnext/pull/25540)) +- Incorrect expense account set in pos invoice ([#25543](https://github.com/frappe/erpnext/pull/25543)) +- Stock balance and batch-wise balance history report showing different closing stock ([#25575](https://github.com/frappe/erpnext/pull/25575)) +- Make strings translatable ([#25521](https://github.com/frappe/erpnext/pull/25521)) +- Serial no changed after saving stock reconciliation ([#25541](https://github.com/frappe/erpnext/pull/25541)) +- Ignore fraction difference while making round off gl entry ([#25438](https://github.com/frappe/erpnext/pull/25438)) +- Sync shopify customer addresses ([#25481](https://github.com/frappe/erpnext/pull/25481)) +- Total stock summary report not working ([#25551](https://github.com/frappe/erpnext/pull/25551)) +- Rename field has not updated value of deposit and withdrawal fields ([#25545](https://github.com/frappe/erpnext/pull/25545)) +- Unexpected keyword argument 'merge_logs' ([#25489](https://github.com/frappe/erpnext/pull/25489)) +- Validation message of quality inspection in purchase receipt ([#25667](https://github.com/frappe/erpnext/pull/25667)) +- Added is_stock_item filter ([#25530](https://github.com/frappe/erpnext/pull/25530)) +- Fetch total stock at company in PO ([#25532](https://github.com/frappe/erpnext/pull/25532)) +- Updated filters for process statement of accounts ([#25384](https://github.com/frappe/erpnext/pull/25384)) +- Incorrect expense account set in pos invoice ([#25571](https://github.com/frappe/erpnext/pull/25571)) +- Client script breaking while settings tax labels ([#25653](https://github.com/frappe/erpnext/pull/25653)) +- Empty payment term column in accounts receivable report ([#25556](https://github.com/frappe/erpnext/pull/25556)) +- Designation insufficient permission on lead doctype. ([#25331](https://github.com/frappe/erpnext/pull/25331)) +- Force https for shopify webhook registration ([#25630](https://github.com/frappe/erpnext/pull/25630)) +- Patch regional fields for old companies ([#25673](https://github.com/frappe/erpnext/pull/25673)) +- Woocommerce order sync issue ([#25692](https://github.com/frappe/erpnext/pull/25692)) +- Allow to receive same serial numbers multiple times ([#25471](https://github.com/frappe/erpnext/pull/25471)) +- Update Allocated amount after Paid Amount is changed in PE ([#25515](https://github.com/frappe/erpnext/pull/25515)) +- Updating Standard Notification's channel field ([#25564](https://github.com/frappe/erpnext/pull/25564)) +- Report summary showing inflated values when values are accumulated in Group Company ([#25577](https://github.com/frappe/erpnext/pull/25577)) +- UI fixes related to overflowing payment section ([#25652](https://github.com/frappe/erpnext/pull/25652)) +- List invoices in Payment Reconciliation Payment ([#25524](https://github.com/frappe/erpnext/pull/25524)) +- Ageing errors in PSOA ([#25490](https://github.com/frappe/erpnext/pull/25490)) +- Prevent spurious defaults for items when making prec from dnote ([#25559](https://github.com/frappe/erpnext/pull/25559)) +- Stock reconciliation getting time out error during submission ([#25557](https://github.com/frappe/erpnext/pull/25557)) +- Timesheet filter date exclusive issue ([#25626](https://github.com/frappe/erpnext/pull/25626)) +- Update cost center in the item table fetched from POS Profile ([#25609](https://github.com/frappe/erpnext/pull/25609)) +- Updated modified time in purchase invoice to pull new fields ([#25678](https://github.com/frappe/erpnext/pull/25678)) +- Stock and Accounts Settings form refactor ([#25534](https://github.com/frappe/erpnext/pull/25534)) +- Payment amount showing in foreign currency ([#25292](https://github.com/frappe/erpnext/pull/25292)) \ No newline at end of file From 2f403f1bcd42e9a8991c604f3a05e94d13dc3b52 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 17 May 2021 10:50:26 +0530 Subject: [PATCH 70/72] fix: renamed change log --- erpnext/change_log/v13/v13.0.2.md | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 erpnext/change_log/v13/v13.0.2.md diff --git a/erpnext/change_log/v13/v13.0.2.md b/erpnext/change_log/v13/v13.0.2.md deleted file mode 100644 index 2bfbdfcc5db..00000000000 --- a/erpnext/change_log/v13/v13.0.2.md +++ /dev/null @@ -1,7 +0,0 @@ -## Version 13.0.2 Release Notes - -### Fixes -- fix: frappe.whitelist for doc methods ([#25231](https://github.com/frappe/erpnext/pull/25231)) -- fix: incorrect incoming rate for the sales return ([#25306](https://github.com/frappe/erpnext/pull/25306)) -- fix(e-invoicing): validations & tax calculation fixes ([#25314](https://github.com/frappe/erpnext/pull/25314)) -- fix: update scheduler check time ([#25295](https://github.com/frappe/erpnext/pull/25295)) \ No newline at end of file From 9ec0f118005732d41e84ad7e7844c72d0a01db9c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 17 May 2021 10:50:42 +0530 Subject: [PATCH 71/72] fix: renamed change log --- erpnext/change_log/v13/v13_0_2.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 erpnext/change_log/v13/v13_0_2.md diff --git a/erpnext/change_log/v13/v13_0_2.md b/erpnext/change_log/v13/v13_0_2.md new file mode 100644 index 00000000000..2bfbdfcc5db --- /dev/null +++ b/erpnext/change_log/v13/v13_0_2.md @@ -0,0 +1,7 @@ +## Version 13.0.2 Release Notes + +### Fixes +- fix: frappe.whitelist for doc methods ([#25231](https://github.com/frappe/erpnext/pull/25231)) +- fix: incorrect incoming rate for the sales return ([#25306](https://github.com/frappe/erpnext/pull/25306)) +- fix(e-invoicing): validations & tax calculation fixes ([#25314](https://github.com/frappe/erpnext/pull/25314)) +- fix: update scheduler check time ([#25295](https://github.com/frappe/erpnext/pull/25295)) \ No newline at end of file From bc92ecb10f36724a967e4bdc17b09fa119f81ec8 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 17 May 2021 11:56:29 +0550 Subject: [PATCH 72/72] bumped to version 13.3.0 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 6775398532b..ad971e2976c 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.2.1' +__version__ = '13.3.0' def get_default_company(user=None): '''Get default company for user'''