From e07ce6efe0afde1bdbade6cbed9f53ac0dd236f0 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 17 May 2022 17:39:45 +0530 Subject: [PATCH 01/12] fix: Job Card excess transfer behaviour - Block excess transfer of items if not allowed in settings - Behaviour made consistent with js behaviour (button disappears if not pending and not allowed in settings) - Test for same case --- .../doctype/job_card/job_card.py | 51 +++++++++++++++---- .../doctype/job_card/test_job_card.py | 33 +++++++++++- .../stock/doctype/stock_entry/stock_entry.py | 2 +- 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index a98fc94868..776f2d0b41 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -42,6 +42,10 @@ class JobCardCancelError(frappe.ValidationError): pass +class JobCardOverTransferError(frappe.ValidationError): + pass + + class JobCard(Document): def onload(self): excess_transfer = frappe.db.get_single_value( @@ -522,23 +526,50 @@ class JobCard(Document): }, ) - def set_transferred_qty_in_job_card(self, ste_doc): + def set_transferred_qty_in_job_card_item(self, ste_doc): + from frappe.query_builder.functions import Sum + + def _validate_over_transfer(row, transferred_qty): + "Block over transfer of items if not allowed in settings." + allow_excess = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer") + required_qty = frappe.db.get_value("Job Card Item", row.job_card_item, "required_qty") + is_excess = flt(transferred_qty) > flt(required_qty) + + if is_excess and not allow_excess: + frappe.throw( + _( + "Row #{0}: Cannot transfer more than Required Qty {1} for Item {2} against Job Card {3}" + ).format( + row.idx, frappe.bold(required_qty), frappe.bold(row.item_code), ste_doc.job_card + ), + title=_("Excess Transfer"), + exc=JobCardOverTransferError, + ) + for row in ste_doc.items: if not row.job_card_item: continue - qty = frappe.db.sql( - """ SELECT SUM(qty) from `tabStock Entry Detail` sed, `tabStock Entry` se - WHERE sed.job_card_item = %s and se.docstatus = 1 and sed.parent = se.name and - se.purpose = 'Material Transfer for Manufacture' - """, - (row.job_card_item), - )[0][0] + sed = frappe.qb.DocType("Stock Entry Detail") + se = frappe.qb.DocType("Stock Entry") + transferred_qty = ( + frappe.qb.from_(sed) + .join(se) + .on(sed.parent == se.name) + .select(Sum(sed.qty)) + .where( + (sed.job_card_item == row.job_card_item) + & (se.docstatus == 1) + & (se.purpose == "Material Transfer for Manufacture") + ) + ).run()[0][0] - frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(qty)) + _validate_over_transfer(row, transferred_qty) + + frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty)) def set_transferred_qty(self, update_status=False): - "Set total FG Qty for which RM was transferred." + "Set total FG Qty in Job Card for which RM was transferred." if not self.items: self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0 diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 4647ddf05f..d21e542f90 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -2,10 +2,14 @@ # See license.txt import frappe -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import random_string -from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError +from erpnext.manufacturing.doctype.job_card.job_card import ( + JobCardOverTransferError, + OperationMismatchError, + OverlapError, +) from erpnext.manufacturing.doctype.job_card.job_card import ( make_stock_entry as make_stock_entry_from_jc, ) @@ -25,6 +29,7 @@ class TestJobCard(FrappeTestCase): "test_job_card_multiple_materials_transfer", "test_job_card_excess_material_transfer", "test_job_card_partial_material_transfer", + "test_job_card_excess_material_transfer_block", ) if self._testMethodName in tests_that_skip_setup: @@ -165,6 +170,7 @@ class TestJobCard(FrappeTestCase): # transfer was made for 2 fg qty in first transfer Stock Entry self.assertEqual(transfer_entry_2.fg_completed_qty, 0) + @change_settings("Manufacturing Settings", {"job_card_excess_transfer": 1}) def test_job_card_excess_material_transfer(self): "Test transferring more than required RM against Job Card." make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100) @@ -208,6 +214,29 @@ class TestJobCard(FrappeTestCase): # JC is Completed with excess transfer self.assertEqual(job_card.status, "Completed") + @change_settings("Manufacturing Settings", {"job_card_excess_transfer": 0}) + def test_job_card_excess_material_transfer_block(self): + make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100) + make_stock_entry( + item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100 + ) + + job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name}) + + # fully transfer both RMs + transfer_entry_1 = make_stock_entry_from_jc(job_card_name) + transfer_entry_1.insert() + transfer_entry_1.submit() + + # transfer extra qty of both RM due to previously damaged RM + transfer_entry_2 = make_stock_entry_from_jc(job_card_name) + # deliberately change 'For Quantity' + transfer_entry_2.fg_completed_qty = 1 + transfer_entry_2.items[0].qty = 5 + transfer_entry_2.items[1].qty = 3 + transfer_entry_2.insert() + self.assertRaises(JobCardOverTransferError, transfer_entry_2.submit) + def test_job_card_partial_material_transfer(self): "Test partial material transfer against Job Card" diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 890ac476a7..26e866034e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1141,7 +1141,7 @@ class StockEntry(StockController): if self.job_card: job_doc = frappe.get_doc("Job Card", self.job_card) job_doc.set_transferred_qty(update_status=True) - job_doc.set_transferred_qty_in_job_card(self) + job_doc.set_transferred_qty_in_job_card_item(self) if self.work_order: pro_doc = frappe.get_doc("Work Order", self.work_order) From 3128f9603ed74d08855c367a4b75bdc76f56399b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 20 May 2022 08:31:37 +0530 Subject: [PATCH 02/12] fix: Loan repayment entries for payroll payable account --- .../doctype/loan_repayment/loan_repayment.py | 2 -- erpnext/payroll/doctype/payroll_entry/payroll_entry.py | 10 ++++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 2535180092..c819de9b55 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -447,8 +447,6 @@ class LoanRepayment(AccountsController): "remarks": remarks, "cost_center": self.cost_center, "posting_date": getdate(self.posting_date), - "party_type": self.applicant_type if self.repay_from_salary else "", - "party": self.applicant if self.repay_from_salary else "", } ) ) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 54d56f9612..473fb0d7c7 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -16,6 +16,7 @@ from frappe.utils import ( comma_and, date_diff, flt, + get_link_to_form, getdate, ) @@ -45,6 +46,7 @@ class PayrollEntry(Document): def before_submit(self): self.validate_employee_details() + self.validate_payroll_payable_account() if self.validate_attendance: if self.validate_employee_attendance(): frappe.throw(_("Cannot Submit, Employees left to mark attendance")) @@ -66,6 +68,14 @@ class PayrollEntry(Document): if len(emp_with_sal_slip): frappe.throw(_("Salary Slip already exists for {0}").format(comma_and(emp_with_sal_slip))) + def validate_payroll_payable_account(self): + if frappe.db.get_value("Account", self.payroll_payable_account, "account_type"): + frappe.throw( + _( + "Account type cannot be set for payroll payable account {0}, please remove and try again" + ).format(frappe.bold(get_link_to_form("Account", self.payroll_payable_account))) + ) + def on_cancel(self): frappe.delete_doc( "Salary Slip", From c41f9f046fb987d942298db99b5b1cf64f8b7684 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 20 May 2022 15:33:03 +0530 Subject: [PATCH 03/12] fix(India): Async issue in company address trigger --- erpnext/regional/india/taxes.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index 5f6dcdeb92..c0e6b91a1c 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -1,6 +1,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => { frappe.ui.form.on(doctype, { company_address: function(frm) { + console.log("#########"); frm.trigger('get_tax_template'); }, shipping_address: function(frm) { @@ -22,6 +23,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => { 'shipping_address': frm.doc.shipping_address || '', 'shipping_address_name': frm.doc.shipping_address_name || '', 'customer_address': frm.doc.customer_address || '', + 'company_address': frm.doc.company_address, 'supplier_address': frm.doc.supplier_address, 'customer': frm.doc.customer, 'supplier': frm.doc.supplier, @@ -39,6 +41,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => { }, debounce: 2000, callback: function(r) { + console.log(r.message); if(r.message) { frm.set_value('taxes_and_charges', r.message.taxes_and_charges); frm.set_value('taxes', r.message.taxes); From 8fd0b3b9f50dfc7794b68fabb136d28c2913e196 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 20 May 2022 15:34:03 +0530 Subject: [PATCH 04/12] chore: Linting issues --- erpnext/regional/india/taxes.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index c0e6b91a1c..88973e36b6 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -1,7 +1,6 @@ erpnext.setup_auto_gst_taxation = (doctype) => { frappe.ui.form.on(doctype, { company_address: function(frm) { - console.log("#########"); frm.trigger('get_tax_template'); }, shipping_address: function(frm) { @@ -41,7 +40,6 @@ erpnext.setup_auto_gst_taxation = (doctype) => { }, debounce: 2000, callback: function(r) { - console.log(r.message); if(r.message) { frm.set_value('taxes_and_charges', r.message.taxes_and_charges); frm.set_value('taxes', r.message.taxes); From a29b92febc4397cebb251d4d3f34210e4fb85c21 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 23 May 2022 10:08:20 +0530 Subject: [PATCH 05/12] fix: Use directly and style it as button instead of using button Since few email servers (like outlook) strips out link in the button making them unclickable. --- .../emails/request_for_quotation.html | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/erpnext/templates/emails/request_for_quotation.html b/erpnext/templates/emails/request_for_quotation.html index 3283987fab..5b073e604f 100644 --- a/erpnext/templates/emails/request_for_quotation.html +++ b/erpnext/templates/emails/request_for_quotation.html @@ -1,24 +1,29 @@

