fix: available qty for consumption
This commit is contained in:
parent
110e152fa3
commit
e5fb23972a
@ -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):
|
||||
|
@ -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",
|
||||
|
@ -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:
|
||||
|
@ -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"))
|
@ -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()
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user