def test_delivery_note(self): '''Test automatic batch selection for outgoing items''' batch_qty = 15 receipt = self.test_purchase_receipt(batch_qty) item_code = 'ITEM-BATCH-1' delivery_note = frappe.get_doc(dict( doctype='Delivery Note', customer='_Test Customer', company=receipt.company, items=[ dict( item_code=item_code, qty=batch_qty, rate=10, warehouse=receipt.items[0].warehouse ) ] )).insert() delivery_note.submit() # shipped from FEFO batch self.assertEquals( delivery_note.items[0].batch_no, get_batch_no(item_code, receipt.items[0].warehouse, batch_qty) )
def test_stock_entry_outgoing(self): '''Test automatic batch selection for outgoing stock entry''' batch_qty = 16 receipt = self.test_purchase_receipt(batch_qty) item_code = 'ITEM-BATCH-1' stock_entry = frappe.get_doc(dict( doctype='Stock Entry', purpose='Material Issue', company=receipt.company, items=[ dict( item_code=item_code, 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, get_batch_no(item_code, receipt.items[0].warehouse, batch_qty) )
def get_item_details(self, args=None, for_update=False): item = frappe.db.sql("""select stock_uom, description, image, item_name, expense_account, buying_cost_center, item_group, has_serial_no, has_batch_no, sample_quantity from `tabItem` where name = %s and disabled=0 and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)""", (args.get('item_code'), nowdate()), as_dict = 1) if not item: frappe.throw(_("Item {0} is not active or end of life has been reached").format(args.get("item_code"))) item = item[0] ret = frappe._dict({ 'uom' : item.stock_uom, 'stock_uom' : item.stock_uom, 'description' : item.description, 'image' : item.image, 'item_name' : item.item_name, 'expense_account' : args.get("expense_account"), 'cost_center' : get_default_cost_center(args, item), 'qty' : 0, 'transfer_qty' : 0, 'conversion_factor' : 1, 'batch_no' : '', 'actual_qty' : 0, 'basic_rate' : 0, 'serial_no' : '', 'has_serial_no' : item.has_serial_no, 'has_batch_no' : item.has_batch_no, 'sample_quantity' : item.sample_quantity }) for d in [["Account", "expense_account", "default_expense_account"], ["Cost Center", "cost_center", "cost_center"]]: company = frappe.db.get_value(d[0], ret.get(d[1]), "company") if not ret[d[1]] or (company and self.company != company): ret[d[1]] = frappe.db.get_value("Company", self.company, d[2]) if d[2] else None # update uom if args.get("uom") and for_update: ret.update(get_uom_details(args.get('item_code'), args.get('uom'), args.get('qty'))) if not ret["expense_account"]: ret["expense_account"] = frappe.db.get_value("Company", self.company, "stock_adjustment_account") args['posting_date'] = self.posting_date args['posting_time'] = self.posting_time stock_and_rate = get_warehouse_details(args) if args.get('warehouse') else {} ret.update(stock_and_rate) # automatically select batch for outgoing item if (args.get('s_warehouse', None) and args.get('qty') and 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']) return ret
def update_stock(args, out): if (args.get("doctype") == "Delivery Note" or (args.get("doctype") == "Sales Invoice" and args.get('update_stock'))) \ and out.warehouse and out.stock_qty > 0: if out.has_batch_no and not args.get("batch_no"): out.batch_no = get_batch_no(out.item_code, out.warehouse, out.qty) actual_batch_qty = get_batch_qty(out.batch_no, out.warehouse, out.item_code) if actual_batch_qty: out.update(actual_batch_qty) if out.has_serial_no and args.get('batch_no'): reserved_so = get_so_reservation_for_item(args) out.batch_no = args.get('batch_no') out.serial_no = get_serial_no(out, args.serial_no, sales_order=reserved_so) elif out.has_serial_no: reserved_so = get_so_reservation_for_item(args) out.serial_no = get_serial_no(out, args.serial_no, sales_order=reserved_so)
def update_stock(args, out): if (args.get("doctype") == "Delivery Note" or (args.get("doctype") == "Sales Invoice" and args.get('update_stock'))) \ and out.warehouse and out.stock_qty > 0: if out.has_batch_no and not args.get("batch_no"): out.batch_no = get_batch_no(out.item_code, out.warehouse, out.qty) actual_batch_qty = get_batch_qty(out.batch_no, out.warehouse, out.item_code) if actual_batch_qty: out.update(actual_batch_qty) if out.has_serial_no and args.get('batch_no'): reserved_so = get_so_reservation_for_item(args) out.batch_no = args.get('batch_no') out.serial_no = get_serial_no(out, args.serial_no, sales_order=reserved_so) elif out.has_serial_no: reserved_so = get_so_reservation_for_item(args) out.serial_no = get_serial_no(out, args.serial_no, sales_order=reserved_so)
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_delivery_note(self): '''Test automatic batch selection for outgoing items''' batch_qty = 15 receipt = self.test_purchase_receipt(batch_qty) item_code = 'ITEM-BATCH-1' delivery_note = frappe.get_doc( dict(doctype='Delivery Note', customer='_Test Customer', company=receipt.company, items=[ dict(item_code=item_code, qty=batch_qty, rate=10, warehouse=receipt.items[0].warehouse) ])).insert() delivery_note.submit() # shipped from FEFO batch self.assertEqual( delivery_note.items[0].batch_no, get_batch_no(item_code, receipt.items[0].warehouse, batch_qty))
def test_stock_entry_outgoing(self): '''Test automatic batch selection for outgoing stock entry''' batch_qty = 16 receipt = self.test_purchase_receipt(batch_qty) item_code = 'ITEM-BATCH-1' stock_entry = frappe.get_doc( dict(doctype='Stock Entry', purpose='Material Issue', company=receipt.company, items=[ dict( item_code=item_code, 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, get_batch_no(item_code, receipt.items[0].warehouse, batch_qty))
def get_item_details(args): """ args = { "item_code": "", "warehouse": None, "customer": "", "conversion_rate": 1.0, "selling_price_list": None, "price_list_currency": None, "plc_conversion_rate": 1.0, "doctype": "", "name": "", "supplier": None, "transaction_date": None, "conversion_rate": 1.0, "buying_price_list": None, "is_subcontracted": "Yes" / "No", "ignore_pricing_rule": 0/1 "project": "" } """ args = process_args(args) item_doc = frappe.get_doc("Item", args.item_code) item = item_doc validate_item_details(args, item) out = get_basic_details(args, item) get_party_item_code(args, item_doc, out) if frappe.db.exists("Product Bundle", args.item_code): valuation_rate = 0.0 bundled_items = frappe.get_doc("Product Bundle", args.item_code) for bundle_item in bundled_items.items: valuation_rate += \ flt(get_valuation_rate(bundle_item.item_code, out.get("warehouse")).get("valuation_rate") \ * bundle_item.qty) out.update({ "valuation_rate": valuation_rate }) else: out.update(get_valuation_rate(args.item_code, out.get("warehouse"))) get_price_list_rate(args, item_doc, out) if args.customer and cint(args.is_pos): out.update(get_pos_profile_item_details(args.company, args)) if out.get("warehouse"): out.update(get_bin_details(args.item_code, out.warehouse)) # update args with out, if key or value not exists for key, value in out.iteritems(): if args.get(key) is None: args[key] = value out.update(get_pricing_rule_for_item(args)) if args.get("doctype") in ("Sales Invoice", "Delivery Note") and out.stock_qty > 0: if out.has_serial_no: out.serial_no = get_serial_no(out) if out.has_batch_no: out.batch_no = get_batch_no(out.item_code, out.warehouse, out.qty) if args.transaction_date and item.lead_time_days: out.schedule_date = out.lead_time_date = add_days(args.transaction_date, item.lead_time_days) if args.get("is_subcontracted") == "Yes": out.bom = get_default_bom(args.item_code) get_gross_profit(out) return out
def get_item_details(args): """ args = { "item_code": "", "warehouse": None, "customer": "", "conversion_rate": 1.0, "selling_price_list": None, "price_list_currency": None, "plc_conversion_rate": 1.0, "doctype": "", "name": "", "supplier": None, "transaction_date": None, "conversion_rate": 1.0, "buying_price_list": None, "is_subcontracted": "Yes" / "No", "ignore_pricing_rule": 0/1 "project": "" } """ args = process_args(args) item_doc = frappe.get_doc("Item", args.item_code) item = item_doc validate_item_details(args, item) out = get_basic_details(args, item) get_party_item_code(args, item_doc, out) if frappe.db.exists("Product Bundle", args.item_code): valuation_rate = 0.0 bundled_items = frappe.get_doc("Product Bundle", args.item_code) for bundle_item in bundled_items.items: valuation_rate += \ flt(get_valuation_rate(bundle_item.item_code, out.get("warehouse")).get("valuation_rate") \ * bundle_item.qty) out.update({"valuation_rate": valuation_rate}) else: out.update(get_valuation_rate(args.item_code, out.get("warehouse"))) get_price_list_rate(args, item_doc, out) if args.customer and cint(args.is_pos): out.update(get_pos_profile_item_details(args.company, args)) if out.get("warehouse"): out.update(get_bin_details(args.item_code, out.warehouse)) # update args with out, if key or value not exists for key, value in out.iteritems(): if args.get(key) is None: args[key] = value out.update(get_pricing_rule_for_item(args)) if (args.get("doctype") == "Delivery Note" or (args.get("doctype") == "Sales Invoice" and args.get('update_stock'))) \ and out.warehouse and out.stock_qty > 0: if out.has_serial_no: out.serial_no = get_serial_no(out, args.serial_no) if out.has_batch_no and not args.get("batch_no"): out.batch_no = get_batch_no(out.item_code, out.warehouse, out.qty) actual_batch_qty = get_batch_qty(out.batch_no, out.warehouse, out.item_code) if actual_batch_qty: out.update(actual_batch_qty) if args.transaction_date and item.lead_time_days: out.schedule_date = out.lead_time_date = add_days( args.transaction_date, item.lead_time_days) if args.get("is_subcontracted") == "Yes": out.bom = args.get('bom') or get_default_bom(args.item_code) get_gross_profit(out) return out
def get_item_details(self, args=None, for_update=False): item = frappe.db.sql( """select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group, i.has_batch_no, i.sample_quantity, i.has_serial_no, id.expense_account, id.buying_cost_center from `tabItem` i LEFT JOIN `tabItem Default` id ON i.name=id.parent and id.company=%s where i.name=%s and i.disabled=0 and (i.end_of_life is null or i.end_of_life='0000-00-00' or i.end_of_life > %s)""", (self.company, args.get('item_code'), nowdate()), as_dict=1) if not item: frappe.throw( _("Item {0} is not active or end of life has been reached"). format(args.get("item_code"))) item = item[0] item_group_defaults = get_item_group_defaults(item.name, self.company) ret = frappe._dict({ 'uom': item.stock_uom, 'stock_uom': item.stock_uom, 'description': item.description, 'image': item.image, 'item_name': item.item_name, 'cost_center': get_default_cost_center(args, item, item_group_defaults, self.company), 'qty': args.get("qty"), 'transfer_qty': args.get('qty'), 'conversion_factor': 1, 'batch_no': '', 'actual_qty': 0, 'basic_rate': 0, 'serial_no': '', 'has_serial_no': item.has_serial_no, 'has_batch_no': item.has_batch_no, 'sample_quantity': item.sample_quantity }) # update uom if args.get("uom") and for_update: ret.update( get_uom_details(args.get('item_code'), args.get('uom'), args.get('qty'))) args['posting_date'] = self.posting_date args['posting_time'] = self.posting_time stock_and_rate = get_warehouse_details(args) if args.get( 'warehouse') else {} ret.update(stock_and_rate) # automatically select batch for outgoing item if (args.get('s_warehouse', None) and args.get('qty') and 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']) return ret
def get_item_details(self, args=None, for_update=False): item = frappe.db.sql( """select stock_uom, description, image, item_name, expense_account, buying_cost_center, item_group, has_serial_no, has_batch_no from `tabItem` where name = %s and disabled=0 and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)""", (args.get('item_code'), nowdate()), as_dict=1) if not item: frappe.throw( _("Item {0} is not active or end of life has been reached"). format(args.get("item_code"))) item = item[0] ret = frappe._dict({ 'uom': item.stock_uom, 'stock_uom': item.stock_uom, 'description': item.description, 'image': item.image, 'item_name': item.item_name, 'expense_account': args.get("expense_account"), 'cost_center': get_default_cost_center(args, item), 'qty': 0, 'transfer_qty': 0, 'conversion_factor': 1, 'batch_no': '', 'actual_qty': 0, 'basic_rate': 0, 'serial_no': '', 'has_serial_no': item.has_serial_no, 'has_batch_no': item.has_batch_no }) for d in [["Account", "expense_account", "default_expense_account"], ["Cost Center", "cost_center", "cost_center"]]: company = frappe.db.get_value(d[0], ret.get(d[1]), "company") if not ret[d[1]] or (company and self.company != company): ret[d[1]] = frappe.db.get_value("Company", self.company, d[2]) if d[2] else None # update uom if args.get("uom") and for_update: ret.update( get_uom_details(args.get('item_code'), args.get('uom'), args.get('qty'))) if not ret["expense_account"]: ret["expense_account"] = frappe.db.get_value( "Company", self.company, "stock_adjustment_account") args['posting_date'] = self.posting_date args['posting_time'] = self.posting_time stock_and_rate = args.get('warehouse') and get_warehouse_details( args) or {} ret.update(stock_and_rate) # automatically select batch for outgoing item if (args.get('s_warehouse', None) and args.get('qty') and 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']) return ret