Merge pull request #39718 from rohitwaghchaure/create-bundle-using-old-fields
fix: use old serial / batch fields to make serial batch bundle
This commit is contained in:
commit
4671f65cbd
@ -80,13 +80,16 @@
|
||||
"target_warehouse",
|
||||
"quality_inspection",
|
||||
"serial_and_batch_bundle",
|
||||
"batch_no",
|
||||
"use_serial_batch_fields",
|
||||
"col_break5",
|
||||
"allow_zero_valuation_rate",
|
||||
"serial_no",
|
||||
"item_tax_rate",
|
||||
"actual_batch_qty",
|
||||
"actual_qty",
|
||||
"section_break_tlhi",
|
||||
"serial_no",
|
||||
"column_break_ciit",
|
||||
"batch_no",
|
||||
"edit_references",
|
||||
"sales_order",
|
||||
"so_detail",
|
||||
@ -628,13 +631,13 @@
|
||||
"options": "Quality Inspection"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break5",
|
||||
@ -649,14 +652,14 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Serial No",
|
||||
"oldfieldname": "serial_no",
|
||||
"oldfieldtype": "Small Text",
|
||||
"read_only": 1
|
||||
"oldfieldtype": "Small Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "item_tax_rate",
|
||||
@ -824,17 +827,33 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
"no_copy": 1,
|
||||
"options": "Serial and Batch Bundle",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "section_break_tlhi",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ciit",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-14 18:33:22.585715",
|
||||
"modified": "2024-02-04 16:36:25.665743",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice Item",
|
||||
|
@ -82,6 +82,7 @@ class POSInvoiceItem(Document):
|
||||
target_warehouse: DF.Link | None
|
||||
total_weight: DF.Float
|
||||
uom: DF.Link
|
||||
use_serial_batch_fields: DF.Check
|
||||
warehouse: DF.Link | None
|
||||
weight_per_unit: DF.Float
|
||||
weight_uom: DF.Link | None
|
||||
|
@ -696,6 +696,7 @@ class PurchaseInvoice(BuyingController):
|
||||
# Updating stock ledger should always be called after updating prevdoc status,
|
||||
# because updating ordered qty in bin depends upon updated ordered qty in PO
|
||||
if self.update_stock == 1:
|
||||
self.make_bundle_using_old_serial_batch_fields()
|
||||
self.update_stock_ledger()
|
||||
|
||||
if self.is_old_subcontracting_flow:
|
||||
|
@ -62,16 +62,19 @@
|
||||
"rm_supp_cost",
|
||||
"warehouse_section",
|
||||
"warehouse",
|
||||
"from_warehouse",
|
||||
"quality_inspection",
|
||||
"add_serial_batch_bundle",
|
||||
"serial_and_batch_bundle",
|
||||
"serial_no",
|
||||
"use_serial_batch_fields",
|
||||
"col_br_wh",
|
||||
"from_warehouse",
|
||||
"quality_inspection",
|
||||
"rejected_warehouse",
|
||||
"rejected_serial_and_batch_bundle",
|
||||
"batch_no",
|
||||
"section_break_rqbe",
|
||||
"serial_no",
|
||||
"rejected_serial_no",
|
||||
"column_break_vbbb",
|
||||
"batch_no",
|
||||
"manufacture_details",
|
||||
"manufacturer",
|
||||
"column_break_13",
|
||||
@ -440,13 +443,11 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@ -454,21 +455,18 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 1,
|
||||
"label": "Serial No",
|
||||
"read_only": 1
|
||||
"label": "Serial No"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "rejected_serial_no",
|
||||
"fieldtype": "Text",
|
||||
"label": "Rejected Serial No",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting",
|
||||
@ -891,7 +889,7 @@
|
||||
"label": "Apply TDS"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1",
|
||||
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
@ -901,7 +899,7 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1",
|
||||
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
|
||||
"fieldname": "rejected_serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Rejected Serial and Batch Bundle",
|
||||
@ -916,16 +914,31 @@
|
||||
"options": "Asset"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock === 1",
|
||||
"depends_on": "eval:parent.update_stock === 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
|
||||
"fieldname": "add_serial_batch_bundle",
|
||||
"fieldtype": "Button",
|
||||
"label": "Add Serial / Batch No"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "section_break_rqbe",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_vbbb",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-21 19:46:25.537861",
|
||||
"modified": "2024-02-04 14:11:52.742228",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
@ -88,6 +88,7 @@ class PurchaseInvoiceItem(Document):
|
||||
stock_uom_rate: DF.Currency
|
||||
total_weight: DF.Float
|
||||
uom: DF.Link
|
||||
use_serial_batch_fields: DF.Check
|
||||
valuation_rate: DF.Currency
|
||||
warehouse: DF.Link | None
|
||||
weight_per_unit: DF.Float
|
||||
|
@ -447,6 +447,7 @@ class SalesInvoice(SellingController):
|
||||
# Updating stock ledger should always be called after updating prevdoc status,
|
||||
# because updating reserved qty in bin depends upon updated delivered qty in SO
|
||||
if self.update_stock == 1:
|
||||
self.make_bundle_using_old_serial_batch_fields()
|
||||
self.update_stock_ledger()
|
||||
|
||||
# this sequence because outstanding may get -ve
|
||||
|
@ -83,14 +83,17 @@
|
||||
"quality_inspection",
|
||||
"pick_serial_and_batch",
|
||||
"serial_and_batch_bundle",
|
||||
"batch_no",
|
||||
"incoming_rate",
|
||||
"use_serial_batch_fields",
|
||||
"col_break5",
|
||||
"allow_zero_valuation_rate",
|
||||
"serial_no",
|
||||
"incoming_rate",
|
||||
"item_tax_rate",
|
||||
"actual_batch_qty",
|
||||
"actual_qty",
|
||||
"section_break_eoec",
|
||||
"serial_no",
|
||||
"column_break_ytgd",
|
||||
"batch_no",
|
||||
"edit_references",
|
||||
"sales_order",
|
||||
"so_detail",
|
||||
@ -600,12 +603,11 @@
|
||||
"options": "Quality Inspection"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@ -621,13 +623,12 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 1,
|
||||
"label": "Serial No",
|
||||
"oldfieldname": "serial_no",
|
||||
"oldfieldtype": "Small Text",
|
||||
"read_only": 1
|
||||
"oldfieldtype": "Small Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "item_group",
|
||||
@ -891,6 +892,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
@ -904,12 +906,27 @@
|
||||
"fieldname": "pick_serial_and_batch",
|
||||
"fieldtype": "Button",
|
||||
"label": "Pick Serial / Batch No"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "section_break_eoec",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ytgd",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-29 13:03:14.121298",
|
||||
"modified": "2024-02-04 11:52:16.106541",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
@ -86,6 +86,7 @@ class SalesInvoiceItem(Document):
|
||||
target_warehouse: DF.Link | None
|
||||
total_weight: DF.Float
|
||||
uom: DF.Link
|
||||
use_serial_batch_fields: DF.Check
|
||||
warehouse: DF.Link | None
|
||||
weight_per_unit: DF.Float
|
||||
weight_uom: DF.Link | None
|
||||
|
@ -126,6 +126,7 @@ class AssetCapitalization(StockController):
|
||||
self.create_target_asset()
|
||||
|
||||
def on_submit(self):
|
||||
self.make_bundle_using_old_serial_batch_fields()
|
||||
self.update_stock_ledger()
|
||||
self.make_gl_entries()
|
||||
self.update_target_asset()
|
||||
|
@ -18,9 +18,12 @@
|
||||
"amount",
|
||||
"batch_and_serial_no_section",
|
||||
"serial_and_batch_bundle",
|
||||
"use_serial_batch_fields",
|
||||
"column_break_13",
|
||||
"batch_no",
|
||||
"section_break_bfqc",
|
||||
"serial_no",
|
||||
"column_break_mbuv",
|
||||
"batch_no",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break"
|
||||
@ -39,13 +42,13 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"no_copy": 1,
|
||||
"options": "Batch",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
@ -102,12 +105,12 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 1,
|
||||
"label": "Serial No",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
@ -148,18 +151,34 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
"no_copy": 1,
|
||||
"options": "Serial and Batch Bundle",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "section_break_bfqc",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_mbuv",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-06 01:10:17.947952",
|
||||
"modified": "2024-02-04 16:41:09.239762",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Capitalization Stock Item",
|
||||
|
@ -27,6 +27,7 @@ class AssetCapitalizationStockItem(Document):
|
||||
serial_no: DF.SmallText | None
|
||||
stock_qty: DF.Float
|
||||
stock_uom: DF.Link
|
||||
use_serial_batch_fields: DF.Check
|
||||
valuation_rate: DF.Currency
|
||||
warehouse: DF.Link
|
||||
# end: auto-generated types
|
||||
|
@ -21,6 +21,9 @@ from erpnext.stock import get_warehouse_account_map
|
||||
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
|
||||
get_evaluated_inventory_dimension,
|
||||
)
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||
get_type_of_transaction,
|
||||
)
|
||||
from erpnext.stock.stock_ledger import get_items_to_be_repost
|
||||
|
||||
|
||||
@ -126,6 +129,81 @@ class StockController(AccountsController):
|
||||
# remove extra whitespace and store one serial no on each line
|
||||
row.serial_no = clean_serial_no_string(row.serial_no)
|
||||
|
||||
def make_bundle_using_old_serial_batch_fields(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||
|
||||
# To handle test cases
|
||||
if frappe.flags.in_test and frappe.flags.use_serial_and_batch_fields:
|
||||
return
|
||||
|
||||
table_name = "items"
|
||||
if self.doctype == "Asset Capitalization":
|
||||
table_name = "stock_items"
|
||||
|
||||
for row in self.get(table_name):
|
||||
if not row.serial_no and not row.batch_no and not row.get("rejected_serial_no"):
|
||||
continue
|
||||
|
||||
if not row.use_serial_batch_fields and (
|
||||
row.serial_no or row.batch_no or row.get("rejected_serial_no")
|
||||
):
|
||||
frappe.throw(_("Please enable Use Old Serial / Batch Fields to make_bundle"))
|
||||
|
||||
if row.use_serial_batch_fields and (
|
||||
not row.serial_and_batch_bundle and not row.get("rejected_serial_and_batch_bundle")
|
||||
):
|
||||
if self.doctype == "Stock Reconciliation":
|
||||
qty = row.qty
|
||||
type_of_transaction = "Inward"
|
||||
else:
|
||||
qty = row.stock_qty
|
||||
type_of_transaction = get_type_of_transaction(self, row)
|
||||
|
||||
sn_doc = SerialBatchCreation(
|
||||
{
|
||||
"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,
|
||||
"qty": qty,
|
||||
"type_of_transaction": type_of_transaction,
|
||||
"company": self.company,
|
||||
"is_rejected": 1 if row.get("rejected_warehouse") else 0,
|
||||
"serial_nos": get_serial_nos(row.serial_no) if row.serial_no else None,
|
||||
"batches": frappe._dict({row.batch_no: qty}) if row.batch_no else None,
|
||||
"batch_no": row.batch_no,
|
||||
"use_serial_batch_fields": row.use_serial_batch_fields,
|
||||
"do_not_submit": True,
|
||||
}
|
||||
).make_serial_and_batch_bundle()
|
||||
|
||||
if sn_doc.is_rejected:
|
||||
row.rejected_serial_and_batch_bundle = sn_doc.name
|
||||
row.db_set(
|
||||
{
|
||||
"rejected_serial_and_batch_bundle": sn_doc.name,
|
||||
"rejected_serial_no": "",
|
||||
}
|
||||
)
|
||||
else:
|
||||
row.serial_and_batch_bundle = sn_doc.name
|
||||
row.db_set(
|
||||
{
|
||||
"serial_and_batch_bundle": sn_doc.name,
|
||||
"serial_no": "",
|
||||
"batch_no": "",
|
||||
}
|
||||
)
|
||||
|
||||
def set_use_serial_batch_fields(self):
|
||||
if frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields"):
|
||||
for row in self.items:
|
||||
row.use_serial_batch_fields = 1
|
||||
|
||||
def get_gl_entries(
|
||||
self, warehouse_account=None, default_expense_account=None, default_cost_center=None
|
||||
):
|
||||
|
@ -7,6 +7,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
super.setup();
|
||||
let me = this;
|
||||
|
||||
this.set_fields_onload_for_line_item();
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['Serial and Batch Bundle'];
|
||||
|
||||
frappe.flags.hide_serial_batch_dialog = true;
|
||||
@ -105,6 +106,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
frappe.ui.form.on(this.frm.doctype + " Item", {
|
||||
items_add: function(frm, cdt, cdn) {
|
||||
debugger
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
if (!item.warehouse && frm.doc.set_warehouse) {
|
||||
item.warehouse = frm.doc.set_warehouse;
|
||||
@ -118,6 +120,13 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
item.from_warehouse = frm.doc.set_from_warehouse;
|
||||
}
|
||||
|
||||
if (item.docstatus === 0
|
||||
&& frappe.meta.has_field(item.doctype, "use_serial_batch_fields")
|
||||
&& cint(frappe.user_defaults?.use_serial_batch_fields) === 1
|
||||
) {
|
||||
frappe.model.set_value(item.doctype, item.name, "use_serial_batch_fields", 1);
|
||||
}
|
||||
|
||||
erpnext.accounts.dimensions.copy_dimension_from_first_row(frm, cdt, cdn, 'items');
|
||||
}
|
||||
});
|
||||
@ -222,7 +231,19 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
set_fields_onload_for_line_item() {
|
||||
if (this.frm.is_new && this.frm.doc?.items) {
|
||||
this.frm.doc.items.forEach(item => {
|
||||
if (item.docstatus === 0
|
||||
&& frappe.meta.has_field(item.doctype, "use_serial_batch_fields")
|
||||
&& cint(frappe.user_defaults?.use_serial_batch_fields) === 1
|
||||
) {
|
||||
frappe.model.set_value(item.doctype, item.name, "use_serial_batch_fields", 1);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
toggle_enable_for_stock_uom(field) {
|
||||
@ -462,6 +483,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
this.frm.doc.doctype === 'Delivery Note') {
|
||||
show_batch_dialog = 1;
|
||||
}
|
||||
|
||||
if (show_batch_dialog && item.use_serial_batch_fields === 1) {
|
||||
show_batch_dialog = 0;
|
||||
}
|
||||
|
||||
item.barcode = null;
|
||||
|
||||
|
||||
@ -706,10 +732,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
item.serial_no = item.serial_no.replace(/,/g, '\n');
|
||||
item.conversion_factor = item.conversion_factor || 1;
|
||||
refresh_field("serial_no", item.name, item.parentfield);
|
||||
if (!doc.is_return && cint(frappe.user_defaults.set_qty_in_transactions_based_on_serial_no_input)) {
|
||||
if (!doc.is_return) {
|
||||
setTimeout(() => {
|
||||
me.update_qty(cdt, cdn);
|
||||
}, 10000);
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1242,20 +1268,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
}
|
||||
|
||||
sync_bundle_data() {
|
||||
let doctypes = ["Sales Invoice", "Purchase Invoice", "Delivery Note", "Purchase Receipt"];
|
||||
|
||||
if (this.frm.is_new() && doctypes.includes(this.frm.doc.doctype)) {
|
||||
const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
|
||||
barcode_scanner.sync_bundle_data();
|
||||
barcode_scanner.remove_item_from_localstorage();
|
||||
}
|
||||
}
|
||||
|
||||
before_save(doc) {
|
||||
this.sync_bundle_data();
|
||||
}
|
||||
|
||||
service_start_date(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn];
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
constructor(opts) {
|
||||
this.frm = opts.frm;
|
||||
// frappe.flags.trigger_from_barcode_scanner is used for custom scripts
|
||||
|
||||
// field from which to capture input of scanned data
|
||||
this.scan_field_name = opts.scan_field_name || "scan_barcode";
|
||||
this.scan_barcode_field = this.frm.fields_dict[this.scan_field_name];
|
||||
|
||||
this.barcode_field = opts.barcode_field || "barcode";
|
||||
this.serial_no_field = opts.serial_no_field || "serial_no";
|
||||
this.batch_no_field = opts.batch_no_field || "batch_no";
|
||||
this.uom_field = opts.uom_field || "uom";
|
||||
this.qty_field = opts.qty_field || "qty";
|
||||
// field name on row which defines max quantity to be scanned e.g. picklist
|
||||
@ -105,53 +108,52 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
this.frm.has_items = false;
|
||||
}
|
||||
|
||||
if (serial_no) {
|
||||
this.is_duplicate_serial_no(row, item_code, serial_no)
|
||||
.then((is_duplicate) => {
|
||||
if (!is_duplicate) {
|
||||
this.run_serially_tasks(row, data, resolve);
|
||||
} else {
|
||||
this.clean_up();
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.run_serially_tasks(row, data, resolve);
|
||||
if (this.is_duplicate_serial_no(row, serial_no)) {
|
||||
this.clean_up();
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
frappe.run_serially([
|
||||
() => this.set_selector_trigger_flag(data),
|
||||
() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
|
||||
this.show_scan_message(row.idx, row.item_code, qty);
|
||||
}),
|
||||
() => this.set_barcode_uom(row, uom),
|
||||
() => this.set_serial_no(row, serial_no),
|
||||
() => this.set_batch_no(row, batch_no),
|
||||
() => this.set_barcode(row, barcode),
|
||||
() => this.clean_up(),
|
||||
() => this.revert_selector_flag(),
|
||||
() => resolve(row)
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
run_serially_tasks(row, data, resolve) {
|
||||
const {item_code, barcode, batch_no, serial_no, uom} = data;
|
||||
// batch and serial selector is reduandant when all info can be added by scan
|
||||
// this flag on item row is used by transaction.js to avoid triggering selector
|
||||
set_selector_trigger_flag(data) {
|
||||
const {batch_no, serial_no, has_batch_no, has_serial_no} = data;
|
||||
|
||||
frappe.run_serially([
|
||||
() => this.set_serial_and_batch(row, item_code, serial_no, batch_no),
|
||||
() => this.set_barcode(row, barcode),
|
||||
() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
|
||||
this.show_scan_message(row.idx, row.item_code, qty);
|
||||
}),
|
||||
() => this.set_barcode_uom(row, uom),
|
||||
() => this.clean_up(),
|
||||
() => {
|
||||
if (row.serial_and_batch_bundle && !this.frm.is_new()) {
|
||||
this.frm.save();
|
||||
}
|
||||
const require_selecting_batch = has_batch_no && !batch_no;
|
||||
const require_selecting_serial = has_serial_no && !serial_no;
|
||||
|
||||
frappe.flags.trigger_from_barcode_scanner = false;
|
||||
},
|
||||
() => resolve(row),
|
||||
]);
|
||||
if (!(require_selecting_batch || require_selecting_serial)) {
|
||||
frappe.flags.hide_serial_batch_dialog = true;
|
||||
}
|
||||
}
|
||||
|
||||
revert_selector_flag() {
|
||||
frappe.flags.hide_serial_batch_dialog = false;
|
||||
frappe.flags.trigger_from_barcode_scanner = false;
|
||||
}
|
||||
|
||||
set_item(row, item_code, barcode, batch_no, serial_no) {
|
||||
return new Promise(resolve => {
|
||||
const increment = async (value = 1) => {
|
||||
const item_data = {item_code: item_code};
|
||||
item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value);
|
||||
const item_data = {item_code: item_code, use_serial_batch_fields: 1.0};
|
||||
frappe.flags.trigger_from_barcode_scanner = true;
|
||||
item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value);
|
||||
await frappe.model.set_value(row.doctype, row.name, item_data);
|
||||
return value;
|
||||
};
|
||||
@ -160,6 +162,8 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
frappe.prompt(__("Please enter quantity for item {0}", [item_code]), ({value}) => {
|
||||
increment(value).then((value) => resolve(value));
|
||||
});
|
||||
} else if (this.frm.has_items) {
|
||||
this.prepare_item_for_scan(row, item_code, barcode, batch_no, serial_no);
|
||||
} else {
|
||||
increment().then((value) => resolve(value));
|
||||
}
|
||||
@ -182,8 +186,9 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
frappe.model.set_value(row.doctype, row.name, item_data);
|
||||
|
||||
frappe.run_serially([
|
||||
() => this.set_batch_no(row, this.dialog.get_value("batch_no")),
|
||||
() => this.set_barcode(row, this.dialog.get_value("barcode")),
|
||||
() => this.set_serial_and_batch(row, item_code, this.dialog.get_value("serial_no"), this.dialog.get_value("batch_no")),
|
||||
() => this.set_serial_no(row, this.dialog.get_value("serial_no")),
|
||||
() => this.add_child_for_remaining_qty(row),
|
||||
() => this.clean_up()
|
||||
]);
|
||||
@ -337,144 +342,32 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
}
|
||||
}
|
||||
|
||||
async set_serial_and_batch(row, item_code, serial_no, batch_no) {
|
||||
if (this.frm.is_new() || !row.serial_and_batch_bundle) {
|
||||
this.set_bundle_in_localstorage(row, item_code, serial_no, batch_no);
|
||||
} else if(row.serial_and_batch_bundle) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.update_serial_or_batch",
|
||||
args: {
|
||||
bundle_id: row.serial_and_batch_bundle,
|
||||
serial_no: serial_no,
|
||||
batch_no: batch_no,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
async set_serial_no(row, serial_no) {
|
||||
if (serial_no && frappe.meta.has_field(row.doctype, this.serial_no_field)) {
|
||||
const existing_serial_nos = row[this.serial_no_field];
|
||||
let new_serial_nos = "";
|
||||
|
||||
get_key_for_localstorage() {
|
||||
let parts = this.frm.doc.name.split("-");
|
||||
return parts[parts.length - 1] + this.frm.doc.doctype;
|
||||
}
|
||||
|
||||
update_localstorage_scanned_data() {
|
||||
let docname = this.frm.doc.name
|
||||
if (localStorage[docname]) {
|
||||
let items = JSON.parse(localStorage[docname]);
|
||||
let existing_items = this.frm.doc.items.map(d => d.item_code);
|
||||
if (!existing_items.length) {
|
||||
localStorage.removeItem(docname);
|
||||
return;
|
||||
if (!!existing_serial_nos) {
|
||||
new_serial_nos = existing_serial_nos + "\n" + serial_no;
|
||||
} else {
|
||||
new_serial_nos = serial_no;
|
||||
}
|
||||
|
||||
for (let item_code in items) {
|
||||
if (!existing_items.includes(item_code)) {
|
||||
delete items[item_code];
|
||||
}
|
||||
}
|
||||
|
||||
localStorage[docname] = JSON.stringify(items);
|
||||
await frappe.model.set_value(row.doctype, row.name, this.serial_no_field, new_serial_nos);
|
||||
}
|
||||
}
|
||||
|
||||
async set_bundle_in_localstorage(row, item_code, serial_no, batch_no) {
|
||||
let docname = this.frm.doc.name
|
||||
|
||||
let entries = JSON.parse(localStorage.getItem(docname));
|
||||
if (!entries) {
|
||||
entries = {};
|
||||
}
|
||||
|
||||
let key = item_code;
|
||||
if (!entries[key]) {
|
||||
entries[key] = [];
|
||||
}
|
||||
|
||||
let existing_row = [];
|
||||
if (!serial_no && batch_no) {
|
||||
existing_row = entries[key].filter((e) => e.batch_no === batch_no);
|
||||
if (existing_row.length) {
|
||||
existing_row[0].qty += 1;
|
||||
}
|
||||
} else if (serial_no) {
|
||||
existing_row = entries[key].filter((e) => e.serial_no === serial_no);
|
||||
if (existing_row.length) {
|
||||
frappe.throw(__("Serial No {0} has already scanned.", [serial_no]));
|
||||
}
|
||||
}
|
||||
|
||||
if (!existing_row.length) {
|
||||
entries[key].push({
|
||||
"serial_no": serial_no,
|
||||
"batch_no": batch_no,
|
||||
"qty": 1
|
||||
});
|
||||
}
|
||||
|
||||
localStorage.setItem(docname, JSON.stringify(entries));
|
||||
|
||||
// Auto remove from localstorage after 1 hour
|
||||
setTimeout(() => {
|
||||
localStorage.removeItem(docname);
|
||||
}, 3600000)
|
||||
}
|
||||
|
||||
remove_item_from_localstorage() {
|
||||
let docname = this.frm.doc.name;
|
||||
if (localStorage[docname]) {
|
||||
localStorage.removeItem(docname);
|
||||
}
|
||||
}
|
||||
|
||||
async sync_bundle_data() {
|
||||
let docname = this.frm.doc.name;
|
||||
|
||||
if (localStorage[docname]) {
|
||||
let entries = JSON.parse(localStorage[docname]);
|
||||
if (entries) {
|
||||
for (let entry in entries) {
|
||||
let row = this.frm.doc.items.filter((item) => {
|
||||
if (item.item_code === entry) {
|
||||
return true;
|
||||
}
|
||||
})[0];
|
||||
|
||||
if (row) {
|
||||
this.create_serial_and_batch_bundle(row, entries, entry)
|
||||
.then(() => {
|
||||
if (!entries) {
|
||||
localStorage.removeItem(docname);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async create_serial_and_batch_bundle(row, entries, key) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.add_serial_batch_ledgers",
|
||||
args: {
|
||||
entries: entries[key],
|
||||
child_row: row,
|
||||
doc: this.frm.doc,
|
||||
warehouse: row.warehouse,
|
||||
do_not_save: 1
|
||||
},
|
||||
callback: function(r) {
|
||||
row.serial_and_batch_bundle = r.message.name;
|
||||
delete entries[key];
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async set_barcode_uom(row, uom) {
|
||||
if (uom && frappe.meta.has_field(row.doctype, this.uom_field)) {
|
||||
await frappe.model.set_value(row.doctype, row.name, this.uom_field, uom);
|
||||
}
|
||||
}
|
||||
|
||||
async set_batch_no(row, batch_no) {
|
||||
if (batch_no && frappe.meta.has_field(row.doctype, this.batch_no_field)) {
|
||||
await frappe.model.set_value(row.doctype, row.name, this.batch_no_field, batch_no);
|
||||
}
|
||||
}
|
||||
|
||||
async set_barcode(row, barcode) {
|
||||
if (barcode && frappe.meta.has_field(row.doctype, this.barcode_field)) {
|
||||
await frappe.model.set_value(row.doctype, row.name, this.barcode_field, barcode);
|
||||
@ -490,58 +383,13 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
}
|
||||
}
|
||||
|
||||
async is_duplicate_serial_no(row, item_code, serial_no) {
|
||||
let is_duplicate = false;
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
if (this.frm.is_new() || !row.serial_and_batch_bundle) {
|
||||
is_duplicate = this.check_duplicate_serial_no_in_localstorage(item_code, serial_no);
|
||||
if (is_duplicate) {
|
||||
this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
|
||||
}
|
||||
is_duplicate_serial_no(row, serial_no) {
|
||||
const is_duplicate = row[this.serial_no_field]?.includes(serial_no);
|
||||
|
||||
resolve(is_duplicate);
|
||||
} else if (row.serial_and_batch_bundle) {
|
||||
this.check_duplicate_serial_no_in_db(row, serial_no, (r) => {
|
||||
if (r.message) {
|
||||
this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
|
||||
}
|
||||
|
||||
is_duplicate = r.message;
|
||||
resolve(is_duplicate);
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
return await promise;
|
||||
}
|
||||
|
||||
check_duplicate_serial_no_in_db(row, serial_no, response) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.is_duplicate_serial_no",
|
||||
args: {
|
||||
serial_no: serial_no,
|
||||
bundle_id: row.serial_and_batch_bundle
|
||||
},
|
||||
callback(r) {
|
||||
response(r);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
check_duplicate_serial_no_in_localstorage(item_code, serial_no) {
|
||||
let docname = this.frm.doc.name
|
||||
let entries = JSON.parse(localStorage.getItem(docname));
|
||||
|
||||
if (!entries) {
|
||||
return false;
|
||||
if (is_duplicate) {
|
||||
this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
|
||||
}
|
||||
|
||||
let existing_row = [];
|
||||
if (entries[item_code]) {
|
||||
existing_row = entries[item_code].filter((e) => e.serial_no === serial_no);
|
||||
}
|
||||
|
||||
return existing_row.length;
|
||||
return is_duplicate;
|
||||
}
|
||||
|
||||
get_row_to_modify_on_scan(item_code, batch_no, uom, barcode) {
|
||||
@ -587,4 +435,4 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
||||
show_alert(msg, indicator, duration=3) {
|
||||
frappe.show_alert({message: msg, indicator: indicator}, duration);
|
||||
}
|
||||
};
|
||||
};
|
@ -906,6 +906,7 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None):
|
||||
target.run_method("set_missing_values")
|
||||
target.run_method("set_po_nos")
|
||||
target.run_method("calculate_taxes_and_totals")
|
||||
target.run_method("set_use_serial_batch_fields")
|
||||
|
||||
if source.company_address:
|
||||
target.update({"company_address": source.company_address})
|
||||
@ -1026,6 +1027,7 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
|
||||
target.run_method("set_missing_values")
|
||||
target.run_method("set_po_nos")
|
||||
target.run_method("calculate_taxes_and_totals")
|
||||
target.run_method("set_use_serial_batch_fields")
|
||||
|
||||
if source.company_address:
|
||||
target.update({"company_address": source.company_address})
|
||||
@ -1608,7 +1610,11 @@ def create_pick_list(source_name, target_doc=None):
|
||||
"Sales Order",
|
||||
source_name,
|
||||
{
|
||||
"Sales Order": {"doctype": "Pick List", "validation": {"docstatus": ["=", 1]}},
|
||||
"Sales Order": {
|
||||
"doctype": "Pick List",
|
||||
"field_map": {"set_warehouse": "parent_warehouse"},
|
||||
"validation": {"docstatus": ["=", 1]},
|
||||
},
|
||||
"Sales Order Item": {
|
||||
"doctype": "Pick List Item",
|
||||
"field_map": {"parent": "sales_order", "name": "sales_order_item"},
|
||||
|
@ -398,6 +398,8 @@ class DeliveryNote(SellingController):
|
||||
self.check_credit_limit()
|
||||
elif self.issue_credit_note:
|
||||
self.make_return_invoice()
|
||||
|
||||
self.make_bundle_using_old_serial_batch_fields()
|
||||
# Updating stock ledger should always be called after updating prevdoc status,
|
||||
# because updating reserved qty in bin depends upon updated delivered qty in SO
|
||||
self.update_stock_ledger()
|
||||
|
@ -200,7 +200,6 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
},
|
||||
)
|
||||
|
||||
frappe.flags.ignore_serial_batch_bundle_validation = True
|
||||
serial_nos = [
|
||||
"OSN-1",
|
||||
"OSN-2",
|
||||
@ -239,6 +238,8 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
)
|
||||
|
||||
se_doc.items[0].serial_no = "\n".join(serial_nos)
|
||||
|
||||
frappe.flags.use_serial_and_batch_fields = True
|
||||
se_doc.submit()
|
||||
|
||||
self.assertEqual(sorted(get_serial_nos(se_doc.items[0].serial_no)), sorted(serial_nos))
|
||||
@ -294,6 +295,8 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
self.assertTrue(serial_no in serial_nos)
|
||||
self.assertFalse(serial_no in returned_serial_nos1)
|
||||
|
||||
frappe.flags.use_serial_and_batch_fields = False
|
||||
|
||||
def test_sales_return_for_non_bundled_items_partial(self):
|
||||
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
|
||||
|
||||
@ -1563,7 +1566,7 @@ def create_delivery_note(**args):
|
||||
dn.return_against = args.return_against
|
||||
|
||||
bundle_id = None
|
||||
if args.get("batch_no") or args.get("serial_no"):
|
||||
if not args.use_serial_batch_fields and (args.get("batch_no") or args.get("serial_no")):
|
||||
type_of_transaction = args.type_of_transaction or "Outward"
|
||||
|
||||
if dn.is_return:
|
||||
@ -1605,6 +1608,9 @@ def create_delivery_note(**args):
|
||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"target_warehouse": args.target_warehouse,
|
||||
"use_serial_batch_fields": args.use_serial_batch_fields,
|
||||
"serial_no": args.serial_no if args.use_serial_batch_fields else None,
|
||||
"batch_no": args.batch_no if args.use_serial_batch_fields else None,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -80,8 +80,11 @@
|
||||
"section_break_40",
|
||||
"pick_serial_and_batch",
|
||||
"serial_and_batch_bundle",
|
||||
"use_serial_batch_fields",
|
||||
"column_break_eaoe",
|
||||
"section_break_qyjv",
|
||||
"serial_no",
|
||||
"column_break_rxvc",
|
||||
"batch_no",
|
||||
"available_qty_section",
|
||||
"actual_batch_qty",
|
||||
@ -850,6 +853,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
@ -859,6 +863,7 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "pick_serial_and_batch",
|
||||
"fieldtype": "Button",
|
||||
"label": "Pick Serial / Batch No"
|
||||
@ -874,27 +879,40 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 1,
|
||||
"label": "Serial No",
|
||||
"read_only": 1
|
||||
"label": "Serial No"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "section_break_qyjv",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_rxvc",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-14 18:37:38.638144",
|
||||
"modified": "2024-02-04 14:10:31.750340",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note Item",
|
||||
|
@ -82,6 +82,7 @@ class DeliveryNoteItem(Document):
|
||||
target_warehouse: DF.Link | None
|
||||
total_weight: DF.Float
|
||||
uom: DF.Link
|
||||
use_serial_batch_fields: DF.Check
|
||||
warehouse: DF.Link | None
|
||||
weight_per_unit: DF.Float
|
||||
weight_uom: DF.Link | None
|
||||
|
@ -20,9 +20,12 @@
|
||||
"uom",
|
||||
"section_break_9",
|
||||
"pick_serial_and_batch",
|
||||
"serial_and_batch_bundle",
|
||||
"serial_no",
|
||||
"use_serial_batch_fields",
|
||||
"column_break_11",
|
||||
"serial_and_batch_bundle",
|
||||
"section_break_bgys",
|
||||
"serial_no",
|
||||
"column_break_qlha",
|
||||
"batch_no",
|
||||
"actual_batch_qty",
|
||||
"section_break_13",
|
||||
@ -118,10 +121,10 @@
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
"label": "Serial No",
|
||||
"read_only": 1
|
||||
"label": "Serial No"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
@ -131,8 +134,7 @@
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"read_only": 1
|
||||
"options": "Batch"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_13",
|
||||
@ -259,6 +261,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
@ -267,16 +270,32 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "pick_serial_and_batch",
|
||||
"fieldtype": "Button",
|
||||
"label": "Pick Serial / Batch No"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "section_break_bgys",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_qlha",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-28 13:16:38.460806",
|
||||
"modified": "2024-02-04 16:30:44.263964",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Packed Item",
|
||||
|
@ -47,6 +47,7 @@ class PackedItem(Document):
|
||||
serial_no: DF.Text | None
|
||||
target_warehouse: DF.Link | None
|
||||
uom: DF.Link | None
|
||||
use_serial_batch_fields: DF.Check
|
||||
warehouse: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
|
@ -16,7 +16,6 @@ frappe.ui.form.on('Pick List', {
|
||||
frm.set_query('parent_warehouse', () => {
|
||||
return {
|
||||
filters: {
|
||||
'is_group': 1,
|
||||
'company': frm.doc.company
|
||||
}
|
||||
};
|
||||
|
@ -51,7 +51,7 @@
|
||||
"description": "Items under this warehouse will be suggested",
|
||||
"fieldname": "parent_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Parent Warehouse",
|
||||
"label": "Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
@ -188,7 +188,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-01-24 10:33:43.244476",
|
||||
"modified": "2024-02-01 16:17:44.877426",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Pick List",
|
||||
|
@ -13,7 +13,7 @@ from frappe.model.mapper import map_child_doc
|
||||
from frappe.query_builder import Case
|
||||
from frappe.query_builder.custom import GROUP_CONCAT
|
||||
from frappe.query_builder.functions import Coalesce, Locate, Replace, Sum
|
||||
from frappe.utils import cint, floor, flt
|
||||
from frappe.utils import ceil, cint, floor, flt
|
||||
from frappe.utils.nestedset import get_descendants_of
|
||||
|
||||
from erpnext.selling.doctype.sales_order.sales_order import (
|
||||
@ -122,11 +122,42 @@ class PickList(Document):
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_serial_and_batch_bundle()
|
||||
self.make_bundle_using_old_serial_batch_fields()
|
||||
self.update_status()
|
||||
self.update_bundle_picked_qty()
|
||||
self.update_reference_qty()
|
||||
self.update_sales_order_picking_status()
|
||||
|
||||
def make_bundle_using_old_serial_batch_fields(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
for row in self.locations:
|
||||
if not row.serial_no and not row.batch_no:
|
||||
continue
|
||||
|
||||
if not row.use_serial_batch_fields and (row.serial_no or row.batch_no):
|
||||
frappe.throw(_("Please enable Use Old Serial / Batch Fields to make_bundle"))
|
||||
|
||||
if row.use_serial_batch_fields and (not row.serial_and_batch_bundle):
|
||||
sn_doc = SerialBatchCreation(
|
||||
{
|
||||
"item_code": row.item_code,
|
||||
"warehouse": row.warehouse,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"voucher_detail_no": row.name,
|
||||
"qty": row.stock_qty,
|
||||
"type_of_transaction": "Outward",
|
||||
"company": self.company,
|
||||
"serial_nos": get_serial_nos(row.serial_no) if row.serial_no else None,
|
||||
"batches": frappe._dict({row.batch_no: row.stock_qty}) if row.batch_no else None,
|
||||
"batch_no": row.batch_no,
|
||||
}
|
||||
).make_serial_and_batch_bundle()
|
||||
|
||||
row.serial_and_batch_bundle = sn_doc.name
|
||||
row.db_set("serial_and_batch_bundle", sn_doc.name)
|
||||
|
||||
def on_update_after_submit(self) -> None:
|
||||
if self.has_reserved_stock():
|
||||
msg = _(
|
||||
@ -156,6 +187,7 @@ class PickList(Document):
|
||||
{"is_cancelled": 1, "voucher_no": ""},
|
||||
)
|
||||
|
||||
frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle).cancel()
|
||||
row.db_set("serial_and_batch_bundle", None)
|
||||
|
||||
def on_update(self):
|
||||
@ -324,7 +356,6 @@ class PickList(Document):
|
||||
locations_replica = self.get("locations")
|
||||
|
||||
# reset
|
||||
self.remove_serial_and_batch_bundle()
|
||||
self.delete_key("locations")
|
||||
updated_locations = frappe._dict()
|
||||
for item_doc in items:
|
||||
@ -639,13 +670,19 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus)
|
||||
if not stock_qty:
|
||||
break
|
||||
|
||||
serial_nos = None
|
||||
if item_location.serial_nos:
|
||||
serial_nos = "\n".join(item_location.serial_nos[0 : cint(stock_qty)])
|
||||
|
||||
locations.append(
|
||||
frappe._dict(
|
||||
{
|
||||
"qty": qty,
|
||||
"stock_qty": stock_qty,
|
||||
"warehouse": item_location.warehouse,
|
||||
"serial_and_batch_bundle": item_location.serial_and_batch_bundle,
|
||||
"serial_no": serial_nos,
|
||||
"batch_no": item_location.batch_no,
|
||||
"use_serial_batch_fields": 1,
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -681,7 +718,15 @@ def get_available_item_locations(
|
||||
has_serial_no = frappe.get_cached_value("Item", item_code, "has_serial_no")
|
||||
has_batch_no = frappe.get_cached_value("Item", item_code, "has_batch_no")
|
||||
|
||||
if has_serial_no:
|
||||
if has_batch_no and has_serial_no:
|
||||
locations = get_available_item_locations_for_serial_and_batched_item(
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
total_picked_qty,
|
||||
)
|
||||
elif has_serial_no:
|
||||
locations = get_available_item_locations_for_serialized_item(
|
||||
item_code, from_warehouses, required_qty, company, total_picked_qty
|
||||
)
|
||||
@ -724,6 +769,47 @@ def get_available_item_locations(
|
||||
return locations
|
||||
|
||||
|
||||
def get_available_item_locations_for_serial_and_batched_item(
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
total_picked_qty=0,
|
||||
):
|
||||
# Get batch nos by FIFO
|
||||
locations = get_available_item_locations_for_batched_item(
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
)
|
||||
|
||||
if locations:
|
||||
sn = frappe.qb.DocType("Serial No")
|
||||
conditions = (sn.item_code == item_code) & (sn.company == company)
|
||||
|
||||
for location in locations:
|
||||
location.qty = (
|
||||
required_qty if location.qty > required_qty else location.qty
|
||||
) # if extra qty in batch
|
||||
|
||||
serial_nos = (
|
||||
frappe.qb.from_(sn)
|
||||
.select(sn.name)
|
||||
.where(
|
||||
(conditions) & (sn.batch_no == location.batch_no) & (sn.warehouse == location.warehouse)
|
||||
)
|
||||
.orderby(sn.creation)
|
||||
.limit(ceil(location.qty + total_picked_qty))
|
||||
).run(as_dict=True)
|
||||
|
||||
serial_nos = [sn.name for sn in serial_nos]
|
||||
location.serial_nos = serial_nos
|
||||
location.qty = len(serial_nos)
|
||||
|
||||
return locations
|
||||
|
||||
|
||||
def get_available_item_locations_for_serialized_item(
|
||||
item_code, from_warehouses, required_qty, company, total_picked_qty=0
|
||||
):
|
||||
@ -757,28 +843,16 @@ def get_available_item_locations_for_serialized_item(
|
||||
picked_qty -= 1
|
||||
|
||||
locations = []
|
||||
|
||||
for warehouse, serial_nos in warehouse_serial_nos_map.items():
|
||||
qty = len(serial_nos)
|
||||
|
||||
bundle_doc = SerialBatchCreation(
|
||||
{
|
||||
"item_code": item_code,
|
||||
"warehouse": warehouse,
|
||||
"voucher_type": "Pick List",
|
||||
"total_qty": qty * -1,
|
||||
"serial_nos": serial_nos,
|
||||
"type_of_transaction": "Outward",
|
||||
"company": company,
|
||||
"do_not_submit": True,
|
||||
}
|
||||
).make_serial_and_batch_bundle()
|
||||
|
||||
locations.append(
|
||||
{
|
||||
"qty": qty,
|
||||
"warehouse": warehouse,
|
||||
"item_code": item_code,
|
||||
"serial_and_batch_bundle": bundle_doc.name,
|
||||
"serial_nos": serial_nos,
|
||||
}
|
||||
)
|
||||
|
||||
@ -808,29 +882,17 @@ def get_available_item_locations_for_batched_item(
|
||||
warehouse_wise_batches[d.warehouse][d.batch_no] += d.qty
|
||||
|
||||
for warehouse, batches in warehouse_wise_batches.items():
|
||||
qty = sum(batches.values())
|
||||
|
||||
bundle_doc = SerialBatchCreation(
|
||||
{
|
||||
"item_code": item_code,
|
||||
"warehouse": warehouse,
|
||||
"voucher_type": "Pick List",
|
||||
"total_qty": qty * -1,
|
||||
"batches": batches,
|
||||
"type_of_transaction": "Outward",
|
||||
"company": company,
|
||||
"do_not_submit": True,
|
||||
}
|
||||
).make_serial_and_batch_bundle()
|
||||
|
||||
locations.append(
|
||||
{
|
||||
"qty": qty,
|
||||
"warehouse": warehouse,
|
||||
"item_code": item_code,
|
||||
"serial_and_batch_bundle": bundle_doc.name,
|
||||
}
|
||||
)
|
||||
for batch_no, qty in batches.items():
|
||||
locations.append(
|
||||
frappe._dict(
|
||||
{
|
||||
"qty": qty,
|
||||
"warehouse": warehouse,
|
||||
"item_code": item_code,
|
||||
"batch_no": batch_no,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
return locations
|
||||
|
||||
|
@ -217,6 +217,8 @@ class TestPickList(FrappeTestCase):
|
||||
)
|
||||
|
||||
pick_list.save()
|
||||
pick_list.submit()
|
||||
|
||||
self.assertEqual(pick_list.locations[0].item_code, "_Test Serialized Item")
|
||||
self.assertEqual(pick_list.locations[0].warehouse, "_Test Warehouse - _TC")
|
||||
self.assertEqual(pick_list.locations[0].qty, 5)
|
||||
@ -239,7 +241,7 @@ class TestPickList(FrappeTestCase):
|
||||
pr1 = make_purchase_receipt(item_code="Batched Item", qty=1, rate=100.0)
|
||||
|
||||
pr1.load_from_db()
|
||||
oldest_batch_no = pr1.items[0].batch_no
|
||||
oldest_batch_no = get_batch_from_bundle(pr1.items[0].serial_and_batch_bundle)
|
||||
|
||||
pr2 = make_purchase_receipt(item_code="Batched Item", qty=2, rate=100.0)
|
||||
|
||||
@ -302,6 +304,8 @@ class TestPickList(FrappeTestCase):
|
||||
}
|
||||
)
|
||||
pick_list.set_item_locations()
|
||||
pick_list.submit()
|
||||
pick_list.reload()
|
||||
|
||||
self.assertEqual(
|
||||
get_batch_from_bundle(pick_list.locations[0].serial_and_batch_bundle), oldest_batch_no
|
||||
@ -310,6 +314,7 @@ class TestPickList(FrappeTestCase):
|
||||
get_serial_nos_from_bundle(pick_list.locations[0].serial_and_batch_bundle), oldest_serial_nos
|
||||
)
|
||||
|
||||
pick_list.cancel()
|
||||
pr1.cancel()
|
||||
pr2.cancel()
|
||||
|
||||
@ -671,29 +676,22 @@ class TestPickList(FrappeTestCase):
|
||||
|
||||
so = make_sales_order(item_code=item, qty=25.0, rate=100)
|
||||
pl = create_pick_list(so.name)
|
||||
pl.submit()
|
||||
# pick half the qty
|
||||
for loc in pl.locations:
|
||||
self.assertEqual(loc.qty, 25.0)
|
||||
self.assertTrue(loc.serial_and_batch_bundle)
|
||||
|
||||
data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
fields=["qty", "batch_no"],
|
||||
filters={"parent": loc.serial_and_batch_bundle},
|
||||
)
|
||||
|
||||
for d in data:
|
||||
self.assertEqual(d.batch_no, "PICKLT-000001")
|
||||
self.assertEqual(d.qty, 25.0 * -1)
|
||||
|
||||
pl.save()
|
||||
pl.submit()
|
||||
|
||||
so1 = make_sales_order(item_code=item, qty=10.0, rate=100)
|
||||
pl = create_pick_list(so1.name)
|
||||
pl1 = create_pick_list(so1.name)
|
||||
pl1.submit()
|
||||
|
||||
# pick half the qty
|
||||
for loc in pl.locations:
|
||||
self.assertEqual(loc.qty, 10.0)
|
||||
for loc in pl1.locations:
|
||||
self.assertEqual(loc.qty, 5.0)
|
||||
self.assertTrue(loc.serial_and_batch_bundle)
|
||||
|
||||
data = frappe.get_all(
|
||||
@ -709,8 +707,7 @@ class TestPickList(FrappeTestCase):
|
||||
elif d.batch_no == "PICKLT-000002":
|
||||
self.assertEqual(d.qty, 5.0 * -1)
|
||||
|
||||
pl.save()
|
||||
pl.submit()
|
||||
pl1.cancel()
|
||||
pl.cancel()
|
||||
|
||||
def test_picklist_for_serial_item(self):
|
||||
@ -723,6 +720,7 @@ class TestPickList(FrappeTestCase):
|
||||
|
||||
so = make_sales_order(item_code=item, qty=25.0, rate=100)
|
||||
pl = create_pick_list(so.name)
|
||||
pl.submit()
|
||||
picked_serial_nos = []
|
||||
# pick half the qty
|
||||
for loc in pl.locations:
|
||||
@ -736,13 +734,11 @@ class TestPickList(FrappeTestCase):
|
||||
picked_serial_nos = [d.serial_no for d in data]
|
||||
self.assertEqual(len(picked_serial_nos), 25)
|
||||
|
||||
pl.save()
|
||||
pl.submit()
|
||||
|
||||
so1 = make_sales_order(item_code=item, qty=10.0, rate=100)
|
||||
pl = create_pick_list(so1.name)
|
||||
pl1 = create_pick_list(so1.name)
|
||||
pl1.submit()
|
||||
# pick half the qty
|
||||
for loc in pl.locations:
|
||||
for loc in pl1.locations:
|
||||
self.assertEqual(loc.qty, 10.0)
|
||||
self.assertTrue(loc.serial_and_batch_bundle)
|
||||
|
||||
@ -756,8 +752,7 @@ class TestPickList(FrappeTestCase):
|
||||
for d in data:
|
||||
self.assertTrue(d.serial_no not in picked_serial_nos)
|
||||
|
||||
pl.save()
|
||||
pl.submit()
|
||||
pl1.cancel()
|
||||
pl.cancel()
|
||||
|
||||
def test_picklist_with_bundles(self):
|
||||
|
@ -24,8 +24,11 @@
|
||||
"serial_no_and_batch_section",
|
||||
"pick_serial_and_batch",
|
||||
"serial_and_batch_bundle",
|
||||
"serial_no",
|
||||
"use_serial_batch_fields",
|
||||
"column_break_20",
|
||||
"section_break_ecxc",
|
||||
"serial_no",
|
||||
"column_break_belw",
|
||||
"batch_no",
|
||||
"column_break_15",
|
||||
"sales_order",
|
||||
@ -72,19 +75,17 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "serial_no",
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Serial No",
|
||||
"read_only": 1
|
||||
"label": "Serial No"
|
||||
},
|
||||
{
|
||||
"depends_on": "batch_no",
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@ -195,6 +196,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
@ -204,6 +206,7 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "pick_serial_and_batch",
|
||||
"fieldtype": "Button",
|
||||
"label": "Pick Serial / Batch No"
|
||||
@ -218,11 +221,26 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "section_break_ecxc",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_belw",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-26 12:54:15.785962",
|
||||
"modified": "2024-02-04 16:12:16.257951",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Pick List Item",
|
||||
|
@ -37,6 +37,7 @@ class PickListItem(Document):
|
||||
stock_reserved_qty: DF.Float
|
||||
stock_uom: DF.Link | None
|
||||
uom: DF.Link | None
|
||||
use_serial_batch_fields: DF.Check
|
||||
warehouse: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
|
@ -369,6 +369,7 @@ class PurchaseReceipt(BuyingController):
|
||||
else:
|
||||
self.db_set("status", "Completed")
|
||||
|
||||
self.make_bundle_using_old_serial_batch_fields()
|
||||
# Updating stock ledger should always be called after updating prevdoc status,
|
||||
# because updating ordered qty, reserved_qty_for_subcontract in bin
|
||||
# depends upon updated ordered qty in PO
|
||||
|
@ -2230,6 +2230,93 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
pr_doc.reload()
|
||||
self.assertFalse(pr_doc.items[0].from_warehouse)
|
||||
|
||||
def test_use_serial_batch_fields_for_serial_nos(self):
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
||||
create_stock_reconciliation,
|
||||
)
|
||||
|
||||
item_code = make_item(
|
||||
"_Test Use Serial Fields Item Serial Item",
|
||||
properties={"has_serial_no": 1, "serial_no_series": "SNU-TSFISI-.#####"},
|
||||
).name
|
||||
|
||||
serial_nos = [
|
||||
"SNU-TSFISI-000011",
|
||||
"SNU-TSFISI-000012",
|
||||
"SNU-TSFISI-000013",
|
||||
"SNU-TSFISI-000014",
|
||||
"SNU-TSFISI-000015",
|
||||
]
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
item_code=item_code,
|
||||
qty=5,
|
||||
serial_no="\n".join(serial_nos),
|
||||
use_serial_batch_fields=1,
|
||||
rate=100,
|
||||
)
|
||||
|
||||
self.assertEqual(pr.items[0].use_serial_batch_fields, 1)
|
||||
self.assertFalse(pr.items[0].serial_no)
|
||||
self.assertTrue(pr.items[0].serial_and_batch_bundle)
|
||||
|
||||
sbb_doc = frappe.get_doc("Serial and Batch Bundle", pr.items[0].serial_and_batch_bundle)
|
||||
|
||||
for row in sbb_doc.entries:
|
||||
self.assertTrue(row.serial_no in serial_nos)
|
||||
|
||||
serial_nos.remove("SNU-TSFISI-000015")
|
||||
|
||||
sr = create_stock_reconciliation(
|
||||
item_code=item_code,
|
||||
serial_no="\n".join(serial_nos),
|
||||
qty=4,
|
||||
warehouse=pr.items[0].warehouse,
|
||||
use_serial_batch_fields=1,
|
||||
do_not_submit=True,
|
||||
)
|
||||
sr.reload()
|
||||
|
||||
serial_nos = get_serial_nos(sr.items[0].current_serial_no)
|
||||
self.assertEqual(len(serial_nos), 5)
|
||||
self.assertEqual(sr.items[0].current_qty, 5)
|
||||
|
||||
new_serial_nos = get_serial_nos(sr.items[0].serial_no)
|
||||
self.assertEqual(len(new_serial_nos), 4)
|
||||
self.assertEqual(sr.items[0].qty, 4)
|
||||
self.assertEqual(sr.items[0].use_serial_batch_fields, 1)
|
||||
self.assertFalse(sr.items[0].current_serial_and_batch_bundle)
|
||||
self.assertFalse(sr.items[0].serial_and_batch_bundle)
|
||||
self.assertTrue(sr.items[0].current_serial_no)
|
||||
sr.submit()
|
||||
|
||||
sr.reload()
|
||||
self.assertTrue(sr.items[0].current_serial_and_batch_bundle)
|
||||
self.assertTrue(sr.items[0].serial_and_batch_bundle)
|
||||
|
||||
serial_no_status = frappe.db.get_value("Serial No", "SNU-TSFISI-000015", "status")
|
||||
|
||||
self.assertTrue(serial_no_status != "Active")
|
||||
|
||||
dn = create_delivery_note(
|
||||
item_code=item_code,
|
||||
qty=4,
|
||||
serial_no="\n".join(new_serial_nos),
|
||||
use_serial_batch_fields=1,
|
||||
)
|
||||
|
||||
self.assertTrue(dn.items[0].serial_and_batch_bundle)
|
||||
self.assertEqual(dn.items[0].qty, 4)
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", dn.items[0].serial_and_batch_bundle)
|
||||
for row in doc.entries:
|
||||
self.assertTrue(row.serial_no in new_serial_nos)
|
||||
|
||||
for sn in new_serial_nos:
|
||||
serial_no_status = frappe.db.get_value("Serial No", sn, "status")
|
||||
self.assertTrue(serial_no_status != "Active")
|
||||
|
||||
|
||||
def prepare_data_for_internal_transfer():
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||
@ -2399,7 +2486,7 @@ def make_purchase_receipt(**args):
|
||||
uom = args.uom or frappe.db.get_value("Item", item_code, "stock_uom") or "_Test UOM"
|
||||
|
||||
bundle_id = None
|
||||
if args.get("batch_no") or args.get("serial_no"):
|
||||
if not args.use_serial_batch_fields and (args.get("batch_no") or args.get("serial_no")):
|
||||
batches = {}
|
||||
if args.get("batch_no"):
|
||||
batches = frappe._dict({args.batch_no: qty})
|
||||
@ -2441,6 +2528,9 @@ def make_purchase_receipt(**args):
|
||||
"cost_center": args.cost_center
|
||||
or frappe.get_cached_value("Company", pr.company, "cost_center"),
|
||||
"asset_location": args.location or "Test Location",
|
||||
"use_serial_batch_fields": args.use_serial_batch_fields or 0,
|
||||
"serial_no": args.serial_no if args.use_serial_batch_fields else "",
|
||||
"batch_no": args.batch_no if args.use_serial_batch_fields else "",
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -94,6 +94,7 @@
|
||||
"section_break_45",
|
||||
"add_serial_batch_bundle",
|
||||
"serial_and_batch_bundle",
|
||||
"use_serial_batch_fields",
|
||||
"col_break5",
|
||||
"add_serial_batch_for_rejected_qty",
|
||||
"rejected_serial_and_batch_bundle",
|
||||
@ -1003,6 +1004,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
@ -1020,24 +1022,22 @@
|
||||
{
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
"label": "Serial No",
|
||||
"read_only": 1
|
||||
"label": "Serial No"
|
||||
},
|
||||
{
|
||||
"fieldname": "rejected_serial_no",
|
||||
"fieldtype": "Text",
|
||||
"label": "Rejected Serial No",
|
||||
"read_only": 1
|
||||
"label": "Rejected Serial No"
|
||||
},
|
||||
{
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "rejected_serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Rejected Serial and Batch Bundle",
|
||||
@ -1045,11 +1045,13 @@
|
||||
"options": "Serial and Batch Bundle"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0",
|
||||
"fieldname": "add_serial_batch_for_rejected_qty",
|
||||
"fieldtype": "Button",
|
||||
"label": "Add Serial / Batch No (Rejected Qty)"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "section_break_3vxt",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
@ -1058,6 +1060,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0",
|
||||
"fieldname": "add_serial_batch_bundle",
|
||||
"fieldtype": "Button",
|
||||
"label": "Add Serial / Batch No"
|
||||
@ -1098,12 +1101,18 @@
|
||||
"read_only": 1,
|
||||
"report_hide": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-25 22:32:09.801965",
|
||||
"modified": "2024-02-04 11:48:06.653771",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
|
@ -99,6 +99,7 @@ class PurchaseReceiptItem(Document):
|
||||
supplier_part_no: DF.Data | None
|
||||
total_weight: DF.Float
|
||||
uom: DF.Link
|
||||
use_serial_batch_fields: DF.Check
|
||||
valuation_rate: DF.Currency
|
||||
warehouse: DF.Link | None
|
||||
weight_per_unit: DF.Float
|
||||
|
@ -1117,7 +1117,7 @@ def parse_serial_nos(data):
|
||||
if isinstance(data, list):
|
||||
return data
|
||||
|
||||
return [s.strip() for s in cstr(data).strip().upper().replace(",", "\n").split("\n") if s.strip()]
|
||||
return [s.strip() for s in cstr(data).strip().replace(",", "\n").split("\n") if s.strip()]
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@ -1256,7 +1256,7 @@ def create_serial_batch_no_ledgers(
|
||||
|
||||
|
||||
def get_type_of_transaction(parent_doc, child_row):
|
||||
type_of_transaction = child_row.type_of_transaction
|
||||
type_of_transaction = child_row.get("type_of_transaction")
|
||||
if parent_doc.get("doctype") == "Stock Entry":
|
||||
type_of_transaction = "Outward" if child_row.s_warehouse else "Inward"
|
||||
|
||||
@ -1384,6 +1384,8 @@ def get_available_serial_nos(kwargs):
|
||||
|
||||
filters = {"item_code": kwargs.item_code}
|
||||
|
||||
# ignore_warehouse is used for backdated stock transactions
|
||||
# There might be chances that the serial no not exists in the warehouse during backdated stock transactions
|
||||
if not kwargs.get("ignore_warehouse"):
|
||||
filters["warehouse"] = ("is", "set")
|
||||
if kwargs.warehouse:
|
||||
@ -1677,7 +1679,10 @@ def get_reserved_batches_for_sre(kwargs) -> dict:
|
||||
query = query.where(sb_entry.batch_no == kwargs.batch_no)
|
||||
|
||||
if kwargs.warehouse:
|
||||
query = query.where(sre.warehouse == kwargs.warehouse)
|
||||
if isinstance(kwargs.warehouse, list):
|
||||
query = query.where(sre.warehouse.isin(kwargs.warehouse))
|
||||
else:
|
||||
query = query.where(sre.warehouse == kwargs.warehouse)
|
||||
|
||||
if kwargs.ignore_voucher_nos:
|
||||
query = query.where(sre.name.notin(kwargs.ignore_voucher_nos))
|
||||
|
@ -136,6 +136,7 @@ class TestSerialandBatchBundle(FrappeTestCase):
|
||||
|
||||
def test_old_batch_valuation(self):
|
||||
frappe.flags.ignore_serial_batch_bundle_validation = True
|
||||
frappe.flags.use_serial_and_batch_fields = True
|
||||
batch_item_code = "Old Batch Item Valuation 1"
|
||||
make_item(
|
||||
batch_item_code,
|
||||
@ -240,6 +241,7 @@ class TestSerialandBatchBundle(FrappeTestCase):
|
||||
bundle_doc.submit()
|
||||
|
||||
frappe.flags.ignore_serial_batch_bundle_validation = False
|
||||
frappe.flags.use_serial_and_batch_fields = False
|
||||
|
||||
def test_old_serial_no_valuation(self):
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
@ -259,6 +261,7 @@ class TestSerialandBatchBundle(FrappeTestCase):
|
||||
)
|
||||
|
||||
frappe.flags.ignore_serial_batch_bundle_validation = True
|
||||
frappe.flags.use_serial_and_batch_fields = True
|
||||
|
||||
serial_no_id = "Old Serial No 1"
|
||||
if not frappe.db.exists("Serial No", serial_no_id):
|
||||
@ -320,6 +323,9 @@ class TestSerialandBatchBundle(FrappeTestCase):
|
||||
for row in bundle_doc.entries:
|
||||
self.assertEqual(flt(row.stock_value_difference, 2), -100.00)
|
||||
|
||||
frappe.flags.ignore_serial_batch_bundle_validation = False
|
||||
frappe.flags.use_serial_and_batch_fields = False
|
||||
|
||||
def test_batch_not_belong_to_serial_no(self):
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
|
||||
|
@ -151,9 +151,7 @@ def get_serial_nos(serial_no):
|
||||
if isinstance(serial_no, list):
|
||||
return serial_no
|
||||
|
||||
return [
|
||||
s.strip() for s in cstr(serial_no).strip().upper().replace(",", "\n").split("\n") if s.strip()
|
||||
]
|
||||
return [s.strip() for s in cstr(serial_no).strip().replace(",", "\n").split("\n") if s.strip()]
|
||||
|
||||
|
||||
def clean_serial_no_string(serial_no: str) -> str:
|
||||
|
@ -274,6 +274,7 @@ class StockEntry(StockController):
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_closed_subcontracting_order()
|
||||
self.make_bundle_using_old_serial_batch_fields()
|
||||
self.update_stock_ledger()
|
||||
self.update_work_order()
|
||||
self.validate_subcontract_order()
|
||||
|
@ -92,6 +92,9 @@ def make_stock_entry(**args):
|
||||
else:
|
||||
args.qty = cint(args.qty)
|
||||
|
||||
if args.serial_no or args.batch_no:
|
||||
args.use_serial_batch_fields = True
|
||||
|
||||
# purpose
|
||||
if not args.purpose:
|
||||
if args.source and args.target:
|
||||
@ -162,6 +165,7 @@ def make_stock_entry(**args):
|
||||
)
|
||||
|
||||
args.serial_no = serial_number
|
||||
|
||||
s.append(
|
||||
"items",
|
||||
{
|
||||
@ -177,6 +181,7 @@ def make_stock_entry(**args):
|
||||
"batch_no": args.batch_no,
|
||||
"cost_center": args.cost_center,
|
||||
"expense_account": args.expense_account,
|
||||
"use_serial_batch_fields": args.use_serial_batch_fields,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -680,6 +680,7 @@ class TestStockEntry(FrappeTestCase):
|
||||
def test_serial_move(self):
|
||||
se = make_serialized_item()
|
||||
serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0]
|
||||
frappe.flags.use_serial_and_batch_fields = True
|
||||
|
||||
se = frappe.copy_doc(test_records[0])
|
||||
se.purpose = "Material Transfer"
|
||||
@ -700,6 +701,7 @@ class TestStockEntry(FrappeTestCase):
|
||||
self.assertTrue(
|
||||
frappe.db.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse - _TC"
|
||||
)
|
||||
frappe.flags.use_serial_and_batch_fields = False
|
||||
|
||||
def test_serial_cancel(self):
|
||||
se, serial_nos = self.test_serial_by_series()
|
||||
@ -999,6 +1001,8 @@ class TestStockEntry(FrappeTestCase):
|
||||
do_not_save=True,
|
||||
)
|
||||
|
||||
frappe.flags.use_serial_and_batch_fields = True
|
||||
|
||||
cls_obj = SerialBatchCreation(
|
||||
{
|
||||
"type_of_transaction": "Inward",
|
||||
@ -1035,84 +1039,7 @@ class TestStockEntry(FrappeTestCase):
|
||||
|
||||
s2.submit()
|
||||
s2.cancel()
|
||||
|
||||
# def test_retain_sample(self):
|
||||
# from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||
# from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
# 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": get_batch_from_bundle(receipt_entry.get("items")[0].serial_and_batch_bundle),
|
||||
# },
|
||||
# )
|
||||
# retention_entry.set_stock_entry_type()
|
||||
# retention_entry.insert()
|
||||
# retention_entry.submit()
|
||||
|
||||
# qty_in_usable_warehouse = get_batch_qty(
|
||||
# get_batch_from_bundle(receipt_entry.get("items")[0].serial_and_batch_bundle), "_Test Warehouse - _TC", "_Test Item"
|
||||
# )
|
||||
# qty_in_retention_warehouse = get_batch_qty(
|
||||
# get_batch_from_bundle(receipt_entry.get("items")[0].serial_and_batch_bundle),
|
||||
# "Test Warehouse for Sample Retention - _TC",
|
||||
# "_Test Item",
|
||||
# )
|
||||
|
||||
# self.assertEqual(qty_in_usable_warehouse, 36)
|
||||
# self.assertEqual(qty_in_retention_warehouse, 4)
|
||||
frappe.flags.use_serial_and_batch_fields = False
|
||||
|
||||
def test_quality_check(self):
|
||||
item_code = "_Test Item For QC"
|
||||
|
@ -47,9 +47,12 @@
|
||||
"amount",
|
||||
"serial_no_batch",
|
||||
"add_serial_batch_bundle",
|
||||
"serial_and_batch_bundle",
|
||||
"use_serial_batch_fields",
|
||||
"col_break4",
|
||||
"serial_and_batch_bundle",
|
||||
"section_break_rdtg",
|
||||
"serial_no",
|
||||
"column_break_prps",
|
||||
"batch_no",
|
||||
"accounting",
|
||||
"expense_account",
|
||||
@ -289,27 +292,27 @@
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Serial No",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "serial_no",
|
||||
"oldfieldtype": "Text",
|
||||
"read_only": 1
|
||||
"oldfieldtype": "Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "batch_no",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Batch",
|
||||
"read_only": 1
|
||||
"options": "Batch"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.inspection_required && doc.t_warehouse",
|
||||
@ -573,24 +576,41 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0",
|
||||
"fieldname": "add_serial_batch_bundle",
|
||||
"fieldtype": "Button",
|
||||
"label": "Add Serial / Batch No"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
"no_copy": 1,
|
||||
"options": "Serial and Batch Bundle",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "section_break_rdtg",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_prps",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-12 11:56:04.626103",
|
||||
"modified": "2024-02-04 16:16:47.606270",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry Detail",
|
||||
|
@ -63,6 +63,7 @@ class StockEntryDetail(Document):
|
||||
transfer_qty: DF.Float
|
||||
transferred_qty: DF.Float
|
||||
uom: DF.Link
|
||||
use_serial_batch_fields: DF.Check
|
||||
valuation_rate: DF.Currency
|
||||
# end: auto-generated types
|
||||
|
||||
|
@ -482,6 +482,8 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
|
||||
(item, warehouses[0], batches[1], 1, 200),
|
||||
(item, warehouses[0], batches[0], 1, 200),
|
||||
]
|
||||
|
||||
frappe.flags.use_serial_and_batch_fields = True
|
||||
dns = create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list)
|
||||
sle_details = fetch_sle_details_for_doc_list(dns, ["stock_value_difference"])
|
||||
svd_list = [-1 * d["stock_value_difference"] for d in sle_details]
|
||||
@ -494,6 +496,8 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
|
||||
"Incorrect 'Incoming Rate' values fetched for DN items",
|
||||
)
|
||||
|
||||
frappe.flags.use_serial_and_batch_fields = False
|
||||
|
||||
def test_batchwise_item_valuation_stock_reco(self):
|
||||
item, warehouses, batches = setup_item_valuation_test()
|
||||
state = {"stock_value": 0.0, "qty": 0.0}
|
||||
|
@ -198,6 +198,7 @@ frappe.ui.form.on("Stock Reconciliation", {
|
||||
frappe.model.set_value(cdt, cdn, "current_amount", r.message.rate * r.message.qty);
|
||||
frappe.model.set_value(cdt, cdn, "amount", row.qty * row.valuation_rate);
|
||||
frappe.model.set_value(cdt, cdn, "current_serial_no", r.message.serial_nos);
|
||||
frappe.model.set_value(cdt, cdn, "use_serial_batch_fields", r.message.use_serial_batch_fields);
|
||||
|
||||
if (frm.doc.purpose == "Stock Reconciliation" && !frm.doc.scan_mode) {
|
||||
frappe.model.set_value(cdt, cdn, "serial_no", r.message.serial_nos);
|
||||
|
@ -99,6 +99,8 @@ class StockReconciliation(StockController):
|
||||
)
|
||||
|
||||
def on_submit(self):
|
||||
self.make_bundle_for_current_qty()
|
||||
self.make_bundle_using_old_serial_batch_fields()
|
||||
self.update_stock_ledger()
|
||||
self.make_gl_entries()
|
||||
self.repost_future_sle_and_gle()
|
||||
@ -116,9 +118,52 @@ class StockReconciliation(StockController):
|
||||
self.repost_future_sle_and_gle()
|
||||
self.delete_auto_created_batches()
|
||||
|
||||
def make_bundle_for_current_qty(self):
|
||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||
|
||||
for row in self.items:
|
||||
if not row.use_serial_batch_fields:
|
||||
continue
|
||||
|
||||
if row.current_serial_and_batch_bundle:
|
||||
continue
|
||||
|
||||
if row.current_qty and (row.current_serial_no or row.batch_no):
|
||||
sn_doc = SerialBatchCreation(
|
||||
{
|
||||
"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,
|
||||
"qty": row.qty,
|
||||
"type_of_transaction": "Outward",
|
||||
"company": self.company,
|
||||
"is_rejected": 0,
|
||||
"serial_nos": get_serial_nos(row.current_serial_no) if row.current_serial_no else None,
|
||||
"batches": frappe._dict({row.batch_no: row.qty}) if row.batch_no else None,
|
||||
"batch_no": row.batch_no,
|
||||
"do_not_submit": True,
|
||||
}
|
||||
).make_serial_and_batch_bundle()
|
||||
|
||||
row.current_serial_and_batch_bundle = sn_doc.name
|
||||
row.db_set(
|
||||
{
|
||||
"current_serial_and_batch_bundle": sn_doc.name,
|
||||
"current_serial_no": "",
|
||||
"batch_no": "",
|
||||
}
|
||||
)
|
||||
|
||||
def set_current_serial_and_batch_bundle(self, voucher_detail_no=None, save=False) -> None:
|
||||
"""Set Serial and Batch Bundle for each item"""
|
||||
for item in self.items:
|
||||
if not save and item.use_serial_batch_fields:
|
||||
continue
|
||||
|
||||
if voucher_detail_no and voucher_detail_no != item.name:
|
||||
continue
|
||||
|
||||
@ -229,6 +274,9 @@ class StockReconciliation(StockController):
|
||||
|
||||
def set_new_serial_and_batch_bundle(self):
|
||||
for item in self.items:
|
||||
if item.use_serial_batch_fields:
|
||||
continue
|
||||
|
||||
if not item.qty:
|
||||
continue
|
||||
|
||||
@ -291,8 +339,10 @@ class StockReconciliation(StockController):
|
||||
inventory_dimensions_dict=inventory_dimensions_dict,
|
||||
)
|
||||
|
||||
if (item.qty is None or item.qty == item_dict.get("qty")) and (
|
||||
item.valuation_rate is None or item.valuation_rate == item_dict.get("rate")
|
||||
if (
|
||||
(item.qty is None or item.qty == item_dict.get("qty"))
|
||||
and (item.valuation_rate is None or item.valuation_rate == item_dict.get("rate"))
|
||||
and (not item.serial_no or (item.serial_no == item_dict.get("serial_nos")))
|
||||
):
|
||||
return False
|
||||
else:
|
||||
@ -303,6 +353,11 @@ class StockReconciliation(StockController):
|
||||
if item.valuation_rate is None:
|
||||
item.valuation_rate = item_dict.get("rate")
|
||||
|
||||
if item_dict.get("serial_nos"):
|
||||
item.current_serial_no = item_dict.get("serial_nos")
|
||||
if self.purpose == "Stock Reconciliation" and not item.serial_no and item.qty:
|
||||
item.serial_no = item.current_serial_no
|
||||
|
||||
item.current_qty = item_dict.get("qty")
|
||||
item.current_valuation_rate = item_dict.get("rate")
|
||||
self.calculate_difference_amount(item, item_dict)
|
||||
@ -1135,9 +1190,16 @@ def get_stock_balance_for(
|
||||
has_serial_no = bool(item_dict.get("has_serial_no"))
|
||||
has_batch_no = bool(item_dict.get("has_batch_no"))
|
||||
|
||||
use_serial_batch_fields = frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields")
|
||||
|
||||
if not batch_no and has_batch_no:
|
||||
# Not enough information to fetch data
|
||||
return {"qty": 0, "rate": 0, "serial_nos": None}
|
||||
return {
|
||||
"qty": 0,
|
||||
"rate": 0,
|
||||
"serial_nos": None,
|
||||
"use_serial_batch_fields": use_serial_batch_fields,
|
||||
}
|
||||
|
||||
# TODO: fetch only selected batch's values
|
||||
data = get_stock_balance(
|
||||
@ -1160,7 +1222,12 @@ def get_stock_balance_for(
|
||||
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}
|
||||
return {
|
||||
"qty": qty,
|
||||
"rate": rate,
|
||||
"serial_nos": serial_nos,
|
||||
"use_serial_batch_fields": use_serial_batch_fields,
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -1094,7 +1094,7 @@ def create_stock_reconciliation(**args):
|
||||
)
|
||||
|
||||
bundle_id = None
|
||||
if args.batch_no or args.serial_no:
|
||||
if not args.use_serial_batch_fields and (args.batch_no or args.serial_no):
|
||||
batches = frappe._dict({})
|
||||
if args.batch_no:
|
||||
batches[args.batch_no] = args.qty
|
||||
@ -1125,7 +1125,10 @@ def create_stock_reconciliation(**args):
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": args.qty,
|
||||
"valuation_rate": args.rate,
|
||||
"serial_no": args.serial_no if args.use_serial_batch_fields else None,
|
||||
"batch_no": args.batch_no if args.use_serial_batch_fields else None,
|
||||
"serial_and_batch_bundle": bundle_id,
|
||||
"use_serial_batch_fields": args.use_serial_batch_fields,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -19,11 +19,14 @@
|
||||
"allow_zero_valuation_rate",
|
||||
"serial_no_and_batch_section",
|
||||
"add_serial_batch_bundle",
|
||||
"serial_and_batch_bundle",
|
||||
"batch_no",
|
||||
"use_serial_batch_fields",
|
||||
"column_break_11",
|
||||
"serial_and_batch_bundle",
|
||||
"current_serial_and_batch_bundle",
|
||||
"section_break_lypk",
|
||||
"serial_no",
|
||||
"column_break_eefq",
|
||||
"batch_no",
|
||||
"section_break_3",
|
||||
"current_qty",
|
||||
"current_amount",
|
||||
@ -103,10 +106,10 @@
|
||||
"label": "Serial No and Batch"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Long Text",
|
||||
"label": "Serial No",
|
||||
"read_only": 1
|
||||
"label": "Serial No"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
@ -171,11 +174,11 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@ -195,6 +198,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial / Batch Bundle",
|
||||
@ -204,6 +208,7 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "current_serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Current Serial / Batch Bundle",
|
||||
@ -212,6 +217,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "add_serial_batch_bundle",
|
||||
"fieldtype": "Button",
|
||||
"label": "Add Serial / Batch No"
|
||||
@ -222,11 +228,26 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Item Group",
|
||||
"options": "Item Group"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "section_break_lypk",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_eefq",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-14 10:04:23.599951",
|
||||
"modified": "2024-02-04 16:19:44.576022",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Reconciliation Item",
|
||||
|
@ -26,6 +26,7 @@ class StockReconciliationItem(Document):
|
||||
current_valuation_rate: DF.Currency
|
||||
has_item_scanned: DF.Data | None
|
||||
item_code: DF.Link
|
||||
item_group: DF.Link | None
|
||||
item_name: DF.Data | None
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
@ -34,6 +35,7 @@ class StockReconciliationItem(Document):
|
||||
quantity_difference: DF.ReadOnly | None
|
||||
serial_and_batch_bundle: DF.Link | None
|
||||
serial_no: DF.LongText | None
|
||||
use_serial_batch_fields: DF.Check
|
||||
valuation_rate: DF.Currency
|
||||
warehouse: DF.Link
|
||||
# end: auto-generated types
|
||||
|
@ -50,6 +50,7 @@
|
||||
"disable_serial_no_and_batch_selector",
|
||||
"use_naming_series",
|
||||
"naming_series_prefix",
|
||||
"use_serial_batch_fields",
|
||||
"stock_planning_tab",
|
||||
"auto_material_request",
|
||||
"auto_indent",
|
||||
@ -420,6 +421,12 @@
|
||||
"fieldname": "auto_reserve_stock_for_sales_order_on_purchase",
|
||||
"fieldtype": "Check",
|
||||
"label": "Auto Reserve Stock for Sales Order on Purchase"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial / Batch Fields"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@ -427,7 +434,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-30 14:03:52.143457",
|
||||
"modified": "2024-02-04 12:01:31.931864",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Settings",
|
||||
|
@ -57,6 +57,7 @@ class StockSettings(Document):
|
||||
stock_uom: DF.Link | None
|
||||
update_existing_price_list_rate: DF.Check
|
||||
use_naming_series: DF.Check
|
||||
use_serial_batch_fields: DF.Check
|
||||
valuation_method: DF.Literal["FIFO", "Moving Average", "LIFO"]
|
||||
# end: auto-generated types
|
||||
|
||||
@ -68,6 +69,7 @@ class StockSettings(Document):
|
||||
"allow_negative_stock",
|
||||
"default_warehouse",
|
||||
"set_qty_in_transactions_based_on_serial_no_input",
|
||||
"use_serial_batch_fields",
|
||||
]:
|
||||
frappe.db.set_default(key, self.get(key, ""))
|
||||
|
||||
|
@ -794,6 +794,9 @@ class SerialBatchCreation:
|
||||
setattr(self, "actual_qty", qty)
|
||||
self.__dict__["actual_qty"] = self.actual_qty
|
||||
|
||||
if not hasattr(self, "use_serial_batch_fields"):
|
||||
setattr(self, "use_serial_batch_fields", 0)
|
||||
|
||||
def duplicate_package(self):
|
||||
if not self.serial_and_batch_bundle:
|
||||
return
|
||||
@ -902,9 +905,14 @@ class SerialBatchCreation:
|
||||
self.batches = get_available_batches(kwargs)
|
||||
|
||||
def set_auto_serial_batch_entries_for_inward(self):
|
||||
print(self.get("serial_nos"))
|
||||
|
||||
if (self.get("batches") and self.has_batch_no) or (
|
||||
self.get("serial_nos") and self.has_serial_no
|
||||
):
|
||||
if self.use_serial_batch_fields and self.get("serial_nos"):
|
||||
self.make_serial_no_if_not_exists()
|
||||
|
||||
return
|
||||
|
||||
self.batch_no = None
|
||||
@ -916,6 +924,59 @@ class SerialBatchCreation:
|
||||
else:
|
||||
self.batches = frappe._dict({self.batch_no: abs(self.actual_qty)})
|
||||
|
||||
def make_serial_no_if_not_exists(self):
|
||||
non_exists_serial_nos = []
|
||||
for row in self.serial_nos:
|
||||
if not frappe.db.exists("Serial No", row):
|
||||
non_exists_serial_nos.append(row)
|
||||
|
||||
if non_exists_serial_nos:
|
||||
self.make_serial_nos(non_exists_serial_nos)
|
||||
|
||||
def make_serial_nos(self, serial_nos):
|
||||
serial_nos_details = []
|
||||
batch_no = None
|
||||
if self.batches:
|
||||
batch_no = list(self.batches.keys())[0]
|
||||
|
||||
for serial_no in serial_nos:
|
||||
serial_nos_details.append(
|
||||
(
|
||||
serial_no,
|
||||
serial_no,
|
||||
now(),
|
||||
now(),
|
||||
frappe.session.user,
|
||||
frappe.session.user,
|
||||
self.warehouse,
|
||||
self.company,
|
||||
self.item_code,
|
||||
self.item_name,
|
||||
self.description,
|
||||
"Active",
|
||||
batch_no,
|
||||
)
|
||||
)
|
||||
|
||||
if serial_nos_details:
|
||||
fields = [
|
||||
"name",
|
||||
"serial_no",
|
||||
"creation",
|
||||
"modified",
|
||||
"owner",
|
||||
"modified_by",
|
||||
"warehouse",
|
||||
"company",
|
||||
"item_code",
|
||||
"item_name",
|
||||
"description",
|
||||
"status",
|
||||
"batch_no",
|
||||
]
|
||||
|
||||
frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
|
||||
|
||||
def set_serial_batch_entries(self, doc):
|
||||
if self.get("serial_nos"):
|
||||
serial_no_wise_batch = frappe._dict({})
|
||||
|
@ -11,6 +11,9 @@ from frappe.query_builder.functions import CombineDatetime, IfNull, Sum
|
||||
from frappe.utils import cstr, flt, get_link_to_form, nowdate, nowtime
|
||||
|
||||
import erpnext
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||
get_available_serial_nos,
|
||||
)
|
||||
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
||||
from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation
|
||||
from erpnext.stock.valuation import FIFOValuation, LIFOValuation
|
||||
@ -125,7 +128,21 @@ def get_stock_balance(
|
||||
|
||||
if with_valuation_rate:
|
||||
if with_serial_no:
|
||||
serial_nos = get_serial_nos_data_after_transactions(args)
|
||||
serial_no_details = get_available_serial_nos(
|
||||
frappe._dict(
|
||||
{
|
||||
"item_code": item_code,
|
||||
"warehouse": warehouse,
|
||||
"posting_date": posting_date,
|
||||
"posting_time": posting_time,
|
||||
"ignore_warehouse": 1,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
serial_nos = ""
|
||||
if serial_no_details:
|
||||
serial_nos = "\n".join(d.serial_no for d in serial_no_details)
|
||||
|
||||
return (
|
||||
(last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos)
|
||||
@ -140,38 +157,6 @@ def get_stock_balance(
|
||||
return last_entry.qty_after_transaction if last_entry else 0.0
|
||||
|
||||
|
||||
def get_serial_nos_data_after_transactions(args):
|
||||
|
||||
serial_nos = set()
|
||||
args = frappe._dict(args)
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
|
||||
stock_ledger_entries = (
|
||||
frappe.qb.from_(sle)
|
||||
.select("serial_no", "actual_qty")
|
||||
.where(
|
||||
(sle.item_code == args.item_code)
|
||||
& (sle.warehouse == args.warehouse)
|
||||
& (
|
||||
CombineDatetime(sle.posting_date, sle.posting_time)
|
||||
< CombineDatetime(args.posting_date, args.posting_time)
|
||||
)
|
||||
& (sle.is_cancelled == 0)
|
||||
)
|
||||
.orderby(sle.posting_date, sle.posting_time, sle.creation)
|
||||
.run(as_dict=1)
|
||||
)
|
||||
|
||||
for stock_ledger_entry in stock_ledger_entries:
|
||||
changed_serial_no = get_serial_nos_data(stock_ledger_entry.serial_no)
|
||||
if stock_ledger_entry.actual_qty > 0:
|
||||
serial_nos.update(changed_serial_no)
|
||||
else:
|
||||
serial_nos.difference_update(changed_serial_no)
|
||||
|
||||
return "\n".join(serial_nos)
|
||||
|
||||
|
||||
def get_serial_nos_data(serial_nos):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
|
@ -149,6 +149,7 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
self.update_prevdoc_status()
|
||||
self.set_subcontracting_order_status()
|
||||
self.set_consumed_qty_in_subcontract_order()
|
||||
self.make_bundle_using_old_serial_batch_fields()
|
||||
self.update_stock_ledger()
|
||||
self.make_gl_entries()
|
||||
self.repost_future_sle_and_gle()
|
||||
|
@ -48,11 +48,14 @@
|
||||
"reference_name",
|
||||
"section_break_45",
|
||||
"serial_and_batch_bundle",
|
||||
"serial_no",
|
||||
"use_serial_batch_fields",
|
||||
"col_break5",
|
||||
"rejected_serial_and_batch_bundle",
|
||||
"batch_no",
|
||||
"section_break_jshh",
|
||||
"serial_no",
|
||||
"rejected_serial_no",
|
||||
"column_break_henr",
|
||||
"batch_no",
|
||||
"manufacture_details",
|
||||
"manufacturer",
|
||||
"column_break_16",
|
||||
@ -311,22 +314,20 @@
|
||||
"label": "Serial and Batch Details"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Serial No",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"no_copy": 1,
|
||||
"options": "Batch",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: !parent.is_return",
|
||||
@ -478,6 +479,7 @@
|
||||
"label": "Accounting Details"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
@ -486,6 +488,7 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "rejected_serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Rejected Serial and Batch Bundle",
|
||||
@ -546,12 +549,27 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Include Exploded Items",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "section_break_jshh",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_henr",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-30 12:05:51.920705",
|
||||
"modified": "2024-02-04 16:23:30.374865",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Receipt Item",
|
||||
|
@ -58,6 +58,7 @@ class SubcontractingReceiptItem(Document):
|
||||
subcontracting_order: DF.Link | None
|
||||
subcontracting_order_item: DF.Data | None
|
||||
subcontracting_receipt_item: DF.Data | None
|
||||
use_serial_batch_fields: DF.Check
|
||||
warehouse: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
|
@ -26,10 +26,13 @@
|
||||
"current_stock",
|
||||
"secbreak_3",
|
||||
"serial_and_batch_bundle",
|
||||
"batch_no",
|
||||
"use_serial_batch_fields",
|
||||
"col_break4",
|
||||
"subcontracting_order",
|
||||
"section_break_zwnh",
|
||||
"serial_no",
|
||||
"subcontracting_order"
|
||||
"column_break_qibi",
|
||||
"batch_no"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -60,19 +63,19 @@
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"no_copy": 1,
|
||||
"options": "Batch",
|
||||
"read_only": 1
|
||||
"options": "Batch"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
"label": "Serial No",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break1",
|
||||
@ -198,6 +201,7 @@
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@ -205,12 +209,27 @@
|
||||
"no_copy": 1,
|
||||
"options": "Serial and Batch Bundle",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "section_break_zwnh",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_qibi",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-03-15 13:55:08.132626",
|
||||
"modified": "2024-02-04 16:32:17.534162",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Receipt Supplied Item",
|
||||
|
@ -35,6 +35,7 @@ class SubcontractingReceiptSuppliedItem(Document):
|
||||
serial_no: DF.Text | None
|
||||
stock_uom: DF.Link | None
|
||||
subcontracting_order: DF.Link | None
|
||||
use_serial_batch_fields: DF.Check
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
|
Loading…
x
Reference in New Issue
Block a user