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 001/115] 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 aa32d95373..c9f15a6a47 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 002/115] 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 d6047e863c..6258c05459 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 003/115] 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 f138a807bc..826d51f712 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 004/115] 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 cbc9478987..e1b0c54b2b 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 005/115] 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 e1b0c54b2b..42e4f96d1f 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 006/115] 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 6258c05459..83e4f97519 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 007/115] 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 16863142bc..df004abc48 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 0000000000..cf1fe219dc --- /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 008/115] 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 cf1fe219dc..41ab945eb1 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 009/115] 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 df004abc48..a03bd00735 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 0000000000..8482730ec8 --- /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 010/115] 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 42e4f96d1f..c81568bfa6 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 011/115] 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 8482730ec8..11e1e9b3b9 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 012/115] 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 c81568bfa6..1a215031c1 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 cb6494876fe780c1900174a9f11957bbe8c88a8d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 27 Mar 2021 22:21:41 +0530 Subject: [PATCH 013/115] feat: purchase receipt creation from purchase invoice --- .../purchase_invoice/purchase_invoice.js | 30 +++++ .../purchase_invoice/purchase_invoice.json | 12 +- .../purchase_invoice/purchase_invoice.py | 38 +++++++ .../purchase_invoice_item.json | 3 +- .../billed_items_to_be_received/__init__.py | 0 .../billed_items_to_be_received.js | 29 +++++ .../billed_items_to_be_received.json | 39 +++++++ .../billed_items_to_be_received.py | 107 ++++++++++++++++++ .../purchase_receipt/purchase_receipt.js | 28 +++++ .../purchase_receipt/purchase_receipt.py | 17 ++- .../purchase_receipt_item.json | 24 +++- 11 files changed, 320 insertions(+), 7 deletions(-) create mode 100644 erpnext/accounts/report/billed_items_to_be_received/__init__.py create mode 100644 erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js create mode 100644 erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json create mode 100644 erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 66a8e206a8..73475a1131 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -523,6 +523,28 @@ frappe.ui.form.on("Purchase Invoice", { } }, + refresh: function(frm) { + frm.events.add_custom_buttons(frm); + }, + + add_custom_buttons: function(frm) { + if (frm.doc.per_received < 100) { + frm.add_custom_button(__('Purchase Receipt'), () => { + frm.events.make_purchase_receipt(frm); + }, __('Create')); + } + + if (frm.doc.docstatus == 1 && frm.doc.per_received > 0) { + frm.add_custom_button(__('Purchase Receipt'), () => { + frappe.route_options = { + 'purchase_invoice': frm.doc.name + } + + frappe.set_route("List", "Purchase Receipt", "List") + }, __('View')); + } + }, + onload: function(frm) { if(frm.doc.__onload && frm.is_new()) { if(frm.doc.supplier) { @@ -548,5 +570,13 @@ frappe.ui.form.on("Purchase Invoice", { update_stock: function(frm) { hide_fields(frm.doc); frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false); + }, + + make_purchase_receipt: function(frm) { + frappe.model.open_mapped_doc({ + method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt", + frm: frm, + freeze_message: __("Creating Purchase Receipt ...") + }) } }) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 739bd671f7..5c37eda8d4 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -163,7 +163,8 @@ "to_date", "column_break_114", "auto_repeat", - "update_auto_repeat_reference" + "update_auto_repeat_reference", + "per_received" ], "fields": [ { @@ -1364,6 +1365,15 @@ "print_hide": 1, "print_width": "50px", "width": "50px" + }, + { + "fieldname": "per_received", + "fieldtype": "Percent", + "hidden": 1, + "label": "Per Received", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-file-text", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 5c4e32e493..7d008507a2 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1207,3 +1207,41 @@ def make_inter_company_sales_invoice(source_name, target_doc=None): def on_doctype_update(): frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"]) + +@frappe.whitelist() +def make_purchase_receipt(source_name, target_doc=None): + def update_item(obj, target, source_parent): + target.qty = flt(obj.qty) - flt(obj.received_qty) + target.received_qty = flt(obj.qty) - flt(obj.received_qty) + target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor) + target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate) + target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \ + flt(obj.rate) * flt(source_parent.conversion_rate) + + doc = get_mapped_doc("Purchase Invoice", source_name, { + "Purchase Invoice": { + "doctype": "Purchase Receipt", + "validation": { + "docstatus": ["=", 1], + } + }, + "Purchase Invoice Item": { + "doctype": "Purchase Receipt Item", + "field_map": { + "name": "purchase_invoice_item", + "parent": "purchase_invoice", + "bom": "bom", + "purchase_order": "purchase_order", + "po_detail": "purchase_order_item", + "material_request": "material_request", + "material_request_item": "material_request_item" + }, + "postprocess": update_item, + "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) + }, + "Purchase Taxes and Charges": { + "doctype": "Purchase Taxes and Charges" + } + }, target_doc) + + return doc diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 96ad0fd785..10e1c73ea9 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -607,6 +607,7 @@ "oldfieldname": "purchase_order", "oldfieldtype": "Link", "options": "Purchase Order", + "print_hide": 1, "read_only": 1, "search_index": 1 }, @@ -853,7 +854,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-23 00:59:52.614805", + "modified": "2021-03-30 09:02:39.256602", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/report/billed_items_to_be_received/__init__.py b/erpnext/accounts/report/billed_items_to_be_received/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js new file mode 100644 index 0000000000..e1fccb6e72 --- /dev/null +++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.js @@ -0,0 +1,29 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports['Billed Items To Be Received'] = { + 'filters': [ + { + 'label': __('Company'), + 'fieldname': 'company', + 'fieldtype': 'Link', + 'options': 'Company', + 'reqd': 1, + 'default': frappe.defaults.get_default('Company') + }, + { + 'label': __('As on Date'), + 'fieldname': 'posting_date', + 'fieldtype': 'Date', + 'reqd': 1, + 'default': get_today() + }, + { + 'label': __('Purchase Invoice'), + 'fieldname': 'purchase_invoice', + 'fieldtype': 'Link', + 'options': 'Purchase Invoice' + } + ] +}; diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json new file mode 100644 index 0000000000..de09b33c96 --- /dev/null +++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.json @@ -0,0 +1,39 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-03-30 09:35:38.683028", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-03-31 08:48:30.944429", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Billed Items To Be Received", + "owner": "Administrator", + "prepared_report": 0, + "query": "", + "ref_doctype": "Purchase Invoice", + "report_name": "Billed Items To Be Received", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Purchase User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Auditor" + }, + { + "role": "Stock User" + } + ] +} \ No newline at end of file diff --git a/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py new file mode 100644 index 0000000000..2ce5d50edf --- /dev/null +++ b/erpnext/accounts/report/billed_items_to_be_received/billed_items_to_be_received.py @@ -0,0 +1,107 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ + +def execute(filters=None): + data = get_data(filters) or [] + columns = get_columns() + + return columns, data + +def get_data(report_filters): + filters = get_report_filters(report_filters) + fields = get_report_fields() + + return frappe.get_all('Purchase Invoice', + fields= fields, filters=filters) + +def get_report_filters(report_filters): + filters = [['Purchase Invoice','company','=',report_filters.get('company')], + ['Purchase Invoice','posting_date','<=',report_filters.get('posting_date')], ['Purchase Invoice','docstatus','=',1], + ['Purchase Invoice','per_received','<',100], ['Purchase Invoice','update_stock','=',0]] + + if report_filters.get('purchase_invoice'): + filters.append(['Purchase Invoice','per_received','in',[report_filters.get('purchase_invoice')]]) + + return filters + +def get_report_fields(): + fields = [] + for p_field in ['name', 'supplier', 'company', 'posting_date', 'currency']: + fields.append('`tabPurchase Invoice`.`{}`'.format(p_field)) + + for c_field in ['item_code', 'item_name', 'uom', 'qty', 'received_qty', 'rate', 'amount']: + fields.append('`tabPurchase Invoice Item`.`{}`'.format(c_field)) + + return fields + +def get_columns(): + return [ + { + 'label': _('Purchase Invoice'), + 'fieldname': 'name', + 'fieldtype': 'Link', + 'options': 'Purchase Invoice', + 'width': 170 + }, + { + 'label': _('Supplier'), + 'fieldname': 'supplier', + 'fieldtype': 'Link', + 'options': 'Supplier', + 'width': 120 + }, + { + 'label': _('Posting Date'), + 'fieldname': 'posting_date', + 'fieldtype': 'Date', + 'width': 100 + }, + { + 'label': _('Item Code'), + 'fieldname': 'item_code', + 'fieldtype': 'Link', + 'options': 'Item', + 'width': 100 + }, + { + 'label': _('Item Name'), + 'fieldname': 'item_name', + 'fieldtype': 'Data', + 'width': 100 + }, + { + 'label': _('UOM'), + 'fieldname': 'uom', + 'fieldtype': 'Link', + 'options': 'UOM', + 'width': 100 + }, + { + 'label': _('Invoiced Qty'), + 'fieldname': 'qty', + 'fieldtype': 'Float', + 'width': 100 + }, + { + 'label': _('Received Qty'), + 'fieldname': 'received_qty', + 'fieldtype': 'Float', + 'width': 100 + }, + { + 'label': _('Rate'), + 'fieldname': 'rate', + 'fieldtype': 'Currency', + 'width': 100 + }, + { + 'label': _('Amount'), + 'fieldname': 'amount', + 'fieldtype': 'Currency', + 'width': 100 + } + ] \ No newline at end of file diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 57cc3504a9..f9abea6453 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -73,6 +73,34 @@ frappe.ui.form.on("Purchase Receipt", { }) }, __('Create')); } + + frm.events.add_custom_buttons(frm); + }, + + add_custom_buttons: function(frm) { + if (frm.doc.docstatus == 0) { + frm.add_custom_button(__('Purchase Invoice'), function () { + if (!frm.doc.supplier) { + frappe.throw({ + title: __("Mandatory"), + message: __("Please Select a Supplier") + }); + } + erpnext.utils.map_current_doc({ + method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt", + source_doctype: "Purchase Invoice", + target: frm, + setters: { + supplier: frm.doc.supplier, + }, + get_query_filters: { + docstatus: 1, + per_received: ["<", 100], + company: frm.doc.company + } + }) + }, __("Get Items From")); + } }, company: function(frm) { diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 5d7597b2db..b4cf659bf3 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -53,7 +53,20 @@ class PurchaseReceipt(BuyingController): 'target_ref_field': 'stock_qty', 'source_field': 'stock_qty', 'percent_join_field': 'material_request' + }, + { + 'source_dt': 'Purchase Receipt Item', + 'target_dt': 'Purchase Invoice Item', + 'join_field': 'purchase_invoice_item', + 'target_field': 'received_qty', + 'target_parent_dt': 'Purchase Invoice', + 'target_parent_field': 'per_received', + 'target_ref_field': 'qty', + 'source_field': 'received_qty', + 'percent_join_field': 'purchase_invoice', + 'overflow_type': 'receipt' }] + if cint(self.is_return): self.status_updater.extend([ { @@ -513,7 +526,9 @@ class PurchaseReceipt(BuyingController): def update_billing_status(self, update_modified=True): updated_pr = [self.name] for d in self.get("items"): - if d.purchase_order_item: + if d.purchase_invoice and d.purchase_invoice_item: + d.db_set('billed_amt', d.amount, update_modified=update_modified) + elif d.purchase_order_item: updated_pr += update_billed_amount_based_on_po(d.purchase_order_item, update_modified) for pr in set(updated_pr): diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index efe3642d23..82cc98e7f7 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -72,16 +72,18 @@ "warehouse", "rejected_warehouse", "from_warehouse", - "purchase_order", "material_request", + "purchase_order", + "purchase_invoice", "column_break_40", "is_fixed_asset", "asset_location", "asset_category", "schedule_date", "quality_inspection", - "purchase_order_item", "material_request_item", + "purchase_order_item", + "purchase_invoice_item", "purchase_receipt_item", "delivery_note_item", "putaway_rule", @@ -937,7 +939,21 @@ "fieldname": "base_rate_with_margin", "fieldtype": "Currency", "label": "Rate With Margin (Company Currency)", - "options": "Company:company:default_currency", + "options": "Company:company:default_currency" + }, + { + "fieldname": "purchase_invoice", + "fieldtype": "Link", + "label": "Purchase Invoice", + "options": "Purchase Invoice", + "read_only": 1 + }, + { + "fieldname": "purchase_invoice_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Purchase Invoice Item", + "no_copy": 1, "print_hide": 1, "read_only": 1 } @@ -945,7 +961,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-23 00:59:14.360847", + "modified": "2021-03-29 04:17:00.336298", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From a5d062453ecf6266d4e420e343bea88f1e54d96c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 8 Apr 2021 22:20:10 +0530 Subject: [PATCH 014/115] feat: added test case --- .../purchase_invoice/purchase_invoice.py | 2 +- .../purchase_order/test_purchase_order.py | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 7d008507a2..83e9f7583e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1218,7 +1218,7 @@ def make_purchase_receipt(source_name, target_doc=None): target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \ flt(obj.rate) * flt(source_parent.conversion_rate) - doc = get_mapped_doc("Purchase Invoice", source_name, { + doc = get_mapped_doc("Purchase Invoice", source_name, { "Purchase Invoice": { "doctype": "Purchase Receipt", "validation": { diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 3c4f908ee4..42f4472f29 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -435,6 +435,35 @@ class TestPurchaseOrder(unittest.TestCase): po.load_from_db() self.assertEqual(po.get("items")[0].received_qty, 5) + def test_purchase_order_invoice_receipt_workflow(self): + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt + + po = create_purchase_order() + pi = make_pi_from_po(po.name) + + pi.submit() + + pr = make_purchase_receipt(pi.name) + pr.submit() + + pi.load_from_db() + + self.assertEquals(pi.per_received, 100.00) + self.assertEquals(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) + + pr.cancel() + + pi.load_from_db() + pi.cancel() + + po.load_from_db() + po.cancel() + def test_make_purchase_invoice(self): po = create_purchase_order(do_not_submit=True) From faca47831780c5cbcee303a2b2a1032c78ab653a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 12 Apr 2021 15:40:27 +0530 Subject: [PATCH 015/115] fix: payment amount showing in foreign currency --- .../accounts/doctype/payment_schedule/payment_schedule.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index e362566af0..39eda331db 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -65,7 +65,6 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Payment Amount", - "options": "currency", "reqd": 1 }, { @@ -150,7 +149,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-02-15 21:03:12.540546", + "modified": "2021-04-12 15:39:47.363609", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", From bb746fcbe4b8caaf7178e0443e2ed67e556e2839 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Apr 2021 12:19:31 +0530 Subject: [PATCH 016/115] fix: Updated filters for process statement of accounts --- .../process_statement_of_accounts.html | 10 +++++----- .../process_statement_of_accounts.py | 7 ++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 94ae79a0c6..b623898829 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -19,7 +19,7 @@ - {% for row in data %} + {% for row in data %} {% if(row.posting_date) %} {{ frappe.format(row.posting_date, 'Date') }} @@ -78,10 +78,10 @@ - {{ aging.range1 }} - {{ aging.range2 }} - {{ aging.range3 }} - {{ aging.range4 }} + {{ frappe.utils.fmt_money(aging.range1, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(aging.range2, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(aging.range3, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(aging.range4, currency=filters.presentation_currency) }} diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 43fbb0600a..3cbf59448a 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -7,8 +7,9 @@ import frappe from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing -from frappe.core.doctype.communication.email import make +from erpnext import get_company_currency +from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf from frappe.utils.pdf import get_pdf from frappe.utils import today, add_days, add_months, getdate, format_date @@ -64,16 +65,16 @@ def get_report_pdf(doc, consolidated=True): 'to_date': doc.to_date, 'company': doc.company, 'finance_book': doc.finance_book if doc.finance_book else None, - "account": doc.account if doc.account else None, + 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], + 'presentation_currency': doc.currency or get_company_currency(doc.company), 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], 'project': [p.project_name for p in doc.project], 'show_opening_entries': 0, 'include_default_book_entries': 0, - 'show_cancelled_entries': 1, 'tax_id': tax_id if tax_id else None }) col, res = get_soa(filters) From 135b852cf01e891e64c56289349cfcd54d124b26 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Apr 2021 17:27:26 +0530 Subject: [PATCH 017/115] fix: Use party account currency --- .../process_statement_of_accounts.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 3cbf59448a..33c32ebce2 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -8,6 +8,7 @@ from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing from erpnext import get_company_currency +from erpnext.accounts.party import get_party_account_currency from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf @@ -68,7 +69,8 @@ def get_report_pdf(doc, consolidated=True): 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], - 'presentation_currency': doc.currency or get_company_currency(doc.company), + 'presentation_currency': get_party_account_currency('Customer', entry.customer, doc.company) \ + or doc.currency or get_company_currency(doc.company), 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], From ebbcc90d89539fd9dd50478d42f25809241aabfb Mon Sep 17 00:00:00 2001 From: Meike Nedwidek Date: Mon, 19 Apr 2021 15:28:37 +0200 Subject: [PATCH 018/115] :sparkles: add check field for subscription invoices if they should be submitted automatically --- .../doctype/subscription/subscription.json | 145 +++++------------- .../doctype/subscription/subscription.py | 4 +- 2 files changed, 45 insertions(+), 104 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json index e80df2ab88..c4e4be7f78 100644 --- a/erpnext/accounts/doctype/subscription/subscription.json +++ b/erpnext/accounts/doctype/subscription/subscription.json @@ -36,6 +36,7 @@ "additional_discount_percentage", "additional_discount_amount", "sb_3", + "submit_invoice", "invoices", "accounting_dimensions_section", "cost_center", @@ -45,9 +46,7 @@ { "allow_on_submit": 1, "fieldname": "cb_1", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "status", @@ -55,97 +54,73 @@ "label": "Status", "no_copy": 1, "options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "subscription_period", "fieldtype": "Section Break", - "label": "Subscription Period", - "show_days": 1, - "show_seconds": 1 + "label": "Subscription Period" }, { "fieldname": "cancelation_date", "fieldtype": "Date", "label": "Cancelation Date", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "allow_on_submit": 1, "fieldname": "trial_period_start", "fieldtype": "Date", "label": "Trial Period Start Date", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "depends_on": "eval:doc.trial_period_start", "fieldname": "trial_period_end", "fieldtype": "Date", "label": "Trial Period End Date", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "column_break_11", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "current_invoice_start", "fieldtype": "Date", "label": "Current Invoice Start Date", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "current_invoice_end", "fieldtype": "Date", "label": "Current Invoice End Date", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "0", "description": "Number of days that the subscriber has to pay invoices generated by this subscription", "fieldname": "days_until_due", "fieldtype": "Int", - "label": "Days Until Due", - "show_days": 1, - "show_seconds": 1 + "label": "Days Until Due" }, { "default": "0", "fieldname": "cancel_at_period_end", "fieldtype": "Check", - "label": "Cancel At End Of Period", - "show_days": 1, - "show_seconds": 1 + "label": "Cancel At End Of Period" }, { "default": "0", "fieldname": "generate_invoice_at_period_start", "fieldtype": "Check", - "label": "Generate Invoice At Beginning Of Period", - "show_days": 1, - "show_seconds": 1 + "label": "Generate Invoice At Beginning Of Period" }, { "allow_on_submit": 1, "fieldname": "sb_4", "fieldtype": "Section Break", - "label": "Plans", - "show_days": 1, - "show_seconds": 1 + "label": "Plans" }, { "allow_on_submit": 1, @@ -153,84 +128,62 @@ "fieldtype": "Table", "label": "Plans", "options": "Subscription Plan Detail", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "depends_on": "eval:['Customer', 'Supplier'].includes(doc.party_type)", "fieldname": "sb_1", "fieldtype": "Section Break", - "label": "Taxes", - "show_days": 1, - "show_seconds": 1 + "label": "Taxes" }, { "fieldname": "sb_2", "fieldtype": "Section Break", - "label": "Discounts", - "show_days": 1, - "show_seconds": 1 + "label": "Discounts" }, { "fieldname": "apply_additional_discount", "fieldtype": "Select", "label": "Apply Additional Discount On", - "options": "\nGrand Total\nNet Total", - "show_days": 1, - "show_seconds": 1 + "options": "\nGrand Total\nNet Total" }, { "fieldname": "cb_2", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "additional_discount_percentage", "fieldtype": "Percent", - "label": "Additional DIscount Percentage", - "show_days": 1, - "show_seconds": 1 + "label": "Additional DIscount Percentage" }, { "collapsible": 1, "fieldname": "additional_discount_amount", "fieldtype": "Currency", - "label": "Additional DIscount Amount", - "show_days": 1, - "show_seconds": 1 + "label": "Additional DIscount Amount" }, { "depends_on": "eval:doc.invoices", "fieldname": "sb_3", "fieldtype": "Section Break", - "label": "Invoices", - "show_days": 1, - "show_seconds": 1 + "label": "Invoices" }, { "collapsible": 1, "fieldname": "invoices", "fieldtype": "Table", "label": "Invoices", - "options": "Subscription Invoice", - "show_days": 1, - "show_seconds": 1 + "options": "Subscription Invoice" }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions", - "show_days": 1, - "show_seconds": 1 + "label": "Accounting Dimensions" }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "party_type", @@ -238,9 +191,7 @@ "label": "Party Type", "options": "DocType", "reqd": 1, - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "party", @@ -249,27 +200,21 @@ "label": "Party", "options": "party_type", "reqd": 1, - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "depends_on": "eval:doc.party_type === 'Customer'", "fieldname": "sales_tax_template", "fieldtype": "Link", "label": "Sales Taxes and Charges Template", - "options": "Sales Taxes and Charges Template", - "show_days": 1, - "show_seconds": 1 + "options": "Sales Taxes and Charges Template" }, { "depends_on": "eval:doc.party_type === 'Supplier'", "fieldname": "purchase_tax_template", "fieldtype": "Link", "label": "Purchase Taxes and Charges Template", - "options": "Purchase Taxes and Charges Template", - "show_days": 1, - "show_seconds": 1 + "options": "Purchase Taxes and Charges Template" }, { "default": "0", @@ -277,55 +222,49 @@ "fieldname": "follow_calendar_months", "fieldtype": "Check", "label": "Follow Calendar Months", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "default": "0", "description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date", "fieldname": "generate_new_invoices_past_due_date", "fieldtype": "Check", - "label": "Generate New Invoices Past Due Date", - "show_days": 1, - "show_seconds": 1 + "label": "Generate New Invoices Past Due Date" }, { "fieldname": "end_date", "fieldtype": "Date", "label": "Subscription End Date", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "start_date", "fieldtype": "Date", "label": "Subscription Start Date", - "set_only_once": 1, - "show_days": 1, - "show_seconds": 1 + "set_only_once": 1 }, { "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "options": "Cost Center", - "show_days": 1, - "show_seconds": 1 + "options": "Cost Center" }, { "fieldname": "company", "fieldtype": "Link", "label": "Company", - "options": "Company", - "show_days": 1, - "show_seconds": 1 + "options": "Company" + }, + { + "default": "1", + "fieldname": "submit_invoice", + "fieldtype": "Check", + "label": "Submit Invoice Automatically" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-02-09 15:44:20.024789", + "modified": "2021-04-19 15:24:27.550797", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription", diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 826044a407..f19bae7465 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -383,7 +383,9 @@ class Subscription(Document): invoice.flags.ignore_mandatory = True invoice.save() - invoice.submit() + + if self.submit_invoice == 1: + invoice.submit() return invoice From 66250351d2e8cd6aa6d02b81bc3e1540243fdd60 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Tue, 20 Apr 2021 14:35:14 -0600 Subject: [PATCH 019/115] fix: Add transtlation function to strings Add transtlation function to strings --- erpnext/payroll/doctype/salary_structure/salary_structure.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js index e00bd87084..44368a69fb 100755 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.js +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js @@ -16,11 +16,11 @@ frappe.ui.form.on('Salary Structure', { onload: function(frm) { let help_button = $(` - Condition and Formula Help + ${__(Condition and Formula Help)} `).click(()=>{ let d = new frappe.ui.Dialog({ - title: 'Condition and Formula Help', + title: __('Condition and Formula Help'), fields: [ { fieldname: 'msg_wrapper', From 090177494dbc4bd6477c7783ee5eaa6113726658 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Wed, 21 Apr 2021 09:04:06 -0600 Subject: [PATCH 020/115] fix: Make strings translatable Make strings translatable --- .../projects/report/project_summary/project_summary.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py index ea7f1ab2e7..2c7bb49cfb 100644 --- a/erpnext/projects/report/project_summary/project_summary.py +++ b/erpnext/projects/report/project_summary/project_summary.py @@ -131,25 +131,25 @@ def get_report_summary(data): { "value": avg_completion, "indicator": "Green" if avg_completion > 50 else "Red", - "label": "Average Completion", + "label": _("Average Completion"), "datatype": "Percent", }, { "value": total, "indicator": "Blue", - "label": "Total Tasks", + "label": _("Total Tasks"), "datatype": "Int", }, { "value": completed, "indicator": "Green", - "label": "Completed Tasks", + "label": _("Completed Tasks"), "datatype": "Int", }, { "value": total_overdue, "indicator": "Green" if total_overdue == 0 else "Red", - "label": "Overdue Tasks", + "label": _("Overdue Tasks"), "datatype": "Int", } ] From b0e160ff7838442270d6918ef1aa4a6668e3b7f4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 22 Apr 2021 13:23:50 +0530 Subject: [PATCH 021/115] fix: ignore fraction difference while making round off gl entry --- erpnext/accounts/general_ledger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index dac0c216c8..f18de020fc 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -170,11 +170,11 @@ def round_off_debit_credit(gl_map): else: allowance = .5 - if abs(debit_credit_diff) >= allowance: + if abs(debit_credit_diff) > allowance: frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.") .format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff)) - elif abs(debit_credit_diff) >= (1.0 / (10**precision)): + elif abs(debit_credit_diff) > (1.0 / (10**precision)): make_round_off_gle(gl_map, debit_credit_diff, precision) def make_round_off_gle(gl_map, debit_credit_diff, precision): From 8787ebf83d7a3ca217e130d8c8eec62d36410a3e Mon Sep 17 00:00:00 2001 From: meike289 <63092915+meike289@users.noreply.github.com> Date: Fri, 23 Apr 2021 08:06:29 +0200 Subject: [PATCH 022/115] Apply suggestions from code review Co-authored-by: Ankush Menat --- erpnext/accounts/doctype/subscription/subscription.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index f19bae7465..175710c9f8 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -384,7 +384,7 @@ class Subscription(Document): invoice.flags.ignore_mandatory = True invoice.save() - if self.submit_invoice == 1: + if self.submit_invoice: invoice.submit() return invoice From 52ea6b126bb68e72780ffef4d79c2e8dafd3f96e Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 23 Apr 2021 14:14:47 +0530 Subject: [PATCH 023/115] Revert "fix: email digest user not found" This reverts commit 188657d05a9f0199de898094bbe033e51d52b930. --- erpnext/setup/doctype/email_digest/email_digest.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index ac55fdfdb8..8c97322a71 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -50,8 +50,12 @@ class EmailDigest(Document): recipients = list(filter(lambda r: r in valid_users, self.recipient_list.split("\n"))) + original_user = frappe.session.user + if recipients: for user_id in recipients: + frappe.set_user(user_id) + frappe.set_user_lang(user_id) msg_for_this_recipient = self.get_msg_html() if msg_for_this_recipient: frappe.sendmail( @@ -62,6 +66,9 @@ class EmailDigest(Document): reference_name = self.name, unsubscribe_message = _("Unsubscribe from this Email Digest")) + frappe.set_user(original_user) + frappe.set_user_lang(original_user) + def get_msg_html(self): """Build email digest content""" frappe.flags.ignore_account_permission = True From d552fe6778f71e940f4acdb594afcf79fe5757c2 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 23 Apr 2021 14:46:52 +0530 Subject: [PATCH 024/115] feat: base payment amount in payment schedule --- .../payment_schedule/payment_schedule.json | 36 +++++++++++-- .../purchase_invoice/test_purchase_invoice.py | 2 +- erpnext/controllers/accounts_controller.py | 53 ++++++++++++------- erpnext/public/js/controllers/transaction.js | 5 +- 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index 39eda331db..e9f8ba29dc 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -20,10 +20,13 @@ "discount", "section_break_9", "payment_amount", + "outstanding", + "paid_amount", "discounted_amount", "column_break_3", - "outstanding", - "paid_amount" + "base_payment_amount", + "base_outstanding", + "base_paid_amount" ], "fields": [ { @@ -65,6 +68,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Payment Amount", + "options": "currency", "reqd": 1 }, { @@ -77,7 +81,8 @@ "depends_on": "paid_amount", "fieldname": "paid_amount", "fieldtype": "Currency", - "label": "Paid Amount" + "label": "Paid Amount", + "options": "currency" }, { "fieldname": "column_break_3", @@ -96,6 +101,7 @@ "fieldname": "outstanding", "fieldtype": "Currency", "label": "Outstanding", + "options": "currency", "read_only": 1 }, { @@ -144,12 +150,34 @@ { "fieldname": "section_break_4", "fieldtype": "Section Break" + }, + { + "fieldname": "base_payment_amount", + "fieldtype": "Currency", + "label": "Payment Amount (Company Currency)", + "options": "Company:company:default_currency" + }, + { + "default": "0", + "fieldname": "base_outstanding", + "fieldtype": "Currency", + "label": "Outstanding (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "base_paid_amount", + "fieldtype": "Currency", + "label": "Paid Amount (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-12 15:39:47.363609", + "modified": "2021-04-23 13:55:59.548043", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 50492f50b5..66be11ff23 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -397,7 +397,7 @@ class TestPurchaseInvoice(unittest.TestCase): pi.update({ "payment_schedule": get_payment_terms("_Test Payment Term Template", - pi.posting_date, pi.grand_total) + pi.posting_date, pi.grand_total, pi.base_grand_total) }) pi.save() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d36e7b03f4..c51d8283e5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -904,20 +904,24 @@ class AccountsController(TransactionBase): date = self.get("due_date") due_date = date or posting_date - if party_account_currency == self.company_currency: - grand_total = self.get("base_rounded_total") or self.base_grand_total - else: - grand_total = self.get("rounded_total") or self.grand_total + base_grand_total = self.get("base_rounded_total") or self.base_grand_total + grand_total = self.get("rounded_total") or self.grand_total if self.doctype in ("Sales Invoice", "Purchase Invoice"): + base_grand_total = base_grand_total - flt(self.base_write_off_amount) grand_total = grand_total - flt(self.write_off_amount) if self.get("total_advance"): - grand_total -= self.get("total_advance") + if party_account_currency == self.company_currency: + base_grand_total -= self.get("total_advance") + grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total")) + else: + grand_total -= self.get("total_advance") + base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) if not self.get("payment_schedule"): if self.get("payment_terms_template"): - data = get_payment_terms(self.payment_terms_template, posting_date, grand_total) + data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total) for item in data: self.append("payment_schedule", item) else: @@ -927,7 +931,9 @@ class AccountsController(TransactionBase): for d in self.get("payment_schedule"): if d.invoice_portion: d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) + d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.outstanding = d.payment_amount + d.base_outstanding = d.base_payment_amount def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] @@ -963,22 +969,28 @@ class AccountsController(TransactionBase): if self.get("payment_schedule"): total = 0 + base_total = 0 for d in self.get("payment_schedule"): total += flt(d.payment_amount) + base_total += flt(d.base_payment_amount) - if party_account_currency == self.company_currency: - total = flt(total, self.precision("base_grand_total")) - grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total')) - else: - total = flt(total, self.precision("grand_total")) - grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total')) - - if self.get("total_advance"): - grand_total -= self.get("total_advance") + base_grand_total = self.get("base_rounded_total") or self.base_grand_total + grand_total = self.get("rounded_total") or self.grand_total if self.doctype in ("Sales Invoice", "Purchase Invoice"): + base_grand_total = base_grand_total - flt(self.base_write_off_amount) grand_total = grand_total - flt(self.write_off_amount) - if total != flt(grand_total, self.precision("grand_total")): + + if self.get("total_advance"): + if party_account_currency == self.company_currency: + base_grand_total -= self.get("total_advance") + grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total")) + else: + grand_total -= self.get("total_advance") + base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) + + if total != flt(grand_total, self.precision("grand_total")) or \ + base_total != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) def is_rounded_total_disabled(self): @@ -1218,7 +1230,7 @@ def update_invoice_status(): @frappe.whitelist() -def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_date=None): +def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): if not terms_template: return @@ -1226,14 +1238,14 @@ def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_ schedule = [] for d in terms_doc.get("terms"): - term_details = get_payment_term_details(d, posting_date, grand_total, bill_date) + term_details = get_payment_term_details(d, posting_date, grand_total, base_grand_total, bill_date) schedule.append(term_details) return schedule @frappe.whitelist() -def get_payment_term_details(term, posting_date=None, grand_total=None, bill_date=None): +def get_payment_term_details(term, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): term_details = frappe._dict() if isinstance(term, text_type): term = frappe.get_doc("Payment Term", term) @@ -1242,10 +1254,11 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, bill_dat term_details.description = term.description term_details.invoice_portion = term.invoice_portion term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100 + term_details.base_payment_amount = flt(term.invoice_portion) * flt(base_grand_total) / 100 term_details.discount_type = term.discount_type term_details.discount = term.discount - # term_details.discounted_amount = flt(grand_total) * (term.discount / 100) if term.discount_type == 'Percentage' else discount term_details.outstanding = term_details.payment_amount + term_details.base_outstanding = term_details.base_payment_amount term_details.mode_of_payment = term.mode_of_payment if bill_date: diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index a0398e718f..258262aed3 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1995,6 +1995,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ terms_template: doc.payment_terms_template, posting_date: posting_date, grand_total: doc.rounded_total || doc.grand_total, + base_grand_total: doc.base_rounded_total || doc.base_grand_total, bill_date: doc.bill_date }, callback: function(r) { @@ -2009,13 +2010,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ payment_term: function(doc, cdt, cdn) { var row = locals[cdt][cdn]; if(row.payment_term) { + debugger; frappe.call({ method: "erpnext.controllers.accounts_controller.get_payment_term_details", args: { term: row.payment_term, bill_date: this.frm.doc.bill_date, posting_date: this.frm.doc.posting_date || this.frm.doc.transaction_date, - grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total + grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total, + base_grand_total: this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total }, callback: function(r) { if(r.message && !r.exc) { From c5c9f9a9416250d8ed4fe72d9b738ba586aa54b4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 23 Apr 2021 15:34:58 +0530 Subject: [PATCH 025/115] feat: set dynamic labels for payment schedule fields --- erpnext/public/js/controllers/transaction.js | 54 +++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 258262aed3..dc731ca213 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -640,6 +640,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ let key = item.name; me.apply_rule_on_other_items({key: item}); } + }, + () => { + var company_currency = me.get_company_currency(); + me.update_item_grid_labels(company_currency); } ]); } @@ -1321,11 +1325,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ change_grid_labels: function(company_currency) { var me = this; - this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount", "base_rate_with_margin"], - company_currency, "items"); + this.update_item_grid_labels(company_currency); - this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate", "rate_with_margin"], - this.frm.doc.currency, "items"); + this.toggle_item_grid_columns(company_currency); if(this.frm.fields_dict["operations"]) { this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations"); @@ -1360,6 +1362,42 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ this.frm.doc.party_account_currency, "advances"); } + this.update_payment_schedule_grid_labels(company_currency); + + // set labels + var $wrapper = $(this.frm.wrapper); + }, + + update_item_grid_labels: function(company_currency) { + this.frm.set_currency_labels([ + "base_rate", "base_net_rate", "base_price_list_rate", + "base_amount", "base_net_amount", "base_rate_with_margin" + ], company_currency, "items"); + + this.frm.set_currency_labels([ + "rate", "net_rate", "price_list_rate", "amount", + "net_amount", "stock_uom_rate", "rate_with_margin" + ], this.frm.doc.currency, "items"); + }, + + update_payment_schedule_grid_labels: function(company_currency) { + const me = this; + if(this.frm.fields_dict["payment_schedule"]) { + 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"], + 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)) + schedule_grid.set_column_disp(fname, me.frm.doc.currency != company_currency); + }); + } + }, + + toggle_item_grid_columns: function(company_currency) { + const me = this; // toggle columns var item_grid = this.frm.fields_dict["items"].grid; $.each(["base_rate", "base_price_list_rate", "base_amount", "base_rate_with_margin"], function(i, fname) { @@ -1379,9 +1417,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(frappe.meta.get_docfield(item_grid.doctype, fname)) item_grid.set_column_disp(fname, (show && (me.frm.doc.currency != company_currency))); }); - - // set labels - var $wrapper = $(this.frm.wrapper); }, recalculate: function() { @@ -2001,6 +2036,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ callback: function(r) { 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); } } }) @@ -2010,7 +2047,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ payment_term: function(doc, cdt, cdn) { var row = locals[cdt][cdn]; if(row.payment_term) { - debugger; frappe.call({ method: "erpnext.controllers.accounts_controller.get_payment_term_details", args: { @@ -2024,6 +2060,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(r.message && !r.exc) { for (var d in r.message) { frappe.model.set_value(cdt, cdn, d, r.message[d]); + const company_currency = me.get_company_currency(); + this.update_payment_schedule_grid_labels(company_currency); } } } From 968ec110f304e8c8f1710ddccef972c35d1cef73 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Apr 2021 14:51:00 +0530 Subject: [PATCH 026/115] fix: Linting and translation issues --- .../process_statement_of_accounts.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 33c32ebce2..b6149e89f5 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -4,13 +4,13 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing from erpnext import get_company_currency from erpnext.accounts.party import get_party_account_currency -from frappe.core.doctype.communication.email import make from frappe.utils.print_format import report_to_pdf from frappe.utils.pdf import get_pdf from frappe.utils import today, add_days, add_months, getdate, format_date @@ -31,7 +31,7 @@ class ProcessStatementOfAccounts(Document): validate_template(self.body) if not self.customers: - frappe.throw(frappe._('Customers not selected.')) + frappe.throw(_('Customers not selected.')) if self.enable_auto_email: self.to_date = self.start_date @@ -60,6 +60,8 @@ def get_report_pdf(doc, consolidated=True): aging[0]['ageing_based_on'] = doc.ageing_based_on tax_id = frappe.get_doc('Customer', entry.customer).tax_id + presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \ + or doc.currency or get_company_currency(doc.company) filters= frappe._dict({ 'from_date': doc.from_date, @@ -69,8 +71,7 @@ def get_report_pdf(doc, consolidated=True): 'account': doc.account if doc.account else None, 'party_type': 'Customer', 'party': [entry.customer], - 'presentation_currency': get_party_account_currency('Customer', entry.customer, doc.company) \ - or doc.currency or get_company_currency(doc.company), + 'presentation_currency': presentation_currency, 'group_by': doc.group_by, 'currency': doc.currency, 'cost_center': [cc.cost_center_name for cc in doc.cost_center], @@ -170,7 +171,7 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory): if customer_collection == 'Sales Person': customers = get_customers_based_on_sales_person(collection_name) if not bool(customers): - frappe.throw('No Customers found with selected options.') + frappe.throw(_('No Customers found with selected options.')) else: if customer_collection == 'Sales Partner': customers = frappe.get_list('Customer', fields=['name', 'email_id'], \ @@ -202,14 +203,14 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr if len(billing_email) == 0 or (billing_email[0][0] is None): if billing_and_primary: - frappe.throw('No billing email found for customer: '+ customer_name) + frappe.throw(_("No billing email found for customer: {0}").format(customer_name)) else: return '' if billing_and_primary: primary_email = frappe.get_value('Customer', customer_name, 'email_id') if primary_email is None and int(primary_mandatory): - frappe.throw('No primary email found for customer: '+ customer_name) + frappe.throw(_("No primary email found for customer: {0}").format(customer_name)) return [primary_email or '', billing_email[0][0]] else: return billing_email[0][0] or '' From 7f1b2de74d989cbaa49d05eb1e1548271bd24092 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 26 Apr 2021 22:23:48 +0530 Subject: [PATCH 027/115] feat: Employee Referral (#24997) * feat: Employee referal design and status sync * feat: create job Applicant and linked Document * feat: Added list view indicator * chore: formatted file * feat: Addedd Employee Referral form Dashboard * feat: pay compensation via additional salary * test: Employee Rreferral * Update erpnext/hr/doctype/employee_advance/employee_advance.js Co-authored-by: Rucha Mahabal * fix: changes requested * fix: changes requested * fix: sider * fix: translation * fix: test * feat: added to Dashboard * fix: changes * chore: clean-up Employee Referral form - fix labels - fix full name field - fix failing test - set title for Employee Referral form - set fields in List View and Standard Filter * feat: option to add a resume link - copy resume and resume link from Employee Referral to Job Applicant * fix: multiple fixes - confirm before rejecting employee referral - set primary actions for custom buttons - Validations for additional salary against employee referrals with status as accepted only - fix list view indicators - code formatting style * feat: Add field to track Referral Bonus Payment Status - fix visibility of additional salary button Co-authored-by: Rucha Mahabal --- .../employee_advance/employee_advance.js | 53 ++-- .../employee_advance_dashboard.py | 8 +- .../hr/doctype/employee_referral/__init__.py | 0 .../employee_referral/employee_referral.js | 68 ++++ .../employee_referral/employee_referral.json | 294 ++++++++++++++++++ .../employee_referral/employee_referral.py | 71 +++++ .../employee_referral_dashboard.py | 15 + .../employee_referral_list.js | 14 + .../test_employee_referral.py | 60 ++++ .../doctype/job_applicant/job_applicant.json | 10 +- .../hr/doctype/job_applicant/job_applicant.py | 11 + erpnext/hr/workspace/hr/hr.json | 11 +- .../additional_salary/additional_salary.py | 33 +- 13 files changed, 613 insertions(+), 35 deletions(-) create mode 100644 erpnext/hr/doctype/employee_referral/__init__.py create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral.js create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral.json create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral.py create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py create mode 100644 erpnext/hr/doctype/employee_referral/employee_referral_list.js create mode 100644 erpnext/hr/doctype/employee_referral/test_employee_referral.py diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js index 5037ceb489..fa4b06aad3 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.js +++ b/erpnext/hr/doctype/employee_advance/employee_advance.js @@ -34,7 +34,7 @@ frappe.ui.form.on('Employee Advance', { }; }); - frm.set_query('salary_component', function(doc) { + frm.set_query('salary_component', function() { return { filters: { "type": "Deduction" @@ -44,48 +44,49 @@ frappe.ui.form.on('Employee Advance', { }, refresh: function(frm) { - if (frm.doc.docstatus===1 - && (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount)) - && frappe.model.can_create("Payment Entry")) { + if (frm.doc.docstatus === 1 && + (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount)) && + frappe.model.can_create("Payment Entry")) { frm.add_custom_button(__('Payment'), - function() { frm.events.make_payment_entry(frm); }, __('Create')); - } - else if ( - frm.doc.docstatus === 1 - && flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount) - && frappe.model.can_create("Expense Claim") + function () { + frm.events.make_payment_entry(frm); + }, __('Create')); + } else if ( + frm.doc.docstatus === 1 && + flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount) && + frappe.model.can_create("Expense Claim") ) { frm.add_custom_button( __("Expense Claim"), - function() { + function () { frm.events.make_expense_claim(frm); }, __('Create') ); } - if (frm.doc.docstatus === 1 - && (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) { + if (frm.doc.docstatus === 1 && + (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) { - if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")){ - frm.add_custom_button(__("Return"), function() { + if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")) { + frm.add_custom_button(__("Return"), function() { frm.trigger('make_return_entry'); }, __('Create')); - }else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")){ - frm.add_custom_button(__("Deduction from salary"), function() { + } else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")) { + frm.add_custom_button(__("Deduction from salary"), function() { frm.events.make_deduction_via_additional_salary(frm); }, __('Create')); } } }, - make_deduction_via_additional_salary: function(frm){ + make_deduction_via_additional_salary: function(frm) { frappe.call({ method: "erpnext.hr.doctype.employee_advance.employee_advance.create_return_through_additional_salary", args: { doc: frm.doc }, - callback: function (r){ + callback: function(r) { var doclist = frappe.model.sync(r.message); frappe.set_route("Form", doclist[0].doctype, doclist[0].name); } @@ -94,7 +95,7 @@ frappe.ui.form.on('Employee Advance', { make_payment_entry: function(frm) { var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry"; - if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) { + if (frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) { method = "erpnext.hr.doctype.employee_advance.employee_advance.make_bank_entry"; } return frappe.call({ @@ -148,11 +149,11 @@ frappe.ui.form.on('Employee Advance', { }); }, - employee: function (frm) { + employee: function(frm) { if (frm.doc.employee) { frappe.run_serially([ - () => frm.trigger('get_employee_currency'), - () => frm.trigger('get_pending_amount') + () => frm.trigger('get_employee_currency'), + () => frm.trigger('get_pending_amount') ]); } }, @@ -199,7 +200,7 @@ frappe.ui.form.on('Employee Advance', { } else { frm.set_value("exchange_rate", 1.0); frm.set_df_property('exchange_rate', 'hidden', 1); - frm.set_df_property("exchange_rate", "description", "" ); + frm.set_df_property("exchange_rate", "description", ""); } frm.refresh_fields(); } @@ -215,8 +216,8 @@ frappe.ui.form.on('Employee Advance', { callback: function(r) { frm.set_value("exchange_rate", flt(r.message)); frm.set_df_property('exchange_rate', 'hidden', 0); - frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency - + " = [?] " + company_currency); + frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency + + " = [?] " + company_currency); } }); } diff --git a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py index c3b4a3a889..2f493e2d4e 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py @@ -4,10 +4,10 @@ from frappe import _ def get_data(): return { 'fieldname': 'employee_advance', - 'non_standard_fieldnames': { - 'Payment Entry': 'reference_name', - 'Journal Entry': 'reference_name' - }, + 'non_standard_fieldnames': { + 'Payment Entry': 'reference_name', + 'Journal Entry': 'reference_name' + }, 'transactions': [ { 'items': ['Expense Claim'] diff --git a/erpnext/hr/doctype/employee_referral/__init__.py b/erpnext/hr/doctype/employee_referral/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.js b/erpnext/hr/doctype/employee_referral/employee_referral.js new file mode 100644 index 0000000000..9c99bbbefa --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral.js @@ -0,0 +1,68 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on("Employee Referral", { + refresh: function(frm) { + if (frm.doc.docstatus === 1 && frm.doc.status === "Pending") { + frm.add_custom_button(__("Reject Employee Referral"), function() { + frappe.confirm( + __("Are you sure you want to reject the Employee Referral?"), + function() { + frm.doc.status = "Rejected"; + frm.dirty(); + frm.save_or_update(); + }, + function() { + window.close(); + } + ); + }); + + frm.add_custom_button(__("Create Job Applicant"), function() { + frm.events.create_job_applicant(frm); + }).addClass("btn-primary"); + } + + // To check whether Payment is done or not + if (frm.doc.docstatus === 1 && frm.doc.status === "Accepted") { + frappe.db.get_list("Additional Salary", { + filters: { + ref_docname: cur_frm.doc.name, + docstatus: 1 + }, + fields: ["count(name) as additional_salary_count"] + }).then((data) => { + + let additional_salary_count = data[0].additional_salary_count; + + if (frm.doc.is_applicable_for_referral_bonus && !additional_salary_count) { + frm.add_custom_button(__("Create Additional Salary"), function() { + frm.events.create_additional_salary(frm); + }).addClass("btn-primary"); + } + }); + } + + + + }, + create_job_applicant: function(frm) { + frappe.model.open_mapped_doc({ + method: "erpnext.hr.doctype.employee_referral.employee_referral.create_job_applicant", + frm: frm + }); + }, + + create_additional_salary: function(frm) { + frappe.call({ + method: "erpnext.hr.doctype.employee_referral.employee_referral.create_additional_salary", + args: { + doc: frm.doc + }, + callback: function (r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + }, +}); diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.json b/erpnext/hr/doctype/employee_referral/employee_referral.json new file mode 100644 index 0000000000..bfd404b435 --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral.json @@ -0,0 +1,294 @@ +{ + "actions": [], + "autoname": "format:HR-REF-{####}", + "creation": "2021-03-23 14:54:45.047051", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "first_name", + "last_name", + "full_name", + "email", + "contact_no", + "resume", + "resume_link", + "column_break_6", + "date", + "status", + "for_designation", + "current_employer", + "current_job_title", + "referrer_details_section", + "referrer", + "referrer_name", + "column_break_14", + "is_applicable_for_referral_bonus", + "referral_payment_status", + "department", + "additional_information_section", + "qualification_reason", + "work_references", + "amended_from" + ], + "fields": [ + { + "fieldname": "first_name", + "fieldtype": "Data", + "label": "First Name ", + "reqd": 1 + }, + { + "fieldname": "last_name", + "fieldtype": "Data", + "label": "Last Name", + "reqd": 1 + }, + { + "fieldname": "full_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Full Name", + "read_only": 1 + }, + { + "fieldname": "contact_no", + "fieldtype": "Data", + "in_standard_filter": 1, + "label": "Contact No.", + "options": "Phone" + }, + { + "fieldname": "current_employer", + "fieldtype": "Data", + "label": "Current Employer " + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "date", + "fieldtype": "Date", + "in_standard_filter": 1, + "label": "Date", + "reqd": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "options": "Pending\nIn Process\nAccepted\nRejected", + "permlevel": 1, + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "current_job_title", + "fieldtype": "Data", + "label": "Current Job Title" + }, + { + "fieldname": "resume", + "fieldtype": "Attach", + "label": "Resume" + }, + { + "fieldname": "referrer_details_section", + "fieldtype": "Section Break", + "label": "Referrer Details" + }, + { + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, + { + "fieldname": "additional_information_section", + "fieldtype": "Section Break", + "label": "Additional Information " + }, + { + "fieldname": "work_references", + "fieldtype": "Text Editor", + "label": "Work References" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Employee Referral", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, + { + "fieldname": "for_designation", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "For Designation ", + "options": "Designation", + "reqd": 1 + }, + { + "fieldname": "email", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Email", + "options": "Email", + "reqd": 1, + "unique": 1 + }, + { + "default": "1", + "fieldname": "is_applicable_for_referral_bonus", + "fieldtype": "Check", + "label": "Is Applicable for Referral Bonus" + }, + { + "fieldname": "qualification_reason", + "fieldtype": "Text Editor", + "label": "Why is this Candidate Qualified for this Position?" + }, + { + "fieldname": "referrer", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Referrer", + "options": "Employee", + "reqd": 1 + }, + { + "fetch_from": "referrer.employee_name", + "fieldname": "referrer_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Referrer Name", + "read_only": 1 + }, + { + "fieldname": "resume_link", + "fieldtype": "Data", + "label": "Resume Link" + }, + { + "fieldname": "referral_payment_status", + "fieldtype": "Select", + "label": "Referral Bonus Payment Status", + "options": "\nUnpaid\nPaid", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-04-26 21:21:38.094086", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Referral", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "amend": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "delete": 1, + "email": 1, + "export": 1, + "permlevel": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "write": 1 + }, + { + "delete": 1, + "email": 1, + "export": 1, + "permlevel": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "permlevel": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "full_name" +} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py new file mode 100644 index 0000000000..45d68729ce --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral.py @@ -0,0 +1,71 @@ +# -*- 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 import _ +from frappe.utils import get_link_to_form +from frappe.model.document import Document + +class EmployeeReferral(Document): + def validate(self): + self.set_full_name() + self.set_referral_bonus_payment_status() + + def set_full_name(self): + self.full_name = " ".join(filter(None, [self.first_name, self.last_name])) + + def set_referral_bonus_payment_status(self): + if not self.is_applicable_for_referral_bonus: + self.referral_payment_status = "" + else: + if not self.referral_payment_status: + self.referral_payment_status = "Unpaid" + + +@frappe.whitelist() +def create_job_applicant(source_name, target_doc=None): + emp_ref = frappe.get_doc("Employee Referral", source_name) + #just for Api call if some set status apart from default Status + status = emp_ref.status + if emp_ref.status in ["Pending", "In process"]: + status = "Open" + + job_applicant = frappe.new_doc("Job Applicant") + job_applicant.employee_referral = emp_ref.name + job_applicant.status = status + job_applicant.applicant_name = emp_ref.full_name + job_applicant.email_id = emp_ref.email + job_applicant.phone_number = emp_ref.contact_no + job_applicant.resume_attachment = emp_ref.resume + job_applicant.resume_link = emp_ref.resume_link + job_applicant.save() + + frappe.msgprint(_("Job Applicant {0} created successfully.").format( + get_link_to_form("Job Applicant", job_applicant.name)), + title=_("Success"), indicator="green") + + emp_ref.db_set("status", "In Process") + + return job_applicant + + +@frappe.whitelist() +def create_additional_salary(doc): + import json + from six import string_types + + if isinstance(doc, string_types): + doc = frappe._dict(json.loads(doc)) + + if not frappe.db.exists("Additional Salary", {"ref_docname": doc.name}): + additional_salary = frappe.new_doc("Additional Salary") + additional_salary.employee = doc.referrer + additional_salary.company = frappe.db.get_value("Employee", doc.referrer, "company") + additional_salary.overwrite_salary_structure_amount = 0 + additional_salary.ref_doctype = doc.doctype + additional_salary.ref_docname = doc.name + + return additional_salary + diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py new file mode 100644 index 0000000000..afa2a1ff1f --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals + +def get_data(): + return { + 'fieldname': 'employee_referral', + 'non_standard_fieldnames': { + 'Additional Salary': 'ref_docname' + }, + 'transactions': [ + { + 'items': ['Job Applicant', 'Additional Salary'] + }, + + ] + } \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_list.js b/erpnext/hr/doctype/employee_referral/employee_referral_list.js new file mode 100644 index 0000000000..7533ab635f --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/employee_referral_list.js @@ -0,0 +1,14 @@ +frappe.listview_settings['Employee Referral'] = { + add_fields: ["status"], + get_indicator: function (doc) { + if (doc.status == "Pending") { + return [__(doc.status), "grey", "status,=," + doc.status]; + } else if (doc.status == "In Process") { + return [__(doc.status), "orange", "status,=," + doc.status]; + } else if (doc.status == "Accepted") { + return [__(doc.status), "green", "status,=," + doc.status]; + } else if (doc.status == "Rejected") { + return [__(doc.status), "red", "status,=," + doc.status]; + } + }, +}; \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_referral/test_employee_referral.py b/erpnext/hr/doctype/employee_referral/test_employee_referral.py new file mode 100644 index 0000000000..a674f39026 --- /dev/null +++ b/erpnext/hr/doctype/employee_referral/test_employee_referral.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +from frappe.utils import today +from erpnext.hr.doctype.designation.test_designation import create_designation +from erpnext.hr.doctype.employee_referral.employee_referral import create_job_applicant, create_additional_salary +from erpnext.hr.doctype.employee.test_employee import make_employee +import unittest + +class TestEmployeeReferral(unittest.TestCase): + def test_workflow_and_status_sync(self): + emp_ref = create_employee_referral() + + #Check Initial status + self.assertTrue(emp_ref.status, "Pending") + + job_applicant = create_job_applicant(emp_ref.name) + + + #Check status sync + emp_ref.reload() + self.assertTrue(emp_ref.status, "In Process") + + job_applicant.reload() + job_applicant.status = "Rejected" + job_applicant.save() + + emp_ref.reload() + self.assertTrue(emp_ref.status, "Rejected") + + job_applicant.reload() + job_applicant.status = "Accepted" + job_applicant.save() + + emp_ref.reload() + self.assertTrue(emp_ref.status, "Accepted") + + + # Check for Referral reference in additional salary + + add_sal = create_additional_salary(emp_ref) + self.assertTrue(add_sal.ref_docname, emp_ref.name) + + +def create_employee_referral(): + emp_ref = frappe.new_doc("Employee Referral") + emp_ref.first_name = "Mahesh" + emp_ref.last_name = "Singh" + emp_ref.email = "a@b.c" + emp_ref.date = today() + emp_ref.for_designation = create_designation().name + emp_ref.referrer = make_employee("testassetmovemp@example.com", company="_Test Company") + emp_ref.is_applicable_for_employee_referral_compensation = 1 + emp_ref.save() + emp_ref.submit() + + return emp_ref \ No newline at end of file diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.json b/erpnext/hr/doctype/job_applicant/job_applicant.json index 1360fd1890..bcea5f50d9 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.json +++ b/erpnext/hr/doctype/job_applicant/job_applicant.json @@ -18,6 +18,7 @@ "job_title", "source", "source_name", + "employee_referral", "applicant_rating", "section_break_6", "notes", @@ -152,13 +153,20 @@ "fieldtype": "Link", "label": "Currency", "options": "Currency" + }, + { + "fieldname": "employee_referral", + "fieldtype": "Link", + "label": "Employee Referral", + "options": "Employee Referral", + "read_only": 1 } ], "icon": "fa fa-user", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-09-18 12:39:02.557563", + "modified": "2021-03-24 15:51:11.117517", "modified_by": "Administrator", "module": "HR", "name": "Job Applicant", diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py index a6aef04919..0594ba395b 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/job_applicant.py @@ -28,10 +28,21 @@ class JobApplicant(Document): if self.email_id: validate_email_address(self.email_id, True) + if self.employee_referral: + self.set_status_for_employee_referral() + if not self.applicant_name and self.email_id: guess = self.email_id.split('@')[0] self.applicant_name = ' '.join([p.capitalize() for p in guess.split('.')]) + def set_status_for_employee_referral(self): + emp_ref = frappe.get_doc("Employee Referral", self.employee_referral) + if self.status in ["Open", "Replied", "Hold"]: + emp_ref.db_set("status", "In Process") + elif self.status in ["Accepted", "Rejected"]: + emp_ref.db_set("status", self.status) + + def check_email_id_is_unique(self): if self.email_id: names = frappe.db.sql_list("""select name from `tabJob Applicant` diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json index f4b56a0e17..c5201c22c9 100644 --- a/erpnext/hr/workspace/hr/hr.json +++ b/erpnext/hr/workspace/hr/hr.json @@ -520,6 +520,15 @@ "onboard": 1, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Employee Referral", + "link_to": "Employee Referral", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "dependencies": "", "hidden": 0, @@ -814,7 +823,7 @@ "type": "Link" } ], - "modified": "2021-03-24 17:35:21.483297", + "modified": "2021-04-26 13:36:15.413819", "modified_by": "Administrator", "module": "HR", "name": "HR", diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index 13b6c05e22..ebeddf97f9 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -13,12 +13,19 @@ class AdditionalSalary(Document): if self.ref_doctype == "Employee Advance" and self.ref_docname: frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount) + self.update_employee_referral() + + def on_cancel(self): + self.update_employee_referral(cancel=True) + def validate(self): self.validate_dates() self.validate_salary_structure() self.validate_recurring_additional_salary_overlap() + self.validate_employee_referral() + if self.amount < 0: - frappe.throw(_("Amount should not be less than zero.")) + frappe.throw(_("Amount should not be less than zero")) def validate_salary_structure(self): if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}): @@ -70,6 +77,27 @@ class AdditionalSalary(Document): if self.payroll_date and getdate(self.payroll_date) > getdate(relieving_date): frappe.throw(_("Payroll date can not be greater than employee's relieving date.")) + def validate_employee_referral(self): + if self.ref_doctype == "Employee Referral": + referral_details = frappe.db.get_value("Employee Referral", self.ref_docname, + ["is_applicable_for_referral_bonus", "status"], as_dict=1) + + if not referral_details.is_applicable_for_referral_bonus: + frappe.throw(_("Employee Referral {0} is not applicable for referral bonus.").format( + self.ref_docname)) + + if self.type == "Deduction": + frappe.throw(_("Earning Salary Component is required for Employee Referral Bonus.")) + + if referral_details.status != "Accepted": + frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format( + frappe.bold("Accepted"))) + + def update_employee_referral(self, cancel=False): + if self.ref_doctype == "Employee Referral": + status = "Unpaid" if cancel else "Paid" + frappe.db.set_value("Employee Referral", self.ref_docname, "referral_payment_status", status) + def get_amount(self, sal_start_date, sal_end_date): start_date = getdate(sal_start_date) end_date = getdate(sal_end_date) @@ -110,8 +138,7 @@ def get_additional_salaries(employee, start_date, end_date, component_type): for d in additional_salary_list: if d.overwrite: if d.component in components_to_overwrite: - frappe.throw(_("Multiple Additional Salaries with overwrite " - "property exist for Salary Component {0} between {1} and {2}.").format( + frappe.throw(_("Multiple Additional Salaries with overwrite property exist for Salary Component {0} between {1} and {2}.").format( frappe.bold(d.component), start_date, end_date), title=_("Error")) components_to_overwrite.append(d.component) From df06e49e0c821e2ea2989588cafbbeeb0a21ec83 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 26 Apr 2021 22:48:13 +0530 Subject: [PATCH 028/115] feat: enable custom field search on POS (#25421) * feat: multi lingual item search in POS * fix: limit the options for selection and broken down funtion for geting condition --- .../doctype/pos_search_fields/__init__.py | 0 .../pos_search_fields/pos_search_fields.json | 37 +++++++++++++++++ .../pos_search_fields/pos_search_fields.py | 10 +++++ .../doctype/pos_settings/pos_settings.js | 40 +++++++++++++++++++ .../doctype/pos_settings/pos_settings.json | 11 ++++- .../page/point_of_sale/point_of_sale.py | 26 +++++++++--- 6 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 erpnext/accounts/doctype/pos_search_fields/__init__.py create mode 100644 erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json create mode 100644 erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py diff --git a/erpnext/accounts/doctype/pos_search_fields/__init__.py b/erpnext/accounts/doctype/pos_search_fields/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json new file mode 100644 index 0000000000..a627f5b5be --- /dev/null +++ b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json @@ -0,0 +1,37 @@ +{ + "actions": [], + "creation": "2021-04-19 14:56:06.652327", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "field", + "fieldname" + ], + "fields": [ + { + "fieldname": "fieldname", + "fieldtype": "Data", + "hidden": 1, + "label": "Fieldname" + }, + { + "fieldname": "field", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Field" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-21 11:12:54.632093", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Search Fields", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.py new file mode 100644 index 0000000000..720ea77745 --- /dev/null +++ b/erpnext/accounts/doctype/pos_search_fields/pos_search_fields.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 POSSearchFields(Document): + pass diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js index 3625393a80..9003af56a5 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.js +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js @@ -1,9 +1,17 @@ // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt +let search_fields_datatypes = ['Data', 'Link', 'Dynamic Link', 'Long Text', 'Select', 'Small Text', 'Text', 'Text Editor']; +let do_not_include_fields = ["naming_series", "item_code", "item_name", "stock_uom", "hub_sync_id", "asset_naming_series", + "default_material_request_type", "valuation_method", "warranty_period", "weight_uom", "batch_number_series", + "serial_no_series", "purchase_uom", "customs_tariff_number", "sales_uom", "deferred_revenue_account", + "deferred_expense_account", "quality_inspection_template", "route", "slideshow", "website_image_alt", "thumbnail", + "web_long_description", "hub_sync_id"] + frappe.ui.form.on('POS Settings', { onload: function(frm) { frm.trigger("get_invoice_fields"); + frm.trigger("add_search_options"); }, get_invoice_fields: function(frm) { @@ -21,6 +29,38 @@ frappe.ui.form.on('POS Settings', { ); }); + }, + + add_search_options: function(frm) { + frappe.model.with_doctype("Item", () => { + var fields = $.map(frappe.get_doc("DocType", "Item").fields, function(d) { + if (search_fields_datatypes.includes(d.fieldtype) && !(do_not_include_fields.includes(d.fieldname))) { + return [d.label]; + } else { + return null; + } + }); + + fields.unshift(''); + frm.fields_dict.pos_search_fields.grid.update_docfield_property('field', 'options', fields); + }); + + } +}); + +frappe.ui.form.on("POS Search Fields", { + field: function(frm, doctype, name) { + var doc = frappe.get_doc(doctype, name); + var df = $.map(frappe.get_doc("DocType", "Item").fields, function(d) { + if (doc.field == d.label && search_fields_datatypes.includes(d.fieldtype)) { + return d; + } else { + return null; + } + })[0]; + + doc.fieldname = df.fieldname; + frm.refresh_field("fields"); } }); diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.json b/erpnext/accounts/doctype/pos_settings/pos_settings.json index 35395889a6..962eb94a29 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.json +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.json @@ -5,7 +5,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "invoice_fields" + "invoice_fields", + "pos_search_fields" ], "fields": [ { @@ -13,11 +14,17 @@ "fieldtype": "Table", "label": "POS Field", "options": "POS Field" + }, + { + "fieldname": "pos_search_fields", + "fieldtype": "Table", + "label": "POS Search Fields", + "options": "POS Search Fields" } ], "issingle": 1, "links": [], - "modified": "2020-06-01 15:46:41.478928", + "modified": "2021-04-19 14:56:24.465218", "modified_by": "Administrator", "module": "Accounts", "name": "POS Settings", diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 062cba19e6..750a1a6071 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -23,7 +23,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va if search_value: data = search_serial_or_batch_or_barcode_number(search_value) - + item_code = data.get("item_code") if data.get("item_code") else search_value serial_no = data.get("serial_no") if data.get("serial_no") else "" batch_no = data.get("batch_no") if data.get("batch_no") else "" @@ -31,7 +31,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va if data: item_info = frappe.db.get_value( - "Item", data.get("item_code"), + "Item", data.get("item_code"), ["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"] , as_dict=1) item_info.setdefault('serial_no', serial_no) @@ -139,8 +139,24 @@ def get_conditions(item_code, serial_no, batch_no, barcode): if serial_no or batch_no or barcode: return "item.name = {0}".format(frappe.db.escape(item_code)) - return """(item.name like {item_code} - or item.item_name like {item_code})""".format(item_code = frappe.db.escape('%' + item_code + '%')) + return make_condition(item_code) + +def make_condition(item_code): + condition = "(" + condition += """item.name like {item_code} + or item.item_name like {item_code}""".format(item_code = frappe.db.escape('%' + item_code + '%')) + condition += add_search_fields_condition(item_code) + condition += ")" + + return condition + +def add_search_fields_condition(item_code): + condition = '' + search_fields = frappe.get_all('POS Search Fields', fields = ['fieldname']) + if search_fields: + for field in search_fields: + condition += " or item.{0} like {1}".format(field['fieldname'], frappe.db.escape('%' + item_code + '%')) + return condition def get_item_group_condition(pos_profile): cond = "and 1=1" @@ -257,4 +273,4 @@ def set_customer_info(fieldname, customer, value=""): elif fieldname == 'mobile_no': contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}]) frappe.db.set_value('Customer', customer, 'mobile_no', value) - contact_doc.save() \ No newline at end of file + contact_doc.save() 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 029/115] 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 1a215031c1..a5ca7eee5d 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 8a1e5e189ca0705ed85db27f9665ac34e066cea3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Apr 2021 16:57:34 +0530 Subject: [PATCH 030/115] fix: Ageing errors in PSOA --- .../process_statement_of_accounts.html | 12 ++++++------ .../process_statement_of_accounts.py | 13 +++++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index b623898829..f61aacbce2 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -60,8 +60,8 @@

