From e9b28452e4997055e505b6c4877cf4664ccb262f Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Wed, 18 May 2022 22:42:25 +0530 Subject: [PATCH] feat: SCR return --- .../controllers/sales_and_purchase_return.py | 48 +++++++++------ .../subcontracting_receipt.js | 2 +- .../test_subcontracting_receipt.py | 59 +++++++++++++++++++ 3 files changed, 91 insertions(+), 18 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 9642c24a9e..ca968e939e 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -77,7 +77,7 @@ def validate_returned_items(doc): if doc.doctype != "Purchase Invoice": select_fields += ",serial_no, batch_no" - if doc.doctype in ["Purchase Invoice", "Purchase Receipt"]: + if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]: select_fields += ",rejected_qty, received_qty" for d in frappe.db.sql( @@ -161,7 +161,7 @@ def validate_returned_items(doc): def validate_quantity(doc, args, ref, valid_items, already_returned_items): fields = ["stock_qty"] - if doc.doctype in ["Purchase Receipt", "Purchase Invoice"]: + if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]: fields.extend(["received_qty", "rejected_qty"]) already_returned_data = already_returned_items.get(args.item_code) or {} @@ -224,7 +224,7 @@ def get_ref_item_dict(valid_items, ref_item_row): if ref_item_row.get("rate", 0) > item_dict["rate"]: item_dict["rate"] = ref_item_row.get("rate", 0) - if ref_item_row.parenttype in ["Purchase Invoice", "Purchase Receipt"]: + if ref_item_row.parenttype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]: item_dict["received_qty"] += ref_item_row.received_qty item_dict["rejected_qty"] += ref_item_row.rejected_qty @@ -239,7 +239,7 @@ def get_ref_item_dict(valid_items, ref_item_row): def get_already_returned_items(doc): column = "child.item_code, sum(abs(child.qty)) as qty, sum(abs(child.stock_qty)) as stock_qty" - if doc.doctype in ["Purchase Invoice", "Purchase Receipt"]: + if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]: column += """, sum(abs(child.rejected_qty) * child.conversion_factor) as rejected_qty, sum(abs(child.received_qty) * child.conversion_factor) as received_qty""" @@ -281,17 +281,21 @@ def get_returned_qty_map_for_row(return_against, party, row_name, doctype): child_doctype = doctype + " Item" reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype) - if doctype in ("Purchase Receipt", "Purchase Invoice"): + if doctype in ("Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"): party_type = "supplier" else: party_type = "customer" fields = [ "sum(abs(`tab{0}`.qty)) as qty".format(child_doctype), - "sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype), ] - if doctype in ("Purchase Receipt", "Purchase Invoice"): + if doctype != "Subcontracting Receipt": + fields += [ + "sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype), + ] + + if doctype in ("Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"): fields += [ "sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype), "sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype), @@ -397,7 +401,7 @@ def make_return_doc(doctype, source_name, target_doc=None): if serial_nos: target_doc.serial_no = "\n".join(serial_nos) - if doctype == "Purchase Receipt": + if doctype in ["Purchase Receipt", "Subcontracting Receipt"]: returned_qty_map = get_returned_qty_map_for_row( source_parent.name, source_parent.supplier, source_doc.name, doctype ) @@ -409,15 +413,24 @@ def make_return_doc(doctype, source_name, target_doc=None): ) target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0)) - target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0)) - target_doc.received_stock_qty = -1 * flt( - source_doc.received_stock_qty - (returned_qty_map.get("received_stock_qty") or 0) - ) + if hasattr(target_doc, "stock_qty"): + target_doc.stock_qty = -1 * flt( + source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0) + ) + target_doc.received_stock_qty = -1 * flt( + source_doc.received_stock_qty - (returned_qty_map.get("received_stock_qty") or 0) + ) - target_doc.purchase_order = source_doc.purchase_order - target_doc.purchase_order_item = source_doc.purchase_order_item - target_doc.rejected_warehouse = source_doc.rejected_warehouse - target_doc.purchase_receipt_item = source_doc.name + if doctype == "Subcontracting Receipt": + target_doc.subcontracting_order = source_doc.subcontracting_order + target_doc.subcontracting_order_item = source_doc.subcontracting_order_item + target_doc.rejected_warehouse = source_doc.rejected_warehouse + target_doc.subcontracting_receipt_item = source_doc.name + else: + target_doc.purchase_order = source_doc.purchase_order + target_doc.purchase_order_item = source_doc.purchase_order_item + target_doc.rejected_warehouse = source_doc.rejected_warehouse + target_doc.purchase_receipt_item = source_doc.name elif doctype == "Purchase Invoice": returned_qty_map = get_returned_qty_map_for_row( @@ -529,7 +542,7 @@ def get_rate_for_return( item_row, ) - if voucher_type in ("Purchase Receipt", "Purchase Invoice"): + if voucher_type in ("Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"): select_field = "incoming_rate" else: select_field = "abs(stock_value_difference / actual_qty)" @@ -564,6 +577,7 @@ def get_return_against_item_fields(voucher_type): "Purchase Invoice": "purchase_invoice_item", "Delivery Note": "dn_detail", "Sales Invoice": "sales_invoice_item", + "Subcontracting Receipt": "subcontracting_receipt_item", } return return_against_item_fields[voucher_type] diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index b98f979c66..87a19a1bf8 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -76,7 +76,7 @@ frappe.ui.form.on('Subcontracting Receipt', { }, __("View")); } - if (!frm.doc.is_return && frm.doc.docstatus == 1) { + if (!frm.doc.is_return && frm.doc.docstatus == 1 && frm.doc.per_returned < 100) { frm.add_custom_button('Subcontract Return', function () { frappe.model.open_mapped_doc({ method: 'erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt.make_subcontract_return', diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 8680311c79..dd1790289a 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -21,6 +21,7 @@ from erpnext.controllers.tests.test_subcontracting_controller import ( set_backflush_based_on, ) from erpnext.stock.doctype.item.test_item import make_item +from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import make_subcontracting_receipt @@ -272,6 +273,64 @@ class TestSubcontractingReceipt(FrappeTestCase): for row in scr.supplied_items: self.assertEqual(transferred_batch.get(row.batch_no), row.consumed_qty) + def test_subcontracting_order_partial_return(self): + sco = get_subcontracting_order() + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + scr1 = make_subcontracting_receipt(sco.name) + scr1.save() + scr1.submit() + + scr1_return = make_return_subcontracting_receipt(scr_name=scr1.name, qty=-3) + scr1.load_from_db() + self.assertEqual(scr1_return.status, "Return") + self.assertEqual(scr1.items[0].returned_qty, 3) + + scr2_return = make_return_subcontracting_receipt(scr_name=scr1.name, qty=-7) + scr1.load_from_db() + self.assertEqual(scr2_return.status, "Return") + self.assertEqual(scr1.status, "Return Issued") + self.assertEqual(scr1.items[0].returned_qty, 10) + + def test_subcontracting_order_over_return(self): + sco = get_subcontracting_order() + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + scr1 = make_subcontracting_receipt(sco.name) + scr1.save() + scr1.submit() + + from erpnext.controllers.status_updater import OverAllowanceError + args = frappe._dict(scr_name=scr1.name, qty=-15) + self.assertRaises(OverAllowanceError, make_return_subcontracting_receipt, **args) + + +def make_return_subcontracting_receipt(**args): + args = frappe._dict(args) + return_doc = make_return_doc("Subcontracting Receipt", args.scr_name) + return_doc.supplier_warehouse = args.supplier_warehouse or args.warehouse or "_Test Warehouse 1 - _TC" + + if args.qty: + for item in return_doc.items: + item.qty = args.qty + + if not args.do_not_save: + return_doc.save() + if not args.do_not_submit: + return_doc.submit() + + return_doc.load_from_db() + return return_doc def get_items(**args): args = frappe._dict(args)