fix: Handle rows with same item code from Purchase Receipt to Invoice. (#20724)
* fix: Handle rows with same item code from Purchase Receipt to Invoice.
* fix: Added patch, fixed tests, fixed delivery note behaviour
* chore: Added comments amd fixed typo
* fix: Added patch to patches.txt
* fix: Patch fix and simplification, json timestamp updation.
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 81fdbbe..90c67f1 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -74,7 +74,7 @@
for d in doc.get("items"):
if d.item_code and (flt(d.qty) < 0 or flt(d.get('received_qty')) < 0):
if d.item_code not in valid_items:
- frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}")
+ frappe.throw(_("Row # {0}: Returned Item {1} does not exist in {2} {3}")
.format(d.idx, d.item_code, doc.doctype, doc.return_against))
else:
ref = valid_items.get(d.item_code, frappe._dict())
@@ -266,6 +266,8 @@
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":
target_doc.received_qty = -1* source_doc.received_qty
target_doc.rejected_qty = -1* source_doc.rejected_qty
@@ -282,6 +284,7 @@
target_doc.so_detail = source_doc.so_detail
target_doc.si_detail = source_doc.si_detail
target_doc.expense_account = source_doc.expense_account
+ target_doc.dn_detail = source_doc.name
if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return
elif doctype == "Sales Invoice":
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 5295399..8a31cf3 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -662,6 +662,7 @@
erpnext.patches.v12_0.move_bank_account_swift_number_to_bank
erpnext.patches.v12_0.rename_bank_reconciliation
erpnext.patches.v12_0.rename_bank_reconciliation_fields # 2020-01-22
+erpnext.patches.v12_0.set_purchase_receipt_delivery_note_detail
erpnext.patches.v12_0.add_permission_in_lower_deduction
erpnext.patches.v12_0.set_received_qty_in_material_request_as_per_stock_uom
erpnext.patches.v12_0.rename_account_type_doctype
diff --git a/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py b/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py
new file mode 100644
index 0000000..f5bd8c3
--- /dev/null
+++ b/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py
@@ -0,0 +1,84 @@
+from __future__ import unicode_literals
+import frappe
+from collections import defaultdict
+
+def execute():
+ def map_rows(doc_row, return_doc_row, detail_field, doctype):
+ """Map rows after identifying similar ones."""
+
+ frappe.db.sql(""" UPDATE `tab{doctype} Item` set {detail_field} = '{doc_row_name}'
+ where name = '{return_doc_row_name}'""" \
+ .format(doctype=doctype,
+ detail_field=detail_field,
+ doc_row_name=doc_row.get('name'),
+ return_doc_row_name=return_doc_row.get('name'))) #nosec
+
+ def row_is_mappable(doc_row, return_doc_row, detail_field):
+ """Checks if two rows are similar enough to be mapped."""
+
+ if doc_row.item_code == return_doc_row.item_code and not return_doc_row.get(detail_field):
+ if doc_row.get('batch_no') and return_doc_row.get('batch_no') and doc_row.batch_no == return_doc_row.batch_no:
+ return True
+
+ elif doc_row.get('serial_no') and return_doc_row.get('serial_no'):
+ doc_sn = doc_row.serial_no.split('\n')
+ return_doc_sn = return_doc_row.serial_no.split('\n')
+
+ if set(doc_sn) & set(return_doc_sn):
+ # if two rows have serial nos in common, map them
+ return True
+
+ elif doc_row.rate == return_doc_row.rate:
+ return True
+ else:
+ return False
+
+ def make_return_document_map(doctype, return_document_map):
+ """Returns a map of documents and it's return documents.
+ Format => { 'document' : ['return_document_1','return_document_2'] }"""
+
+ return_against_documents = frappe.db.sql("""
+ SELECT
+ return_against as document, name as return_document
+ FROM `tab{doctype}`
+ WHERE
+ is_return = 1 and docstatus = 1""".format(doctype=doctype),as_dict=1) #nosec
+
+ for entry in return_against_documents:
+ return_document_map[entry.document].append(entry.return_document)
+
+ return return_document_map
+
+ def set_document_detail_in_return_document(doctype):
+ """Map each row of the original document in the return document."""
+ mapped = []
+ return_document_map = defaultdict(list)
+ detail_field = "purchase_receipt_item" if doctype=="Purchase Receipt" else "dn_detail"
+
+ child_doc = frappe.scrub("{0} Item".format(doctype))
+ frappe.reload_doc("stock", "doctype", child_doc)
+
+ return_document_map = make_return_document_map(doctype, return_document_map)
+
+ #iterate through original documents and its return documents
+ for docname in return_document_map:
+ doc_items = frappe.get_doc(doctype, docname).get("items")
+ for return_doc in return_document_map[docname]:
+ return_doc_items = frappe.get_doc(doctype, return_doc).get("items")
+
+ #iterate through return document items and original document items for mapping
+ for return_item in return_doc_items:
+ for doc_item in doc_items:
+ if row_is_mappable(doc_item, return_item, detail_field) and doc_item.get('name') not in mapped:
+ map_rows(doc_item, return_item, detail_field, doctype)
+ mapped.append(doc_item.get('name'))
+ break
+ else:
+ continue
+
+ set_document_detail_in_return_document("Purchase Receipt")
+ set_document_detail_in_return_document("Delivery Note")
+ frappe.db.commit()
+
+
+
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 37f9097..d04cf78 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -388,13 +388,12 @@
def get_returned_qty_map(delivery_note):
"""returns a map: {so_detail: returned_qty}"""
- returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.item_code, sum(abs(dn_item.qty)) as qty
+ returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.dn_detail, abs(dn_item.qty) as qty
from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
where dn.name = dn_item.parent
and dn.docstatus = 1
and dn.is_return = 1
and dn.return_against = %s
- group by dn_item.item_code
""", delivery_note))
return returned_qty_map
@@ -413,7 +412,7 @@
target.run_method("set_po_nos")
if len(target.get("items")) == 0:
- frappe.throw(_("All these items have already been invoiced"))
+ frappe.throw(_("All these items have already been Invoiced/Returned"))
target.run_method("calculate_taxes_and_totals")
@@ -438,9 +437,9 @@
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
returned_qty = 0
- if returned_qty_map.get(item_row.item_code, 0) > 0:
- returned_qty = flt(returned_qty_map.get(item_row.item_code, 0))
- returned_qty_map[item_row.item_code] -= pending_qty
+ if returned_qty_map.get(item_row.name, 0) > 0:
+ returned_qty = flt(returned_qty_map.get(item_row.name, 0))
+ returned_qty_map[item_row.name] -= pending_qty
if returned_qty:
if returned_qty >= pending_qty:
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index bf7007a..a921a56 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -612,6 +612,7 @@
dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-1, do_not_submit=True)
dn1.items[0].against_sales_order = so.name
dn1.items[0].so_detail = so.items[0].name
+ dn1.items[0].dn_detail = dn.items[0].name
dn1.submit()
si = make_sales_invoice(dn.name)
@@ -638,7 +639,9 @@
si1.save()
si1.submit()
- create_delivery_note(is_return=1, return_against=dn.name, qty=-2)
+ dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-2, do_not_submit=True)
+ dn1.items[0].dn_detail = dn.items[0].name
+ dn1.submit()
si2 = make_sales_invoice(dn.name)
self.assertEquals(si2.items[0].qty, 2)
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index 782ac84..7ea2de2 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -67,6 +67,7 @@
"so_detail",
"against_sales_invoice",
"si_detail",
+ "dn_detail",
"section_break_40",
"batch_no",
"serial_no",
@@ -699,6 +700,15 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "dn_detail",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Against Delivery Note Item",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"idx": 1,
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 8dfe1d1..e6ab8d6 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -504,7 +504,7 @@
def set_missing_values(source, target):
if len(target.get("items")) == 0:
- frappe.throw(_("All items have already been invoiced"))
+ frappe.throw(_("All items have already been Invoiced/Returned"))
doc = frappe.get_doc(target)
doc.ignore_pricing_rule = 1
@@ -514,11 +514,11 @@
def update_item(source_doc, target_doc, source_parent):
target_doc.qty, returned_qty = get_pending_qty(source_doc)
- returned_qty_map[source_doc.item_code] = returned_qty
+ returned_qty_map[source_doc.name] = returned_qty
def get_pending_qty(item_row):
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
- returned_qty = flt(returned_qty_map.get(item_row.item_code, 0))
+ returned_qty = flt(returned_qty_map.get(item_row.name, 0))
if returned_qty:
if returned_qty >= pending_qty:
pending_qty = 0
@@ -576,13 +576,12 @@
def get_returned_qty_map(purchase_receipt):
"""returns a map: {so_detail: returned_qty}"""
- returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.item_code, sum(abs(pr_item.qty)) as qty
+ returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.purchase_receipt_item, abs(pr_item.qty) as qty
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
where pr.name = pr_item.parent
and pr.docstatus = 1
and pr.is_return = 1
and pr.return_against = %s
- group by pr_item.item_code
""", purchase_receipt))
return returned_qty_map
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 3d42590..649cfdc 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -475,6 +475,7 @@
pr1 = make_purchase_receipt(is_return=1, return_against=pr.name, qty=-1, do_not_submit=True)
pr1.items[0].purchase_order = po.name
pr1.items[0].purchase_order_item = po.items[0].name
+ pr1.items[0].purchase_receipt_item = pr.items[0].name
pr1.submit()
pi = make_purchase_invoice(pr.name)
@@ -498,7 +499,9 @@
pi1.save()
pi1.submit()
- make_purchase_receipt(is_return=1, return_against=pr1.name, qty=-2)
+ pr2 = make_purchase_receipt(is_return=1, return_against=pr1.name, qty=-2, do_not_submit=True)
+ pr2.items[0].purchase_receipt_item = pr1.items[0].name
+ pr2.submit()
pi2 = make_purchase_invoice(pr1.name)
self.assertEquals(pi2.items[0].qty, 2)
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 bc6bce9..c1e1f90 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -71,6 +71,7 @@
"quality_inspection",
"purchase_order_item",
"material_request_item",
+ "purchase_receipt_item",
"section_break_45",
"allow_zero_valuation_rate",
"bom",
@@ -821,6 +822,15 @@
"options": "Warehouse"
},
{
+ "fieldname": "purchase_receipt_item",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Purchase Receipt Item",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
"collapsible": 1,
"fieldname": "image_column",
"fieldtype": "Column Break"
@@ -829,7 +839,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-04-10 19:01:21.154963",
+ "modified": "2020-04-28 19:01:21.154963",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",