-{% if aging %} -

{{ _("Ageing Report Based On ") }} {{ aging.ageing_based_on }}

+{% if ageing %} +

{{ _("Ageing Report Based On ") }} {{ ageing.ageing_based_on }}

{{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
@@ -78,10 +78,10 @@ - {{ frappe.utils.fmt_money(aging.range1, currency=filters.presentation_currency) }} - {{ frappe.utils.fmt_money(aging.range2, currency=filters.presentation_currency) }} - {{ frappe.utils.fmt_money(aging.range3, currency=filters.presentation_currency) }} - {{ frappe.utils.fmt_money(aging.range4, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }} + {{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }} diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index b6149e89f5..62090c0255 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -40,7 +40,7 @@ class ProcessStatementOfAccounts(Document): def get_report_pdf(doc, consolidated=True): statement_dict = {} - aging = '' + ageing = '' base_template_path = "frappe/www/printview.html" template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html" @@ -56,8 +56,10 @@ def get_report_pdf(doc, consolidated=True): 'range4': 120, 'customer': entry.customer }) - col1, aging = get_ageing(ageing_filters) - aging[0]['ageing_based_on'] = doc.ageing_based_on + col1, ageing = get_ageing(ageing_filters) + + if ageing: + ageing[0]['ageing_based_on'] = doc.ageing_based_on tax_id = frappe.get_doc('Customer', entry.customer).tax_id presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \ @@ -87,11 +89,14 @@ def get_report_pdf(doc, consolidated=True): if len(res) == 3: continue + html = frappe.render_template(template_path, \ - {"filters": filters, "data": res, "aging": aging[0] if doc.include_ageing else None}) + {"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None}) + html = frappe.render_template(base_template_path, {"body": html, \ "css": get_print_style(), "title": "Statement For " + entry.customer}) statement_dict[entry.customer] = html + if not bool(statement_dict): return False elif consolidated: From ad4365eb0eb4583333243c5c7a18995e736ec0d0 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 27 Apr 2021 20:00:21 +0530 Subject: [PATCH 031/115] fix: unexpected keyword argument 'merge_logs' (#25489) * fix: unexpected keyword arguement 'merge_logs' * fix: reference error * fix: test --- .../pos_invoice_merge_log.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) 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 6d2cffcf68..4d5472df4b 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 @@ -235,11 +235,11 @@ def get_invoice_customer_map(pos_invoices): return pos_invoice_customer_map -def consolidate_pos_invoices(pos_invoices=[], closing_entry={}): - invoices = pos_invoices or closing_entry.get('pos_transactions') or get_all_unconsolidated_invoices() +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) >= 5 and closing_entry: + if len(invoices) >= 1 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,18 +252,18 @@ def unconsolidate_pos_invoices(closing_entry): pluck='name' ) - if len(merge_logs) >= 5: + if len(merge_logs) >= 1: 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={}): +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')) + 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', None) + 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) @@ -273,7 +273,7 @@ def create_merge_logs(invoice_by_customer, closing_entry={}): closing_entry.set_status(update=True, status='Submitted') closing_entry.update_opening_entry() -def cancel_merge_logs(merge_logs, closing_entry={}): +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 @@ -283,20 +283,20 @@ def cancel_merge_logs(merge_logs, closing_entry={}): closing_entry.set_status(update=True, status='Cancelled') closing_entry.update_opening_entry(for_cancel=True) -def enqueue_job(job, merge_logs=None, invoice_by_customer=None, closing_entry=None): +def enqueue_job(job, **kwargs): check_scheduler_status() + closing_entry = kwargs.get('closing_entry') or {} + job_name = closing_entry.get("name") if not job_already_enqueued(job_name): enqueue( job, + **kwargs, queue="long", timeout=10000, event="processing_merge_logs", job_name=job_name, - closing_entry=closing_entry, - invoice_by_customer=invoice_by_customer, - merge_logs=merge_logs, now=frappe.conf.developer_mode or frappe.flags.in_test ) From c9187b00af2d13ec3f5c1a696e66c74346778fa0 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Wed, 28 Apr 2021 13:32:27 +0530 Subject: [PATCH 032/115] fix: change coveralls version (#25499) --- .github/workflows/ci-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 824b74e013..4103bc68a9 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -85,8 +85,8 @@ jobs: run: | cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} cd ${GITHUB_WORKSPACE} - pip install coveralls==3.0.1 - pip install coverage==5.5 + pip install coveralls==2.2.0 + pip install coverage==4.5.4 coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From aa4f750d8c9eae6cdf3d4b853ed3de3e08a5f38e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Apr 2021 14:42:49 +0530 Subject: [PATCH 033/115] fix: test --- erpnext/accounts/general_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 4c285ff088..f1717c50d8 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -175,7 +175,7 @@ def round_off_debit_credit(gl_map): frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.") .format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff)) - elif abs(debit_credit_diff) > (1.0 / (10**precision)): + elif abs(debit_credit_diff) >= (1.0 / (10**precision)): make_round_off_gle(gl_map, debit_credit_diff, precision) def make_round_off_gle(gl_map, debit_credit_diff, precision): From da5b55c4f5fa89770ace9a400f7523d0e83550d0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Apr 2021 14:55:33 +0530 Subject: [PATCH 034/115] fix: test --- erpnext/controllers/accounts_controller.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d7f89b6270..a61292491c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -944,7 +944,7 @@ class AccountsController(TransactionBase): for item in data: self.append("payment_schedule", item) else: - data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total) + data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total) self.append("payment_schedule", data) else: for d in self.get("payment_schedule"): @@ -1007,7 +1007,8 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - + print(total, base_total) + print(grand_total, base_grand_total) if total != flt(grand_total, self.precision("grand_total")) or \ base_total != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) From 44b07e4ef590d5f8a41b14c75291b86e251faeec Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Apr 2021 15:11:57 +0530 Subject: [PATCH 035/115] refactor: remove extra fields --- .../payment_schedule/payment_schedule.json | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index e9f8ba29dc..6ed7a3154e 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -24,9 +24,7 @@ "paid_amount", "discounted_amount", "column_break_3", - "base_payment_amount", - "base_outstanding", - "base_paid_amount" + "base_payment_amount" ], "fields": [ { @@ -156,28 +154,12 @@ "fieldtype": "Currency", "label": "Payment Amount (Company Currency)", "options": "Company:company:default_currency" - }, - { - "default": "0", - "fieldname": "base_outstanding", - "fieldtype": "Currency", - "label": "Outstanding (Company Currency)", - "options": "Company:company:default_currency", - "read_only": 1 - }, - { - "default": "0", - "fieldname": "base_paid_amount", - "fieldtype": "Currency", - "label": "Paid Amount (Company Currency)", - "options": "Company:company:default_currency", - "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-23 13:55:59.548043", + "modified": "2021-04-28 05:41:35.084233", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", From a949480acf0e124e013dd0a936412b5593df7fe6 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 28 Apr 2021 12:38:23 +0200 Subject: [PATCH 036/115] fix: add translation to make semgrep pass --- erpnext/accounts/doctype/subscription/subscription.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 175710c9f8..7c4ff73d90 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -276,7 +276,7 @@ class Subscription(Document): frappe.throw(_('Subscription End Date is mandatory to follow calendar months')) if billing_info[0]['billing_interval'] != 'Month': - frappe.throw('Billing Interval in Subscription Plan must be Month to follow calendar months') + frappe.throw(_('Billing Interval in Subscription Plan must be Month to follow calendar months')) def after_insert(self): # todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype? From 64a38f52cf72a56568876bcc376e69aec3680007 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 28 Apr 2021 13:46:47 +0530 Subject: [PATCH 037/115] chore: remove frappe from requirements.txt Due to recent changes in pip dependency resolver, in some random cases pip thinks frappe is not installed and tries to fetch it from pypi, which results in errors like #25496 Until a better solution is available, frappe should not be part of requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 377fd7df6c..f1ffeb8f48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -frappe +# frappe # https://github.com/frappe/frappe is installed during bench-init gocardless-pro~=1.22.0 googlemaps # used in ERPNext, but dependency is defined in Frappe pandas~=1.1.5 From ab052599c055789a5519c10cdd1567c4a86c78c4 Mon Sep 17 00:00:00 2001 From: Afshan Date: Wed, 28 Apr 2021 20:21:04 +0530 Subject: [PATCH 038/115] fix: allow to cancel loan with cancelled replayment entry --- erpnext/loan_management/doctype/loan/loan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 83a813f947..20b44a15e3 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -44,6 +44,7 @@ class Loan(AccountsController): def on_cancel(self): self.unlink_loan_security_pledge() + self.ignore_linked_doctypes = ['GL Entry'] def set_missing_fields(self): if not self.company: @@ -359,4 +360,4 @@ def get_shortfall_applicants(): return { "value": len(applicants), "fieldtype": "Int" - } \ No newline at end of file + } From dc086dd52ffa6a06afbc965587f2a9c0f34f7edf Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 29 Apr 2021 11:03:27 +0530 Subject: [PATCH 039/115] fix: item stock levels displaying inconsistently (#25506) * fix: fixed stock levels dashboard not displaying after any interactions * fix: minor translation fix --- erpnext/stock/doctype/item/item.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 2079cf88dd..8aec89381a 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -46,9 +46,6 @@ frappe.ui.form.on("Item", { }, __("View")); } - if (!frm.doc.is_fixed_asset) { - erpnext.item.make_dashboard(frm); - } if (frm.doc.is_fixed_asset) { frm.trigger('is_fixed_asset'); @@ -96,6 +93,10 @@ frappe.ui.form.on("Item", { erpnext.item.edit_prices_button(frm); erpnext.item.toggle_attributes(frm); + + if (!frm.doc.is_fixed_asset) { + erpnext.item.make_dashboard(frm); + } frm.add_custom_button(__('Duplicate'), function() { var new_item = frappe.model.copy_doc(frm.doc); @@ -473,11 +474,15 @@ $.extend(erpnext.item, { me.multiple_variant_dialog.get_primary_btn().html(__('Create Variants')); me.multiple_variant_dialog.disable_primary_action(); } else { + let no_of_combinations = lengths.reduce((a, b) => a * b, 1); - me.multiple_variant_dialog.get_primary_btn() - .html(__( - `Make ${no_of_combinations} Variant${no_of_combinations === 1 ? '' : 's'}` - )); + let msg; + if (no_of_combinations === 1) { + msg = __("Make {0} Variant", [no_of_combinations]); + } else { + msg = __("Make {0} Variants", [no_of_combinations]); + } + me.multiple_variant_dialog.get_primary_btn().html(msg); me.multiple_variant_dialog.enable_primary_action(); } } From 83e3820575a2706f7b6ca8b150e0455df716a9cb Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 29 Apr 2021 12:21:56 +0530 Subject: [PATCH 040/115] fix: remove print statement --- erpnext/controllers/accounts_controller.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a61292491c..c409850734 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -952,7 +952,6 @@ class AccountsController(TransactionBase): d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.outstanding = d.payment_amount - d.base_outstanding = d.base_payment_amount def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] @@ -1007,7 +1006,6 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - print(total, base_total) print(grand_total, base_grand_total) if total != flt(grand_total, self.precision("grand_total")) or \ base_total != flt(base_grand_total, self.precision("base_grand_total")): @@ -1278,7 +1276,6 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, base_gra term_details.discount_type = term.discount_type term_details.discount = term.discount term_details.outstanding = term_details.payment_amount - term_details.base_outstanding = term_details.base_payment_amount term_details.mode_of_payment = term.mode_of_payment if bill_date: From 6fb417590fd4ed431aae585b5a30612ab5e900a0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 29 Apr 2021 14:05:20 +0530 Subject: [PATCH 041/115] fix: sider issues --- erpnext/public/js/controllers/transaction.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index dc731ca213..a2b95cb757 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1363,9 +1363,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } this.update_payment_schedule_grid_labels(company_currency); - - // set labels - var $wrapper = $(this.frm.wrapper); }, update_item_grid_labels: function(company_currency) { @@ -1382,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.fields_dict["payment_schedule"]) { 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"], @@ -1390,7 +1387,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ 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)) + if (frappe.meta.get_docfield(schedule_grid.doctype, fname)) schedule_grid.set_column_disp(fname, me.frm.doc.currency != company_currency); }); } @@ -2045,6 +2042,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, payment_term: function(doc, cdt, cdn) { + const me = this; var row = locals[cdt][cdn]; if(row.payment_term) { frappe.call({ @@ -2061,7 +2059,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ for (var d in r.message) { frappe.model.set_value(cdt, cdn, d, r.message[d]); const company_currency = me.get_company_currency(); - this.update_payment_schedule_grid_labels(company_currency); + me.update_payment_schedule_grid_labels(company_currency); } } } From 0dc00219e8e0dbc32f0b6b00c3b268c2f2fb908a Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 29 Apr 2021 15:21:16 +0530 Subject: [PATCH 042/115] fix: coveralls different services for different events (#25513) --- .github/workflows/ci-tests.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 4103bc68a9..84ecfb1457 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -80,8 +80,8 @@ jobs: env: TYPE: ${{ matrix.TYPE }} - - name: Coverage - if: matrix.TYPE == 'server' + - name: Coverage - Pull Request + if: matrix.TYPE == 'server' && github.event_name == 'pull_request' run: | cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} cd ${GITHUB_WORKSPACE} @@ -91,3 +91,18 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + COVERALLS_SERVICE_NAME: github + + - name: Coverage - Push + if: matrix.TYPE == 'server' && github.event_name == 'push' + run: | + cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} + cd ${GITHUB_WORKSPACE} + pip install coveralls==2.2.0 + pip install coverage==4.5.4 + coveralls --service=github-actions + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} + COVERALLS_SERVICE_NAME: github-actions + From dd1e7624bf62aaa6b7a4fa797bdde4d61ea6f215 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Thu, 29 Apr 2021 17:00:59 +0530 Subject: [PATCH 043/115] fix: changing paid amount should change allocated amount --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 830a7f25c0..b80e8ada38 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -561,7 +561,7 @@ frappe.ui.form.on('Payment Entry', { flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)); if(frm.doc.payment_type == "Pay") - frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount); + frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, 1); else frm.events.set_unallocated_amount(frm); From 76e1e68cf4d3bcbc29a8061c53e31c1960230071 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Thu, 29 Apr 2021 08:24:56 -0600 Subject: [PATCH 044/115] fix: Make strings translatable Make strings translatable --- erpnext/payroll/doctype/salary_structure/salary_structure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js index 44368a69fb..d5c20dce6b 100755 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.js +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js @@ -16,7 +16,7 @@ frappe.ui.form.on('Salary Structure', { onload: function(frm) { let help_button = $(` - ${__(Condition and Formula Help)} + ${__("Condition and Formula Help")} `).click(()=>{ let d = new frappe.ui.Dialog({ From 7783a56c08d343baab2e583d7f90f6e25e69a8ac Mon Sep 17 00:00:00 2001 From: Vignesh S Date: Thu, 29 Apr 2021 22:39:42 +0530 Subject: [PATCH 045/115] fix: List invoices in Payment Reconciliation Payment Invoices are not listed in the Payment Reconciliation Payment table due to a typo in the code --- .../doctype/payment_reconciliation/payment_reconciliation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 08103184d5..d1523cd7ac 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -234,7 +234,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext }); if (invoices) { - this.frm.fields_dict.payment.grid.update_docfield_property( + this.frm.fields_dict.payments.grid.update_docfield_property( 'invoice_number', 'options', "\n" + invoices.join("\n") ); From a90c81626f662890a0ee7bcbf87878c1ffa0d8d6 Mon Sep 17 00:00:00 2001 From: noahjacob Date: Fri, 30 Apr 2021 15:49:09 +0530 Subject: [PATCH 046/115] fix: updated item filters for material request --- .../doctype/material_request/material_request.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 7dfc5da50d..92c8d21387 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -433,13 +433,21 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten if (doc.material_request_type == "Customer Provided") { return{ query: "erpnext.controllers.queries.item_query", - filters:{ 'customer': me.frm.doc.customer } + filters:{ + 'customer': me.frm.doc.customer, + 'is_stock_item':1 + } } - } else if (doc.material_request_type != "Manufacture") { + } else if (doc.material_request_type == "Purchase") { return{ query: "erpnext.controllers.queries.item_query", filters: {'is_purchase_item': 1} } + } else { + return{ + query: "erpnext.controllers.queries.item_query", + filters: {'is_stock_item': 1} + } } }); }, From da0ba15cbf1d5c5b1e96d97f6b915c2ad508df1c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 30 Apr 2021 18:38:41 +0530 Subject: [PATCH 047/115] fix: Fetch total stock at company in PO (#25532) --- erpnext/stock/get_item_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 1a61f30b9a..3fc1df76bc 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -86,7 +86,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru out.update(get_bin_details(args.item_code, args.get("from_warehouse"))) elif out.get("warehouse"): - out.update(get_bin_details(args.item_code, out.warehouse)) + out.update(get_bin_details(args.item_code, out.warehouse, args.company)) # update args with out, if key or value not exists for key, value in iteritems(out): From e91b0021ac362ae46e894c1f954da743cc476e4b Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Fri, 30 Apr 2021 18:41:19 +0530 Subject: [PATCH 048/115] fix: fieldname when updating docfield property (#25516) From 00ea336b5241b2695f41c87378c2c34c89cb2143 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 1 May 2021 13:53:39 +0530 Subject: [PATCH 049/115] fix: stock ledger entry created against draft stock entry (#25540) --- erpnext/stock/stock_ledger.py | 2 +- erpnext/stock/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 985901fc44..bbfcb7ad7d 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -416,7 +416,7 @@ class update_entries_after(object): frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate) # Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount - stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no) + stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no, for_update=True) stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False) stock_entry.db_update() for d in stock_entry.items: diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 0af3d90822..034d3ebbb5 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -172,7 +172,7 @@ def get_bin(item_code, warehouse): bin_obj.flags.ignore_permissions = 1 bin_obj.insert() else: - bin_obj = frappe.get_cached_doc('Bin', bin) + bin_obj = frappe.get_doc('Bin', bin, for_update=True) bin_obj.flags.ignore_permissions = True return bin_obj From 2fb573781df1bbbe0d45dc7a2a13585c836b620e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 1 May 2021 17:56:40 +0530 Subject: [PATCH 050/115] fix: rename field has not updated value of deposit and withdrawal fields --- .../patches/v13_0/delete_old_bank_reconciliation_doctypes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py index af1f6e7ec1..77a23cfc3f 100644 --- a/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py +++ b/erpnext/patches/v13_0/delete_old_bank_reconciliation_doctypes.py @@ -22,5 +22,7 @@ def execute(): frappe.delete_doc("Page", "bank-reconciliation", force=1) + frappe.reload_doc('accounts', 'doctype', 'bank_transaction') + rename_field("Bank Transaction", "debit", "deposit") rename_field("Bank Transaction", "credit", "withdrawal") From 1efafb3ce841948116c57f2203a880d9df796c6a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 1 May 2021 21:52:43 +0530 Subject: [PATCH 051/115] fix: Do not roundoff balance amount --- erpnext/loan_management/doctype/loan/loan.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 83a813f947..f480402c35 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -70,7 +70,6 @@ class Loan(AccountsController): frappe.throw(_("Repay From Salary can be selected only for term loans")) def make_repayment_schedule(self): - if not self.repayment_start_date: frappe.throw(_("Repayment Start Date is mandatory for term loans")) @@ -78,10 +77,9 @@ class Loan(AccountsController): payment_date = self.repayment_start_date balance_amount = self.loan_amount while(balance_amount > 0): - interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100)) + interest_amount = flt(balance_amount * flt(self.rate_of_interest) / (12*100)) principal_amount = self.monthly_repayment_amount - interest_amount - balance_amount = rounded(balance_amount + interest_amount - self.monthly_repayment_amount) - + balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount) if balance_amount < 0: principal_amount += balance_amount balance_amount = 0.0 From 824f08956951de31839a79f27d35d055dc39ddee Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 1 May 2021 22:07:13 +0530 Subject: [PATCH 052/115] fix: Auto write off on loan closure --- erpnext/loan_management/doctype/loan/loan.py | 3 ++- .../loan_management/doctype/loan_repayment/loan_repayment.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index f480402c35..c570e5efda 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -193,7 +193,8 @@ def request_loan_closure(loan, posting_date=None): posting_date = getdate() amounts = calculate_amounts(loan, posting_date) - pending_amount = amounts['payable_amount'] + amounts['unaccrued_interest'] + pending_amount = amounts['pending_principal_amount'] + amounts['unaccrued_interest'] + \ + amounts['interest_amount'] + amounts['penalty_amount'] loan_type = frappe.get_value('Loan', loan, 'loan_type') write_off_limit = frappe.get_value('Loan Type', loan_type, 'write_off_amount') diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 728eadf22a..3d99b1f304 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -435,7 +435,6 @@ def get_amounts(amounts, against_loan, posting_date): @frappe.whitelist() def calculate_amounts(against_loan, posting_date, payment_type=''): - amounts = { 'penalty_amount': 0.0, 'interest_amount': 0.0, From c571141c1f4b0cac7a0e556e9aa7b47ee89ff5b4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 2 May 2021 15:31:04 +0530 Subject: [PATCH 053/115] fix: Test cases --- .../loan_management/doctype/loan/test_loan.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 6f8da3166f..fae6f860b6 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -56,25 +56,25 @@ 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(loan.total_interest_payable, 21034) - self.assertEquals(loan.total_payment, 301034) + self.assertEquals(flt(loan.total_interest_payable, 0), 21034) + self.assertEquals(flt(loan.total_payment, 0), 301034) schedule = loan.repayment_schedule self.assertEqual(len(schedule), 20) - for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227079], [19, 14941, 105, 0], [17, 14740, 312, 29785]]: - self.assertEqual(schedule[idx].principal_amount, principal_amount) - self.assertEqual(schedule[idx].interest_amount, interest_amount) - self.assertEqual(schedule[idx].balance_loan_amount, balance_loan_amount) + for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227080], [19, 14941, 105, 0], [17, 14740, 312, 29785]]: + self.assertEqual(flt(schedule[idx].principal_amount, 0), principal_amount) + self.assertEqual(flt(schedule[idx].interest_amount, 0), interest_amount) + self.assertEqual(flt(schedule[idx].balance_loan_amount, 0), balance_loan_amount) loan.repayment_method = "Repay Fixed Amount per Period" loan.monthly_repayment_amount = 14000 loan.save() self.assertEquals(len(loan.repayment_schedule), 22) - self.assertEquals(loan.total_interest_payable, 22712) - self.assertEquals(loan.total_payment, 302712) + self.assertEquals(flt(loan.total_interest_payable, 0), 22712) + self.assertEquals(flt(loan.total_payment, 0), 302712) def test_loan_with_security(self): From 35d4829383e278809bc64784b5d8a558db2ac61f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 1 May 2021 13:07:26 +0530 Subject: [PATCH 054/115] 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 5ad2d85232..9139d84fac 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 1396f19d3f..e4cdcb4116 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 055/115] 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 ed52393923..59c253c425 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 056/115] 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 e4cdcb4116..2029b0708a 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 cdc99cdd49c152b00ecfce464917390feb323890 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 3 May 2021 11:54:55 +0530 Subject: [PATCH 057/115] fix(pos): incorrect expense account set in pos invoice (#25543) --- 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 a2b95cb757..f91b432a39 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -562,7 +562,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ weight_uom: item.weight_uom, manufacturer: item.manufacturer, stock_uom: item.stock_uom, - pos_profile: me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '', + pos_profile: cint(me.frm.doc.is_pos) ? me.frm.doc.pos_profile : '', cost_center: item.cost_center, tax_category: me.frm.doc.tax_category, item_tax_template: item.item_tax_template, From 8f34ca4ac6133c38f2d789e9b8bfd67c21555894 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 3 May 2021 15:32:13 +0530 Subject: [PATCH 058/115] fix: stock reconciliation getting time out error during submission --- .../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 1396f19d3f..0ee6dc7877 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -469,7 +469,7 @@ class StockReconciliation(StockController): def submit(self): if len(self.items) > 100: msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage")) - self.queue_action('submit') + self.queue_action('submit', timeout=2000) else: self._submit() From f1bdfac7a8855be07d101d99b6ca863d1f42ed3c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 3 May 2021 18:37:00 +0530 Subject: [PATCH 059/115] fix: Employee Separation (#25503) * fix: Employee Separation - add ignore_mandatory flag for project creation - form clean-up * fix: Employee Separation test --- .../employee_separation.json | 761 ++++-------------- .../test_employee_separation.py | 2 +- erpnext/hr/utils.py | 4 +- 3 files changed, 160 insertions(+), 607 deletions(-) diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.json b/erpnext/hr/doctype/employee_separation/employee_separation.json index f44d83060a..7af209887f 100644 --- a/erpnext/hr/doctype/employee_separation/employee_separation.json +++ b/erpnext/hr/doctype/employee_separation/employee_separation.json @@ -1,626 +1,177 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "HR-EMP-SEP-.YYYY.-.#####", - "beta": 0, - "creation": "2018-05-10 02:29:16.740490", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "HR-EMP-SEP-.YYYY.-.#####", + "creation": "2018-05-10 02:29:16.740490", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "employee", + "employee_name", + "department", + "designation", + "employee_grade", + "column_break_7", + "company", + "boarding_status", + "resignation_letter_date", + "project", + "table_for_activity", + "employee_separation_template", + "activities", + "notify_users_by_email", + "section_break_14", + "exit_interview", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Employee", - "length": 0, - "no_copy": 0, - "options": "Employee", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.employee_name", - "fieldname": "employee_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Employee Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.resignation_letter_date", - "fieldname": "resignation_letter_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Resignation Letter Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "boarding_status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "\nPending\nIn Process\nCompleted", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "employee", + "fieldtype": "Link", + "label": "Employee", + "options": "Employee", + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_bulk_edit": 0, - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_in_quick_entry": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "notify_users_by_email", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notify users by email", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_7", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee_separation_template", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Employee Separation Template", - "length": 0, - "no_copy": 0, - "options": "Employee Separation Template", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.company", - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Employee Name", + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "project", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Project", - "length": 0, - "no_copy": 0, - "options": "Project", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "employee.resignation_letter_date", + "fieldname": "resignation_letter_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Resignation Letter Date", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.department", - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "boarding_status", + "fieldtype": "Select", + "label": "Status", + "options": "\nPending\nIn Process\nCompleted", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.designation", - "fieldname": "designation", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Designation", - "length": 0, - "no_copy": 0, - "options": "Designation", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "default": "0", + "fieldname": "notify_users_by_email", + "fieldtype": "Check", + "label": "Notify users by email" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.grade", - "fieldname": "employee_grade", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Employee Grade", - "length": 0, - "no_copy": 0, - "options": "Employee Grade", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "table_for_activity", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "employee_separation_template", + "fieldtype": "Link", + "label": "Employee Separation Template", + "options": "Employee Separation Template" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "activities", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Activities", - "length": 0, - "no_copy": 0, - "options": "Employee Boarding Activity", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "employee.company", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_14", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "exit_interview", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Exit Interview Summary", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Department", + "options": "Department", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Employee Separation", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fetch_from": "employee.designation", + "fieldname": "designation", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Designation", + "options": "Designation", + "read_only": 1 + }, + { + "fetch_from": "employee.grade", + "fieldname": "employee_grade", + "fieldtype": "Link", + "label": "Employee Grade", + "options": "Employee Grade", + "read_only": 1 + }, + { + "fieldname": "table_for_activity", + "fieldtype": "Section Break", + "label": "Separation Activities" + }, + { + "allow_on_submit": 1, + "fieldname": "activities", + "fieldtype": "Table", + "label": "Activities", + "options": "Employee Boarding Activity" + }, + { + "fieldname": "section_break_14", + "fieldtype": "Section Break" + }, + { + "fieldname": "exit_interview", + "fieldtype": "Text Editor", + "label": "Exit Interview Summary" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Employee Separation", + "print_hide": 1, + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-08-03 16:15:39.025898", - "modified_by": "Administrator", - "module": "HR", - "name": "Employee Separation", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "links": [], + "modified": "2021-04-28 15:58:36.020196", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Separation", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "employee_name", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "employee_name", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py index 2fa114d345..713fcf526b 100644 --- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py +++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py @@ -18,7 +18,7 @@ class TestEmployeeSeparation(unittest.TestCase): 'activity_name': 'Deactivate Employee', 'role': 'HR User' }) - separation.status = 'Pending' + separation.boarding_status = 'Pending' separation.insert() separation.submit() self.assertEqual(separation.docstatus, 1) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 190eb4f10a..2540b3db63 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -32,13 +32,15 @@ class EmployeeBoardingController(Document): project_name += self.job_applicant else: project_name += self.employee + project = frappe.get_doc({ "doctype": "Project", "project_name": project_name, "expected_start_date": self.date_of_joining if self.doctype == "Employee Onboarding" else self.resignation_letter_date, "department": self.department, "company": self.company - }).insert(ignore_permissions=True) + }).insert(ignore_permissions=True, ignore_mandatory=True) + self.db_set("project", project.name) self.db_set("boarding_status", "Pending") self.reload() 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 060/115] 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 3fc1df76bc..98d08c0a18 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 061/115] 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 98d08c0a18..3832415db6 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 062/115] 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 444b40ed79..db605f7285 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 1e554378c7b79b03fc669056fc2653d993b3466a Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 4 May 2021 12:28:24 +0530 Subject: [PATCH 063/115] fix(pos): incorrect expense account set in pos invoice (#25571) From 384f4b5b7e6d23436046d0f84888c5687cdca7f7 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 4 May 2021 12:33:49 +0530 Subject: [PATCH 064/115] 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 09479221fb..1363b53746 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 0c4a422440..094f5db89b 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 065/115] 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 087c12ed2d..01927c2d10 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 6dfede4590..bbd73e9112 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 066/115] 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 709fe57747..9384ae5542 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 067/115] 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 4c3888be4a..bab6b90d1a 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 068/115] 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 f91b432a39..d218a5ee5f 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 069/115] 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 b686dc026c..3f2d3390c0 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 070/115] 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 d218a5ee5f..10c802c6f0 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 071/115] 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 4461f29fe3..4de877353a 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 072/115] 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 10c802c6f0..0af8da77a0 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 073/115] 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 cf6ec18f3b..6635128f9e 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 074/115] 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 16eea24f84..f9b2c1d787 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 c02dd2e518..5ecc9f8140 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 0000000000..e69de29bb2 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 0000000000..616312e311 --- /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 0000000000..e20e74c78b --- /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 0000000000..c3339fd341 --- /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 bbfcb7ad7d..9729987d2d 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 075/115] 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 23f9fd8ecb..7faaf26158 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 0000000000..87d98f1a56 --- /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 699441be7e..b4e7a8889e 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 9ded8dab5b..b12e152b14 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 076/115] 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 49ca9423e8..51d86ff0bf 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 077/115] 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 9ea616f8e7..aa0c53e228 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 a9b91e02a9..4d6e4a2ba0 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 1065168a50..82528728dd 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 20fd610899..cffeb4d535 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 4d5472df4b..bc7874305c 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 5276da9720..4bb6138e5d 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 7faaf26158..82d223cada 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 0000000000..1c576db1c7 --- /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 078/115] 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 48cfa51041..2f76bc7d56 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 079/115] 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 0606823821..1be2fbf5c8 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 0e3b24cda3..927adc7086 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 5593466fc2..4d3388090d 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 a85d3e70f3..658380ea42 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 2cef509276..539a360269 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 5efa41db1f..459b7eacb4 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 d297883876..b219e7ecce 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 76a8450829..e587217181 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 373b0a58c9..c1f9433b41 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 5053c6a512..5c725d332d 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 e74d837ef5..f63d2695aa 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 3b9608b805..2dd7c6f35b 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 080/115] 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 fbfd801a11..a09a5e3430 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 979f7ca312..d1f63854c7 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 081/115] 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 4de877353a..bb74a02606 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 082/115] 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 3832415db6..d1dcdc21c8 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 083/115] 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 f0a05ed192..5d5b2e19ce 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 cbdf90681d..7634fd0caf 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 f9f0bb3cec..16efb6caee 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 084/115] 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 06331d7ff7..a6471eb53c 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 085/115] 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 1729abce9e..287b8a7484 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 cf0946beab..3577457c98 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 094f5db89b..7793af737f 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 14efa1f8fc..d20ddbde5c 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 fe261b30b4..5d04824b57 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 086/115] 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 bd7a126517..4c7faf4f65 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 2cc064f34a..92b68a98a9 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 50db0338c4..37381fa942 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 087/115] 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 e1276e7da3..781f94e203 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 f18eabc84b..cf5d98d092 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 088/115] 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 bc1ac5ea06..b31724fa48 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 3cdfcb212f..2570df7026 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 089/115] 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 8d7b238c17..4e2d9e6170 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 090/115] 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 1e6a3d1b3b..473db565fa 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 52f7fe238e..cfbd7fd0c8 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 babc6dc960..cb109f8050 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 1183e41d04..808d279170 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 091/115] 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 ed02f79c2d..8d99b48b59 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 092/115] 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 7634fd0caf..381c5e5dec 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 362f6cf88e..3840e781b4 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 093/115] 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 cb18309e3c..c5ce514cdd 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 b4a547b21b..4167ca70df 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 1c23e2a0ec..5fdde07faa 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 ef9aad562d..ffe8be1162 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 a0d76031fc..40a8f85d8d 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 42f4472f29..aaa98f2f1f 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 29487962f6..d370fbcda7 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 fb72073a07..03e96a4b3b 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 79ab8a4d7f..c9f0029ed8 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 2bb8a53c45..5f2dc480a1 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 7fb159d6b5..d079bedb42 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 03a1be8a4e..21f6369975 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 74ce30108f..3b99c57051 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 3f22ca2141..578eccf787 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 690a692ddc..b3e1dc8d87 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 0b71036c86..6e7ae87d08 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 b54c9712c8..a4a96b813e 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 aafc9642d4..e0ffa5dd41 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 fae6f860b6..fa4707ce2b 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 a8753877a6..da56710c67 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 85e008ac29..eb626f3eee 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 7108338dab..e1cca9e3ef 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 ac9a409bcb..80d1cdfc8f 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 6b1fafe5f4..cb1ee92196 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 c6a534dac3..bbe9bf5228 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 bf5c4025a0..b717491a82 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 094/115] 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 8a42d9e13c..7c73ad6c90 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 19c9073090..472746ab84 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 334bdeac9d..7875b9cd87 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 ef7d54ac96..772c8df96e 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 095/115] 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 e3e6e80616..a268c15c70 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 a6fbb66aa2..68cba2993c 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 879901f6a8..11b71c2076 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 de85bc3922..81e730126e 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 eeea6da7a4..1e71603175 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 096/115] 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 9aff1440d6..8799275fc4 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 40a8f85d8d..30a270c204 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 bb6cd8bdc2..9d1ce9bbbf 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 6338056698..052d7bdedf 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 097/115] 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 7866fdea31..2af57f4c89 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 098/115] 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 4bb6138e5d..ed3aee5c1a 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 c2b5e4f9a9..9957aad019 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 64e027dd28..077538d479 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 8367a257ea..0000000000 --- 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 29f6c3731d..e1c803a038 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 0000000000..e69de29bb2 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 0000000000..bbe68369ff --- /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 0000000000..20caa15ee4 --- /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 0000000000..9313f95516 --- /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 0000000000..38f8de7a66 --- /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 0000000000..d7175ddac4 --- /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 0000000000..e69de29bb2 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 0000000000..be0be945c4 --- /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 0000000000..2176cb10de --- /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 099/115] 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 f91b432a39..43eea1357a 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 100/115] 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 43eea1357a..a3f4de48b8 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 101/115] 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 0bb8e68b69..9bdaa8d1ee 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 8adf5bf747..8e0a1e1c18 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 9384ae5542..b8a82a9eda 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 102/115] 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 a3f4de48b8..7cfd939e95 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 103/115] 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 09db7fee2b..5c1cbaa4aa 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 104/115] 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 9ffa481c1c..df68318052 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 105/115] 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 24e67febca..d3d3ffa17f 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 106/115] 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 8e0a1e1c18..4f4f1b2240 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 107/115] 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 6dedaa8c53..a505ee09d2 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 108/115] 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 287b8a7484..26bb44f4f7 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 109/115] 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 3dbd605344..016f29a7b5 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 5e913cc2aa..7ffff02850 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 9f41b13f4b..5f110e2727 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 110/115] 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 3f83780569..63c71891e4 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 111/115] 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 b14c274515..41ca404d9b 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 a7dfc9ee28..56b046a92e 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 112/115] 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 0000000000..016dbb01f4 --- /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 113/115] 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 2bfbdfcc5d..0000000000 --- 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 114/115] 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 0000000000..2bfbdfcc5d --- /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 115/115] 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 6775398532..ad971e2976 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'''