refactor: serial and batch reposting
This commit is contained in:
parent
6c9b212dd1
commit
f1b5966680
@ -70,6 +70,7 @@
|
|||||||
"target_warehouse",
|
"target_warehouse",
|
||||||
"quality_inspection",
|
"quality_inspection",
|
||||||
"col_break4",
|
"col_break4",
|
||||||
|
"allow_zero_valuation_rate",
|
||||||
"against_sales_order",
|
"against_sales_order",
|
||||||
"so_detail",
|
"so_detail",
|
||||||
"against_sales_invoice",
|
"against_sales_invoice",
|
||||||
@ -79,6 +80,10 @@
|
|||||||
"section_break_40",
|
"section_break_40",
|
||||||
"pick_serial_and_batch",
|
"pick_serial_and_batch",
|
||||||
"serial_and_batch_bundle",
|
"serial_and_batch_bundle",
|
||||||
|
"column_break_eaoe",
|
||||||
|
"serial_no",
|
||||||
|
"batch_no",
|
||||||
|
"available_qty_section",
|
||||||
"actual_batch_qty",
|
"actual_batch_qty",
|
||||||
"actual_qty",
|
"actual_qty",
|
||||||
"installed_qty",
|
"installed_qty",
|
||||||
@ -88,7 +93,6 @@
|
|||||||
"received_qty",
|
"received_qty",
|
||||||
"accounting_details_section",
|
"accounting_details_section",
|
||||||
"expense_account",
|
"expense_account",
|
||||||
"allow_zero_valuation_rate",
|
|
||||||
"column_break_71",
|
"column_break_71",
|
||||||
"internal_transfer_section",
|
"internal_transfer_section",
|
||||||
"material_request",
|
"material_request",
|
||||||
@ -505,7 +509,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_40",
|
"fieldname": "section_break_40",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Serial and Batch No"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
@ -847,19 +852,44 @@
|
|||||||
"fieldname": "serial_and_batch_bundle",
|
"fieldname": "serial_and_batch_bundle",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Serial and Batch Bundle",
|
"label": "Serial and Batch Bundle",
|
||||||
"options": "Serial and Batch Bundle"
|
"no_copy": 1,
|
||||||
|
"options": "Serial and Batch Bundle",
|
||||||
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "pick_serial_and_batch",
|
"fieldname": "pick_serial_and_batch",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"label": "Pick Serial / Batch No"
|
"label": "Pick Serial / Batch No"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "available_qty_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Available Qty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_eaoe",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "serial_no",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
"label": "Serial No",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "batch_no",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Batch No",
|
||||||
|
"options": "Batch",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-05-01 21:05:14.175640",
|
"modified": "2023-05-02 21:05:14.175640",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Note Item",
|
"name": "Delivery Note Item",
|
||||||
|
@ -79,6 +79,7 @@
|
|||||||
"purchase_order",
|
"purchase_order",
|
||||||
"purchase_invoice",
|
"purchase_invoice",
|
||||||
"column_break_40",
|
"column_break_40",
|
||||||
|
"allow_zero_valuation_rate",
|
||||||
"is_fixed_asset",
|
"is_fixed_asset",
|
||||||
"asset_location",
|
"asset_location",
|
||||||
"asset_category",
|
"asset_category",
|
||||||
@ -93,8 +94,12 @@
|
|||||||
"section_break_45",
|
"section_break_45",
|
||||||
"update_serial_batch_bundle",
|
"update_serial_batch_bundle",
|
||||||
"serial_and_batch_bundle",
|
"serial_and_batch_bundle",
|
||||||
|
"rejected_serial_and_batch_bundle",
|
||||||
"col_break5",
|
"col_break5",
|
||||||
"allow_zero_valuation_rate",
|
"serial_no",
|
||||||
|
"rejected_serial_no",
|
||||||
|
"batch_no",
|
||||||
|
"subcontract_bom_section",
|
||||||
"include_exploded_items",
|
"include_exploded_items",
|
||||||
"bom",
|
"bom",
|
||||||
"item_weight_details",
|
"item_weight_details",
|
||||||
@ -998,12 +1003,43 @@
|
|||||||
"fieldname": "update_serial_batch_bundle",
|
"fieldname": "update_serial_batch_bundle",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"label": "Add Serial / Batch No"
|
"label": "Add Serial / Batch No"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||||
|
"fieldname": "subcontract_bom_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Subcontract BOM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "serial_no",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
"label": "Serial No",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "rejected_serial_no",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
"label": "Rejected Serial No",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "batch_no",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Batch No",
|
||||||
|
"options": "Batch",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "rejected_serial_and_batch_bundle",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Rejected Serial and Batch Bundle",
|
||||||
|
"options": "Serial and Batch Bundle"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-02-28 16:43:04.470104",
|
"modified": "2023-03-03 12:45:03.087766",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt Item",
|
"name": "Purchase Receipt Item",
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"item_details_tab",
|
"item_details_tab",
|
||||||
"company",
|
"company",
|
||||||
"item_group",
|
|
||||||
"warehouse",
|
"warehouse",
|
||||||
|
"type_of_transaction",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"item_code",
|
"item_code",
|
||||||
"item_name",
|
"item_name",
|
||||||
@ -18,6 +18,7 @@
|
|||||||
"ledgers",
|
"ledgers",
|
||||||
"quantity_and_rate_section",
|
"quantity_and_rate_section",
|
||||||
"total_qty",
|
"total_qty",
|
||||||
|
"item_group",
|
||||||
"column_break_13",
|
"column_break_13",
|
||||||
"avg_rate",
|
"avg_rate",
|
||||||
"total_amount",
|
"total_amount",
|
||||||
@ -46,6 +47,7 @@
|
|||||||
"fetch_from": "item_code.item_group",
|
"fetch_from": "item_code.item_group",
|
||||||
"fieldname": "item_group",
|
"fieldname": "item_group",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"hidden": 1,
|
||||||
"label": "Item Group",
|
"label": "Item Group",
|
||||||
"options": "Item Group"
|
"options": "Item Group"
|
||||||
},
|
},
|
||||||
@ -171,12 +173,19 @@
|
|||||||
"label": "Warehouse",
|
"label": "Warehouse",
|
||||||
"options": "Warehouse",
|
"options": "Warehouse",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "type_of_transaction",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Type of Transaction",
|
||||||
|
"options": "\nInward\nOutward",
|
||||||
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-01-10 11:32:09.018760",
|
"modified": "2023-03-03 16:18:53.709069",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Serial and Batch Bundle",
|
"name": "Serial and Batch Bundle",
|
||||||
|
@ -267,7 +267,11 @@ def get_serial_and_batch_ledger(**kwargs):
|
|||||||
serial_batch_table.qty,
|
serial_batch_table.qty,
|
||||||
serial_batch_table.incoming_rate,
|
serial_batch_table.incoming_rate,
|
||||||
)
|
)
|
||||||
.where((sle_table.item_code == kwargs.item_code) & (sle_table.warehouse == kwargs.warehouse))
|
.where(
|
||||||
|
(sle_table.item_code == kwargs.item_code)
|
||||||
|
& (sle_table.warehouse == kwargs.warehouse)
|
||||||
|
& (serial_batch_table.is_outward == 0)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if kwargs.serial_nos:
|
if kwargs.serial_nos:
|
||||||
|
@ -15,7 +15,8 @@
|
|||||||
"incoming_rate",
|
"incoming_rate",
|
||||||
"column_break_8",
|
"column_break_8",
|
||||||
"outgoing_rate",
|
"outgoing_rate",
|
||||||
"stock_value_difference"
|
"stock_value_difference",
|
||||||
|
"is_outward"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -93,12 +94,19 @@
|
|||||||
"label": "Change in Stock Value",
|
"label": "Change in Stock Value",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_outward",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Outward",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-01-10 12:55:57.368650",
|
"modified": "2023-03-03 16:52:26.039613",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Serial and Batch Ledger",
|
"name": "Serial and Batch Ledger",
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Serial and Batch No Bundle", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
@ -0,0 +1,176 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2022-09-29 14:56:38.338267",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"item_details_tab",
|
||||||
|
"company",
|
||||||
|
"item_group",
|
||||||
|
"has_serial_no",
|
||||||
|
"column_break_4",
|
||||||
|
"item_code",
|
||||||
|
"item_name",
|
||||||
|
"has_batch_no",
|
||||||
|
"serial_no_and_batch_no_tab",
|
||||||
|
"ledgers",
|
||||||
|
"qty",
|
||||||
|
"reference_tab",
|
||||||
|
"voucher_type",
|
||||||
|
"voucher_no",
|
||||||
|
"posting_date",
|
||||||
|
"posting_time",
|
||||||
|
"is_cancelled",
|
||||||
|
"amended_from"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "item_details_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Item Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "item_code.item_group",
|
||||||
|
"fieldname": "item_group",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Item Group",
|
||||||
|
"options": "Item Group"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fetch_from": "item_code.has_serial_no",
|
||||||
|
"fieldname": "has_serial_no",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Has Serial No",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_4",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Item Code",
|
||||||
|
"options": "Item",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "item_code.item_name",
|
||||||
|
"fieldname": "item_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Item Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fetch_from": "item_code.has_batch_no",
|
||||||
|
"fieldname": "has_batch_no",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Has Batch No",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "serial_no_and_batch_no_tab",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 1,
|
||||||
|
"fieldname": "ledgers",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Serial No and Batch No Transaction",
|
||||||
|
"options": "Serial and Batch No Ledger",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Total Qty",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reference_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Reference"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Voucher Type",
|
||||||
|
"options": "DocType",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_no",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Voucher No",
|
||||||
|
"options": "voucher_type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Posting Date",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_cancelled",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Cancelled",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amended_from",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Amended From",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Serial and Batch No Bundle",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"label": "Posting Time",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"is_submittable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-03-05 17:38:51.871723",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Stock",
|
||||||
|
"name": "Serial and Batch No Bundle",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"cancel": 1,
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"title_field": "item_code"
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class SerialandBatchNoBundle(Document):
|
||||||
|
pass
|
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestSerialandBatchNoBundle(FrappeTestCase):
|
||||||
|
pass
|
385
erpnext/stock/serial_batch_bundle.py
Normal file
385
erpnext/stock/serial_batch_bundle.py
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe.model.naming import make_autoname
|
||||||
|
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||||
|
from frappe.utils import cint, cstr, flt, now
|
||||||
|
|
||||||
|
from erpnext.stock.valuation import round_off_if_near_zero
|
||||||
|
|
||||||
|
|
||||||
|
class SerialBatchBundle:
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
for key, value in kwargs.iteritems():
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
self.set_item_details()
|
||||||
|
|
||||||
|
def process_serial_and_batch_bundle(self):
|
||||||
|
if self.item_details.has_serial_no:
|
||||||
|
self.process_serial_no
|
||||||
|
elif self.item_details.has_batch_no:
|
||||||
|
self.process_batch_no
|
||||||
|
|
||||||
|
def set_item_details(self):
|
||||||
|
fields = [
|
||||||
|
"has_batch_no",
|
||||||
|
"has_serial_no",
|
||||||
|
"item_name",
|
||||||
|
"item_group",
|
||||||
|
"serial_no_series",
|
||||||
|
"create_new_batch",
|
||||||
|
"batch_number_series",
|
||||||
|
]
|
||||||
|
|
||||||
|
self.item_details = frappe.get_cached_value("Item", self.sle.item_code, fields, as_dict=1)
|
||||||
|
|
||||||
|
def process_serial_no(self):
|
||||||
|
if (
|
||||||
|
not self.sle.is_cancelled
|
||||||
|
and not self.sle.serial_and_batch_bundle
|
||||||
|
and self.sle.actual_qty > 0
|
||||||
|
and self.item_details.has_serial_no == 1
|
||||||
|
and self.item_details.serial_no_series
|
||||||
|
):
|
||||||
|
sr_nos = self.auto_create_serial_nos()
|
||||||
|
self.make_serial_no_bundle(sr_nos)
|
||||||
|
|
||||||
|
def auto_create_serial_nos(self):
|
||||||
|
sr_nos = []
|
||||||
|
serial_nos_details = []
|
||||||
|
|
||||||
|
for i in range(cint(self.sle.actual_qty)):
|
||||||
|
serial_no = make_autoname(self.item_details.serial_no_series, "Serial No")
|
||||||
|
sr_nos.append(serial_no)
|
||||||
|
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_details.item_name,
|
||||||
|
self.item_details.description,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if serial_nos_details:
|
||||||
|
fields = [
|
||||||
|
"name",
|
||||||
|
"serial_no",
|
||||||
|
"creation",
|
||||||
|
"modified",
|
||||||
|
"owner",
|
||||||
|
"modified_by",
|
||||||
|
"warehouse",
|
||||||
|
"company",
|
||||||
|
"item_code",
|
||||||
|
"item_name",
|
||||||
|
"description",
|
||||||
|
]
|
||||||
|
|
||||||
|
frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
|
||||||
|
|
||||||
|
return sr_nos
|
||||||
|
|
||||||
|
def make_serial_no_bundle(self, serial_nos=None):
|
||||||
|
sn_doc = frappe.new_doc("Serial and Batch Bundle")
|
||||||
|
sn_doc.item_code = self.item_code
|
||||||
|
sn_doc.item_name = self.item_details.item_name
|
||||||
|
sn_doc.item_group = self.item_details.item_group
|
||||||
|
sn_doc.has_serial_no = self.item_details.has_serial_no
|
||||||
|
sn_doc.has_batch_no = self.item_details.has_batch_no
|
||||||
|
sn_doc.voucher_type = self.sle.voucher_type
|
||||||
|
sn_doc.voucher_no = self.sle.voucher_no
|
||||||
|
sn_doc.flags.ignore_mandatory = True
|
||||||
|
sn_doc.flags.ignore_validate = True
|
||||||
|
sn_doc.total_qty = self.sle.actual_qty
|
||||||
|
sn_doc.avg_rate = self.sle.incoming_rate
|
||||||
|
sn_doc.total_amount = flt(self.sle.actual_qty) * flt(self.sle.incoming_rate)
|
||||||
|
sn_doc.insert()
|
||||||
|
|
||||||
|
batch_no = ""
|
||||||
|
if self.item_details.has_batch_no:
|
||||||
|
batch_no = self.create_batch()
|
||||||
|
|
||||||
|
if serial_nos:
|
||||||
|
self.add_serial_no_to_bundle(sn_doc, serial_nos, batch_no)
|
||||||
|
elif self.item_details.has_batch_no:
|
||||||
|
self.add_batch_no_to_bundle(sn_doc, batch_no)
|
||||||
|
sn_doc.save()
|
||||||
|
|
||||||
|
sn_doc.load_from_db()
|
||||||
|
sn_doc.flags.ignore_validate = True
|
||||||
|
sn_doc.flags.ignore_mandatory = True
|
||||||
|
|
||||||
|
sn_doc.submit()
|
||||||
|
|
||||||
|
self.sle.serial_and_batch_bundle = sn_doc.name
|
||||||
|
|
||||||
|
def add_serial_no_to_bundle(self, sn_doc, serial_nos, batch_no=None):
|
||||||
|
ledgers = []
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
"name",
|
||||||
|
"serial_no",
|
||||||
|
"batch_no",
|
||||||
|
"warehouse",
|
||||||
|
"item_code",
|
||||||
|
"qty",
|
||||||
|
"incoming_rate",
|
||||||
|
"parent",
|
||||||
|
"parenttype",
|
||||||
|
"parentfield",
|
||||||
|
]
|
||||||
|
|
||||||
|
for serial_no in serial_nos:
|
||||||
|
ledgers.append(
|
||||||
|
(
|
||||||
|
frappe.generate_hash("Serial and Batch Ledger", 10),
|
||||||
|
serial_no,
|
||||||
|
batch_no,
|
||||||
|
self.warehouse,
|
||||||
|
self.item_details.item_code,
|
||||||
|
1,
|
||||||
|
self.sle.incoming_rate,
|
||||||
|
sn_doc.name,
|
||||||
|
sn_doc.doctype,
|
||||||
|
"ledgers",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.db.bulk_insert("Serial and Batch Ledger", fields=fields, values=set(ledgers))
|
||||||
|
|
||||||
|
def add_batch_no_to_bundle(self, sn_doc, batch_no):
|
||||||
|
sn_doc.append(
|
||||||
|
"ledgers",
|
||||||
|
{
|
||||||
|
"batch_no": batch_no,
|
||||||
|
"qty": self.sle.actual_qty,
|
||||||
|
"incoming_rate": self.sle.incoming_rate,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_batch(self):
|
||||||
|
from erpnext.stock.doctype.batch.batch import make_batch
|
||||||
|
|
||||||
|
return make_batch(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"item": self.item_code,
|
||||||
|
"reference_doctype": self.sle.voucher_type,
|
||||||
|
"reference_name": self.sle.voucher_no,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def process_batch_no(self):
|
||||||
|
if (
|
||||||
|
not self.sle.is_cancelled
|
||||||
|
and not self.sle.serial_and_batch_bundle
|
||||||
|
and self.sle.actual_qty > 0
|
||||||
|
and self.item_details.has_batch_no == 1
|
||||||
|
and self.item_details.create_new_batch
|
||||||
|
and self.item_details.batch_number_series
|
||||||
|
):
|
||||||
|
self.make_serial_no_bundle()
|
||||||
|
|
||||||
|
|
||||||
|
class RepostSerialBatchBundle:
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
for key, value in kwargs.iteritems():
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
def get_valuation_rate(self):
|
||||||
|
if self.sle.actual_qty > 0:
|
||||||
|
self.sle.incoming_rate = self.sle.valuation_rate
|
||||||
|
|
||||||
|
if self.sle.actual_qty < 0:
|
||||||
|
self.sle.outgoing_rate = self.sle.valuation_rate
|
||||||
|
|
||||||
|
def get_valuation_rate_for_serial_nos(self):
|
||||||
|
serial_nos = self.get_serial_nos()
|
||||||
|
|
||||||
|
subquery = f"""
|
||||||
|
SELECT
|
||||||
|
MAX(ledger.posting_date), name
|
||||||
|
FROM
|
||||||
|
ledger
|
||||||
|
WHERE
|
||||||
|
ledger.serial_no IN {tuple(serial_nos)}
|
||||||
|
AND ledger.is_outward = 0
|
||||||
|
AND ledger.warehouse = {frappe.db.escape(self.sle.warehouse)}
|
||||||
|
AND ledger.item_code = {frappe.db.escape(self.sle.item_code)}
|
||||||
|
AND (
|
||||||
|
ledger.posting_date < '{self.sle.posting_date}'
|
||||||
|
OR (
|
||||||
|
ledger.posting_date = '{self.sle.posting_date}'
|
||||||
|
AND ledger.posting_time <= '{self.sle.posting_time}'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
frappe.db.sql(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
serial_no, incoming_rate
|
||||||
|
FROM
|
||||||
|
`tabSerial and Batch Ledger` AS ledger,
|
||||||
|
({subquery}) AS SubQuery
|
||||||
|
WHERE
|
||||||
|
ledger.name = SubQuery.name
|
||||||
|
GROUP BY
|
||||||
|
ledger.serial_no
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_serial_nos(self):
|
||||||
|
ledgers = frappe.get_all(
|
||||||
|
"Serial and Batch Ledger",
|
||||||
|
fields=["serial_no"],
|
||||||
|
filters={"parent": self.sle.serial_and_batch_bundle, "is_outward": 1},
|
||||||
|
)
|
||||||
|
|
||||||
|
return [d.serial_no for d in ledgers]
|
||||||
|
|
||||||
|
|
||||||
|
class DeprecatedRepostSerialBatchBundle(RepostSerialBatchBundle):
|
||||||
|
def get_serialized_values(self, sle):
|
||||||
|
incoming_rate = flt(sle.incoming_rate)
|
||||||
|
actual_qty = flt(sle.actual_qty)
|
||||||
|
serial_nos = cstr(sle.serial_no).split("\n")
|
||||||
|
|
||||||
|
if incoming_rate < 0:
|
||||||
|
# wrong incoming rate
|
||||||
|
incoming_rate = self.wh_data.valuation_rate
|
||||||
|
|
||||||
|
stock_value_change = 0
|
||||||
|
if actual_qty > 0:
|
||||||
|
stock_value_change = actual_qty * incoming_rate
|
||||||
|
else:
|
||||||
|
# In case of delivery/stock issue, get average purchase rate
|
||||||
|
# of serial nos of current entry
|
||||||
|
if not sle.is_cancelled:
|
||||||
|
outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos)
|
||||||
|
stock_value_change = -1 * outgoing_value
|
||||||
|
else:
|
||||||
|
stock_value_change = actual_qty * sle.outgoing_rate
|
||||||
|
|
||||||
|
new_stock_qty = self.wh_data.qty_after_transaction + actual_qty
|
||||||
|
|
||||||
|
if new_stock_qty > 0:
|
||||||
|
new_stock_value = (
|
||||||
|
self.wh_data.qty_after_transaction * self.wh_data.valuation_rate
|
||||||
|
) + stock_value_change
|
||||||
|
if new_stock_value >= 0:
|
||||||
|
# calculate new valuation rate only if stock value is positive
|
||||||
|
# else it remains the same as that of previous entry
|
||||||
|
self.wh_data.valuation_rate = new_stock_value / new_stock_qty
|
||||||
|
|
||||||
|
if not self.wh_data.valuation_rate and sle.voucher_detail_no:
|
||||||
|
allow_zero_rate = self.check_if_allow_zero_valuation_rate(
|
||||||
|
sle.voucher_type, sle.voucher_detail_no
|
||||||
|
)
|
||||||
|
if not allow_zero_rate:
|
||||||
|
self.wh_data.valuation_rate = self.get_fallback_rate(sle)
|
||||||
|
|
||||||
|
def get_incoming_value_for_serial_nos(self, sle, serial_nos):
|
||||||
|
# get rate from serial nos within same company
|
||||||
|
all_serial_nos = frappe.get_all(
|
||||||
|
"Serial No", fields=["purchase_rate", "name", "company"], filters={"name": ("in", serial_nos)}
|
||||||
|
)
|
||||||
|
|
||||||
|
incoming_values = sum(flt(d.purchase_rate) for d in all_serial_nos if d.company == sle.company)
|
||||||
|
|
||||||
|
# Get rate for serial nos which has been transferred to other company
|
||||||
|
invalid_serial_nos = [d.name for d in all_serial_nos if d.company != sle.company]
|
||||||
|
for serial_no in invalid_serial_nos:
|
||||||
|
incoming_rate = frappe.db.sql(
|
||||||
|
"""
|
||||||
|
select incoming_rate
|
||||||
|
from `tabStock Ledger Entry`
|
||||||
|
where
|
||||||
|
company = %s
|
||||||
|
and actual_qty > 0
|
||||||
|
and is_cancelled = 0
|
||||||
|
and (serial_no = %s
|
||||||
|
or serial_no like %s
|
||||||
|
or serial_no like %s
|
||||||
|
or serial_no like %s
|
||||||
|
)
|
||||||
|
order by posting_date desc
|
||||||
|
limit 1
|
||||||
|
""",
|
||||||
|
(sle.company, serial_no, serial_no + "\n%", "%\n" + serial_no, "%\n" + serial_no + "\n%"),
|
||||||
|
)
|
||||||
|
|
||||||
|
incoming_values += flt(incoming_rate[0][0]) if incoming_rate else 0
|
||||||
|
|
||||||
|
return incoming_values
|
||||||
|
|
||||||
|
def update_batched_values(self, sle):
|
||||||
|
incoming_rate = flt(sle.incoming_rate)
|
||||||
|
actual_qty = flt(sle.actual_qty)
|
||||||
|
|
||||||
|
self.wh_data.qty_after_transaction = round_off_if_near_zero(
|
||||||
|
self.wh_data.qty_after_transaction + actual_qty
|
||||||
|
)
|
||||||
|
|
||||||
|
if actual_qty > 0:
|
||||||
|
stock_value_difference = incoming_rate * actual_qty
|
||||||
|
else:
|
||||||
|
outgoing_rate = get_batch_incoming_rate(
|
||||||
|
item_code=sle.item_code,
|
||||||
|
warehouse=sle.warehouse,
|
||||||
|
batch_no=sle.batch_no,
|
||||||
|
posting_date=sle.posting_date,
|
||||||
|
posting_time=sle.posting_time,
|
||||||
|
creation=sle.creation,
|
||||||
|
)
|
||||||
|
if outgoing_rate is None:
|
||||||
|
# This can *only* happen if qty available for the batch is zero.
|
||||||
|
# in such case fall back various other rates.
|
||||||
|
# future entries will correct the overall accounting as each
|
||||||
|
# batch individually uses moving average rates.
|
||||||
|
outgoing_rate = self.get_fallback_rate(sle)
|
||||||
|
stock_value_difference = outgoing_rate * actual_qty
|
||||||
|
|
||||||
|
self.wh_data.stock_value = round_off_if_near_zero(
|
||||||
|
self.wh_data.stock_value + stock_value_difference
|
||||||
|
)
|
||||||
|
if self.wh_data.qty_after_transaction:
|
||||||
|
self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction
|
||||||
|
|
||||||
|
|
||||||
|
def get_batch_incoming_rate(
|
||||||
|
item_code, warehouse, batch_no, posting_date, posting_time, creation=None
|
||||||
|
):
|
||||||
|
|
||||||
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
|
|
||||||
|
timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime(
|
||||||
|
posting_date, posting_time
|
||||||
|
)
|
||||||
|
if creation:
|
||||||
|
timestamp_condition |= (
|
||||||
|
CombineDatetime(sle.posting_date, sle.posting_time)
|
||||||
|
== CombineDatetime(posting_date, posting_time)
|
||||||
|
) & (sle.creation < creation)
|
||||||
|
|
||||||
|
batch_details = (
|
||||||
|
frappe.qb.from_(sle)
|
||||||
|
.select(Sum(sle.stock_value_difference).as_("batch_value"), Sum(sle.actual_qty).as_("batch_qty"))
|
||||||
|
.where(
|
||||||
|
(sle.item_code == item_code)
|
||||||
|
& (sle.warehouse == warehouse)
|
||||||
|
& (sle.batch_no == batch_no)
|
||||||
|
& (sle.is_cancelled == 0)
|
||||||
|
)
|
||||||
|
.where(timestamp_condition)
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
if batch_details and batch_details[0].batch_qty:
|
||||||
|
return batch_details[0].batch_value / batch_details[0].batch_qty
|
Loading…
x
Reference in New Issue
Block a user