{{_("Request for Quotation")}}

{{ supplier_salutation if supplier_salutation else ''}} {{ supplier_name }},

{{ message }}

-

{{_("The Request for Quotation can be accessed by clicking on the following button")}}:

-

- -


- -

{{_("Regards")}},
-{{ user_fullname }}


- +
+ + {{ _("Submit your Quotation") }} + +
+
{% if update_password_link %} - +

{{_("Please click on the following button to set your new password")}}:

-

- -

- + + {{_("Set Password") }} + +
+
{% endif %} +

+ {{_("Regards")}},
+ {{ user_fullname }} +

From 9f6e10663b77489ba1f98ede96e30c23682c111a Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 23 May 2022 11:05:55 +0530 Subject: [PATCH 06/12] chore: Run `_validate_over_transfer` only if excess transfer is blocked in settings --- erpnext/manufacturing/doctype/job_card/job_card.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index d16281a5ba..0a9fd8a099 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -531,11 +531,9 @@ class JobCard(Document): def _validate_over_transfer(row, transferred_qty): "Block over transfer of items if not allowed in settings." - allow_excess = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer") required_qty = frappe.db.get_value("Job Card Item", row.job_card_item, "required_qty") is_excess = flt(transferred_qty) > flt(required_qty) - - if is_excess and not allow_excess: + if is_excess: frappe.throw( _( "Row #{0}: Cannot transfer more than Required Qty {1} for Item {2} against Job Card {3}" @@ -564,7 +562,9 @@ class JobCard(Document): ) ).run()[0][0] - _validate_over_transfer(row, transferred_qty) + allow_excess = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer") + if not allow_excess: + _validate_over_transfer(row, transferred_qty) frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty)) From a36174afdf09e70e636aca2125a6cb092fb24735 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 23 May 2022 15:12:11 +0530 Subject: [PATCH 07/12] test: search test failing because of stale data (#31098) --- erpnext/tests/test_search.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/tests/test_search.py b/erpnext/tests/test_search.py index ffe9a5ae54..3685828667 100644 --- a/erpnext/tests/test_search.py +++ b/erpnext/tests/test_search.py @@ -8,6 +8,7 @@ class TestSearch(unittest.TestCase): # Search for the word "cond", part of the word "conduire" (Lead) in french. def test_contact_search_in_foreign_language(self): try: + frappe.local.lang_full_dict = None # reset cached translations frappe.local.lang = "fr" output = filter_dynamic_link_doctypes( "DocType", "cond", "name", 0, 20, {"fieldtype": "HTML", "fieldname": "contact_html"} From ecb39d81e021f786f5a4ded3c344cf4f5c71bc26 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 23 May 2022 20:06:24 +0530 Subject: [PATCH 08/12] chore: error logging for auto material requests (#31103) --- erpnext/stock/reorder_item.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py index 4763b472c2..f19c75f54e 100644 --- a/erpnext/stock/reorder_item.py +++ b/erpnext/stock/reorder_item.py @@ -252,11 +252,14 @@ def notify_errors(exceptions_list): ) for exception in exceptions_list: - exception = json.loads(exception) - error_message = """
{0}

""".format( - _(exception.get("message")) - ) - content += error_message + try: + exception = json.loads(exception) + error_message = """
{0}

""".format( + _(exception.get("message")) + ) + content += error_message + except Exception: + pass content += _("Regards,") + "
" + _("Administrator") From 1ecb8f4a67c5a05291944b0275ec1a45a42a84e5 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 24 May 2022 14:32:56 +0530 Subject: [PATCH 09/12] chore: disable feed for material request --- erpnext/stock/doctype/material_request/material_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index c998629e76..2614a7f1f4 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -23,7 +23,7 @@ form_grid_templates = {"items": "templates/form_grid/material_request_grid.html" class MaterialRequest(BuyingController): def get_feed(self): - return _("{0}: {1}").format(self.status, self.material_request_type) + return def check_if_already_pulled(self): pass From 55276f11f8d138c3a0748fe080ac68ddd1fd0f22 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 25 May 2022 00:05:22 -0400 Subject: [PATCH 10/12] fix: don't fetch item_code if already exists. (#31113) fix: check if item_code exists before fetching --- 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 324ff4f409..c6241f8df6 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -199,7 +199,7 @@ def process_args(args): if not args.get("price_list"): args.price_list = args.get("selling_price_list") or args.get("buying_price_list") - if args.barcode: + if not args.item_code and args.barcode: args.item_code = get_item_code(barcode=args.barcode) elif not args.item_code and args.serial_no: args.item_code = get_item_code(serial_no=args.serial_no) From 273b21c0cba764e7237bd8f43885dbc9b4c83304 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 25 May 2022 11:34:25 +0530 Subject: [PATCH 11/12] chore: move patch for updating Employee Advance status to v13 (#31118) --- erpnext/patches.txt | 2 +- .../patches/{v14_0 => v13_0}/update_employee_advance_status.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename erpnext/patches/{v14_0 => v13_0}/update_employee_advance_status.py (100%) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4d9a7e06bf..8c0ebe7a90 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -359,7 +359,7 @@ erpnext.patches.v13_0.set_work_order_qty_in_so_from_mr erpnext.patches.v13_0.update_accounts_in_loan_docs erpnext.patches.v14_0.update_batch_valuation_flag erpnext.patches.v14_0.delete_non_profit_doctypes -erpnext.patches.v14_0.update_employee_advance_status +erpnext.patches.v13_0.update_employee_advance_status erpnext.patches.v13_0.add_cost_center_in_loans erpnext.patches.v13_0.set_return_against_in_pos_invoice_references erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022 diff --git a/erpnext/patches/v14_0/update_employee_advance_status.py b/erpnext/patches/v13_0/update_employee_advance_status.py similarity index 100% rename from erpnext/patches/v14_0/update_employee_advance_status.py rename to erpnext/patches/v13_0/update_employee_advance_status.py From 268f413f56396a10f11559f8760b48f83fb6a7b4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 25 May 2022 11:40:49 +0530 Subject: [PATCH 12/12] fix(pos): paid amount calculation for multicurrency invoice (#31112) --- .../public/js/controllers/taxes_and_totals.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 3dd11f69a7..16b0b4a866 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -789,11 +789,23 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { if(this.frm.doc.is_pos && (update_paid_amount===undefined || update_paid_amount)) { $.each(this.frm.doc['payments'] || [], function(index, data) { if(data.default && payment_status && total_amount_to_pay > 0) { - let base_amount = flt(total_amount_to_pay, precision("base_amount", data)); + let base_amount, amount; + + if (me.frm.doc.party_account_currency == me.frm.doc.currency) { + // if customer/supplier currency is same as company currency + // total_amount_to_pay is already in customer/supplier currency + // so base_amount has to be calculated using total_amount_to_pay + base_amount = flt(total_amount_to_pay * me.frm.doc.conversion_rate, precision("base_amount", data)); + amount = flt(total_amount_to_pay, precision("amount", data)); + } else { + base_amount = flt(total_amount_to_pay, precision("base_amount", data)); + amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount", data)); + } + frappe.model.set_value(data.doctype, data.name, "base_amount", base_amount); - let amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount", data)); frappe.model.set_value(data.doctype, data.name, "amount", amount); payment_status = false; + } else if(me.frm.doc.paid_amount) { frappe.model.set_value(data.doctype, data.name, "amount", 0.0); }