mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-04 12:49:10 +00:00
fix: incorrect calculation for consumed qty for subcontract item (#23257)
* fix: incorrect calculation for consumed qty for subcontract item * added test case
This commit is contained in:
@@ -322,7 +322,7 @@ class BuyingController(StockController):
|
|||||||
|
|
||||||
if raw_material.batch_nos:
|
if raw_material.batch_nos:
|
||||||
batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
|
batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
|
||||||
qty, transferred_batch_qty_map, backflushed_batch_qty_map)
|
qty, transferred_batch_qty_map, backflushed_batch_qty_map, item.purchase_order)
|
||||||
for batch_data in batches_qty:
|
for batch_data in batches_qty:
|
||||||
qty = batch_data['qty']
|
qty = batch_data['qty']
|
||||||
raw_material.batch_no = batch_data['batch']
|
raw_material.batch_no = batch_data['batch']
|
||||||
@@ -334,6 +334,9 @@ class BuyingController(StockController):
|
|||||||
rm = self.append('supplied_items', {})
|
rm = self.append('supplied_items', {})
|
||||||
rm.update(raw_material_data)
|
rm.update(raw_material_data)
|
||||||
|
|
||||||
|
if not rm.main_item_code:
|
||||||
|
rm.main_item_code = fg_item_doc.item_code
|
||||||
|
|
||||||
rm.required_qty = qty
|
rm.required_qty = qty
|
||||||
rm.consumed_qty = qty
|
rm.consumed_qty = qty
|
||||||
|
|
||||||
@@ -844,7 +847,7 @@ def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
|
|||||||
AND se.purpose='Send to Subcontractor'
|
AND se.purpose='Send to Subcontractor'
|
||||||
AND se.purchase_order = %s
|
AND se.purchase_order = %s
|
||||||
AND IFNULL(sed.t_warehouse, '') != ''
|
AND IFNULL(sed.t_warehouse, '') != ''
|
||||||
AND sed.subcontracted_item = %s
|
AND IFNULL(sed.subcontracted_item, '') in ('', %s)
|
||||||
GROUP BY sed.item_code, sed.subcontracted_item
|
GROUP BY sed.item_code, sed.subcontracted_item
|
||||||
"""
|
"""
|
||||||
raw_materials = frappe.db.multisql({
|
raw_materials = frappe.db.multisql({
|
||||||
@@ -975,14 +978,15 @@ def get_transferred_batch_qty_map(purchase_order, fg_item):
|
|||||||
SELECT
|
SELECT
|
||||||
sed.batch_no,
|
sed.batch_no,
|
||||||
SUM(sed.qty) AS qty,
|
SUM(sed.qty) AS qty,
|
||||||
sed.item_code
|
sed.item_code,
|
||||||
|
sed.subcontracted_item
|
||||||
FROM `tabStock Entry` se,`tabStock Entry Detail` sed
|
FROM `tabStock Entry` se,`tabStock Entry Detail` sed
|
||||||
WHERE
|
WHERE
|
||||||
se.name = sed.parent
|
se.name = sed.parent
|
||||||
AND se.docstatus=1
|
AND se.docstatus=1
|
||||||
AND se.purpose='Send to Subcontractor'
|
AND se.purpose='Send to Subcontractor'
|
||||||
AND se.purchase_order = %s
|
AND se.purchase_order = %s
|
||||||
AND sed.subcontracted_item = %s
|
AND ifnull(sed.subcontracted_item, '') in ('', %s)
|
||||||
AND sed.batch_no IS NOT NULL
|
AND sed.batch_no IS NOT NULL
|
||||||
GROUP BY
|
GROUP BY
|
||||||
sed.batch_no,
|
sed.batch_no,
|
||||||
@@ -990,8 +994,10 @@ def get_transferred_batch_qty_map(purchase_order, fg_item):
|
|||||||
""", (purchase_order, fg_item), as_dict=1)
|
""", (purchase_order, fg_item), as_dict=1)
|
||||||
|
|
||||||
for batch_data in transferred_batches:
|
for batch_data in transferred_batches:
|
||||||
transferred_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
|
key = ((batch_data.item_code, fg_item)
|
||||||
transferred_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
|
if batch_data.subcontracted_item else (batch_data.item_code, purchase_order))
|
||||||
|
transferred_batch_qty_map.setdefault(key, {})
|
||||||
|
transferred_batch_qty_map[key][batch_data.batch_no] = batch_data.qty
|
||||||
|
|
||||||
return transferred_batch_qty_map
|
return transferred_batch_qty_map
|
||||||
|
|
||||||
@@ -1028,9 +1034,12 @@ def get_backflushed_batch_qty_map(purchase_order, fg_item):
|
|||||||
|
|
||||||
return backflushed_batch_qty_map
|
return backflushed_batch_qty_map
|
||||||
|
|
||||||
def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map):
|
def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map, po):
|
||||||
# Returns available batches to be backflushed based on requirements
|
# Returns available batches to be backflushed based on requirements
|
||||||
transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
|
transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
|
||||||
|
if not transferred_batches:
|
||||||
|
transferred_batches = transferred_batch_qty_map.get((item_code, po), {})
|
||||||
|
|
||||||
backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {})
|
backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {})
|
||||||
|
|
||||||
available_batches = []
|
available_batches = []
|
||||||
|
|||||||
@@ -579,6 +579,67 @@ class TestPurchaseReceipt(unittest.TestCase):
|
|||||||
self.assertEquals(pi2.items[0].qty, 2)
|
self.assertEquals(pi2.items[0].qty, 2)
|
||||||
self.assertEquals(pi2.items[1].qty, 1)
|
self.assertEquals(pi2.items[1].qty, 1)
|
||||||
|
|
||||||
|
def test_subcontracted_pr_for_multi_transfer_batches(self):
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
from erpnext.buying.doctype.purchase_order.purchase_order import make_rm_stock_entry, make_purchase_receipt
|
||||||
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import (update_backflush_based_on,
|
||||||
|
create_purchase_order)
|
||||||
|
|
||||||
|
update_backflush_based_on("Material Transferred for Subcontract")
|
||||||
|
item_code = "_Test Subcontracted FG Item 3"
|
||||||
|
|
||||||
|
make_item('Sub Contracted Raw Material 3', {
|
||||||
|
'is_stock_item': 1,
|
||||||
|
'is_sub_contracted_item': 1,
|
||||||
|
'has_batch_no': 1,
|
||||||
|
'create_new_batch': 1
|
||||||
|
})
|
||||||
|
|
||||||
|
create_subcontracted_item(item_code=item_code, has_batch_no=1,
|
||||||
|
raw_materials=["Sub Contracted Raw Material 3"])
|
||||||
|
|
||||||
|
order_qty = 500
|
||||||
|
po = create_purchase_order(item_code=item_code, qty=order_qty,
|
||||||
|
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
||||||
|
|
||||||
|
ste1=make_stock_entry(target="_Test Warehouse - _TC",
|
||||||
|
item_code = "Sub Contracted Raw Material 3", qty=300, basic_rate=100)
|
||||||
|
ste2=make_stock_entry(target="_Test Warehouse - _TC",
|
||||||
|
item_code = "Sub Contracted Raw Material 3", qty=200, basic_rate=100)
|
||||||
|
|
||||||
|
transferred_batch = {
|
||||||
|
ste1.items[0].batch_no : 300,
|
||||||
|
ste2.items[0].batch_no : 200
|
||||||
|
}
|
||||||
|
|
||||||
|
rm_items = [
|
||||||
|
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 3","item_name":"_Test Item",
|
||||||
|
"qty":300,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
|
||||||
|
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 3","item_name":"_Test Item",
|
||||||
|
"qty":200,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}
|
||||||
|
]
|
||||||
|
|
||||||
|
rm_item_string = json.dumps(rm_items)
|
||||||
|
se = frappe.get_doc(make_rm_stock_entry(po.name, rm_item_string))
|
||||||
|
self.assertEqual(len(se.items), 2)
|
||||||
|
se.items[0].batch_no = ste1.items[0].batch_no
|
||||||
|
se.items[1].batch_no = ste2.items[0].batch_no
|
||||||
|
se.submit()
|
||||||
|
|
||||||
|
supplied_qty = frappe.db.get_value("Purchase Order Item Supplied",
|
||||||
|
{"parent": po.name, "rm_item_code": "Sub Contracted Raw Material 3"}, "supplied_qty")
|
||||||
|
|
||||||
|
self.assertEqual(supplied_qty, 500.00)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(po.name)
|
||||||
|
pr.save()
|
||||||
|
self.assertEqual(len(pr.supplied_items), 2)
|
||||||
|
|
||||||
|
for row in pr.supplied_items:
|
||||||
|
self.assertEqual(transferred_batch.get(row.batch_no), row.consumed_qty)
|
||||||
|
|
||||||
|
update_backflush_based_on("BOM")
|
||||||
|
|
||||||
def get_gl_entries(voucher_type, voucher_no):
|
def get_gl_entries(voucher_type, voucher_no):
|
||||||
return frappe.db.sql("""select account, debit, credit, cost_center
|
return frappe.db.sql("""select account, debit, credit, cost_center
|
||||||
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
|
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
|
||||||
@@ -714,6 +775,33 @@ def make_purchase_receipt(**args):
|
|||||||
pr.submit()
|
pr.submit()
|
||||||
return pr
|
return pr
|
||||||
|
|
||||||
|
def create_subcontracted_item(**args):
|
||||||
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
|
|
||||||
|
args = frappe._dict(args)
|
||||||
|
|
||||||
|
if not frappe.db.exists('Item', args.item_code):
|
||||||
|
make_item(args.item_code, {
|
||||||
|
'is_stock_item': 1,
|
||||||
|
'is_sub_contracted_item': 1,
|
||||||
|
'has_batch_no': args.get("has_batch_no") or 0
|
||||||
|
})
|
||||||
|
|
||||||
|
if not args.raw_materials:
|
||||||
|
if not frappe.db.exists('Item', "Test Extra Item 1"):
|
||||||
|
make_item("Test Extra Item 1", {
|
||||||
|
'is_stock_item': 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
if not frappe.db.exists('Item', "Test Extra Item 2"):
|
||||||
|
make_item("Test Extra Item 2", {
|
||||||
|
'is_stock_item': 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
args.raw_materials = ['_Test FG Item', 'Test Extra Item 1']
|
||||||
|
|
||||||
|
if not frappe.db.get_value('BOM', {'item': args.item_code}, 'name'):
|
||||||
|
make_bom(item = args.item_code, raw_materials = args.get("raw_materials"))
|
||||||
|
|
||||||
test_dependencies = ["BOM", "Item Price", "Location"]
|
test_dependencies = ["BOM", "Item Price", "Location"]
|
||||||
test_records = frappe.get_test_records('Purchase Receipt')
|
test_records = frappe.get_test_records('Purchase Receipt')
|
||||||
|
|||||||
@@ -556,8 +556,9 @@ class StockEntry(StockController):
|
|||||||
qty_allowance = flt(frappe.db.get_single_value("Buying Settings",
|
qty_allowance = flt(frappe.db.get_single_value("Buying Settings",
|
||||||
"over_transfer_allowance"))
|
"over_transfer_allowance"))
|
||||||
|
|
||||||
if (self.purpose == "Send to Subcontractor" and self.purchase_order and
|
if not (self.purpose == "Send to Subcontractor" and self.purchase_order): return
|
||||||
backflush_raw_materials_based_on == 'BOM'):
|
|
||||||
|
if (backflush_raw_materials_based_on == 'BOM'):
|
||||||
purchase_order = frappe.get_doc("Purchase Order", self.purchase_order)
|
purchase_order = frappe.get_doc("Purchase Order", self.purchase_order)
|
||||||
for se_item in self.items:
|
for se_item in self.items:
|
||||||
item_code = se_item.original_item or se_item.item_code
|
item_code = se_item.original_item or se_item.item_code
|
||||||
@@ -594,6 +595,11 @@ class StockEntry(StockController):
|
|||||||
if flt(total_supplied, precision) > flt(total_allowed, precision):
|
if flt(total_supplied, precision) > flt(total_allowed, precision):
|
||||||
frappe.throw(_("Row {0}# Item {1} cannot be transferred more than {2} against Purchase Order {3}")
|
frappe.throw(_("Row {0}# Item {1} cannot be transferred more than {2} against Purchase Order {3}")
|
||||||
.format(se_item.idx, se_item.item_code, total_allowed, self.purchase_order))
|
.format(se_item.idx, se_item.item_code, total_allowed, self.purchase_order))
|
||||||
|
elif backflush_raw_materials_based_on == "Material Transferred for Subcontract":
|
||||||
|
for row in self.items:
|
||||||
|
if not row.subcontracted_item:
|
||||||
|
frappe.throw(_("Row {0}: Subcontracted Item is mandatory for the raw material {1}")
|
||||||
|
.format(row.idx, frappe.bold(row.item_code)))
|
||||||
|
|
||||||
def validate_bom(self):
|
def validate_bom(self):
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
@@ -797,6 +803,13 @@ class StockEntry(StockController):
|
|||||||
ret.get('has_batch_no') and not args.get('batch_no')):
|
ret.get('has_batch_no') and not args.get('batch_no')):
|
||||||
args.batch_no = get_batch_no(args['item_code'], args['s_warehouse'], args['qty'])
|
args.batch_no = get_batch_no(args['item_code'], args['s_warehouse'], args['qty'])
|
||||||
|
|
||||||
|
if self.purpose == "Send to Subcontractor" and self.get("purchase_order") and args.get('item_code'):
|
||||||
|
subcontract_items = frappe.get_all("Purchase Order Item Supplied",
|
||||||
|
{"parent": self.purchase_order, "rm_item_code": args.get('item_code')}, "main_item_code")
|
||||||
|
|
||||||
|
if subcontract_items and len(subcontract_items) == 1:
|
||||||
|
ret["subcontracted_item"] = subcontract_items[0].main_item_code
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def set_items_for_stock_in(self):
|
def set_items_for_stock_in(self):
|
||||||
@@ -1237,9 +1250,15 @@ class StockEntry(StockController):
|
|||||||
#Update Supplied Qty in PO Supplied Items
|
#Update Supplied Qty in PO Supplied Items
|
||||||
|
|
||||||
frappe.db.sql("""UPDATE `tabPurchase Order Item Supplied` pos
|
frappe.db.sql("""UPDATE `tabPurchase Order Item Supplied` pos
|
||||||
SET pos.supplied_qty = (SELECT ifnull(sum(transfer_qty), 0) FROM `tabStock Entry Detail` sed
|
SET
|
||||||
WHERE pos.name = sed.po_detail and sed.docstatus = 1)
|
pos.supplied_qty = IFNULL((SELECT ifnull(sum(transfer_qty), 0)
|
||||||
WHERE pos.docstatus = 1 and pos.parent = %s""", self.purchase_order)
|
FROM
|
||||||
|
`tabStock Entry Detail` sed, `tabStock Entry` se
|
||||||
|
WHERE
|
||||||
|
(pos.name = sed.po_detail OR sed.subcontracted_item = pos.main_item_code)
|
||||||
|
AND sed.docstatus = 1 AND se.name = sed.parent and se.purchase_order = %(po)s
|
||||||
|
), 0)
|
||||||
|
WHERE pos.docstatus = 1 and pos.parent = %(po)s""", {"po": self.purchase_order})
|
||||||
|
|
||||||
#Update reserved sub contracted quantity in bin based on Supplied Item Details and
|
#Update reserved sub contracted quantity in bin based on Supplied Item Details and
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
|
||||||
"autoname": "hash",
|
"autoname": "hash",
|
||||||
"creation": "2013-03-29 18:22:12",
|
"creation": "2013-03-29 18:22:12",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@@ -17,6 +16,7 @@
|
|||||||
"item_group",
|
"item_group",
|
||||||
"col_break2",
|
"col_break2",
|
||||||
"item_name",
|
"item_name",
|
||||||
|
"subcontracted_item",
|
||||||
"section_break_8",
|
"section_break_8",
|
||||||
"description",
|
"description",
|
||||||
"column_break_10",
|
"column_break_10",
|
||||||
@@ -57,7 +57,6 @@
|
|||||||
"material_request",
|
"material_request",
|
||||||
"material_request_item",
|
"material_request_item",
|
||||||
"original_item",
|
"original_item",
|
||||||
"subcontracted_item",
|
|
||||||
"reference_section",
|
"reference_section",
|
||||||
"against_stock_entry",
|
"against_stock_entry",
|
||||||
"ste_detail",
|
"ste_detail",
|
||||||
@@ -415,6 +414,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:parent.purpose == 'Send to Subcontractor'",
|
||||||
"fieldname": "subcontracted_item",
|
"fieldname": "subcontracted_item",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Subcontracted Item",
|
"label": "Subcontracted Item",
|
||||||
@@ -497,15 +497,12 @@
|
|||||||
"depends_on": "eval:parent.purpose===\"Repack\" && doc.t_warehouse",
|
"depends_on": "eval:parent.purpose===\"Repack\" && doc.t_warehouse",
|
||||||
"fieldname": "set_basic_rate_manually",
|
"fieldname": "set_basic_rate_manually",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Set Basic Rate Manually",
|
"label": "Set Basic Rate Manually"
|
||||||
"show_days": 1,
|
|
||||||
"show_seconds": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"modified": "2020-09-04 12:12:35.668198",
|
||||||
"modified": "2020-06-08 12:57:03.172887",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Entry Detail",
|
"name": "Stock Entry Detail",
|
||||||
|
|||||||
Reference in New Issue
Block a user