From 6d4ca6a1ee2adffd58362161d89f484143f6b0c6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 27 Jan 2024 23:25:13 +0530 Subject: [PATCH] fix: prevent extra transfer against inter transfer transaction (backport #39213) (#39596) fix: prevent extra transfer against inter transfer transaction (#39213) * fix: prevent extra transfer against inter transfer transaction * fix: internal transfer dashboard (cherry picked from commit 8fdc244e16c26a74944a3a67613f8b64009a69b0) Co-authored-by: rohitwaghchaure --- erpnext/controllers/stock_controller.py | 115 +++++++++++++++++- .../delivery_note/delivery_note_dashboard.py | 6 +- .../purchase_receipt/test_purchase_receipt.py | 3 +- 3 files changed, 121 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index a86d7388df..c8516820ef 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -6,7 +6,7 @@ from collections import defaultdict from typing import List, Tuple import frappe -from frappe import _ +from frappe import _, bold from frappe.utils import cint, flt, get_link_to_form, getdate import erpnext @@ -697,6 +697,9 @@ class StockController(AccountsController): self.validate_in_transit_warehouses() self.validate_multi_currency() self.validate_packed_items() + + if self.get("is_internal_supplier"): + self.validate_internal_transfer_qty() else: self.validate_internal_transfer_warehouse() @@ -735,6 +738,116 @@ class StockController(AccountsController): if self.doctype in ("Sales Invoice", "Delivery Note Item") and self.get("packed_items"): frappe.throw(_("Packed Items cannot be transferred internally")) + def validate_internal_transfer_qty(self): + if self.doctype not in ["Purchase Invoice", "Purchase Receipt"]: + return + + item_wise_transfer_qty = self.get_item_wise_inter_transfer_qty() + if not item_wise_transfer_qty: + return + + item_wise_received_qty = self.get_item_wise_inter_received_qty() + precision = frappe.get_precision(self.doctype + " Item", "qty") + + over_receipt_allowance = frappe.db.get_single_value( + "Stock Settings", "over_delivery_receipt_allowance" + ) + + parent_doctype = { + "Purchase Receipt": "Delivery Note", + "Purchase Invoice": "Sales Invoice", + }.get(self.doctype) + + for key, transferred_qty in item_wise_transfer_qty.items(): + recevied_qty = flt(item_wise_received_qty.get(key), precision) + if over_receipt_allowance: + transferred_qty = transferred_qty + flt( + transferred_qty * over_receipt_allowance / 100, precision + ) + + if recevied_qty > flt(transferred_qty, precision): + frappe.throw( + _("For Item {0} cannot be received more than {1} qty against the {2} {3}").format( + bold(key[1]), + bold(flt(transferred_qty, precision)), + bold(parent_doctype), + get_link_to_form(parent_doctype, self.get("inter_company_reference")), + ) + ) + + def get_item_wise_inter_transfer_qty(self): + reference_field = "inter_company_reference" + if self.doctype == "Purchase Invoice": + reference_field = "inter_company_invoice_reference" + + parent_doctype = { + "Purchase Receipt": "Delivery Note", + "Purchase Invoice": "Sales Invoice", + }.get(self.doctype) + + child_doctype = parent_doctype + " Item" + + parent_tab = frappe.qb.DocType(parent_doctype) + child_tab = frappe.qb.DocType(child_doctype) + + query = ( + frappe.qb.from_(parent_doctype) + .inner_join(child_tab) + .on(child_tab.parent == parent_tab.name) + .select( + child_tab.name, + child_tab.item_code, + child_tab.qty, + ) + .where((parent_tab.name == self.get(reference_field)) & (parent_tab.docstatus == 1)) + ) + + data = query.run(as_dict=True) + item_wise_transfer_qty = defaultdict(float) + for row in data: + item_wise_transfer_qty[(row.name, row.item_code)] += flt(row.qty) + + return item_wise_transfer_qty + + def get_item_wise_inter_received_qty(self): + child_doctype = self.doctype + " Item" + + parent_tab = frappe.qb.DocType(self.doctype) + child_tab = frappe.qb.DocType(child_doctype) + + query = ( + frappe.qb.from_(self.doctype) + .inner_join(child_tab) + .on(child_tab.parent == parent_tab.name) + .select( + child_tab.item_code, + child_tab.qty, + ) + .where(parent_tab.docstatus < 2) + ) + + if self.doctype == "Purchase Invoice": + query = query.select( + child_tab.sales_invoice_item.as_("name"), + ) + + query = query.where( + parent_tab.inter_company_invoice_reference == self.inter_company_invoice_reference + ) + else: + query = query.select( + child_tab.delivery_note_item.as_("name"), + ) + + query = query.where(parent_tab.inter_company_reference == self.inter_company_reference) + + data = query.run(as_dict=True) + item_wise_transfer_qty = defaultdict(float) + for row in data: + item_wise_transfer_qty[(row.name, row.item_code)] += flt(row.qty) + + return item_wise_transfer_qty + def validate_putaway_capacity(self): # if over receipt is attempted while 'apply putaway rule' is disabled # and if rule was applied on the transaction, validate it. diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py index d4a574da73..2440701af9 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py @@ -8,6 +8,7 @@ def get_data(): "Stock Entry": "delivery_note_no", "Quality Inspection": "reference_name", "Auto Repeat": "reference_document", + "Purchase Receipt": "inter_company_reference", }, "internal_links": { "Sales Order": ["items", "against_sales_order"], @@ -22,6 +23,9 @@ def get_data(): {"label": _("Reference"), "items": ["Sales Order", "Shipment", "Quality Inspection"]}, {"label": _("Returns"), "items": ["Stock Entry"]}, {"label": _("Subscription"), "items": ["Auto Repeat"]}, - {"label": _("Internal Transfer"), "items": ["Material Request", "Purchase Order"]}, + { + "label": _("Internal Transfer"), + "items": ["Material Request", "Purchase Order", "Purchase Receipt"], + }, ], } diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 146cbff1aa..ab2b99757d 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1669,7 +1669,7 @@ class TestPurchaseReceipt(FrappeTestCase): pr.items[0].rejected_warehouse = from_warehouse pr.save() - self.assertRaises(OverAllowanceError, pr.submit) + self.assertRaises(frappe.ValidationError, pr.submit) # Step 5: Test Over Receipt Allowance frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 50) @@ -1683,6 +1683,7 @@ class TestPurchaseReceipt(FrappeTestCase): to_warehouse=target_warehouse, ) + pr.reload() pr.submit() frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0)