def test_stock_reco_for_serialized_item(self): to_delete_records = [] to_delete_serial_nos = [] # Add new serial nos serial_item_code = "Stock-Reco-Serial-Item-1" serial_warehouse = "_Test Warehouse for Stock Reco1 - _TC" sr = create_stock_reconciliation(item_code=serial_item_code, warehouse = serial_warehouse, qty=5, rate=200) serial_nos = get_serial_nos(sr.items[0].serial_no) self.assertEqual(len(serial_nos), 5) args = { "item_code": serial_item_code, "warehouse": serial_warehouse, "posting_date": nowdate(), "posting_time": nowtime(), "serial_no": sr.items[0].serial_no } valuation_rate = get_incoming_rate(args) self.assertEqual(valuation_rate, 200) to_delete_records.append(sr.name) sr = create_stock_reconciliation(item_code=serial_item_code, warehouse = serial_warehouse, qty=5, rate=300) serial_nos1 = get_serial_nos(sr.items[0].serial_no) self.assertEqual(len(serial_nos1), 5) args = { "item_code": serial_item_code, "warehouse": serial_warehouse, "posting_date": nowdate(), "posting_time": nowtime(), "serial_no": sr.items[0].serial_no } valuation_rate = get_incoming_rate(args) self.assertEqual(valuation_rate, 300) to_delete_records.append(sr.name) to_delete_records.reverse() for d in to_delete_records: stock_doc = frappe.get_doc("Stock Reconciliation", d) stock_doc.cancel()
def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None): from erpbee.stock.doctype.serial_no.serial_no import get_serial_nos cond = '' if serial_no and frappe.get_cached_value('Item', item_code, 'has_batch_no'): serial_nos = get_serial_nos(serial_no) batch = frappe.get_all("Serial No", fields=["distinct batch_no"], filters={ "item_code": item_code, "warehouse": warehouse, "name": ("in", serial_nos) }) if not batch: validate_serial_no_with_batch(serial_nos, item_code) if batch and len(batch) > 1: return [] cond = " and `tabBatch`.name = %s" % (frappe.db.escape( batch[0].batch_no)) return frappe.db.sql(""" select batch_id, sum(`tabStock Ledger Entry`.actual_qty) as qty from `tabBatch` join `tabStock Ledger Entry` ignore index (item_code, warehouse) on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no ) where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL) {0} group by batch_id order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC """.format(cond), (item_code, warehouse), as_dict=True)
def validate_serialised_or_batched_item(self): error_msg = [] for d in self.get("items"): serialized = d.get("has_serial_no") batched = d.get("has_batch_no") no_serial_selected = not d.get("serial_no") no_batch_selected = not d.get("batch_no") msg = "" item_code = frappe.bold(d.item_code) serial_nos = get_serial_nos(d.serial_no) if serialized and batched and (no_batch_selected or no_serial_selected): msg = (_( 'Row #{}: Please select a serial no and batch against item: {} or remove it to complete transaction.' ).format(d.idx, item_code)) elif serialized and no_serial_selected: msg = (_( 'Row #{}: No serial number selected against item: {}. Please select one or remove it to complete transaction.' ).format(d.idx, item_code)) elif batched and no_batch_selected: msg = (_( 'Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.' ).format(d.idx, item_code)) elif serialized and not no_serial_selected and len( serial_nos) != d.qty: msg = ( _("Row #{}: You must select {} serial numbers for item {}." ).format(d.idx, frappe.bold(cint(d.qty)), item_code)) if msg: error_msg.append(msg) if error_msg: frappe.throw(error_msg, title=_("Invalid Item"), as_list=True)
def update_rate_in_serial_no_for_non_asset_items(self, receipt_document): for item in receipt_document.get("items"): if not item.is_fixed_asset and item.serial_no: serial_nos = get_serial_nos(item.serial_no) if serial_nos: frappe.db.sql( "update `tabSerial No` set purchase_rate=%s where name in ({0})" .format(", ".join(["%s"] * len(serial_nos))), tuple([item.valuation_rate] + serial_nos))
def test_serialized_partial_sales_invoice(self): se = make_serialized_item() serial_no = get_serial_nos(se.get("items")[0].serial_no) serial_no = '\n'.join(serial_no) dn = create_delivery_note( item_code="_Test Serialized Item With Series", qty=2, serial_no=serial_no) si = make_sales_invoice(dn.name) si.items[0].qty = 1 si.submit() self.assertEqual(si.items[0].qty, 1) si = make_sales_invoice(dn.name) si.submit() self.assertEqual(si.items[0].qty, len(get_serial_nos(si.items[0].serial_no)))
def test_serialized_item_transaction(self): from erpbee.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpbee.stock.doctype.serial_no.serial_no import get_serial_nos se = make_serialized_item(company='_Test Company', target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC') serial_nos = get_serial_nos(se.get("items")[0].serial_no) pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC', account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC', expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC', item=se.get("items")[0].item_code, rate=1000, do_not_save=1) pos.get("items")[0].serial_no = serial_nos[0] pos.append( "payments", { 'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000 }) pos.insert() pos.submit() pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC', account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC', expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC', item=se.get("items")[0].item_code, rate=1000, do_not_save=1) pos2.get("items")[0].serial_no = serial_nos[0] pos2.append( "payments", { 'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000 }) self.assertRaises(frappe.ValidationError, pos2.insert)
def validate_stock_availablility(self): if self.is_return: return allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock') error_msg = [] for d in self.get('items'): msg = "" if d.serial_no: filters = {"item_code": d.item_code, "warehouse": d.warehouse} if d.batch_no: filters["batch_no"] = d.batch_no reserved_serial_nos = get_pos_reserved_serial_nos(filters) serial_nos = get_serial_nos(d.serial_no) invalid_serial_nos = [ s for s in serial_nos if s in reserved_serial_nos ] bold_invalid_serial_nos = frappe.bold( ', '.join(invalid_serial_nos)) if len(invalid_serial_nos) == 1: msg = (_( "Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no." ).format(d.idx, bold_invalid_serial_nos)) elif invalid_serial_nos: msg = (_( "Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no." ).format(d.idx, bold_invalid_serial_nos)) else: if allow_negative_stock: return available_stock = get_stock_availability( d.item_code, d.warehouse) item_code, warehouse, qty = frappe.bold( d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty) if flt(available_stock) <= 0: msg = (_( 'Row #{}: Item Code: {} is not available under warehouse {}.' ).format(d.idx, item_code, warehouse)) elif flt(available_stock) < flt(d.qty): msg = (_( 'Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.' ).format(d.idx, item_code, warehouse, qty)) if msg: error_msg.append(msg) if error_msg: frappe.throw(error_msg, title=_("Item Unavailable"), as_list=True)
def validate_serialized_batch(self): from erpbee.stock.doctype.serial_no.serial_no import get_serial_nos for d in self.get("items"): if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no: serial_nos = get_serial_nos(d.serial_no) for serial_no_data in frappe.get_all("Serial No", filters={"name": ("in", serial_nos)}, fields=["batch_no", "name"]): if serial_no_data.batch_no != d.batch_no: frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}") .format(d.idx, serial_no_data.name, d.batch_no)) if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2: expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date") if expiry_date and getdate(expiry_date) < getdate(self.posting_date): frappe.throw(_("Row #{0}: The batch {1} has already expired.") .format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))
def test_return_for_serialized_items(self): se = make_serialized_item() serial_no = get_serial_nos(se.get("items")[0].serial_no)[0] dn = create_delivery_note( item_code="_Test Serialized Item With Series", rate=500, serial_no=serial_no) self.check_serial_no_values(serial_no, { "warehouse": "", "delivery_document_no": dn.name }) # return entry dn1 = create_delivery_note( item_code="_Test Serialized Item With Series", is_return=1, return_against=dn.name, qty=-1, rate=500, serial_no=serial_no) self.check_serial_no_values(serial_no, { "warehouse": "_Test Warehouse - _TC", "delivery_document_no": "" }) dn1.cancel() self.check_serial_no_values(serial_no, { "warehouse": "", "delivery_document_no": dn.name }) dn.cancel() self.check_serial_no_values( serial_no, { "warehouse": "_Test Warehouse - _TC", "delivery_document_no": "", "purchase_document_no": se.name })
def update_available_serial_nos(available_serial_nos, sle): serial_nos = get_serial_nos(sle.serial_no) key = (sle.item_code, sle.warehouse) if key not in available_serial_nos: available_serial_nos.setdefault(key, []) existing_serial_no = available_serial_nos[key] for sn in serial_nos: if sle.actual_qty > 0: if sn in existing_serial_no: existing_serial_no.remove(sn) else: existing_serial_no.append(sn) else: if sn in existing_serial_no: existing_serial_no.remove(sn) else: existing_serial_no.append(sn) sle.balance_serial_no = '\n'.join(existing_serial_no)
def get_returned_serial_nos(child_doc, parent_doc): from erpbee.stock.doctype.serial_no.serial_no import get_serial_nos return_ref_field = frappe.scrub(child_doc.doctype) if child_doc.doctype == "Delivery Note Item": return_ref_field = "dn_detail" serial_nos = [] fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)] filters = [[parent_doc.doctype, "return_against", "=", parent_doc.name], [parent_doc.doctype, "is_return", "=", 1], [child_doc.doctype, return_ref_field, "=", child_doc.name], [parent_doc.doctype, "docstatus", "=", 1]] for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters): serial_nos.extend(get_serial_nos(row.serial_no)) return serial_nos
def get_sle_for_items(self, row, serial_nos=None): """Insert Stock Ledger Entries""" if not serial_nos and row.serial_no: serial_nos = get_serial_nos(row.serial_no) data = frappe._dict({ "doctype": "Stock Ledger Entry", "item_code": row.item_code, "warehouse": row.warehouse, "posting_date": self.posting_date, "posting_time": self.posting_time, "voucher_type": self.doctype, "voucher_no": self.name, "voucher_detail_no": row.name, "company": self.company, "stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"), "is_cancelled": 1 if self.docstatus == 2 else 0, "serial_no": '\n'.join(serial_nos) if serial_nos else '', "batch_no": row.batch_no, "valuation_rate": flt(row.valuation_rate, row.precision("valuation_rate")) }) if not row.batch_no: data.qty_after_transaction = flt(row.qty, row.precision("qty")) if self.docstatus == 2 and not row.batch_no: if row.current_qty: data.actual_qty = -1 * row.current_qty data.qty_after_transaction = flt(row.current_qty) data.valuation_rate = flt(row.current_valuation_rate) data.stock_value = data.qty_after_transaction * data.valuation_rate data.stock_value_difference = -1 * flt(row.amount_difference) else: data.actual_qty = row.qty data.qty_after_transaction = 0.0 data.valuation_rate = flt(row.valuation_rate) data.stock_value_difference = -1 * flt(row.amount_difference) return data
def make_sle_on_cancel(self): sl_entries = [] has_serial_no = False for row in self.items: if row.serial_no or row.batch_no or row.current_serial_no: has_serial_no = True serial_nos = '' if row.current_serial_no: serial_nos = get_serial_nos(row.current_serial_no) sl_entries.append(self.get_sle_for_items(row, serial_nos)) else: sl_entries.append(self.get_sle_for_items(row)) if sl_entries: if has_serial_no: sl_entries = self.merge_similar_item_serial_nos(sl_entries) sl_entries.reverse() allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock") self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)
def test_inter_company_transfer(self): se = make_serialized_item(target_warehouse="_Test Warehouse - _TC") serial_nos = get_serial_nos(se.get("items")[0].serial_no) create_delivery_note(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0]) wh = create_warehouse("_Test Warehouse", company="_Test Company 1") make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh) serial_no = frappe.db.get_value("Serial No", serial_nos[0], ["warehouse", "company"], as_dict=1) self.assertEqual(serial_no.warehouse, wh) self.assertEqual(serial_no.company, "_Test Company 1")
def test_serialized(self): se = make_serialized_item() serial_no = get_serial_nos(se.get("items")[0].serial_no)[0] dn = create_delivery_note( item_code="_Test Serialized Item With Series", serial_no=serial_no) self.check_serial_no_values(serial_no, { "warehouse": "", "delivery_document_no": dn.name }) si = make_sales_invoice(dn.name) si.insert(ignore_permissions=True) self.assertEqual(dn.items[0].serial_no, si.items[0].serial_no) dn.cancel() self.check_serial_no_values(serial_no, { "warehouse": "_Test Warehouse - _TC", "delivery_document_no": "" })
def get_ref_item_dict(valid_items, ref_item_row): from erpbee.stock.doctype.serial_no.serial_no import get_serial_nos valid_items.setdefault( ref_item_row.item_code, frappe._dict({ "qty": 0, "rate": 0, "stock_qty": 0, "rejected_qty": 0, "received_qty": 0, "serial_no": [], "conversion_factor": ref_item_row.get("conversion_factor", 1), "batch_no": [] })) item_dict = valid_items[ref_item_row.item_code] item_dict["qty"] += ref_item_row.qty item_dict["stock_qty"] += ref_item_row.get('stock_qty', 0) if ref_item_row.get("rate", 0) > item_dict["rate"]: item_dict["rate"] = ref_item_row.get("rate", 0) if ref_item_row.parenttype in ['Purchase Invoice', 'Purchase Receipt']: item_dict["received_qty"] += ref_item_row.received_qty item_dict["rejected_qty"] += ref_item_row.rejected_qty if ref_item_row.get("serial_no"): item_dict["serial_no"] += get_serial_nos(ref_item_row.serial_no) if ref_item_row.get("batch_no"): item_dict["batch_no"].append(ref_item_row.batch_no) return valid_items
def validate_return_items_qty(self): if not self.get("is_return"): return for d in self.get("items"): if d.get("qty") > 0: frappe.throw(_( "Row #{}: You cannot add postive quantities in a return invoice. Please remove item {} to complete the return." ).format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item")) if d.get("serial_no"): serial_nos = get_serial_nos(d.serial_no) for sr in serial_nos: serial_no_exists = frappe.db.exists( "POS Invoice Item", { "parent": self.return_against, "serial_no": ["like", d.get("serial_no")] }) if not serial_no_exists: bold_return_against = frappe.bold(self.return_against) bold_serial_no = frappe.bold(sr) frappe.throw( _("Row #{}: Serial No {} cannot be returned since it was not transacted in original invoice {}" ).format(d.idx, bold_serial_no, bold_return_against))
def validate_returned_items(doc): from erpbee.stock.doctype.serial_no.serial_no import get_serial_nos valid_items = frappe._dict() select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor" if doc.doctype != 'Purchase Invoice': select_fields += ",serial_no, batch_no" if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']: select_fields += ",rejected_qty, received_qty" for d in frappe.db.sql( """select {0} from `tab{1} Item` where parent = %s""".format( select_fields, doc.doctype), doc.return_against, as_dict=1): valid_items = get_ref_item_dict(valid_items, d) if doc.doctype in ("Delivery Note", "Sales Invoice"): for d in frappe.db.sql( """select item_code, qty, serial_no, batch_no from `tabPacked Item` where parent = %s""".format(doc.doctype), doc.return_against, as_dict=1): valid_items = get_ref_item_dict(valid_items, d) already_returned_items = get_already_returned_items(doc) # ( not mandatory when it is Purchase Invoice or a Sales Invoice without Update Stock ) warehouse_mandatory = not ( (doc.doctype == "Purchase Invoice" or doc.doctype == "Sales Invoice") and not doc.update_stock) items_returned = False 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 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()) validate_quantity(doc, d, ref, valid_items, already_returned_items) if ref.rate and doc.doctype in ("Delivery Note", "Sales Invoice") and flt( d.rate) > ref.rate: frappe.throw( _("Row # {0}: Rate cannot be greater than the rate used in {1} {2}" ).format(d.idx, doc.doctype, doc.return_against)) elif ref.batch_no and d.batch_no not in ref.batch_no: frappe.throw( _("Row # {0}: Batch No must be same as {1} {2}"). format(d.idx, doc.doctype, doc.return_against)) elif ref.serial_no: if not d.serial_no: frappe.throw( _("Row # {0}: Serial No is mandatory").format( d.idx)) else: serial_nos = get_serial_nos(d.serial_no) for s in serial_nos: if s not in ref.serial_no: frappe.throw( _("Row # {0}: Serial No {1} does not match with {2} {3}" ).format(d.idx, s, doc.doctype, doc.return_against)) if warehouse_mandatory and frappe.db.get_value("Item", d.item_code, "is_stock_item") \ and not d.get("warehouse"): frappe.throw(_("Warehouse is mandatory")) items_returned = True elif d.item_name: items_returned = True if not items_returned: frappe.throw( _("Atleast one item should be entered with negative quantity in return document" ))
def update_item(source_doc, target_doc, source_parent): target_doc.qty = -1 * source_doc.qty if source_doc.serial_no: returned_serial_nos = get_returned_serial_nos( source_doc, source_parent) serial_nos = list( set(get_serial_nos(source_doc.serial_no)) - set(returned_serial_nos)) if serial_nos: target_doc.serial_no = '\n'.join(serial_nos) if doctype == "Purchase Receipt": returned_qty_map = get_returned_qty_map_for_row( source_doc.name, doctype) target_doc.received_qty = -1 * flt(source_doc.received_qty - ( returned_qty_map.get('received_qty') or 0)) target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - ( returned_qty_map.get('rejected_qty') or 0)) target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0)) target_doc.stock_qty = -1 * flt(source_doc.stock_qty - ( returned_qty_map.get('stock_qty') or 0)) target_doc.received_stock_qty = -1 * flt( source_doc.received_stock_qty - (returned_qty_map.get('received_stock_qty') or 0)) 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": returned_qty_map = get_returned_qty_map_for_row( source_doc.name, doctype) target_doc.received_qty = -1 * flt(source_doc.received_qty - ( returned_qty_map.get('received_qty') or 0)) target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - ( returned_qty_map.get('rejected_qty') or 0)) target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0)) target_doc.stock_qty = -1 * flt(source_doc.stock_qty - ( returned_qty_map.get('stock_qty') or 0)) target_doc.purchase_order = source_doc.purchase_order target_doc.purchase_receipt = source_doc.purchase_receipt target_doc.rejected_warehouse = source_doc.rejected_warehouse target_doc.po_detail = source_doc.po_detail target_doc.pr_detail = source_doc.pr_detail target_doc.purchase_invoice_item = source_doc.name target_doc.price_list_rate = 0 elif doctype == "Delivery Note": returned_qty_map = get_returned_qty_map_for_row( source_doc.name, doctype) target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0)) target_doc.stock_qty = -1 * flt(source_doc.stock_qty - ( returned_qty_map.get('stock_qty') or 0)) target_doc.against_sales_order = source_doc.against_sales_order target_doc.against_sales_invoice = source_doc.against_sales_invoice 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" or doctype == "POS Invoice": returned_qty_map = get_returned_qty_map_for_row( source_doc.name, doctype) target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0)) target_doc.stock_qty = -1 * flt(source_doc.stock_qty - ( returned_qty_map.get('stock_qty') or 0)) target_doc.sales_order = source_doc.sales_order target_doc.delivery_note = source_doc.delivery_note target_doc.so_detail = source_doc.so_detail target_doc.dn_detail = source_doc.dn_detail target_doc.expense_account = source_doc.expense_account target_doc.sales_invoice_item = source_doc.name target_doc.price_list_rate = 0 if default_warehouse_for_sales_return: target_doc.warehouse = default_warehouse_for_sales_return
def update_valuation_rate_for_serial_no(self): for d in self.items: if not d.serial_no: continue serial_nos = get_serial_nos(d.serial_no) self.update_valuation_rate_for_serial_nos(d, serial_nos)
def get_serial_nos_data(serial_nos): from erpbee.stock.doctype.serial_no.serial_no import get_serial_nos return get_serial_nos(serial_nos)
def get_sle_for_serialized_items(self, row, sl_entries): from erpbee.stock.stock_ledger import get_previous_sle serial_nos = get_serial_nos(row.serial_no) # To issue existing serial nos if row.current_qty and (row.current_serial_no or row.batch_no): args = self.get_sle_for_items(row) args.update({ 'actual_qty': -1 * row.current_qty, 'serial_no': row.current_serial_no, 'batch_no': row.batch_no, 'valuation_rate': row.current_valuation_rate }) if row.current_serial_no: args.update({ 'qty_after_transaction': 0, }) sl_entries.append(args) qty_after_transaction = 0 for serial_no in serial_nos: args = self.get_sle_for_items(row, [serial_no]) previous_sle = get_previous_sle({ "item_code": row.item_code, "posting_date": self.posting_date, "posting_time": self.posting_time, "serial_no": serial_no }) if previous_sle and row.warehouse != previous_sle.get("warehouse"): # If serial no exists in different warehouse warehouse = previous_sle.get("warehouse", '') or row.warehouse if not qty_after_transaction: qty_after_transaction = get_stock_balance(row.item_code, warehouse, self.posting_date, self.posting_time) qty_after_transaction -= 1 new_args = args.copy() new_args.update({ 'actual_qty': -1, 'qty_after_transaction': qty_after_transaction, 'warehouse': warehouse, 'valuation_rate': previous_sle.get("valuation_rate") }) sl_entries.append(new_args) if row.qty: args = self.get_sle_for_items(row) args.update({ 'actual_qty': row.qty, 'incoming_rate': row.valuation_rate, 'valuation_rate': row.valuation_rate }) sl_entries.append(args) if serial_nos == get_serial_nos(row.current_serial_no): # update valuation rate self.update_valuation_rate_for_serial_nos(row, serial_nos)
def get_fifo_queue(filters, sle=None): item_details = {} transferred_item_details = {} serial_no_batch_purchase_details = {} if sle == None: sle = get_stock_ledger_entries(filters) for d in sle: key = (d.name, d.warehouse) if filters.get('show_warehouse_wise_stock') else d.name item_details.setdefault(key, {"details": d, "fifo_queue": []}) fifo_queue = item_details[key]["fifo_queue"] transferred_item_key = (d.voucher_no, d.name, d.warehouse) transferred_item_details.setdefault(transferred_item_key, []) if d.voucher_type == "Stock Reconciliation": d.actual_qty = flt(d.qty_after_transaction) - flt(item_details[key].get("qty_after_transaction", 0)) serial_no_list = get_serial_nos(d.serial_no) if d.serial_no else [] if d.actual_qty > 0: if transferred_item_details.get(transferred_item_key): batch = transferred_item_details[transferred_item_key][0] fifo_queue.append(batch) transferred_item_details[transferred_item_key].pop(0) else: if serial_no_list: for serial_no in serial_no_list: if serial_no_batch_purchase_details.get(serial_no): fifo_queue.append([serial_no, serial_no_batch_purchase_details.get(serial_no)]) else: serial_no_batch_purchase_details.setdefault(serial_no, d.posting_date) fifo_queue.append([serial_no, d.posting_date]) else: fifo_queue.append([d.actual_qty, d.posting_date]) else: if serial_no_list: for serial_no in fifo_queue: if serial_no[0] in serial_no_list: fifo_queue.remove(serial_no) else: qty_to_pop = abs(d.actual_qty) while qty_to_pop: batch = fifo_queue[0] if fifo_queue else [0, None] if 0 < flt(batch[0]) <= qty_to_pop: # if batch qty > 0 # not enough or exactly same qty in current batch, clear batch qty_to_pop -= flt(batch[0]) transferred_item_details[transferred_item_key].append(fifo_queue.pop(0)) else: # all from current batch batch[0] = flt(batch[0]) - qty_to_pop transferred_item_details[transferred_item_key].append([qty_to_pop, batch[1]]) qty_to_pop = 0 item_details[key]["qty_after_transaction"] = d.qty_after_transaction if "total_qty" not in item_details[key]: item_details[key]["total_qty"] = d.actual_qty else: item_details[key]["total_qty"] += d.actual_qty return item_details