From a7d66fa3526567f5846469d25b445ee4383fc7d5 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 29 Jul 2022 12:41:40 +0530 Subject: [PATCH 01/14] fix: set `billing_address` for purchases in `get_party_details` (cherry picked from commit a3625b3817cdadb2db0ff718ca4740e8753d6611) --- erpnext/accounts/party.py | 29 ++++++++++++++++---- erpnext/controllers/buying_controller.py | 1 + erpnext/public/js/controllers/transaction.js | 19 ------------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index f07fc64737c..54d52ce4f86 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -208,7 +208,7 @@ def set_address_details( ) if company_address: - party_details.update({"company_address": company_address}) + party_details.company_address = company_address else: party_details.update(get_company_address(company)) @@ -220,12 +220,31 @@ def set_address_details( get_regional_address_details(party_details, doctype, company) elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]: - if party_details.company_address: - party_details["shipping_address"] = shipping_address or party_details["company_address"] - party_details.shipping_address_display = get_address_display(party_details["shipping_address"]) + if shipping_address: party_details.update( - get_fetch_values(doctype, "shipping_address", party_details.shipping_address) + shipping_address=shipping_address, + shipping_address_display=get_address_display(shipping_address), + **get_fetch_values(doctype, "shipping_address", shipping_address) ) + + if party_details.company_address: + # billing address + party_details.update( + billing_address=party_details.company_address, + billing_address_display=( + party_details.company_address_display or get_address_display(party_details.company_address) + ), + **get_fetch_values(doctype, "billing_address", party_details.company_address) + ) + + # shipping address - if not already set + if not party_details.shipping_address: + party_details.update( + shipping_address=party_details.billing_address, + shipping_address_display=party_details.billing_address_display, + **get_fetch_values(doctype, "shipping_address", party_details.billing_address) + ) + get_regional_address_details(party_details, doctype, company) return party_details.get(billing_address_field), party_details.shipping_address_name diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 9c31ebfbb33..d0f81c2b270 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -86,6 +86,7 @@ class BuyingController(StockController, Subcontracting): company=self.company, party_address=self.get("supplier_address"), shipping_address=self.get("shipping_address"), + company_address=self.get("billing_address"), fetch_payment_terms_template=not self.get("ignore_default_payment_terms_template"), ignore_permissions=self.flags.ignore_permissions, ) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 61f650b64b6..d7892be082a 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1,7 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -frappe.provide('erpnext.accounts.dimensions'); erpnext.TransactionController = erpnext.taxes_and_totals.extend({ setup: function() { @@ -910,24 +909,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ set_party_account(set_pricing); }); - // Get default company billing address in Purchase Invoice, Order and Receipt - if (this.frm.doc.company && frappe.meta.get_docfield(this.frm.doctype, "billing_address")) { - frappe.call({ - method: "erpnext.setup.doctype.company.company.get_default_company_address", - args: {name: this.frm.doc.company, existing_address: this.frm.doc.billing_address || ""}, - debounce: 2000, - callback: function(r) { - if (r.message) { - me.frm.set_value("billing_address", r.message); - } else { - if (frappe.meta.get_docfield(me.frm.doctype, 'company_address')) { - me.frm.set_value("company_address", ""); - } - } - } - }); - } - } else { set_party_account(set_pricing); } From d1de4b027cd80637b78ab3b44a57fe73aa1a7f33 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 29 Jul 2022 13:20:34 +0530 Subject: [PATCH 02/14] fix: set `company_address` for purchases in `party.js` (cherry picked from commit d05082987f47d9f3582447524b314862951507e0) --- erpnext/public/js/utils/party.js | 64 ++++++++++++++------------------ 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index a492b32a9f6..58594b0a13d 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -3,25 +3,14 @@ frappe.provide("erpnext.utils"); +const SALES_DOCTYPES = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']; +const PURCHASE_DOCTYPES = ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']; + erpnext.utils.get_party_details = function(frm, method, args, callback) { if (!method) { method = "erpnext.accounts.party.get_party_details"; } - if (args) { - if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) { - if (frm.doc.company_address && (!args.company_address)) { - args.company_address = frm.doc.company_address; - } - } - - if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) { - if (frm.doc.shipping_address && (!args.shipping_address)) { - args.shipping_address = frm.doc.shipping_address; - } - } - } - if (!args) { if ((frm.doctype != "Purchase Order" && frm.doc.customer) || (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) { @@ -45,41 +34,44 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) { }; } - if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) { - if (!args) { + if (!args) { + if (in_list(SALES_DOCTYPES, frm.doc.doctype)) { args = { party: frm.doc.customer || frm.doc.party_name, party_type: 'Customer' - } - } - if (frm.doc.company_address && (!args.company_address)) { - args.company_address = frm.doc.company_address; + }; } - if (frm.doc.shipping_address_name &&(!args.shipping_address_name)) { - args.shipping_address_name = frm.doc.shipping_address_name; - } - } - - if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) { - if (!args) { + if (in_list(PURCHASE_DOCTYPES, frm.doc.doctype)) { args = { party: frm.doc.supplier, party_type: 'Supplier' - } - } - - if (frm.doc.shipping_address && (!args.shipping_address)) { - args.shipping_address = frm.doc.shipping_address; + }; } } - if (args) { - args.posting_date = frm.doc.posting_date || frm.doc.transaction_date; - args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template); + if (!args || !args.party) return; + + args.posting_date = frm.doc.posting_date || frm.doc.transaction_date; + args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template); + } + + if (in_list(SALES_DOCTYPES, frm.doc.doctype)) { + if (!args.company_address && frm.doc.company_address) { + args.company_address = frm.doc.company_address; } } - if (!args || !args.party) return; + + if (in_list(PURCHASE_DOCTYPES, frm.doc.doctype)) { + if (!args.company_address && frm.doc.billing_address) { + args.company_address = frm.doc.billing_address; + } + + if (!args.shipping_address && frm.doc.shipping_address) { + args.shipping_address = frm.doc.shipping_address; + } + } + if (frappe.meta.get_docfield(frm.doc.doctype, "taxes")) { if (!erpnext.utils.validate_mandatory(frm, "Posting / Transaction Date", From 15654aae8b3c5bb2df53401e25c94bd58a118b63 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 8 Aug 2022 18:00:02 +0530 Subject: [PATCH 03/14] fix: use old style for `_dict.update` --- erpnext/accounts/party.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 54d52ce4f86..c2ba0b43c6e 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -222,27 +222,33 @@ def set_address_details( elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]: if shipping_address: party_details.update( - shipping_address=shipping_address, - shipping_address_display=get_address_display(shipping_address), - **get_fetch_values(doctype, "shipping_address", shipping_address) + { + "shipping_address": shipping_address, + "shipping_address_display": get_address_display(shipping_address), + **get_fetch_values(doctype, "shipping_address", shipping_address), + } ) if party_details.company_address: # billing address party_details.update( - billing_address=party_details.company_address, - billing_address_display=( - party_details.company_address_display or get_address_display(party_details.company_address) - ), - **get_fetch_values(doctype, "billing_address", party_details.company_address) + { + "billing_address": party_details.company_address, + "billing_address_display": ( + party_details.company_address_display or get_address_display(party_details.company_address) + ), + **get_fetch_values(doctype, "billing_address", party_details.company_address), + } ) # shipping address - if not already set if not party_details.shipping_address: party_details.update( - shipping_address=party_details.billing_address, - shipping_address_display=party_details.billing_address_display, - **get_fetch_values(doctype, "shipping_address", party_details.billing_address) + { + "shipping_address": party_details.billing_address, + "shipping_address_display": party_details.billing_address_display, + **get_fetch_values(doctype, "shipping_address", party_details.billing_address), + } ) get_regional_address_details(party_details, doctype, company) From ba8dc8925fe07526e8d65f52807b9527415767db Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 11:21:47 +0530 Subject: [PATCH 04/14] fix: check item_code in all rows of po_items (backport #31741) (#31843) fix: check item_code in all rows of po_items (#31741) fix: check item-code in each row of po-items (cherry picked from commit 0047e18a9b121e45798f7b7a6345feef85ee7c01) Co-authored-by: Sagar Sharma --- .../manufacturing/doctype/production_plan/production_plan.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index b7e3b4280d6..1ca61d76663 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -639,6 +639,9 @@ class ProductionPlan(Document): def get_sub_assembly_items(self, manufacturing_type=None): self.sub_assembly_items = [] for row in self.po_items: + if not row.item_code: + frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx)) + bom_data = [] get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty) self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type) From 2a615af00a754162c202d10f77e1c1d36a6d48cd Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 11:23:24 +0530 Subject: [PATCH 05/14] fix: contact search in request for quotation (backport #31828) (#31841) * fix: contact search in request for quotation (#31828) (cherry picked from commit e5e88bb9f1728ce85aecda7a305dd70378c5099e) # Conflicts: # erpnext/buying/doctype/request_for_quotation/request_for_quotation.py * chore: conflicts Co-authored-by: Sagar Sharma --- .../request_for_quotation/request_for_quotation.js | 9 ++++++--- .../request_for_quotation/request_for_quotation.py | 12 ------------ 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 17b45b29707..3cc25580013 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -15,9 +15,12 @@ frappe.ui.form.on("Request for Quotation",{ frm.fields_dict["suppliers"].grid.get_field("contact").get_query = function(doc, cdt, cdn) { let d = locals[cdt][cdn]; return { - query: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_supplier_contacts", - filters: {'supplier': d.supplier} - } + query: "frappe.contacts.doctype.contact.contact.contact_query", + filters: { + link_doctype: "Supplier", + link_name: d.supplier || "" + } + }; } }, diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 24555961666..45a2ad43e7c 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -287,18 +287,6 @@ def get_list_context(context=None): return list_context -@frappe.whitelist() -@frappe.validate_and_sanitize_search_inputs -def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters): - return frappe.db.sql( - """select `tabContact`.name from `tabContact`, `tabDynamic Link` - where `tabDynamic Link`.link_doctype = 'Supplier' and (`tabDynamic Link`.link_name=%(name)s - and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent - limit %(start)s, %(page_len)s""", - {"start": start, "page_len": page_len, "txt": "%%%s%%" % txt, "name": filters.get("supplier")}, - ) - - @frappe.whitelist() def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None): def postprocess(source, target_doc): From 7ddb332f47b3777bd789e104249d086c1e560caa Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 13:49:51 +0530 Subject: [PATCH 06/14] fix: incorrect produced-qty in production-plan-item (backport #31706) (#31862) fix: incorrect produced-qty in production-plan-item (#31706) (cherry picked from commit 538cd6fdcf2cc627515b7effd0e98f3538a7a11a) Co-authored-by: Sagar Sharma --- .../production_plan/production_plan.py | 1 - .../production_plan/test_production_plan.py | 60 ++++++++++++++++--- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 1ca61d76663..b6567ec15d8 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -482,7 +482,6 @@ class ProductionPlan(Document): "bom_no", "stock_uom", "bom_level", - "production_plan_item", "schedule_date", ]: if row.get(field): diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index fa40b3d56ce..cbae275031e 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -11,8 +11,9 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import ( get_warehouse_list, ) from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError +from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry as make_se_from_wo from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.item.test_item import create_item, make_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( create_stock_reconciliation, @@ -536,9 +537,6 @@ class TestProductionPlan(FrappeTestCase): Test Prod Plan impact via: SO -> Prod Plan -> WO -> SE -> SE (cancel) """ from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record - from erpnext.manufacturing.doctype.work_order.work_order import ( - make_stock_entry as make_se_from_wo, - ) make_stock_entry( item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100 @@ -581,9 +579,6 @@ class TestProductionPlan(FrappeTestCase): def test_production_plan_pending_qty_independent_items(self): "Test Prod Plan impact if items are added independently (no from SO or MR)." from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record - from erpnext.manufacturing.doctype.work_order.work_order import ( - make_stock_entry as make_se_from_wo, - ) make_stock_entry( item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100 @@ -679,6 +674,57 @@ class TestProductionPlan(FrappeTestCase): for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items): self.assertEqual(po_item.name, subassy_item.production_plan_item) + def test_produced_qty_for_multi_level_bom_item(self): + # Create Items and BOMs + rm_item = make_item(properties={"is_stock_item": 1}).name + sub_assembly_item = make_item(properties={"is_stock_item": 1}).name + fg_item = make_item(properties={"is_stock_item": 1}).name + + make_stock_entry( + item_code=rm_item, + qty=60, + to_warehouse="Work In Progress - _TC", + rate=99, + purpose="Material Receipt", + ) + + make_bom(item=sub_assembly_item, raw_materials=[rm_item], rm_qty=3) + make_bom(item=fg_item, raw_materials=[sub_assembly_item], rm_qty=4) + + # Step - 1: Create Production Plan + pln = create_production_plan(item_code=fg_item, planned_qty=5, skip_getting_mr_items=1) + pln.get_sub_assembly_items() + + # Step - 2: Create Work Orders + pln.make_work_order() + work_orders = frappe.get_all("Work Order", filters={"production_plan": pln.name}, pluck="name") + sa_wo = fg_wo = None + for work_order in work_orders: + wo_doc = frappe.get_doc("Work Order", work_order) + if wo_doc.production_plan_item: + wo_doc.update( + {"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Finished Goods - _TC"} + ) + fg_wo = wo_doc.name + else: + wo_doc.update( + {"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Work In Progress - _TC"} + ) + sa_wo = wo_doc.name + wo_doc.submit() + + # Step - 3: Complete Work Orders + se = frappe.get_doc(make_se_from_wo(sa_wo, "Manufacture")) + se.submit() + + se = frappe.get_doc(make_se_from_wo(fg_wo, "Manufacture")) + se.submit() + + # Step - 4: Check Production Plan Item Produced Qty + pln.load_from_db() + self.assertEqual(pln.status, "Completed") + self.assertEqual(pln.po_items[0].produced_qty, 5) + def create_production_plan(**args): """ From 3274e7681d9da02fba1fe92e4825ed1ef260a260 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 14:39:15 +0530 Subject: [PATCH 07/14] fix: incorrect rate in BOM exploded items (backport #31513) (#31865) * fix: incorrect rate in BOM exploded items (#31513) (cherry picked from commit 313625c3497d7db2d86a2835c458ac1028c27fe8) # Conflicts: # erpnext/manufacturing/doctype/bom_item/bom_item.json * chore: conflicts Co-authored-by: Sagar Sharma --- erpnext/manufacturing/doctype/bom/bom.py | 2 +- erpnext/manufacturing/doctype/bom/test_bom.py | 28 +++++++++++++++++++ .../doctype/bom_item/bom_item.json | 3 +- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index dd6c802aab4..4cf29866326 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -189,8 +189,8 @@ class BOM(WebsiteGenerator): self.validate_transfer_against() self.set_routing_operations() self.validate_operations() - self.update_exploded_items(save=False) self.calculate_cost() + self.update_exploded_items(save=False) self.update_stock_qty() self.validate_scrap_items() self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index edb97937f02..3c91c6d4d9b 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -555,6 +555,34 @@ class TestBOM(FrappeTestCase): bom.reload() self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name) + def test_exploded_items_rate(self): + rm_item = make_item( + properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89} + ).name + fg_item = make_item(properties={"is_stock_item": 1}).name + + from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + + bom = make_bom(item=fg_item, raw_materials=[rm_item], do_not_save=True) + + bom.rm_cost_as_per = "Last Purchase Rate" + bom.save() + self.assertEqual(bom.items[0].base_rate, 89) + self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate) + + bom.rm_cost_as_per = "Price List" + bom.save() + self.assertEqual(bom.items[0].base_rate, 0.0) + self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate) + + bom.rm_cost_as_per = "Valuation Rate" + bom.save() + self.assertEqual(bom.items[0].base_rate, 99) + self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate) + + bom.submit() + self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate) + 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_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json index 3406215cbbb..33ca2d93d8f 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.json +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json @@ -185,6 +185,7 @@ "in_list_view": 1, "label": "Rate", "options": "currency", + "read_only": 1, "reqd": 1 }, { @@ -298,7 +299,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-01-24 16:57:57.020232", + "modified": "2022-07-28 10:20:51.559010", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Item", From 7ee75ff762fd15b4c02a4ca58edece81f00bca5b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 17 Aug 2022 13:48:56 +0530 Subject: [PATCH 08/14] fix: not able to issue expired batches (cherry picked from commit 795c94384a5dd8919b0dc989e19d8b9ee81071d4) # Conflicts: # erpnext/stock/doctype/stock_entry/test_stock_entry.py --- erpnext/controllers/stock_controller.py | 14 ++++++++- erpnext/stock/doctype/batch/test_batch.py | 8 ++++- .../doctype/stock_entry/test_stock_entry.py | 29 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index e90a4f62411..2eea0bde8c6 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -33,6 +33,10 @@ class QualityInspectionNotSubmittedError(frappe.ValidationError): pass +class BatchExpiredError(frappe.ValidationError): + pass + + class StockController(AccountsController): def validate(self): super(StockController, self).validate() @@ -74,6 +78,10 @@ class StockController(AccountsController): def validate_serialized_batch(self): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + is_material_issue = False + if self.doctype == "Stock Entry" and self.purpose == "Material Issue": + is_material_issue = True + for d in self.get("items"): if hasattr(d, "serial_no") and hasattr(d, "batch_no") and d.serial_no and d.batch_no: serial_nos = frappe.get_all( @@ -90,6 +98,9 @@ class StockController(AccountsController): ) ) + if is_material_issue: + continue + if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2: expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date") @@ -97,7 +108,8 @@ class StockController(AccountsController): frappe.throw( _("Row #{0}: The batch {1} has already expired.").format( d.idx, get_link_to_form("Batch", d.get("batch_no")) - ) + ), + BatchExpiredError, ) def clean_serial_nos(self): diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index e67504a5f5a..998c8e35c79 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -366,8 +366,14 @@ def make_new_batch(**args): "doctype": "Batch", "batch_id": args.batch_id, "item": args.item_code, + "expiry_date": args.expiry_date, } - ).insert() + ) + + if args.expiry_date: + batch.expiry_date = args.expiry_date + + batch.insert() except frappe.DuplicateEntryError: batch = frappe.get_doc("Batch", args.batch_id) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 6156a6458b6..be2409ddf61 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -5,8 +5,12 @@ import frappe from frappe.permissions import add_user_permission, remove_user_permission from frappe.tests.utils import FrappeTestCase, change_settings +<<<<<<< HEAD from frappe.utils import add_days, flt, nowdate, nowtime from six import iteritems +======= +from frappe.utils import add_days, flt, nowdate, nowtime, today +>>>>>>> 795c94384a (fix: not able to issue expired batches) from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import ( @@ -1546,6 +1550,31 @@ class TestStockEntry(FrappeTestCase): self.assertEqual(obj.items[index].basic_rate, 200) self.assertEqual(obj.items[index].basic_amount, 2000) + def test_batch_expiry(self): + from erpnext.controllers.stock_controller import BatchExpiredError + from erpnext.stock.doctype.batch.test_batch import make_new_batch + + item_code = "Test Batch Expiry Test Item - 001" + item_doc = create_item(item_code=item_code, is_stock_item=1, valuation_rate=10) + + item_doc.has_batch_no = 1 + item_doc.save() + + batch = make_new_batch( + batch_id=frappe.generate_hash("", 5), item_code=item_doc.name, expiry_date=add_days(today(), -1) + ) + + se = make_stock_entry( + item_code=item_code, + purpose="Material Receipt", + qty=4, + to_warehouse="_Test Warehouse - _TC", + batch_no=batch.name, + do_not_save=True, + ) + + self.assertRaises(BatchExpiredError, se.save) + def make_serialized_item(**args): args = frappe._dict(args) From 41b8563b7ad201a2d8bbf43a6153b64d48f5db43 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 17 Aug 2022 14:47:47 +0530 Subject: [PATCH 09/14] fix: conflicts --- erpnext/stock/doctype/stock_entry/test_stock_entry.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index be2409ddf61..64ea0435e16 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -5,12 +5,8 @@ import frappe from frappe.permissions import add_user_permission, remove_user_permission from frappe.tests.utils import FrappeTestCase, change_settings -<<<<<<< HEAD -from frappe.utils import add_days, flt, nowdate, nowtime -from six import iteritems -======= from frappe.utils import add_days, flt, nowdate, nowtime, today ->>>>>>> 795c94384a (fix: not able to issue expired batches) +from six import iteritems from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import ( From b0917aaf8af6ff67bd85a58090b9b74f973c027a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 17:25:31 +0530 Subject: [PATCH 10/14] fix: Transit filter for Default Target Warehouse in SE (backport #31839) (#31874) fix: Transit filter for Default Target Warehouse in SE (#31839) (cherry picked from commit f1a612245c05aa56f7654694495d4ec5b9d3d8b9) Co-authored-by: Sagar Sharma --- .../stock/doctype/stock_entry/stock_entry.js | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index ad84c36b811..9cc8e237b6a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -581,18 +581,23 @@ frappe.ui.form.on('Stock Entry', { }, add_to_transit: function(frm) { - if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') { - frm.set_value('to_warehouse', ''); + if(frm.doc.purpose=='Material Transfer') { + var filters = { + 'is_group': 0, + 'company': frm.doc.company + } + + if(frm.doc.add_to_transit){ + filters['warehouse_type'] = 'Transit'; + frm.set_value('to_warehouse', ''); + frm.trigger('set_transit_warehouse'); + } + frm.fields_dict.to_warehouse.get_query = function() { return { - filters:{ - 'warehouse_type' : 'Transit', - 'is_group': 0, - 'company': frm.doc.company - } + filters:filters }; }; - frm.trigger('set_transit_warehouse'); } }, From 4775c425934d9844512a3f97d32b029613c91c0c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 18 Aug 2022 11:15:26 +0530 Subject: [PATCH 11/14] fix: Make expense account editable in Purchase Receipt Item (backport #31730) (#31879) * fix: Make expense account editable in Purchase Receipt Item (#31730) Co-authored-by: Sagar Sharma (cherry picked from commit 1a6508972e3555aabd06d93a46af9cc513e32819) # Conflicts: # erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json * chore: conflicts Co-authored-by: Deepesh Garg Co-authored-by: Sagar Sharma --- .../purchase_receipt_item/purchase_receipt_item.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 aa217441c0b..5a04e7d2eec 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -786,10 +786,8 @@ { "fieldname": "expense_account", "fieldtype": "Link", - "hidden": 1, "label": "Expense Account", - "options": "Account", - "read_only": 1 + "options": "Account" }, { "fieldname": "accounting_dimensions_section", @@ -994,7 +992,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2022-04-11 13:07:32.061402", + "modified": "2022-07-28 19:27:54.880781", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 36130c62926814bd74412ff4c10e9c670157f82c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 18 Aug 2022 11:42:29 +0530 Subject: [PATCH 12/14] fix(minor): save the employee doc on promotion/transfer update --- erpnext/hr/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index b80226b224c..d256f34732e 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -224,7 +224,7 @@ def delete_employee_work_history(details, employee, date): filters["from_date"] = date if filters: frappe.db.delete("Employee Internal Work History", filters) - employee.reload() + employee.save() @frappe.whitelist() From a4828407d09cd41088120671e000afa3c6c484a4 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 18 Aug 2022 12:02:32 +0530 Subject: [PATCH 13/14] fix: interview rescheduling not working --- erpnext/hr/doctype/interview/interview.py | 11 ++++++++--- erpnext/hr/doctype/interview/test_interview.py | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/erpnext/hr/doctype/interview/interview.py b/erpnext/hr/doctype/interview/interview.py index 8f0ff021335..a1d5c0249ba 100644 --- a/erpnext/hr/doctype/interview/interview.py +++ b/erpnext/hr/doctype/interview/interview.py @@ -94,8 +94,8 @@ class Interview(Document): @frappe.whitelist() def reschedule_interview(self, scheduled_on, from_time, to_time): original_date = self.scheduled_on - from_time = self.from_time - to_time = self.to_time + original_from_time = self.from_time + original_to_time = self.to_time self.db_set({"scheduled_on": scheduled_on, "from_time": from_time, "to_time": to_time}) self.notify_update() @@ -107,7 +107,12 @@ class Interview(Document): recipients=recipients, subject=_("Interview: {0} Rescheduled").format(self.name), message=_("Your Interview session is rescheduled from {0} {1} - {2} to {3} {4} - {5}").format( - original_date, from_time, to_time, self.scheduled_on, self.from_time, self.to_time + original_date, + original_from_time, + original_to_time, + self.scheduled_on, + self.from_time, + self.to_time, ), reference_doctype=self.doctype, reference_name=self.name, diff --git a/erpnext/hr/doctype/interview/test_interview.py b/erpnext/hr/doctype/interview/test_interview.py index 840a5ad919d..1062003dd8c 100644 --- a/erpnext/hr/doctype/interview/test_interview.py +++ b/erpnext/hr/doctype/interview/test_interview.py @@ -8,7 +8,7 @@ import unittest import frappe from frappe import _ from frappe.core.doctype.user_permission.test_user_permission import create_user -from frappe.utils import add_days, getdate, nowtime +from frappe.utils import add_days, get_time, getdate, nowtime from erpnext.hr.doctype.designation.test_designation import create_designation from erpnext.hr.doctype.interview.interview import DuplicateInterviewRoundError @@ -26,18 +26,23 @@ class TestInterview(unittest.TestCase): def test_notification_on_rescheduling(self): job_applicant = create_job_applicant() interview = create_interview_and_dependencies( - job_applicant.name, scheduled_on=add_days(getdate(), -4) + job_applicant.name, + scheduled_on=add_days(getdate(), -4), + from_time="10:00:00", + to_time="11:00:00", ) previous_scheduled_date = interview.scheduled_on frappe.db.sql("DELETE FROM `tabEmail Queue`") interview.reschedule_interview( - add_days(getdate(previous_scheduled_date), 2), from_time=nowtime(), to_time=nowtime() + add_days(getdate(previous_scheduled_date), 2), from_time="11:00:00", to_time="12:00:00" ) interview.reload() self.assertEqual(interview.scheduled_on, add_days(getdate(previous_scheduled_date), 2)) + self.assertEqual(get_time(interview.from_time), get_time("11:00:00")) + self.assertEqual(get_time(interview.to_time), get_time("12:00:00")) notification = frappe.get_all( "Email Queue", filters={"message": ("like", "%Your Interview session is rescheduled from%")} From a4521437825a960e14556fa3963bd1bd1a55a2dc Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Fri, 19 Aug 2022 12:30:47 +0530 Subject: [PATCH 14/14] fix: TDS calculation for advance payment "against_voucher": ["is", "not set"] was used in query due to which if TDS was added on "advance" payment vouchers and then reconciled against purchase invoice. it will not find those vouchers and consider this as first-time threshold due to which it will calculate Tax for all transactions. --- .../doctype/tax_withholding_category/tax_withholding_category.py | 1 - 1 file changed, 1 deletion(-) 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 63698439be1..3db21dc5a41 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -309,7 +309,6 @@ def get_advance_vouchers( "is_cancelled": 0, "party_type": party_type, "party": ["in", parties], - "against_voucher": ["is", "not set"], } if company: