fix: available qty for consumption

This commit is contained in:
Rohit Waghchaure 2021-06-18 20:37:42 +05:30
parent 110e152fa3
commit e5fb23972a
6 changed files with 110 additions and 22 deletions

View File

@ -847,9 +847,6 @@ class TestPurchaseOrder(unittest.TestCase):
for item in rm_items:
transferred_rm_map[item.get('rm_item_code')] = item
for item in pr.get('supplied_items'):
self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty)
update_backflush_based_on("BOM")
def test_supplied_qty_against_subcontracted_po(self):

View File

@ -26,7 +26,8 @@
"secbreak_3",
"batch_no",
"col_break4",
"serial_no"
"serial_no",
"purchase_order"
],
"fields": [
{
@ -81,9 +82,10 @@
"fieldname": "required_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Required Qty",
"label": "Available Qty For Consumption",
"oldfieldname": "required_qty",
"oldfieldtype": "Currency",
"print_hide": 1,
"read_only": 1
},
{
@ -91,7 +93,7 @@
"fieldname": "consumed_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Consumed Qty",
"label": "Qty to Be Consumed",
"oldfieldname": "consumed_qty",
"oldfieldtype": "Currency",
"reqd": 1
@ -190,12 +192,22 @@
"fieldtype": "Data",
"label": "Item Name",
"read_only": 1
},
{
"fieldname": "purchase_order",
"fieldtype": "Link",
"hidden": 1,
"label": "Purchase Order",
"no_copy": 1,
"options": "Purchase Order",
"print_hide": 1,
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2021-05-29 17:22:14.977117",
"modified": "2021-06-19 19:33:04.431213",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Receipt Item Supplied",

View File

@ -292,11 +292,13 @@ class BuyingController(StockController, Subcontracting):
if item in self.sub_contracted_items and not item.bom:
frappe.throw(_("Please select BOM in BOM field for Item {0}").format(item.item_code))
if self.doctype == "Purchase Order":
for supplied_item in self.get("supplied_items"):
if not supplied_item.reserve_warehouse:
frappe.throw(_("Reserved Warehouse is mandatory for Item {0} in Raw Materials supplied").format(frappe.bold(supplied_item.rm_item_code)))
if self.doctype != "Purchase Order":
return
for row in self.get("supplied_items"):
if not row.reserve_warehouse:
msg = f"Reserved Warehouse is mandatory for the Item {frappe.bold(row.rm_item_code)} in Raw Materials supplied"
frappe.throw(_(msg))
else:
for item in self.get("items"):
if item.bom:

View File

@ -1,6 +1,7 @@
import frappe
import copy
from frappe import _
from frappe.utils import flt, cint
from frappe.utils import flt, cint, get_link_to_form
from collections import defaultdict
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@ -12,7 +13,7 @@ class Subcontracting():
self.raw_material_table = raw_material_table
self.__identify_change_in_item_table()
self.__prepare_supplied_items()
self.__validate_consumed_qty()
self.__validate_supplied_items()
def __prepare_supplied_items(self):
self.initialized_fields()
@ -24,6 +25,7 @@ class Subcontracting():
def initialized_fields(self):
self.available_materials = frappe._dict()
self.__transferred_items = frappe._dict()
self.alternative_item_details = frappe._dict()
self.__get_backflush_based_on()
@ -100,6 +102,7 @@ class Subcontracting():
self.__set_alternative_item_details(row)
self.__transferred_items = copy.deepcopy(self.available_materials)
for doctype in ['Purchase Receipt', 'Purchase Invoice']:
self.__update_consumed_materials(doctype)
@ -254,6 +257,8 @@ class Subcontracting():
if self.qty_to_be_received:
qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0))
transfer_item.item_details.required_qty = transfer_item.qty
if (transfer_item.serial_no or frappe.get_cached_value('UOM',
transfer_item.item_details.stock_uom, 'must_be_whole_number')):
return frappe.utils.ceil(qty)
@ -272,12 +277,15 @@ class Subcontracting():
if self.doctype == 'Purchase Order':
rm_obj.required_qty = qty
else:
rm_obj.consumed_qty = 0
rm_obj.purchase_order = item_row.purchase_order
self.__set_batch_nos(bom_item, item_row, rm_obj, qty)
def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)
if (self.available_materials.get(key) and self.available_materials[key]['batch_no']):
new_rm_obj = None
for batch_no, batch_qty in self.available_materials[key]['batch_no'].items():
if batch_qty >= qty:
self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
@ -290,13 +298,21 @@ class Subcontracting():
new_rm_obj.reference_name = item_row.name
self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty)
self.available_materials[key]['batch_no'][batch_no] = 0
if abs(qty) > 0 and not new_rm_obj:
self.__set_consumed_qty(rm_obj, qty)
else:
rm_obj.required_qty = qty
rm_obj.consumed_qty = qty
self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty)
self.__set_serial_nos(item_row, rm_obj)
def __set_consumed_qty(self, rm_obj, consumed_qty, required_qty=0):
rm_obj.required_qty = required_qty
rm_obj.consumed_qty = consumed_qty
def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty):
rm_obj.update({'consumed_qty': qty, 'batch_no': batch_no, 'required_qty': qty})
rm_obj.update({'consumed_qty': qty, 'batch_no': batch_no,
'required_qty': qty, 'purchase_order': item_row.purchase_order})
self.__set_serial_nos(item_row, rm_obj)
def __set_serial_nos(self, item_row, rm_obj):
@ -339,9 +355,39 @@ class Subcontracting():
itemwise_consumed_qty[key] -= consumed_qty
frappe.db.set_value('Purchase Order Item Supplied', row.name, 'consumed_qty', consumed_qty)
def __validate_consumed_qty(self):
for row in self.get(self.raw_material_table):
if flt(row.consumed_qty) == 0.0 and row.get('serial_no'):
msg = f'Row {row.idx}: the consumed qty cannot be zero for the item {frappe.bold(row.rm_item_code)}'
def __validate_supplied_items(self):
if self.doctype not in ['Purchase Invoice', 'Purchase Receipt']:
return
frappe.throw(_(msg),title=_('Consumed Items Qty Check'))
for row in self.get(self.raw_material_table):
self.__validate_consumed_qty(row)
key = (row.rm_item_code, row.main_item_code, row.purchase_order)
if not self.__transferred_items or not self.__transferred_items.get(key):
return
self.__validate_batch_no(row, key)
self.__validate_serial_no(row, key)
def __validate_consumed_qty(self, row):
if self.backflush_based_on != 'BOM' and flt(row.consumed_qty) == 0.0:
msg = f'Row {row.idx}: the consumed qty cannot be zero for the item {frappe.bold(row.rm_item_code)}'
frappe.throw(_(msg),title=_('Consumed Items Qty Check'))
def __validate_batch_no(self, row, key):
if row.get('batch_no') and row.get('batch_no') not in self.__transferred_items.get(key).get('batch_no'):
link = get_link_to_form('Purchase Order', row.purchase_order)
msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the Purchase Order {link}'
frappe.throw(_(msg), title=_("Incorrect Batch Consumed"))
def __validate_serial_no(self, row, key):
if row.get('serial_no'):
serial_nos = get_serial_nos(row.get('serial_no'))
incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get('serial_no'))
if incorrect_sn:
incorrect_sn = "\n".join(incorrect_sn)
link = get_link_to_form('Purchase Order', row.purchase_order)
msg = f'The Serial Nos {incorrect_sn} has not supplied against the Purchase Order {link}'
frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))

