def get_stock_balance_for( item_code, warehouse, posting_date, posting_time, batch_no=None, with_valuation_rate=True, ): frappe.has_permission("Backported Stock Reconciliation", "write", throw=True) item_dict = frappe.db.get_value("Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=1) serial_nos = "" if item_dict.get("has_serial_no"): qty, rate, serial_nos = _get_qty_rate_for_serial_nos( item_code, warehouse, posting_date, posting_time, item_dict) else: qty, rate = get_stock_balance( item_code, warehouse, posting_date, posting_time, with_valuation_rate=with_valuation_rate, ) if item_dict.get("has_batch_no"): qty = get_batch_qty(batch_no, warehouse) or 0 print(get_batch_qty(batch_no, warehouse)) return {"qty": qty, "rate": rate, "serial_nos": serial_nos}
def test_retain_sample(self): from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.batch.batch import get_batch_qty create_warehouse("Test Warehouse for Sample Retention") frappe.db.set_value("Stock Settings", None, "sample_retention_warehouse", "Test Warehouse for Sample Retention - _TC") test_item_code = "Retain Sample Item" if not frappe.db.exists('Item', test_item_code): item = frappe.new_doc("Item") item.item_code = test_item_code item.item_name = "Retain Sample Item" item.description = "Retain Sample Item" item.item_group = "All Item Groups" item.is_stock_item = 1 item.has_batch_no = 1 item.create_new_batch = 1 item.retain_sample = 1 item.sample_quantity = 4 item.save() receipt_entry = frappe.new_doc("Stock Entry") receipt_entry.company = "_Test Company" receipt_entry.purpose = "Material Receipt" receipt_entry.append("items", { "item_code": test_item_code, "t_warehouse": "_Test Warehouse - _TC", "qty": 40, "basic_rate": 12, "cost_center": "_Test Cost Center - _TC", "sample_quantity": 4 }) receipt_entry.set_stock_entry_type() receipt_entry.insert() receipt_entry.submit() retention_data = move_sample_to_retention_warehouse(receipt_entry.company, receipt_entry.get("items")) retention_entry = frappe.new_doc("Stock Entry") retention_entry.company = retention_data.company retention_entry.purpose = retention_data.purpose retention_entry.append("items", { "item_code": test_item_code, "t_warehouse": "Test Warehouse for Sample Retention - _TC", "s_warehouse": "_Test Warehouse - _TC", "qty": 4, "basic_rate": 12, "cost_center": "_Test Cost Center - _TC", "batch_no": receipt_entry.get("items")[0].batch_no }) retention_entry.set_stock_entry_type() retention_entry.insert() retention_entry.submit() qty_in_usable_warehouse = get_batch_qty(receipt_entry.get("items")[0].batch_no, "_Test Warehouse - _TC", "_Test Item") qty_in_retention_warehouse = get_batch_qty(receipt_entry.get("items")[0].batch_no, "Test Warehouse for Sample Retention - _TC", "_Test Item") self.assertEqual(qty_in_usable_warehouse, 36) self.assertEqual(qty_in_retention_warehouse, 4)
def test_batch_split(self): '''Test batch splitting''' receipt = self.test_purchase_receipt() from erpnext.stock.doctype.batch.batch import split_batch new_batch = split_batch(receipt.items[0].batch_no, 'ITEM-BATCH-1', receipt.items[0].warehouse, 22) self.assertEquals(get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), 78) self.assertEquals(get_batch_qty(new_batch, receipt.items[0].warehouse), 22)
def test_batch_split(self): '''Test batch splitting''' receipt = self.test_purchase_receipt() from erpnext.stock.doctype.batch.batch import split_batch new_batch = split_batch(receipt.items[0].batch_no, 'ITEM-BATCH-1', receipt.items[0].warehouse, 22) self.assertEqual(get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), 78) self.assertEqual(get_batch_qty(new_batch, receipt.items[0].warehouse), 22)
def test_serial_batch_item_qty_deduction(self): """ Behaviour: Create 2 Stock Entries, both adding Serial Nos to same batch Expected: 1) Cancelling first Stock Entry (origin transaction of created batch) should throw a LinkExistsError 2) Cancelling second Stock Entry should make Serial Nos that are, linked to mentioned batch and in that transaction only, Inactive. """ from erpnext.stock.doctype.batch.batch import get_batch_qty item = frappe.db.exists("Item", {"item_name": "Batched and Serialised Item"}) if not item: item = create_item("Batched and Serialised Item") item.has_batch_no = 1 item.create_new_batch = 1 item.has_serial_no = 1 item.batch_number_series = "B-BATCH-.##" item.serial_no_series = "S-.####" item.save() else: item = frappe.get_doc("Item", {"item_name": "Batched and Serialised Item"}) se1 = make_stock_entry( item_code=item.item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100 ) batch_no = se1.items[0].batch_no serial_no1 = get_serial_nos(se1.items[0].serial_no)[0] # Check Source (Origin) Document of Batch self.assertEqual(frappe.db.get_value("Batch", batch_no, "reference_name"), se1.name) se2 = make_stock_entry( item_code=item.item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100, batch_no=batch_no, ) serial_no2 = get_serial_nos(se2.items[0].serial_no)[0] batch_qty = get_batch_qty(batch_no, "_Test Warehouse - _TC", item.item_code) self.assertEqual(batch_qty, 2) se2.cancel() # Check decrease in Batch Qty batch_qty = get_batch_qty(batch_no, "_Test Warehouse - _TC", item.item_code) self.assertEqual(batch_qty, 1) # Check if Serial No from Stock Entry 1 is intact self.assertEqual(frappe.db.get_value("Serial No", serial_no1, "batch_no"), batch_no) self.assertEqual(frappe.db.get_value("Serial No", serial_no1, "status"), "Active") # Check if Serial No from Stock Entry 2 is Unlinked and Inactive self.assertEqual(frappe.db.get_value("Serial No", serial_no2, "batch_no"), None) self.assertEqual(frappe.db.get_value("Serial No", serial_no2, "status"), "Inactive")
def test_get_batch_qty(self): '''Test getting batch quantities by batch_numbers, item_code or warehouse''' self.make_batch_item('ITEM-BATCH-2') self.make_new_batch_and_entry('ITEM-BATCH-2', 'batch a', '_Test Warehouse - _TC') self.make_new_batch_and_entry('ITEM-BATCH-2', 'batch b', '_Test Warehouse - _TC') self.assertEquals(get_batch_qty(item_code = 'ITEM-BATCH-2', warehouse = '_Test Warehouse - _TC'), [{'batch_no': u'batch a', 'qty': 90.0}, {'batch_no': u'batch b', 'qty': 90.0}]) self.assertEquals(get_batch_qty('batch a', '_Test Warehouse - _TC'), 90)
def test_get_batch_qty(self): '''Test getting batch quantities by batch_numbers, item_code or warehouse''' self.make_batch_item('ITEM-BATCH-2') self.make_new_batch_and_entry('ITEM-BATCH-2', 'batch a', '_Test Warehouse - _TC') self.make_new_batch_and_entry('ITEM-BATCH-2', 'batch b', '_Test Warehouse - _TC') self.assertEqual(get_batch_qty(item_code = 'ITEM-BATCH-2', warehouse = '_Test Warehouse - _TC'), [{'batch_no': u'batch a', 'qty': 90.0}, {'batch_no': u'batch b', 'qty': 90.0}]) self.assertEqual(get_batch_qty('batch a', '_Test Warehouse - _TC'), 90)
def test_retain_sample(self): from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.batch.batch import get_batch_qty create_warehouse("Test Warehouse for Sample Retention") frappe.db.set_value("Stock Settings", None, "sample_retention_warehouse", "Test Warehouse for Sample Retention - _TC") item = frappe.new_doc("Item") item.item_code = "Retain Sample Item" item.item_name = "Retain Sample Item" item.description = "Retain Sample Item" item.item_group = "All Item Groups" item.is_stock_item = 1 item.has_batch_no = 1 item.create_new_batch = 1 item.retain_sample = 1 item.sample_quantity = 4 item.save() receipt_entry = frappe.new_doc("Stock Entry") receipt_entry.company = "_Test Company" receipt_entry.purpose = "Material Receipt" receipt_entry.append("items", { "item_code": item.item_code, "t_warehouse": "_Test Warehouse - _TC", "qty": 40, "basic_rate": 12, "cost_center": "_Test Cost Center - _TC", "sample_quantity": 4 }) receipt_entry.insert() receipt_entry.submit() retention_data = move_sample_to_retention_warehouse(receipt_entry.company, receipt_entry.get("items")) retention_entry = frappe.new_doc("Stock Entry") retention_entry.company = retention_data.company retention_entry.purpose = retention_data.purpose retention_entry.append("items", { "item_code": item.item_code, "t_warehouse": "Test Warehouse for Sample Retention - _TC", "s_warehouse": "_Test Warehouse - _TC", "qty": 4, "basic_rate": 12, "cost_center": "_Test Cost Center - _TC", "batch_no": receipt_entry.get("items")[0].batch_no }) retention_entry.insert() retention_entry.submit() qty_in_usable_warehouse = get_batch_qty(receipt_entry.get("items")[0].batch_no, "_Test Warehouse - _TC", "_Test Item") qty_in_retention_warehouse = get_batch_qty(receipt_entry.get("items")[0].batch_no, "Test Warehouse for Sample Retention - _TC", "_Test Item") self.assertEquals(qty_in_usable_warehouse, 36) self.assertEquals(qty_in_retention_warehouse, 4)
def test_stock_reco_for_serial_and_batch_item_with_future_dependent_entry(self): """ Behaviour: 1) Create Stock Reconciliation, which will be the origin document of a new batch having a serial no 2) Create a Stock Entry that adds a serial no to the same batch following this Stock Reconciliation 3) Cancel Stock Entry Expected Result: 3) Serial No only in the Stock Entry is Inactive and Batch qty decreases """ from erpnext.stock.doctype.batch.batch import get_batch_qty from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry item = create_item("_TestBatchSerialItemDependentReco") item.has_batch_no = 1 item.create_new_batch = 1 item.has_serial_no = 1 item.batch_number_series = "TBSD-BATCH-.##" item.serial_no_series = "TBSD-.####" item.save() warehouse = "_Test Warehouse for Stock Reco2 - _TC" stock_reco = create_stock_reconciliation( item_code=item.item_code, warehouse=warehouse, qty=1, rate=100 ) batch_no = stock_reco.items[0].batch_no reco_serial_no = get_serial_nos(stock_reco.items[0].serial_no)[0] stock_entry = make_stock_entry( item_code=item.item_code, target=warehouse, qty=1, basic_rate=100, batch_no=batch_no ) serial_no_2 = get_serial_nos(stock_entry.items[0].serial_no)[0] # Check Batch qty after 2 transactions batch_qty = get_batch_qty(batch_no, warehouse, item.item_code) self.assertEqual(batch_qty, 2) # Cancel latest stock document stock_entry.cancel() # Check Batch qty after cancellation batch_qty = get_batch_qty(batch_no, warehouse, item.item_code) self.assertEqual(batch_qty, 1) # Check if Serial No from Stock Reconcilation is intact self.assertEqual(frappe.db.get_value("Serial No", reco_serial_no, "batch_no"), batch_no) self.assertEqual(frappe.db.get_value("Serial No", reco_serial_no, "status"), "Active") # Check if Serial No from Stock Entry is Unlinked and Inactive self.assertEqual(frappe.db.get_value("Serial No", serial_no_2, "batch_no"), None) self.assertEqual(frappe.db.get_value("Serial No", serial_no_2, "status"), "Inactive") stock_reco.cancel()
def adjust_qty_for_expired_items(item_code, stock_qty, warehouse): batches = frappe.get_all('Batch', filters=[{'item': item_code}], fields=['expiry_date', 'name']) expired_batches = get_expired_batches(batches) stock_qty = [list(item) for item in stock_qty] for batch in expired_batches: if warehouse: stock_qty[0][0] = max(0, stock_qty[0][0] - get_batch_qty(batch, warehouse)) else: stock_qty[0][0] = max(0, stock_qty[0][0] - qty_from_all_warehouses(get_batch_qty(batch))) if not stock_qty[0][0]: break return stock_qty
def update_bactch_stock_status(self, cdt): if self.doctype == "Purchase Receipt": for item in self.items: if "Strip-MS" in str(item.item_code): msg = get_batch_qty(item.batch_no) total_batch_qty = 0 for x in msg: total_batch_qty +=int(x.qty) if msg: batch_msg = frappe.get_doc("Batch",item.batch_no) batch_msg.batch_stock_status = "Available" batch_msg.save() else: batch_msg = frappe.get_doc("Batch",item.batch_no) batch_msg.batch_stock_status = "Empty" batch_msg.save() elif self.doctype == "Delivery Note": for item in self.items: if "Strip-MS" in str(item.item_code): msg = get_batch_qty(item.batch_no) total_batch_qty = 0 for x in msg: total_batch_qty +=int(x.qty) if total_batch_qty>0: batch_msg = frappe.get_doc("Batch",item.batch_no) batch_msg.batch_stock_status = "Available" batch_msg.save() else: batch_msg = frappe.get_doc("Batch",item.batch_no) batch_msg.batch_stock_status = "Empty" batch_msg.save() else: for item in self.items: if "Strip-MS" in str(item.item_code): msg = get_batch_qty(item.batch_no) total_batch_qty = 0 for x in msg: total_batch_qty +=int(x.qty) if total_batch_qty>0: batch_msg = frappe.get_doc("Batch",item.batch_no) batch_msg.batch_stock_status = "Available" batch_msg.save() else: batch_msg = frappe.get_doc("Batch",item.batch_no) batch_msg.batch_stock_status = "Empty" batch_msg.save()
def validate_pos_reserved_batch_qty(self, item): filters = {"item_code": item.item_code, "warehouse": item.warehouse, "batch_no": item.batch_no} available_batch_qty = get_batch_qty(item.batch_no, item.warehouse, item.item_code) reserved_batch_qty = get_pos_reserved_batch_qty(filters) bold_item_name = frappe.bold(item.item_name) bold_extra_batch_qty_needed = frappe.bold( abs(available_batch_qty - reserved_batch_qty - item.qty) ) bold_invalid_batch_no = frappe.bold(item.batch_no) if (available_batch_qty - reserved_batch_qty) == 0: frappe.throw( _( "Row #{}: Batch No. {} of item {} has no stock available. Please select valid batch no." ).format(item.idx, bold_invalid_batch_no, bold_item_name), title=_("Item Unavailable"), ) elif (available_batch_qty - reserved_batch_qty - item.qty) < 0: frappe.throw( _( "Row #{}: Batch No. {} of item {} has less than required stock available, {} more required" ).format( item.idx, bold_invalid_batch_no, bold_item_name, bold_extra_batch_qty_needed ), title=_("Item Unavailable"), )
def test_delivery_note(self): '''Test automatic batch selection for outgoing items''' batch_qty = 15 receipt = self.test_purchase_receipt(batch_qty) delivery_note = frappe.get_doc(dict( doctype = 'Delivery Note', customer = '_Test Customer', company = receipt.company, items = [ dict( item_code = 'ITEM-BATCH-1', qty = batch_qty, rate = 10, warehouse = receipt.items[0].warehouse ) ] )).insert() delivery_note.submit() # shipped with same batch self.assertEquals(delivery_note.items[0].batch_no, receipt.items[0].batch_no) # balance is 0 self.assertEquals(get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), 0)
def test_stock_entry_outgoing(self): '''Test automatic batch selection for outgoing stock entry''' batch_qty = 16 receipt = self.test_purchase_receipt(batch_qty) stock_entry = frappe.get_doc(dict( doctype = 'Stock Entry', purpose = 'Material Issue', company = receipt.company, items = [ dict( item_code = 'ITEM-BATCH-1', qty = batch_qty, s_warehouse = receipt.items[0].warehouse, ) ] )).insert() stock_entry.submit() # assert same batch is selected self.assertEqual(stock_entry.items[0].batch_no, receipt.items[0].batch_no) # balance is 0 self.assertEquals(get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), 0)
def get_transfer_items(work_order, items): from erpnext.stock.doctype.batch.batch import get_batch_qty if isinstance(items, basestring): items = json.loads(items) se_data = frappe.get_list("Stock Entry", filters={ "purpose": "Material Transfer for Manufacture", "work_order": work_order, "docstatus": 1 }) items_list = [] for se in se_data: doc = frappe.get_doc("Stock Entry", se.name) items_list += [ row for row in doc.items if get_batch_qty(batch_no=row.batch_no, warehouse=row.t_warehouse, item_code=row.item_code) ] items_list += items[-1:] return items_list
def update_batch_expired_patch(): batch_items = frappe.db.sql ("""SELECT name, item, expiry_date, expiry_start_day FROM `tabBatch` where expiry_status IN ('Expired')""", as_dict=1) for d in batch_items: if formatdate(d.expiry_date) == "06-09-2017": from erpnext.stock.doctype.batch.batch import get_batch_qty batch_qty = get_batch_qty(d.name, "DM Arcadia - MS", None) #frappe.throw(_("batch qty is {0}").format(batch_qty)) batch_doc = frappe.get_doc("Batch", d.name) #days_to_expiry = frappe.utils.date_diff(d.expiry_date, nowdate()) #batch_doc.days_to_expiry = days_to_expiry if flt(batch_qty) > 0: _data = frappe.db.sql ("""SELECT data from `tabVersion` where docname=%s and ref_doctype='Batch' order by modified desc limit 1""", d.name, as_dict=0) #frappe.throw(_("{0}").format(_data)) _data = json.loads(_data[0][0]) _changes = _data.get("changed") #frappe.throw(_("{0}").format(_changes)) if _changes: print(d.name) batch_doc.days_to_expiry = _changes[2][1] batch_doc.expiry_status = _changes[0][1] batch_doc.expiry_date = get_datetime(formatdate(_changes[1][1])) batch_doc.save()
def get_stock_balance_for(item_code, warehouse, posting_date, posting_time, batch_no=None, with_valuation_rate=True): frappe.has_permission("Stock Reconciliation", "write", throw=True) item_dict = frappe.db.get_value("Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=1) serial_nos = "" with_serial_no = True if item_dict.get("has_serial_no") else False data = get_stock_balance(item_code, warehouse, posting_date, posting_time, with_valuation_rate=with_valuation_rate, with_serial_no=with_serial_no) if with_serial_no: qty, rate, serial_nos = data else: qty, rate = data if item_dict.get("has_batch_no"): qty = get_batch_qty(batch_no, warehouse) or 0 return {'qty': qty, 'rate': rate, 'serial_nos': serial_nos}
def update_batch_expired_date_daily(): batch_items = frappe.db.sql ("""SELECT name, item, expiry_date, expiry_start_day FROM `tabBatch` where expiry_date <> '' and days_to_expiry >= 0 and expiry_status NOT IN ('Not Set','Expired')""", as_dict=1) for d in batch_items: if d.expiry_date: from erpnext.stock.doctype.batch.batch import get_batch_qty batch_qty = get_batch_qty(d.name, "DM Arcadia - MS", None) batch_doc = frappe.get_doc("Batch", d.name) days_to_expiry = frappe.utils.date_diff(d.expiry_date, nowdate()) batch_doc.days_to_expiry = days_to_expiry if batch_qty and flt(batch_qty) <= 0: batch_doc.expiry_date = nowdate() batch_doc.expiry_status = "Expired" batch_doc.days_to_expiry = 0 elif int(days_to_expiry) <= int(d.expiry_start_day) and int(days_to_expiry) > 30: batch_doc.expiry_status = "Expired Soon" elif int(days_to_expiry) <= 30 and int(days_to_expiry) > 0: batch_doc.expiry_status = "Expired Very Soon" elif int(days_to_expiry) <= 0: batch_doc.expiry_status = "Expired" else: batch_doc.expiry_status = "Open" batch_doc.save()
def test_stock_entry_incoming(self): """Test batch creation via Stock Entry (Work Order)""" self.make_batch_item("ITEM-BATCH-1") stock_entry = frappe.get_doc( dict( doctype="Stock Entry", purpose="Material Receipt", company="_Test Company", items=[ dict( item_code="ITEM-BATCH-1", qty=90, t_warehouse="_Test Warehouse - _TC", cost_center="Main - _TC", rate=10, ) ], )) stock_entry.set_stock_entry_type() stock_entry.insert() stock_entry.submit() self.assertTrue(stock_entry.items[0].batch_no) self.assertEqual( get_batch_qty(stock_entry.items[0].batch_no, stock_entry.items[0].t_warehouse), 90)
def validate_sample_quantity(item_code, sample_quantity, qty, batch_no=None): if cint(qty) < cint(sample_quantity): frappe.throw( _("Sample quantity {0} cannot be more than received quantity {1}"). format(sample_quantity, qty)) retention_warehouse = frappe.db.get_single_value( 'Stock Settings', 'sample_retention_warehouse') retainted_qty = 0 if batch_no: retainted_qty = get_batch_qty(batch_no, retention_warehouse, item_code) max_retain_qty = frappe.get_value('Item', item_code, 'sample_quantity') if retainted_qty >= max_retain_qty: frappe.msgprint(_( "Maximum Samples - {0} have already been retained for Batch {1} and Item {2} in Batch {3}." ).format(retainted_qty, batch_no, item_code, batch_no), alert=True) sample_quantity = 0 qty_diff = max_retain_qty - retainted_qty if cint(sample_quantity) > cint(qty_diff): frappe.msgprint(_( "Maximum Samples - {0} can be retained for Batch {1} and Item {2}." ).format(max_retain_qty, batch_no, item_code), alert=True) sample_quantity = qty_diff return sample_quantity
def test_stock_entry_incoming(self): '''Test batch creation via Stock Entry (Work Order)''' self.make_batch_item('ITEM-BATCH-1') stock_entry = frappe.get_doc( dict(doctype='Stock Entry', purpose='Material Receipt', company='_Test Company', items=[ dict(item_code='ITEM-BATCH-1', qty=90, t_warehouse='_Test Warehouse - _TC', cost_center='Main - _TC', rate=10) ])) stock_entry.set_stock_entry_type() stock_entry.insert() stock_entry.submit() self.assertTrue(stock_entry.items[0].batch_no) self.assertEqual( get_batch_qty(stock_entry.items[0].batch_no, stock_entry.items[0].t_warehouse), 90)
def get_items_details(pos_profile, items_data): pos_profile = json.loads(pos_profile) items_data = json.loads(items_data) warehouse = pos_profile.get("warehouse") result = [] if len(items_data) > 0: for item in items_data: item_code = item.get("item_code") item_stock_qty = get_stock_availability(item_code, warehouse) has_batch_no, has_serial_no = frappe.get_value( "Item", item_code, ["has_batch_no", "has_serial_no"]) uoms = frappe.get_all("UOM Conversion Detail", filters={"parent": item_code}, fields=["uom", "conversion_factor"]) serial_no_data = frappe.get_all('Serial No', filters={ "item_code": item_code, "status": "Active" }, fields=["name as serial_no"]) batch_no_data = [] from erpnext.stock.doctype.batch.batch import get_batch_qty batch_list = get_batch_qty(warehouse=warehouse, item_code=item_code) for batch in batch_list: if batch.qty > 0 and batch.batch_no: batch_doc = frappe.get_doc("Batch", batch.batch_no) if (str(batch_doc.expiry_date) > str(nowdate()) or batch_doc.expiry_date in ["", None]) and batch_doc.disabled == 0: batch_no_data.append({ "batch_no": batch.batch_no, "batch_qty": batch.qty, "expiry_date": batch_doc.expiry_date, "btach_price": batch_doc.posa_btach_price, }) row = {} row.update(item) row.update({ 'item_uoms': uoms or [], 'serial_no_data': serial_no_data or [], 'batch_no_data': batch_no_data or [], 'actual_qty': item_stock_qty or 0, 'has_batch_no': has_batch_no, 'has_serial_no': has_serial_no, }) result.append(row) return result
def test_get_batch_qty(self): """Test getting batch quantities by batch_numbers, item_code or warehouse""" self.make_batch_item("ITEM-BATCH-2") self.make_new_batch_and_entry("ITEM-BATCH-2", "batch a", "_Test Warehouse - _TC") self.make_new_batch_and_entry("ITEM-BATCH-2", "batch b", "_Test Warehouse - _TC") self.assertEqual( get_batch_qty(item_code="ITEM-BATCH-2", warehouse="_Test Warehouse - _TC"), [{ "batch_no": "batch a", "qty": 90.0 }, { "batch_no": "batch b", "qty": 90.0 }], ) self.assertEqual(get_batch_qty("batch a", "_Test Warehouse - _TC"), 90)
def validate_batch_actual_qty(self): from erpnext.stock.doctype.batch.batch import get_batch_qty for d in self.get("purchase_receipts"): doc = frappe.get_doc(d.receipt_document_type, d.receipt_document) for row in doc.items: if row.batch_no: batch_qty = get_batch_qty(row.batch_no, row.warehouse) if batch_qty < row.stock_qty: frappe.throw(_("The batch <b>{0}</b> does not have sufficient quantity for item <b>{1}</b> in row {2}.".format(row.batch_no, row.item_code, d.idx)))
def batch_qty(batch_no, s_warehouse): batch_doc = frappe.get_doc('Batch', batch_no) required_item = batch_doc.item s_batch_qty = get_batch_qty(batch_no, s_warehouse) strip_bin = get_bin(required_item, s_warehouse) #s_reserved_qty = strip_bin.reserved_qty_for_production s_reserved_qty = batch_doc.allocated_quantity available_batch_qty = s_batch_qty - s_reserved_qty return { 'available_qty': available_batch_qty, 'required_item': required_item }
def get_stock_balance_for( item_code: str, warehouse: str, posting_date: str, posting_time: str, batch_no: Optional[str] = None, with_valuation_rate: bool = True, ): frappe.has_permission("Stock Reconciliation", "write", throw=True) item_dict = frappe.get_cached_value("Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=1) if not item_dict: # In cases of data upload to Items table msg = _("Item {} does not exist.").format(item_code) frappe.throw(msg, title=_("Missing")) serial_nos = None has_serial_no = bool(item_dict.get("has_serial_no")) has_batch_no = bool(item_dict.get("has_batch_no")) if not batch_no and has_batch_no: # Not enough information to fetch data return {"qty": 0, "rate": 0, "serial_nos": None} # TODO: fetch only selected batch's values data = get_stock_balance( item_code, warehouse, posting_date, posting_time, with_valuation_rate=with_valuation_rate, with_serial_no=has_serial_no, ) if has_serial_no: qty, rate, serial_nos = data else: qty, rate = data if item_dict.get("has_batch_no"): qty = (get_batch_qty(batch_no, warehouse, posting_date=posting_date, posting_time=posting_time) or 0) return {"qty": qty, "rate": rate, "serial_nos": serial_nos}
def get_stock_balance_for(item_code, warehouse, posting_date, posting_time, batch_no=None, with_valuation_rate=True): frappe.has_permission("Stock Reconciliation", "write", throw=True) item_dict = frappe.db.get_value("Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=1) serial_nos = "" if item_dict.get("has_serial_no"): qty, rate, serial_nos = get_qty_rate_for_serial_nos( item_code, warehouse, posting_date, posting_time, item_dict) else: qty, rate = get_stock_balance(item_code, warehouse, posting_date, posting_time, with_valuation_rate=with_valuation_rate) if item_dict.get("has_batch_no"): qty = get_batch_qty(batch_no, warehouse) or 0 stock_val = frappe.db.sql( """SELECT sum(stock_value),sum(actual_qty) from tabBin where item_code='{}'""" .format(item_code)) warehouse_val = frappe.db.sql( """SELECT stock_value,actual_qty from tabBin where item_code='{}' and warehouse = '{}' """ .format(item_code, warehouse)) valuation_rate = 0.00 if stock_val: if stock_val[0][0] and stock_val[0][1]: valuation_rate = stock_val[0][0] / stock_val[0][1] if warehouse_val: if warehouse_val[0][0] and warehouse_val[0][1]: rate = warehouse_val[0][0] / warehouse_val[0][1] return { 'qty': qty, 'rate': rate, 'serial_nos': serial_nos, 'valuation_rate': valuation_rate }
def test_serial_batch_item_stock_entry(self): """ Behaviour: 1) Submit Stock Entry (Receipt) with Serial & Batched Item 2) Cancel same Stock Entry Expected Result: 1) Batch is created with Reference in Serial No 2) Batch is deleted and Serial No is Inactive """ from erpnext.stock.doctype.batch.batch import get_batch_qty item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item'}) if not item: item = create_item("Batched and Serialised Item") item.has_batch_no = 1 item.create_new_batch = 1 item.has_serial_no = 1 item.batch_number_series = "B-BATCH-.##" item.serial_no_series = "S-.####" item.save() else: item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item'}) se = make_stock_entry(item_code=item.item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100) batch_no = se.items[0].batch_no serial_no = get_serial_nos(se.items[0].serial_no)[0] batch_qty = get_batch_qty(batch_no, "_Test Warehouse - _TC", item.item_code) batch_in_serial_no = frappe.db.get_value("Serial No", serial_no, "batch_no") self.assertEqual(batch_in_serial_no, batch_no) self.assertEqual(batch_qty, 1) se.cancel() batch_in_serial_no = frappe.db.get_value("Serial No", serial_no, "batch_no") self.assertEqual(batch_in_serial_no, None) self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Inactive") self.assertEqual(frappe.db.exists("Batch", batch_no), None)
def validate_batch_item(sales_order, method): for item in sales_order.items: qty = item.stock_qty or item.transfer_qty or item.qty or 0 has_batch_no = frappe.db.get_value('Item', item.item_code, 'has_batch_no') warehouse = item.warehouse if has_batch_no and warehouse and qty > 0: if not item.batch_no: return batch_qty = get_batch_qty(batch_no=item.batch_no, warehouse=warehouse) if flt(batch_qty, item.precision("qty")) < flt(qty, item.precision("qty")): frappe.throw(_(""" Row #{0}: The batch {1} has only {2} qty. Either select a different batch that has more than {3} qty available, or split the row to sell from multiple batches. """).format(item.idx, item.batch_no, batch_qty, qty))
def update_bin_batch(self): coil_bin = get_bin(self.required_item, self.s_warehouse) if self.batch_qty != coil_bin.projected_qty: frappe.throw('Batch available quantity is: {0} kg'.format( coil_bin.projected_qty)) update_projected_qty(self.required_item, self.s_warehouse, None, None, self.allocate_quantity) update_projected_qty(self.required_item, self.wip_warehouse, self.allocate_quantity, None, self.allocate_quantity) batch = frappe.get_doc('Batch', self.batch_no) s_batch_qty = get_batch_qty(self.batch_no, self.s_warehouse) s_reserved_qty = coil_bin.reserved_qty_for_production available_batch_qty = s_batch_qty - s_reserved_qty if available_batch_qty == 0: batch.db_set('batch_stock_status', 'Empty')
def validate_sample_quantity(item_code, sample_quantity, qty, batch_no = None): if cint(qty) < cint(sample_quantity): frappe.throw(_("Sample quantity {0} cannot be more than received quantity {1}").format(sample_quantity, qty)) retention_warehouse = frappe.db.get_single_value('Stock Settings', 'sample_retention_warehouse') retainted_qty = 0 if batch_no: retainted_qty = get_batch_qty(batch_no, retention_warehouse, item_code) max_retain_qty = frappe.get_value('Item', item_code, 'sample_quantity') if retainted_qty >= max_retain_qty: frappe.msgprint(_("Maximum Samples - {0} have already been retained for Batch {1} and Item {2} in Batch {3}."). format(retainted_qty, batch_no, item_code, batch_no), alert=True) sample_quantity = 0 qty_diff = max_retain_qty-retainted_qty if cint(sample_quantity) > cint(qty_diff): frappe.msgprint(_("Maximum Samples - {0} can be retained for Batch {1} and Item {2}."). format(max_retain_qty, batch_no, item_code), alert=True) sample_quantity = qty_diff return sample_quantity
def set_batch_nos_for_bundels(doc, warehouse_field, throw=False): """Automatically select `batch_no` for outgoing items in item table""" for d in doc.packed_items: qty = d.get("stock_qty") or d.get("transfer_qty") or d.get("qty") or 0 has_batch_no = frappe.db.get_value("Item", d.item_code, "has_batch_no") warehouse = d.get(warehouse_field, None) if has_batch_no and warehouse and qty > 0: if not d.batch_no: d.batch_no = get_batch_no(d.item_code, warehouse, qty, throw, d.serial_no) else: batch_qty = get_batch_qty(batch_no=d.batch_no, warehouse=warehouse) if flt(batch_qty, d.precision("qty")) < flt( qty, d.precision("qty")): frappe.throw( _("Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches" ).format(d.idx, d.batch_no, batch_qty, qty))
def test_purchase_receipt(self, batch_qty=100): '''Test automated batch creation from Purchase Receipt''' self.make_batch_item('ITEM-BATCH-1') receipt = frappe.get_doc( dict( doctype='Purchase Receipt', supplier='_Test Supplier', items=[dict(item_code='ITEM-BATCH-1', qty=batch_qty, rate=10)])).insert() receipt.submit() self.assertTrue(receipt.items[0].batch_no) self.assertEqual( get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), batch_qty) return receipt
def get_stock_balance_for(item_code, warehouse, posting_date, posting_time, batch_no=None, with_valuation_rate=True): frappe.has_permission("Stock Reconciliation", "write", throw=True) item_dict = frappe.db.get_value("Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=1) if not item_dict: # In cases of data upload to Items table msg = _("Item {} does not exist.").format(item_code) frappe.throw(msg, title=_("Missing")) serial_nos = "" with_serial_no = True if item_dict.get("has_serial_no") else False data = get_stock_balance( item_code, warehouse, posting_date, posting_time, with_valuation_rate=with_valuation_rate, with_serial_no=with_serial_no, ) if with_serial_no: qty, rate, serial_nos = data else: qty, rate = data if item_dict.get("has_batch_no"): qty = (get_batch_qty(batch_no, warehouse, posting_date=posting_date, posting_time=posting_time) or 0) return {"qty": qty, "rate": rate, "serial_nos": serial_nos}
def test_purchase_receipt(self, batch_qty = 100): '''Test automated batch creation from Purchase Receipt''' self.make_batch_item('ITEM-BATCH-1') receipt = frappe.get_doc(dict( doctype='Purchase Receipt', supplier='_Test Supplier', items=[ dict( item_code='ITEM-BATCH-1', qty=batch_qty, rate=10 ) ] )).insert() receipt.submit() self.assertTrue(receipt.items[0].batch_no) self.assertEquals(get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), batch_qty) return receipt
def test_stock_entry_incoming(self): '''Test batch creation via Stock Entry (Production Order)''' self.make_batch_item('ITEM-BATCH-1') stock_entry = frappe.get_doc(dict( doctype = 'Stock Entry', purpose = 'Material Receipt', company = '_Test Company', items = [ dict( item_code = 'ITEM-BATCH-1', qty = 90, t_warehouse = '_Test Warehouse - _TC', cost_center = 'Main - _TC', rate = 10 ) ] )).insert() stock_entry.submit() self.assertTrue(stock_entry.items[0].batch_no) self.assertEquals(get_batch_qty(stock_entry.items[0].batch_no, stock_entry.items[0].t_warehouse), 90)
def get_batch_qty(batch_no, warehouse, item_code): from erpnext.stock.doctype.batch import batch if batch_no: return {'actual_batch_qty': batch.get_batch_qty(batch_no, warehouse)}