fix: serial / batch barcode scanner (#39114)
This commit is contained in:
parent
c2f88f29dc
commit
f09e2130a1
@ -129,6 +129,17 @@ class AccountsController(TransactionBase):
|
|||||||
if self.doctype in relevant_docs:
|
if self.doctype in relevant_docs:
|
||||||
self.set_payment_schedule()
|
self.set_payment_schedule()
|
||||||
|
|
||||||
|
def remove_bundle_for_non_stock_invoices(self):
|
||||||
|
has_sabb = False
|
||||||
|
if self.doctype in ("Sales Invoice", "Purchase Invoice") and not self.update_stock:
|
||||||
|
for item in self.get("items"):
|
||||||
|
if item.serial_and_batch_bundle:
|
||||||
|
item.serial_and_batch_bundle = None
|
||||||
|
has_sabb = True
|
||||||
|
|
||||||
|
if has_sabb:
|
||||||
|
self.remove_serial_and_batch_bundle()
|
||||||
|
|
||||||
def ensure_supplier_is_not_blocked(self):
|
def ensure_supplier_is_not_blocked(self):
|
||||||
is_supplier_payment = self.doctype == "Payment Entry" and self.party_type == "Supplier"
|
is_supplier_payment = self.doctype == "Payment Entry" and self.party_type == "Supplier"
|
||||||
is_buying_invoice = self.doctype in ["Purchase Invoice", "Purchase Order"]
|
is_buying_invoice = self.doctype in ["Purchase Invoice", "Purchase Order"]
|
||||||
@ -156,6 +167,9 @@ class AccountsController(TransactionBase):
|
|||||||
if self.get("_action") and self._action != "update_after_submit":
|
if self.get("_action") and self._action != "update_after_submit":
|
||||||
self.set_missing_values(for_validate=True)
|
self.set_missing_values(for_validate=True)
|
||||||
|
|
||||||
|
if self.get("_action") == "submit":
|
||||||
|
self.remove_bundle_for_non_stock_invoices()
|
||||||
|
|
||||||
self.ensure_supplier_is_not_blocked()
|
self.ensure_supplier_is_not_blocked()
|
||||||
|
|
||||||
self.validate_date_with_fiscal_year()
|
self.validate_date_with_fiscal_year()
|
||||||
|
@ -454,7 +454,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
item.weight_uom = '';
|
item.weight_uom = '';
|
||||||
item.conversion_factor = 0;
|
item.conversion_factor = 0;
|
||||||
|
|
||||||
if(['Sales Invoice'].includes(this.frm.doc.doctype)) {
|
if(['Sales Invoice', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
|
||||||
update_stock = cint(me.frm.doc.update_stock);
|
update_stock = cint(me.frm.doc.update_stock);
|
||||||
show_batch_dialog = update_stock;
|
show_batch_dialog = update_stock;
|
||||||
|
|
||||||
@ -545,7 +545,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
},
|
},
|
||||||
() => me.toggle_conversion_factor(item),
|
() => me.toggle_conversion_factor(item),
|
||||||
() => {
|
() => {
|
||||||
if (show_batch_dialog)
|
if (show_batch_dialog && !frappe.flags.trigger_from_barcode_scanner)
|
||||||
return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
|
return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
if (r.message &&
|
if (r.message &&
|
||||||
@ -1239,6 +1239,20 @@ 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) {
|
service_start_date(frm, cdt, cdn) {
|
||||||
var child = locals[cdt][cdn];
|
var child = locals[cdt][cdn];
|
||||||
|
|
||||||
@ -1576,6 +1590,18 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
return item_list;
|
return item_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items_delete() {
|
||||||
|
this.update_localstorage_scanned_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
update_localstorage_scanned_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.update_localstorage_scanned_data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_set_values_for_item_list(children) {
|
_set_values_for_item_list(children) {
|
||||||
const items_rule_dict = {};
|
const items_rule_dict = {};
|
||||||
|
|
||||||
|
@ -7,8 +7,6 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
this.scan_barcode_field = this.frm.fields_dict[this.scan_field_name];
|
this.scan_barcode_field = this.frm.fields_dict[this.scan_field_name];
|
||||||
|
|
||||||
this.barcode_field = opts.barcode_field || "barcode";
|
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.uom_field = opts.uom_field || "uom";
|
||||||
this.qty_field = opts.qty_field || "qty";
|
this.qty_field = opts.qty_field || "qty";
|
||||||
// field name on row which defines max quantity to be scanned e.g. picklist
|
// field name on row which defines max quantity to be scanned e.g. picklist
|
||||||
@ -84,6 +82,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
update_table(data) {
|
update_table(data) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
|
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
|
||||||
|
frappe.flags.trigger_from_barcode_scanner = true;
|
||||||
|
|
||||||
const {item_code, barcode, batch_no, serial_no, uom} = data;
|
const {item_code, barcode, batch_no, serial_no, uom} = data;
|
||||||
|
|
||||||
@ -106,50 +105,38 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
this.frm.has_items = false;
|
this.frm.has_items = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.is_duplicate_serial_no(row, serial_no)) {
|
if (serial_no && this.is_duplicate_serial_no(row, item_code, serial_no)) {
|
||||||
this.clean_up();
|
this.clean_up();
|
||||||
reject();
|
reject();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
frappe.run_serially([
|
frappe.run_serially([
|
||||||
() => this.set_selector_trigger_flag(data),
|
() => this.set_serial_and_batch(row, item_code, serial_no, batch_no),
|
||||||
() => this.set_serial_no(row, serial_no),
|
|
||||||
() => this.set_batch_no(row, batch_no),
|
|
||||||
() => this.set_barcode(row, barcode),
|
() => this.set_barcode(row, barcode),
|
||||||
() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
|
() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
|
||||||
this.show_scan_message(row.idx, row.item_code, qty);
|
this.show_scan_message(row.idx, row.item_code, qty);
|
||||||
}),
|
}),
|
||||||
() => this.set_barcode_uom(row, uom),
|
() => this.set_barcode_uom(row, uom),
|
||||||
() => this.clean_up(),
|
() => this.clean_up(),
|
||||||
() => this.revert_selector_flag(),
|
() => resolve(row),
|
||||||
() => resolve(row)
|
() => {
|
||||||
|
if (row.serial_and_batch_bundle && !this.frm.is_new()) {
|
||||||
|
this.frm.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.flags.trigger_from_barcode_scanner = false;
|
||||||
|
}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {has_batch_no, has_serial_no} = data;
|
|
||||||
|
|
||||||
const require_selecting_batch = has_batch_no;
|
|
||||||
const require_selecting_serial = has_serial_no;
|
|
||||||
|
|
||||||
if (!(require_selecting_batch || require_selecting_serial)) {
|
|
||||||
frappe.flags.hide_serial_batch_dialog = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
revert_selector_flag() {
|
|
||||||
frappe.flags.hide_serial_batch_dialog = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_item(row, item_code, barcode, batch_no, serial_no) {
|
set_item(row, item_code, barcode, batch_no, serial_no) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const increment = async (value = 1) => {
|
const increment = async (value = 1) => {
|
||||||
const item_data = {item_code: item_code};
|
const item_data = {item_code: item_code};
|
||||||
item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value);
|
item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value);
|
||||||
|
frappe.flags.trigger_from_barcode_scanner = true;
|
||||||
await frappe.model.set_value(row.doctype, row.name, item_data);
|
await frappe.model.set_value(row.doctype, row.name, item_data);
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
@ -158,8 +145,6 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
frappe.prompt(__("Please enter quantity for item {0}", [item_code]), ({value}) => {
|
frappe.prompt(__("Please enter quantity for item {0}", [item_code]), ({value}) => {
|
||||||
increment(value).then((value) => resolve(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 {
|
} else {
|
||||||
increment().then((value) => resolve(value));
|
increment().then((value) => resolve(value));
|
||||||
}
|
}
|
||||||
@ -182,9 +167,8 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
frappe.model.set_value(row.doctype, row.name, item_data);
|
frappe.model.set_value(row.doctype, row.name, item_data);
|
||||||
|
|
||||||
frappe.run_serially([
|
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_barcode(row, this.dialog.get_value("barcode")),
|
||||||
() => this.set_serial_no(row, this.dialog.get_value("serial_no")),
|
() => this.set_serial_and_batch(row, item_code, this.dialog.get_value("serial_no"), this.dialog.get_value("batch_no")),
|
||||||
() => this.add_child_for_remaining_qty(row),
|
() => this.add_child_for_remaining_qty(row),
|
||||||
() => this.clean_up()
|
() => this.clean_up()
|
||||||
]);
|
]);
|
||||||
@ -338,32 +322,144 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async set_serial_no(row, serial_no) {
|
async set_serial_and_batch(row, item_code, serial_no, batch_no) {
|
||||||
if (serial_no && frappe.meta.has_field(row.doctype, this.serial_no_field)) {
|
if (this.frm.is_new() || !row.serial_and_batch_bundle) {
|
||||||
const existing_serial_nos = row[this.serial_no_field];
|
this.set_bundle_in_localstorage(row, item_code, serial_no, batch_no);
|
||||||
let new_serial_nos = "";
|
} else if(row.serial_and_batch_bundle) {
|
||||||
|
frappe.call({
|
||||||
if (!!existing_serial_nos) {
|
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.update_serial_or_batch",
|
||||||
new_serial_nos = existing_serial_nos + "\n" + serial_no;
|
args: {
|
||||||
} else {
|
bundle_id: row.serial_and_batch_bundle,
|
||||||
new_serial_nos = serial_no;
|
serial_no: serial_no,
|
||||||
}
|
batch_no: batch_no,
|
||||||
await frappe.model.set_value(row.doctype, row.name, this.serial_no_field, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let item_code in items) {
|
||||||
|
if (!existing_items.includes(item_code)) {
|
||||||
|
delete items[item_code];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage[docname] = JSON.stringify(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
async set_barcode_uom(row, uom) {
|
||||||
if (uom && frappe.meta.has_field(row.doctype, this.uom_field)) {
|
if (uom && frappe.meta.has_field(row.doctype, this.uom_field)) {
|
||||||
await frappe.model.set_value(row.doctype, row.name, this.uom_field, uom);
|
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) {
|
async set_barcode(row, barcode) {
|
||||||
if (barcode && frappe.meta.has_field(row.doctype, this.barcode_field)) {
|
if (barcode && frappe.meta.has_field(row.doctype, this.barcode_field)) {
|
||||||
await frappe.model.set_value(row.doctype, row.name, this.barcode_field, barcode);
|
await frappe.model.set_value(row.doctype, row.name, this.barcode_field, barcode);
|
||||||
@ -379,13 +475,52 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is_duplicate_serial_no(row, serial_no) {
|
is_duplicate_serial_no(row, item_code, serial_no) {
|
||||||
const is_duplicate = row[this.serial_no_field]?.includes(serial_no);
|
if (this.frm.is_new() || !row.serial_and_batch_bundle) {
|
||||||
|
let 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");
|
||||||
|
}
|
||||||
|
|
||||||
if (is_duplicate) {
|
return is_duplicate;
|
||||||
this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
|
} 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.message;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return is_duplicate;
|
}
|
||||||
|
|
||||||
|
async 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
let existing_row = [];
|
||||||
|
if (entries[item_code]) {
|
||||||
|
existing_row = entries[item_code].filter((e) => e.serial_no === serial_no);
|
||||||
|
}
|
||||||
|
|
||||||
|
return existing_row.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
get_row_to_modify_on_scan(item_code, batch_no, uom, barcode) {
|
get_row_to_modify_on_scan(item_code, batch_no, uom, barcode) {
|
||||||
|
@ -729,19 +729,13 @@ class SerialandBatchBundle(Document):
|
|||||||
|
|
||||||
def before_cancel(self):
|
def before_cancel(self):
|
||||||
self.delink_serial_and_batch_bundle()
|
self.delink_serial_and_batch_bundle()
|
||||||
self.clear_table()
|
|
||||||
|
|
||||||
def delink_serial_and_batch_bundle(self):
|
def delink_serial_and_batch_bundle(self):
|
||||||
self.voucher_no = None
|
|
||||||
|
|
||||||
sles = frappe.get_all("Stock Ledger Entry", filters={"serial_and_batch_bundle": self.name})
|
sles = frappe.get_all("Stock Ledger Entry", filters={"serial_and_batch_bundle": self.name})
|
||||||
|
|
||||||
for sle in sles:
|
for sle in sles:
|
||||||
frappe.db.set_value("Stock Ledger Entry", sle.name, "serial_and_batch_bundle", None)
|
frappe.db.set_value("Stock Ledger Entry", sle.name, "serial_and_batch_bundle", None)
|
||||||
|
|
||||||
def clear_table(self):
|
|
||||||
self.set("entries", [])
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def child_table(self):
|
def child_table(self):
|
||||||
if self.voucher_type == "Job Card":
|
if self.voucher_type == "Job Card":
|
||||||
@ -876,7 +870,6 @@ class SerialandBatchBundle(Document):
|
|||||||
self.validate_voucher_no_docstatus()
|
self.validate_voucher_no_docstatus()
|
||||||
self.delink_refernce_from_voucher()
|
self.delink_refernce_from_voucher()
|
||||||
self.delink_reference_from_batch()
|
self.delink_reference_from_batch()
|
||||||
self.clear_table()
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def add_serial_batch(self, data):
|
def add_serial_batch(self, data):
|
||||||
@ -1156,7 +1149,7 @@ def get_filters_for_bundle(item_code=None, docstatus=None, voucher_no=None, name
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def add_serial_batch_ledgers(entries, child_row, doc, warehouse) -> object:
|
def add_serial_batch_ledgers(entries, child_row, doc, warehouse, do_not_save=False) -> object:
|
||||||
if isinstance(child_row, str):
|
if isinstance(child_row, str):
|
||||||
child_row = frappe._dict(parse_json(child_row))
|
child_row = frappe._dict(parse_json(child_row))
|
||||||
|
|
||||||
@ -1170,20 +1163,23 @@ def add_serial_batch_ledgers(entries, child_row, doc, warehouse) -> object:
|
|||||||
if frappe.db.exists("Serial and Batch Bundle", child_row.serial_and_batch_bundle):
|
if frappe.db.exists("Serial and Batch Bundle", child_row.serial_and_batch_bundle):
|
||||||
sb_doc = update_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse)
|
sb_doc = update_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse)
|
||||||
else:
|
else:
|
||||||
sb_doc = create_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse)
|
sb_doc = create_serial_batch_no_ledgers(
|
||||||
|
entries, child_row, parent_doc, warehouse, do_not_save=do_not_save
|
||||||
|
)
|
||||||
|
|
||||||
return sb_doc
|
return sb_doc
|
||||||
|
|
||||||
|
|
||||||
def create_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse=None) -> object:
|
def create_serial_batch_no_ledgers(
|
||||||
|
entries, child_row, parent_doc, warehouse=None, do_not_save=False
|
||||||
|
) -> object:
|
||||||
|
|
||||||
warehouse = warehouse or (
|
warehouse = warehouse or (
|
||||||
child_row.rejected_warehouse if child_row.is_rejected else child_row.warehouse
|
child_row.rejected_warehouse if child_row.is_rejected else child_row.warehouse
|
||||||
)
|
)
|
||||||
|
|
||||||
type_of_transaction = child_row.type_of_transaction
|
type_of_transaction = get_type_of_transaction(parent_doc, child_row)
|
||||||
if parent_doc.get("doctype") == "Stock Entry":
|
if parent_doc.get("doctype") == "Stock Entry":
|
||||||
type_of_transaction = "Outward" if child_row.s_warehouse else "Inward"
|
|
||||||
warehouse = warehouse or child_row.s_warehouse or child_row.t_warehouse
|
warehouse = warehouse or child_row.s_warehouse or child_row.t_warehouse
|
||||||
|
|
||||||
doc = frappe.get_doc(
|
doc = frappe.get_doc(
|
||||||
@ -1214,13 +1210,30 @@ def create_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse=Non
|
|||||||
|
|
||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
frappe.db.set_value(child_row.doctype, child_row.name, "serial_and_batch_bundle", doc.name)
|
if do_not_save:
|
||||||
|
frappe.db.set_value(child_row.doctype, child_row.name, "serial_and_batch_bundle", doc.name)
|
||||||
|
|
||||||
frappe.msgprint(_("Serial and Batch Bundle created"), alert=True)
|
frappe.msgprint(_("Serial and Batch Bundle created"), alert=True)
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
def get_type_of_transaction(parent_doc, child_row):
|
||||||
|
type_of_transaction = child_row.type_of_transaction
|
||||||
|
if parent_doc.get("doctype") == "Stock Entry":
|
||||||
|
type_of_transaction = "Outward" if child_row.s_warehouse else "Inward"
|
||||||
|
|
||||||
|
if not type_of_transaction:
|
||||||
|
type_of_transaction = "Outward"
|
||||||
|
if parent_doc.get("doctype") in ["Purchase Receipt", "Purchase Invoice"]:
|
||||||
|
type_of_transaction = "Inward"
|
||||||
|
|
||||||
|
if parent_doc.get("is_return"):
|
||||||
|
type_of_transaction = "Inward" if type_of_transaction == "Outward" else "Outward"
|
||||||
|
|
||||||
|
return type_of_transaction
|
||||||
|
|
||||||
|
|
||||||
def update_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse=None) -> object:
|
def update_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse=None) -> object:
|
||||||
doc = frappe.get_doc("Serial and Batch Bundle", child_row.serial_and_batch_bundle)
|
doc = frappe.get_doc("Serial and Batch Bundle", child_row.serial_and_batch_bundle)
|
||||||
doc.voucher_detail_no = child_row.name
|
doc.voucher_detail_no = child_row.name
|
||||||
@ -1247,6 +1260,25 @@ def update_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse=Non
|
|||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def update_serial_or_batch(bundle_id, serial_no=None, batch_no=None):
|
||||||
|
if batch_no and not serial_no:
|
||||||
|
if qty := frappe.db.get_value(
|
||||||
|
"Serial and Batch Entry", {"parent": bundle_id, "batch_no": batch_no}, "qty"
|
||||||
|
):
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Serial and Batch Entry", {"parent": bundle_id, "batch_no": batch_no}, "qty", qty + 1
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
doc = frappe.get_cached_doc("Serial and Batch Bundle", bundle_id)
|
||||||
|
if not serial_no and not batch_no:
|
||||||
|
return
|
||||||
|
|
||||||
|
doc.append("entries", {"serial_no": serial_no, "batch_no": batch_no, "qty": 1})
|
||||||
|
doc.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
def get_serial_and_batch_ledger(**kwargs):
|
def get_serial_and_batch_ledger(**kwargs):
|
||||||
kwargs = frappe._dict(kwargs)
|
kwargs = frappe._dict(kwargs)
|
||||||
|
|
||||||
@ -2032,3 +2064,8 @@ def get_stock_ledgers_batches(kwargs):
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_batch_no_from_serial_no(serial_no):
|
def get_batch_no_from_serial_no(serial_no):
|
||||||
return frappe.get_cached_value("Serial No", serial_no, "batch_no")
|
return frappe.get_cached_value("Serial No", serial_no, "batch_no")
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def is_duplicate_serial_no(bundle_id, serial_no):
|
||||||
|
return frappe.db.exists("Serial and Batch Entry", {"parent": bundle_id, "serial_no": serial_no})
|
||||||
|
@ -209,7 +209,7 @@ class SerialBatchBundle:
|
|||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
"Serial and Batch Bundle",
|
"Serial and Batch Bundle",
|
||||||
{"voucher_no": self.sle.voucher_no, "voucher_type": self.sle.voucher_type},
|
{"voucher_no": self.sle.voucher_no, "voucher_type": self.sle.voucher_type},
|
||||||
{"is_cancelled": 1, "voucher_no": ""},
|
{"is_cancelled": 1},
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.sle.serial_and_batch_bundle:
|
if self.sle.serial_and_batch_bundle:
|
||||||
|
@ -591,6 +591,13 @@ def scan_barcode(search_value: str) -> BarcodeScanResult:
|
|||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
if batch_no_data:
|
if batch_no_data:
|
||||||
|
if frappe.get_cached_value("Item", batch_no_data.item_code, "has_serial_no"):
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"Batch No {0} is linked with Item {1} which has serial no. Please scan serial no instead."
|
||||||
|
).format(search_value, batch_no_data.item_code)
|
||||||
|
)
|
||||||
|
|
||||||
_update_item_info(batch_no_data)
|
_update_item_info(batch_no_data)
|
||||||
set_cache(batch_no_data)
|
set_cache(batch_no_data)
|
||||||
return batch_no_data
|
return batch_no_data
|
||||||
|
Loading…
x
Reference in New Issue
Block a user