View File

@ -485,7 +485,7 @@ class update_entries_after(object):
# Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice
if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted") == 'Yes':
doc = frappe.get_cached_doc(sle.voucher_type, sle.voucher_no)
doc = frappe.get_doc(sle.voucher_type, sle.voucher_no)
doc.update_valuation_rate(reset_outgoing_rate=False)
for d in (doc.items + doc.supplied_items):
d.db_update()

View File

@ -395,6 +395,37 @@ class TestSubcontracting(unittest.TestCase):
self.assertEqual(value.qty, details.qty)
self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
def test_incorrect_serial_no_components_based_on_material_transfer(self):
'''
- Set backflush based on Material Transferred for Subcontract
- Create subcontracted PO for the item Subcontracted Item SA2.
- Transfer the serialized componenets to the supplier.
- Create purchase receipt and change the serial no which is not transferred.
- System should throw the error and not allowed to save the purchase receipt.
'''
set_backflush_based_on('Material Transferred for Subcontract')
item_code = 'Subcontracted Item SA2'
items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 10}]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
supplier_warehouse="_Test Warehouse 1 - _TC")
for d in rm_items:
d['po_detail'] = po.items[0].name
make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
pr1 = make_purchase_receipt(po.name)
pr1.save()
pr1.supplied_items[0].serial_no = 'ABCD'
self.assertRaises(frappe.ValidationError, pr1.save)
pr1.delete()
def test_partial_transfer_batch_based_on_material_transfer(self):
'''
- Set backflush based on Material Transferred for Subcontract