fix: serial and batch selector
This commit is contained in:
parent
39da92929b
commit
f4cfc589c6
@ -1527,7 +1527,6 @@ class TestWorkOrder(FrappeTestCase):
|
||||
ste_doc.load_from_db()
|
||||
|
||||
# Create a stock entry to manufacture the item
|
||||
print("remove 2 qty from each item")
|
||||
ste_doc = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 5))
|
||||
for row in ste_doc.items:
|
||||
if row.s_warehouse and not row.t_warehouse:
|
||||
|
@ -12,12 +12,12 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
||||
}
|
||||
|
||||
make() {
|
||||
let label = this.item?.has_serial_no ? __('Serial No') : __('Batch No');
|
||||
let label = this.item?.has_serial_no ? __('Serial Nos') : __('Batch Nos');
|
||||
let primary_label = this.bundle
|
||||
? __('Update') : __('Add');
|
||||
|
||||
if (this.item?.has_serial_no && this.item?.batch_no) {
|
||||
label = __('Serial No / Batch No');
|
||||
label = __('Serial Nos / Batch Nos');
|
||||
}
|
||||
|
||||
primary_label += ' ' + label;
|
||||
@ -26,7 +26,9 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
||||
title: this.item?.title || primary_label,
|
||||
fields: this.get_dialog_fields(),
|
||||
primary_action_label: primary_label,
|
||||
primary_action: () => this.update_ledgers()
|
||||
primary_action: () => this.update_ledgers(),
|
||||
secondary_action_label: __('Edit Full Form'),
|
||||
secondary_action: () => this.edit_full_form(),
|
||||
});
|
||||
|
||||
this.dialog.set_value("qty", this.item.qty);
|
||||
@ -48,7 +50,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
||||
|
||||
if (this.item.has_serial_no) {
|
||||
fields.push({
|
||||
fieldtype: 'Link',
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'scan_serial_no',
|
||||
label: __('Scan Serial No'),
|
||||
options: 'Serial No',
|
||||
@ -279,6 +281,37 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
||||
})
|
||||
}
|
||||
|
||||
edit_full_form() {
|
||||
let bundle_id = this.item.serial_and_batch_bundle
|
||||
if (!bundle_id) {
|
||||
_new = frappe.model.get_new_doc(
|
||||
"Serial and Batch Bundle", null, null, true
|
||||
);
|
||||
|
||||
_new.item_code = this.item.item_code;
|
||||
_new.warehouse = this.get_warehouse();
|
||||
_new.has_serial_no = this.item.has_serial_no;
|
||||
_new.has_batch_no = this.item.has_batch_no;
|
||||
_new.type_of_transaction = this.get_type_of_transaction();
|
||||
_new.company = this.frm.doc.company;
|
||||
_new.voucher_type = this.frm.doc.doctype;
|
||||
bundle_id = _new.name;
|
||||
}
|
||||
|
||||
frappe.set_route("Form", "Serial and Batch Bundle", bundle_id);
|
||||
this.dialog.hide();
|
||||
}
|
||||
|
||||
get_warehouse() {
|
||||
return (this.item?.outward ?
|
||||
(this.item.warehouse || this.item.s_warehouse)
|
||||
: (this.item.warehouse || this.item.t_warehouse));
|
||||
}
|
||||
|
||||
get_type_of_transaction() {
|
||||
return (this.item?.outward ? 'Outward' : 'Inward');
|
||||
}
|
||||
|
||||
render_data() {
|
||||
if (!this.frm.is_new() && this.bundle) {
|
||||
frappe.call({
|
||||
|
@ -4,6 +4,7 @@ import frappe
|
||||
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||
from frappe.utils import flt
|
||||
from frappe.utils.deprecations import deprecated
|
||||
from pypika import Order
|
||||
|
||||
|
||||
class DeprecatedSerialNoValuation:
|
||||
@ -39,25 +40,25 @@ class DeprecatedSerialNoValuation:
|
||||
# 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 != self.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 serial_and_batch_bundle IS NULL
|
||||
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
|
||||
table = frappe.qb.DocType("Stock Ledger Entry")
|
||||
incoming_rate = (
|
||||
frappe.qb.from_(table)
|
||||
.select(table.incoming_rate)
|
||||
.where(
|
||||
(
|
||||
(table.serial_no == serial_no)
|
||||
| (table.serial_no.like(serial_no + "\n%"))
|
||||
| (table.serial_no.like("%\n" + serial_no))
|
||||
| (table.serial_no.like("%\n" + serial_no + "\n%"))
|
||||
)
|
||||
order by posting_date desc
|
||||
limit 1
|
||||
""",
|
||||
(self.sle.company, serial_no, serial_no + "\n%", "%\n" + serial_no, "%\n" + serial_no + "\n%"),
|
||||
)
|
||||
& (table.company == self.sle.company)
|
||||
& (table.serial_and_batch_bundle.isnull())
|
||||
& (table.actual_qty > 0)
|
||||
& (table.is_cancelled == 0)
|
||||
)
|
||||
.orderby(table.posting_date, order=Order.desc)
|
||||
.limit(1)
|
||||
).run()
|
||||
|
||||
self.serial_no_incoming_rate[serial_no] += flt(incoming_rate[0][0]) if incoming_rate else 0
|
||||
incoming_values += self.serial_no_incoming_rate[serial_no]
|
||||
|
@ -8,6 +8,17 @@ frappe.ui.form.on('Serial and Batch Bundle', {
|
||||
|
||||
refresh(frm) {
|
||||
frm.trigger('toggle_fields');
|
||||
frm.trigger('prepare_serial_batch_prompt');
|
||||
},
|
||||
|
||||
item_code(frm) {
|
||||
frm.clear_custom_buttons();
|
||||
frm.trigger('prepare_serial_batch_prompt');
|
||||
},
|
||||
|
||||
type_of_transaction(frm) {
|
||||
frm.clear_custom_buttons();
|
||||
frm.trigger('prepare_serial_batch_prompt');
|
||||
},
|
||||
|
||||
warehouse(frm) {
|
||||
@ -30,6 +41,91 @@ frappe.ui.form.on('Serial and Batch Bundle', {
|
||||
frm.trigger('toggle_fields');
|
||||
},
|
||||
|
||||
prepare_serial_batch_prompt(frm) {
|
||||
if (frm.doc.docstatus === 0 && frm.doc.item_code
|
||||
&& frm.doc.type_of_transaction === "Inward") {
|
||||
let label = frm.doc?.has_serial_no === 1
|
||||
? __('Serial Nos') : __('Batch Nos');
|
||||
|
||||
if (frm.doc?.has_serial_no === 1 && frm.doc?.has_batch_no === 1) {
|
||||
label = __('Serial and Batch Nos');
|
||||
}
|
||||
|
||||
let fields = frm.events.get_prompt_fields(frm);
|
||||
|
||||
frm.add_custom_button(__("Make " + label), () => {
|
||||
frappe.prompt(fields, (data) => {
|
||||
frm.events.add_serial_batch(frm, data);
|
||||
}, "Add " + label, "Make " + label);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
get_prompt_fields(frm) {
|
||||
let attach_field = {
|
||||
"label": __("Attach CSV File"),
|
||||
"fieldname": "csv_file",
|
||||
"fieldtype": "Attach"
|
||||
}
|
||||
|
||||
if (!frm.doc.has_batch_no) {
|
||||
attach_field.depends_on = "eval:doc.using_csv_file === 1"
|
||||
}
|
||||
|
||||
let fields = [
|
||||
{
|
||||
"label": __("Using CSV File"),
|
||||
"fieldname": "using_csv_file",
|
||||
"default": 1,
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
attach_field,
|
||||
{
|
||||
"fieldtype": "Section Break",
|
||||
}
|
||||
]
|
||||
|
||||
if (frm.doc.has_serial_no) {
|
||||
fields.push({
|
||||
"label": "Serial Nos",
|
||||
"fieldname": "serial_nos",
|
||||
"fieldtype": "Small Text",
|
||||
"depends_on": "eval:doc.using_csv_file === 0"
|
||||
})
|
||||
}
|
||||
|
||||
if (frm.doc.has_batch_no) {
|
||||
fields = attach_field
|
||||
}
|
||||
|
||||
return fields;
|
||||
},
|
||||
|
||||
add_serial_batch(frm, prompt_data) {
|
||||
frm.events.validate_prompt_data(frm, prompt_data);
|
||||
|
||||
frm.call({
|
||||
method: "add_serial_batch",
|
||||
doc: frm.doc,
|
||||
args: {
|
||||
"data": prompt_data,
|
||||
},
|
||||
callback(r) {
|
||||
refresh_field("entries");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
validate_prompt_data(frm, prompt_data) {
|
||||
if (prompt_data.using_csv_file && !prompt_data.csv_file) {
|
||||
frappe.throw(__("Please attach CSV file"));
|
||||
}
|
||||
|
||||
if (frm.doc.has_serial_no && !prompt_data.using_csv_file && !prompt_data.serial_nos) {
|
||||
frappe.throw(__("Please enter serial nos"));
|
||||
}
|
||||
},
|
||||
|
||||
toggle_fields(frm) {
|
||||
frm.fields_dict.entries.grid.update_docfield_property(
|
||||
'serial_no', 'read_only', !frm.doc.has_serial_no
|
||||
|
@ -9,13 +9,13 @@
|
||||
"item_details_tab",
|
||||
"naming_series",
|
||||
"company",
|
||||
"warehouse",
|
||||
"type_of_transaction",
|
||||
"column_break_4",
|
||||
"item_code",
|
||||
"item_name",
|
||||
"has_serial_no",
|
||||
"has_batch_no",
|
||||
"column_break_4",
|
||||
"item_code",
|
||||
"warehouse",
|
||||
"type_of_transaction",
|
||||
"serial_no_and_batch_no_tab",
|
||||
"entries",
|
||||
"quantity_and_rate_section",
|
||||
@ -84,7 +84,8 @@
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Item Name"
|
||||
"label": "Item Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -243,7 +244,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-06 02:35:38.404537",
|
||||
"modified": "2023-04-10 20:02:42.964309",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Serial and Batch Bundle",
|
||||
|
@ -2,6 +2,7 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
import collections
|
||||
import csv
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List
|
||||
|
||||
@ -9,7 +10,17 @@ import frappe
|
||||
from frappe import _, _dict, bold
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||
from frappe.utils import add_days, cint, flt, get_link_to_form, nowtime, today
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
cint,
|
||||
cstr,
|
||||
flt,
|
||||
get_link_to_form,
|
||||
now,
|
||||
nowtime,
|
||||
parse_json,
|
||||
today,
|
||||
)
|
||||
|
||||
from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation
|
||||
from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
|
||||
@ -626,6 +637,173 @@ class SerialandBatchBundle(Document):
|
||||
self.delink_reference_from_batch()
|
||||
self.clear_table()
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_serial_batch(self, data):
|
||||
serial_nos, batch_nos = [], []
|
||||
if isinstance(data, str):
|
||||
data = parse_json(data)
|
||||
|
||||
if data.get("csv_file"):
|
||||
serial_nos, batch_nos = get_serial_batch_from_csv(self.item_code, data.get("csv_file"))
|
||||
else:
|
||||
serial_nos, batch_nos = get_serial_batch_from_data(self.item_code, data)
|
||||
|
||||
if not serial_nos and not batch_nos:
|
||||
return
|
||||
|
||||
if serial_nos:
|
||||
self.set("entries", serial_nos)
|
||||
elif batch_nos:
|
||||
self.set("entries", batch_nos)
|
||||
|
||||
|
||||
def get_serial_batch_from_csv(item_code, file_path):
|
||||
file_path = frappe.get_site_path() + file_path
|
||||
serial_nos = []
|
||||
batch_nos = []
|
||||
|
||||
with open(file_path, "r") as f:
|
||||
reader = csv.reader(f)
|
||||
serial_nos, batch_nos = parse_csv_file_to_get_serial_batch(reader)
|
||||
|
||||
if serial_nos:
|
||||
make_serial_nos(item_code, serial_nos)
|
||||
|
||||
print(batch_nos)
|
||||
if batch_nos:
|
||||
make_batch_nos(item_code, batch_nos)
|
||||
|
||||
return serial_nos, batch_nos
|
||||
|
||||
|
||||
def parse_csv_file_to_get_serial_batch(reader):
|
||||
has_serial_no, has_batch_no = False, False
|
||||
serial_nos = []
|
||||
batch_nos = []
|
||||
|
||||
for index, row in enumerate(reader):
|
||||
if index == 0:
|
||||
has_serial_no = row[0] == "Serial No"
|
||||
has_batch_no = row[0] == "Batch No"
|
||||
continue
|
||||
|
||||
if not row[0]:
|
||||
continue
|
||||
|
||||
if has_serial_no or (has_serial_no and has_batch_no):
|
||||
_dict = {"serial_no": row[0], "qty": 1}
|
||||
|
||||
if has_batch_no:
|
||||
_dict.update(
|
||||
{
|
||||
"batch_no": row[1],
|
||||
"qty": row[2],
|
||||
}
|
||||
)
|
||||
|
||||
serial_nos.append(_dict)
|
||||
elif has_batch_no:
|
||||
batch_nos.append(
|
||||
{
|
||||
"batch_no": row[0],
|
||||
"qty": row[1],
|
||||
}
|
||||
)
|
||||
|
||||
return serial_nos, batch_nos
|
||||
|
||||
|
||||
def get_serial_batch_from_data(item_code, kwargs):
|
||||
serial_nos = []
|
||||
batch_nos = []
|
||||
if kwargs.get("serial_nos"):
|
||||
data = parse_serial_nos(kwargs.get("serial_nos"))
|
||||
for serial_no in data:
|
||||
if not serial_no:
|
||||
continue
|
||||
serial_nos.append({"serial_no": serial_no, "qty": 1})
|
||||
|
||||
make_serial_nos(item_code, serial_nos)
|
||||
|
||||
return serial_nos, batch_nos
|
||||
|
||||
|
||||
def make_serial_nos(item_code, serial_nos):
|
||||
item = frappe.get_cached_value("Item", item_code, ["description", "item_code"], as_dict=1)
|
||||
|
||||
serial_nos = [d.get("serial_no") for d in serial_nos if d.get("serial_no")]
|
||||
|
||||
serial_nos_details = []
|
||||
user = frappe.session.user
|
||||
for serial_no in serial_nos:
|
||||
serial_nos_details.append(
|
||||
(
|
||||
serial_no,
|
||||
serial_no,
|
||||
now(),
|
||||
now(),
|
||||
user,
|
||||
user,
|
||||
item.item_code,
|
||||
item.item_name,
|
||||
item.description,
|
||||
"Inactive",
|
||||
)
|
||||
)
|
||||
|
||||
fields = [
|
||||
"name",
|
||||
"serial_no",
|
||||
"creation",
|
||||
"modified",
|
||||
"owner",
|
||||
"modified_by",
|
||||
"item_code",
|
||||
"item_name",
|
||||
"description",
|
||||
"status",
|
||||
]
|
||||
|
||||
frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
|
||||
|
||||
frappe.msgprint(_("Serial Nos are created successfully"))
|
||||
|
||||
|
||||
def make_batch_nos(item_code, batch_nos):
|
||||
item = frappe.get_cached_value("Item", item_code, ["description", "item_code"], as_dict=1)
|
||||
|
||||
batch_nos = [d.get("batch_no") for d in batch_nos if d.get("batch_no")]
|
||||
|
||||
batch_nos_details = []
|
||||
user = frappe.session.user
|
||||
for batch_no in batch_nos:
|
||||
batch_nos_details.append(
|
||||
(batch_no, batch_no, now(), now(), user, user, item.item_code, item.item_name, item.description)
|
||||
)
|
||||
|
||||
fields = [
|
||||
"name",
|
||||
"batch_id",
|
||||
"creation",
|
||||
"modified",
|
||||
"owner",
|
||||
"modified_by",
|
||||
"item",
|
||||
"item_name",
|
||||
"description",
|
||||
]
|
||||
|
||||
frappe.db.bulk_insert("Batch", fields=fields, values=set(batch_nos_details))
|
||||
|
||||
frappe.msgprint(_("Batch Nos are created successfully"))
|
||||
|
||||
|
||||
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()]
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
@ -690,13 +868,13 @@ def get_filters_for_bundle(item_code, docstatus=None, voucher_no=None, name=None
|
||||
@frappe.whitelist()
|
||||
def add_serial_batch_ledgers(entries, child_row, doc) -> object:
|
||||
if isinstance(child_row, str):
|
||||
child_row = frappe._dict(frappe.parse_json(child_row))
|
||||
child_row = frappe._dict(parse_json(child_row))
|
||||
|
||||
if isinstance(entries, str):
|
||||
entries = frappe.parse_json(entries)
|
||||
entries = parse_json(entries)
|
||||
|
||||
if doc and isinstance(doc, str):
|
||||
parent_doc = frappe.parse_json(doc)
|
||||
parent_doc = parse_json(doc)
|
||||
|
||||
if frappe.db.exists("Serial and Batch Bundle", child_row.serial_and_batch_bundle):
|
||||
doc = update_serial_batch_no_ledgers(entries, child_row, parent_doc)
|
||||
|
Loading…
x
Reference in New Issue
Block a user