fix: dialog issue
This commit is contained in:
parent
74ab20f97a
commit
f79f2a3bab
@ -432,7 +432,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
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;
|
||||||
|
|
||||||
} else if((this.frm.doc.doctype === 'Purchase Receipt' && me.frm.doc.is_return) ||
|
} else if((this.frm.doc.doctype === 'Purchase Receipt') ||
|
||||||
this.frm.doc.doctype === 'Delivery Note') {
|
this.frm.doc.doctype === 'Delivery Note') {
|
||||||
show_batch_dialog = 1;
|
show_batch_dialog = 1;
|
||||||
}
|
}
|
||||||
@ -538,7 +538,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
if(show_batch_dialog && !frappe.flags.hide_serial_batch_dialog) {
|
if(show_batch_dialog && !frappe.flags.hide_serial_batch_dialog && !frappe.flags.dialog_set) {
|
||||||
var d = locals[cdt][cdn];
|
var d = locals[cdt][cdn];
|
||||||
$.each(r.message, function(k, v) {
|
$.each(r.message, function(k, v) {
|
||||||
if(!d[k]) d[k] = v;
|
if(!d[k]) d[k] = v;
|
||||||
@ -548,12 +548,15 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
d.batch_no = undefined;
|
d.batch_no = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frappe.flags.dialog_set = true;
|
||||||
erpnext.show_serial_batch_selector(me.frm, d, (item) => {
|
erpnext.show_serial_batch_selector(me.frm, d, (item) => {
|
||||||
me.frm.script_manager.trigger('qty', item.doctype, item.name);
|
me.frm.script_manager.trigger('qty', item.doctype, item.name);
|
||||||
if (!me.frm.doc.set_warehouse)
|
if (!me.frm.doc.set_warehouse)
|
||||||
me.frm.script_manager.trigger('warehouse', item.doctype, item.name);
|
me.frm.script_manager.trigger('warehouse', item.doctype, item.name);
|
||||||
me.apply_price_list(item, true);
|
me.apply_price_list(item, true);
|
||||||
}, undefined, !frappe.flags.hide_serial_batch_dialog);
|
}, undefined, !frappe.flags.hide_serial_batch_dialog);
|
||||||
|
} else {
|
||||||
|
frappe.flags.dialog_set = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => me.conversion_factor(doc, cdt, cdn, true),
|
() => me.conversion_factor(doc, cdt, cdn, true),
|
||||||
@ -2287,6 +2290,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
};
|
};
|
||||||
|
|
||||||
erpnext.show_serial_batch_selector = function (frm, item_row, callback, on_close, show_dialog) {
|
erpnext.show_serial_batch_selector = function (frm, item_row, callback, on_close, show_dialog) {
|
||||||
|
debugger
|
||||||
let warehouse, receiving_stock, existing_stock;
|
let warehouse, receiving_stock, existing_stock;
|
||||||
if (frm.doc.is_return) {
|
if (frm.doc.is_return) {
|
||||||
if (["Purchase Receipt", "Purchase Invoice"].includes(frm.doc.doctype)) {
|
if (["Purchase Receipt", "Purchase Invoice"].includes(frm.doc.doctype)) {
|
||||||
|
@ -29,10 +29,6 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
primary_action: () => this.update_ledgers()
|
primary_action: () => this.update_ledgers()
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.item?.outward) {
|
|
||||||
this.prepare_for_auto_fetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dialog.show();
|
this.dialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +72,13 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
fieldname: 'scan_batch_no',
|
fieldname: 'scan_batch_no',
|
||||||
label: __('Scan Batch No'),
|
label: __('Scan Batch No'),
|
||||||
options: 'Batch',
|
options: 'Batch',
|
||||||
|
get_query: () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'item': this.item.item_code
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
onchange: () => this.update_serial_batch_no()
|
onchange: () => this.update_serial_batch_no()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -97,7 +100,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.item?.outward) {
|
if (this.item?.outward) {
|
||||||
fields = [...fields, ...this.get_filter_fields()];
|
fields = [...this.get_filter_fields(), ...fields];
|
||||||
}
|
}
|
||||||
|
|
||||||
fields.push({
|
fields.push({
|
||||||
@ -126,6 +129,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
fieldname: 'qty',
|
fieldname: 'qty',
|
||||||
default: this.item.qty || 0,
|
default: this.item.qty || 0,
|
||||||
label: __('Qty to Fetch'),
|
label: __('Qty to Fetch'),
|
||||||
|
onchange: () => this.get_auto_data()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldtype: 'Column Break',
|
fieldtype: 'Column Break',
|
||||||
@ -135,16 +139,11 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
options: ['FIFO', 'LIFO', 'Expiry'],
|
options: ['FIFO', 'LIFO', 'Expiry'],
|
||||||
default: 'FIFO',
|
default: 'FIFO',
|
||||||
fieldname: 'based_on',
|
fieldname: 'based_on',
|
||||||
label: __('Fetch Based On')
|
label: __('Fetch Based On'),
|
||||||
|
onchange: () => this.get_auto_data()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldtype: 'Column Break',
|
fieldtype: 'Section Break',
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldtype: 'Button',
|
|
||||||
fieldname: 'get_auto_data',
|
|
||||||
label: __('Fetch {0}',
|
|
||||||
[this.item?.has_serial_no ? 'Serial Nos' : 'Batch Nos']),
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -177,6 +176,13 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
fieldname: 'batch_no',
|
fieldname: 'batch_no',
|
||||||
label: __('Batch No'),
|
label: __('Batch No'),
|
||||||
in_list_view: 1,
|
in_list_view: 1,
|
||||||
|
get_query: () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'item': this.item.item_code
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -202,12 +208,6 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare_for_auto_fetch() {
|
|
||||||
this.dialog.fields_dict.get_auto_data.$input.on('click', () => {
|
|
||||||
this.get_auto_data();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get_auto_data() {
|
get_auto_data() {
|
||||||
const { qty, based_on } = this.dialog.get_values();
|
const { qty, based_on } = this.dialog.get_values();
|
||||||
|
|
||||||
@ -215,6 +215,10 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
frappe.throw(__('Please enter Qty to Fetch'));
|
frappe.throw(__('Please enter Qty to Fetch'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!based_on) {
|
||||||
|
based_on = 'FIFO';
|
||||||
|
}
|
||||||
|
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_auto_data',
|
method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_auto_data',
|
||||||
args: {
|
args: {
|
||||||
|
@ -17,13 +17,9 @@ class DeprecatedSerialNoValuation:
|
|||||||
|
|
||||||
stock_value_change = 0
|
stock_value_change = 0
|
||||||
if actual_qty < 0:
|
if actual_qty < 0:
|
||||||
# In case of delivery/stock issue, get average purchase rate
|
|
||||||
# of serial nos of current entry
|
|
||||||
if not self.sle.is_cancelled:
|
if not self.sle.is_cancelled:
|
||||||
outgoing_value = self.get_incoming_value_for_serial_nos(serial_nos)
|
outgoing_value = self.get_incoming_value_for_serial_nos(serial_nos)
|
||||||
stock_value_change = -1 * outgoing_value
|
stock_value_change = -1 * outgoing_value
|
||||||
else:
|
|
||||||
stock_value_change = actual_qty * self.sle.outgoing_rate
|
|
||||||
|
|
||||||
self.stock_value_change += stock_value_change
|
self.stock_value_change += stock_value_change
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "quantity_and_rate_section",
|
"fieldname": "quantity_and_rate_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Quantity and Rate"
|
"label": "Quantity and Rate"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -243,7 +243,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-03-24 13:39:17.843812",
|
"modified": "2023-04-03 16:22:30.767805",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Serial and Batch Bundle",
|
"name": "Serial and Batch Bundle",
|
||||||
|
@ -154,7 +154,10 @@ class SerialandBatchBundle(Document):
|
|||||||
if sn_obj.batch_avg_rate.get(d.batch_no):
|
if sn_obj.batch_avg_rate.get(d.batch_no):
|
||||||
d.incoming_rate = abs(sn_obj.batch_avg_rate.get(d.batch_no))
|
d.incoming_rate = abs(sn_obj.batch_avg_rate.get(d.batch_no))
|
||||||
|
|
||||||
available_qty = flt(sn_obj.available_qty.get(d.batch_no)) + flt(d.qty)
|
available_qty = flt(sn_obj.available_qty.get(d.batch_no))
|
||||||
|
if self.docstatus == 1:
|
||||||
|
available_qty += flt(d.qty)
|
||||||
|
|
||||||
self.validate_negative_batch(d.batch_no, available_qty)
|
self.validate_negative_batch(d.batch_no, available_qty)
|
||||||
|
|
||||||
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
|
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
|
||||||
@ -553,6 +556,38 @@ class SerialandBatchBundle(Document):
|
|||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.validate_serial_nos_inventory()
|
self.validate_serial_nos_inventory()
|
||||||
|
|
||||||
|
def validate_serial_and_batch_inventory(self):
|
||||||
|
self.check_future_entries_exists()
|
||||||
|
self.validate_batch_inventory()
|
||||||
|
|
||||||
|
def validate_batch_inventory(self):
|
||||||
|
if not self.has_batch_no:
|
||||||
|
return
|
||||||
|
|
||||||
|
batches = [d.batch_no for d in self.entries if d.batch_no]
|
||||||
|
if not batches:
|
||||||
|
return
|
||||||
|
|
||||||
|
available_batches = get_auto_batch_nos(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"item_code": self.item_code,
|
||||||
|
"warehouse": self.warehouse,
|
||||||
|
"batch_no": batches,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not available_batches:
|
||||||
|
return
|
||||||
|
|
||||||
|
available_batches = get_availabel_batches_qty(available_batches)
|
||||||
|
for batch_no in batches:
|
||||||
|
if batch_no not in available_batches or available_batches[batch_no] < 0:
|
||||||
|
self.throw_error_message(
|
||||||
|
f"Batch {bold(batch_no)} is not available in the selected warehouse {self.warehouse}"
|
||||||
|
)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.validate_voucher_no_docstatus()
|
self.validate_voucher_no_docstatus()
|
||||||
|
|
||||||
@ -599,6 +634,7 @@ def get_serial_batch_ledgers(item_code, docstatus=None, voucher_no=None, name=No
|
|||||||
"`tabSerial and Batch Entry`.`serial_no`",
|
"`tabSerial and Batch Entry`.`serial_no`",
|
||||||
],
|
],
|
||||||
filters=filters,
|
filters=filters,
|
||||||
|
order_by="`tabSerial and Batch Entry`.`idx`",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -762,6 +798,14 @@ def get_auto_data(**kwargs):
|
|||||||
return get_auto_batch_nos(kwargs)
|
return get_auto_batch_nos(kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def get_availabel_batches_qty(available_batches):
|
||||||
|
available_batches_qty = defaultdict(float)
|
||||||
|
for batch in available_batches:
|
||||||
|
available_batches_qty[batch.batch_no] += batch.qty
|
||||||
|
|
||||||
|
return available_batches_qty
|
||||||
|
|
||||||
|
|
||||||
def get_available_serial_nos(kwargs):
|
def get_available_serial_nos(kwargs):
|
||||||
fields = ["name as serial_no", "warehouse"]
|
fields = ["name as serial_no", "warehouse"]
|
||||||
if kwargs.has_batch_no:
|
if kwargs.has_batch_no:
|
||||||
@ -778,6 +822,7 @@ def get_available_serial_nos(kwargs):
|
|||||||
if kwargs.warehouse:
|
if kwargs.warehouse:
|
||||||
filters["warehouse"] = kwargs.warehouse
|
filters["warehouse"] = kwargs.warehouse
|
||||||
|
|
||||||
|
# Since SLEs are not present against POS invoices, need to ignore serial nos present in the POS invoice
|
||||||
ignore_serial_nos = get_reserved_serial_nos_for_pos(kwargs)
|
ignore_serial_nos = get_reserved_serial_nos_for_pos(kwargs)
|
||||||
|
|
||||||
# To ignore serial nos in the same record for the draft state
|
# To ignore serial nos in the same record for the draft state
|
||||||
@ -792,6 +837,13 @@ def get_available_serial_nos(kwargs):
|
|||||||
elif ignore_serial_nos:
|
elif ignore_serial_nos:
|
||||||
filters["name"] = ("not in", ignore_serial_nos)
|
filters["name"] = ("not in", ignore_serial_nos)
|
||||||
|
|
||||||
|
if kwargs.get("batches"):
|
||||||
|
batches = get_non_expired_batches(kwargs.get("batches"))
|
||||||
|
if not batches:
|
||||||
|
return []
|
||||||
|
|
||||||
|
filters["batch_no"] = ("in", batches)
|
||||||
|
|
||||||
return frappe.get_all(
|
return frappe.get_all(
|
||||||
"Serial No",
|
"Serial No",
|
||||||
fields=fields,
|
fields=fields,
|
||||||
@ -801,6 +853,23 @@ def get_available_serial_nos(kwargs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_non_expired_batches(batches):
|
||||||
|
filters = {}
|
||||||
|
if isinstance(batches, list):
|
||||||
|
filters["name"] = ("in", batches)
|
||||||
|
else:
|
||||||
|
filters["name"] = batches
|
||||||
|
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Batch",
|
||||||
|
filters=filters,
|
||||||
|
or_filters=[["expiry_date", ">=", today()], ["expiry_date", "is", "not set"]],
|
||||||
|
fields=["name"],
|
||||||
|
)
|
||||||
|
|
||||||
|
return [d.name for d in data] if data else []
|
||||||
|
|
||||||
|
|
||||||
def get_serial_nos_based_on_posting_date(kwargs, ignore_serial_nos):
|
def get_serial_nos_based_on_posting_date(kwargs, ignore_serial_nos):
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _, _dict
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
from frappe.utils import today
|
||||||
|
|
||||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
@ -49,26 +51,22 @@ class TestSerialNo(FrappeTestCase):
|
|||||||
|
|
||||||
def test_inter_company_transfer(self):
|
def test_inter_company_transfer(self):
|
||||||
se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
|
se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
|
||||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
dn = create_delivery_note(
|
dn = create_delivery_note(
|
||||||
item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0]
|
item_code="_Test Serialized Item With Series", qty=1, serial_no=[serial_nos[0]]
|
||||||
)
|
)
|
||||||
|
|
||||||
serial_no = frappe.get_doc("Serial No", serial_nos[0])
|
serial_no = frappe.get_doc("Serial No", serial_nos[0])
|
||||||
|
|
||||||
# check Serial No details after delivery
|
# check Serial No details after delivery
|
||||||
self.assertEqual(serial_no.status, "Delivered")
|
|
||||||
self.assertEqual(serial_no.warehouse, None)
|
self.assertEqual(serial_no.warehouse, None)
|
||||||
self.assertEqual(serial_no.company, "_Test Company")
|
|
||||||
self.assertEqual(serial_no.delivery_document_type, "Delivery Note")
|
|
||||||
self.assertEqual(serial_no.delivery_document_no, dn.name)
|
|
||||||
|
|
||||||
wh = create_warehouse("_Test Warehouse", company="_Test Company 1")
|
wh = create_warehouse("_Test Warehouse", company="_Test Company 1")
|
||||||
pr = make_purchase_receipt(
|
pr = make_purchase_receipt(
|
||||||
item_code="_Test Serialized Item With Series",
|
item_code="_Test Serialized Item With Series",
|
||||||
qty=1,
|
qty=1,
|
||||||
serial_no=serial_nos[0],
|
serial_no=[serial_nos[0]],
|
||||||
company="_Test Company 1",
|
company="_Test Company 1",
|
||||||
warehouse=wh,
|
warehouse=wh,
|
||||||
)
|
)
|
||||||
@ -76,11 +74,7 @@ class TestSerialNo(FrappeTestCase):
|
|||||||
serial_no.reload()
|
serial_no.reload()
|
||||||
|
|
||||||
# check Serial No details after purchase in second company
|
# check Serial No details after purchase in second company
|
||||||
self.assertEqual(serial_no.status, "Active")
|
|
||||||
self.assertEqual(serial_no.warehouse, wh)
|
self.assertEqual(serial_no.warehouse, wh)
|
||||||
self.assertEqual(serial_no.company, "_Test Company 1")
|
|
||||||
self.assertEqual(serial_no.purchase_document_type, "Purchase Receipt")
|
|
||||||
self.assertEqual(serial_no.purchase_document_no, pr.name)
|
|
||||||
|
|
||||||
def test_inter_company_transfer_intermediate_cancellation(self):
|
def test_inter_company_transfer_intermediate_cancellation(self):
|
||||||
"""
|
"""
|
||||||
@ -89,25 +83,19 @@ class TestSerialNo(FrappeTestCase):
|
|||||||
Try to cancel intermediate receipts/deliveries to test if it is blocked.
|
Try to cancel intermediate receipts/deliveries to test if it is blocked.
|
||||||
"""
|
"""
|
||||||
se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
|
se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
|
||||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
sn_doc = frappe.get_doc("Serial No", serial_nos[0])
|
sn_doc = frappe.get_doc("Serial No", serial_nos[0])
|
||||||
|
|
||||||
# check Serial No details after purchase in first company
|
# check Serial No details after purchase in first company
|
||||||
self.assertEqual(sn_doc.status, "Active")
|
|
||||||
self.assertEqual(sn_doc.company, "_Test Company")
|
|
||||||
self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
|
self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
|
||||||
self.assertEqual(sn_doc.purchase_document_no, se.name)
|
|
||||||
|
|
||||||
dn = create_delivery_note(
|
dn = create_delivery_note(
|
||||||
item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0]
|
item_code="_Test Serialized Item With Series", qty=1, serial_no=[serial_nos[0]]
|
||||||
)
|
)
|
||||||
sn_doc.reload()
|
sn_doc.reload()
|
||||||
# check Serial No details after delivery from **first** company
|
# check Serial No details after delivery from **first** company
|
||||||
self.assertEqual(sn_doc.status, "Delivered")
|
|
||||||
self.assertEqual(sn_doc.company, "_Test Company")
|
|
||||||
self.assertEqual(sn_doc.warehouse, None)
|
self.assertEqual(sn_doc.warehouse, None)
|
||||||
self.assertEqual(sn_doc.delivery_document_no, dn.name)
|
|
||||||
|
|
||||||
# try cancelling the first Serial No Receipt, even though it is delivered
|
# try cancelling the first Serial No Receipt, even though it is delivered
|
||||||
# block cancellation is Serial No is out of the warehouse
|
# block cancellation is Serial No is out of the warehouse
|
||||||
@ -118,7 +106,7 @@ class TestSerialNo(FrappeTestCase):
|
|||||||
pr = make_purchase_receipt(
|
pr = make_purchase_receipt(
|
||||||
item_code="_Test Serialized Item With Series",
|
item_code="_Test Serialized Item With Series",
|
||||||
qty=1,
|
qty=1,
|
||||||
serial_no=serial_nos[0],
|
serial_no=[serial_nos[0]],
|
||||||
company="_Test Company 1",
|
company="_Test Company 1",
|
||||||
warehouse=wh,
|
warehouse=wh,
|
||||||
)
|
)
|
||||||
@ -133,17 +121,14 @@ class TestSerialNo(FrappeTestCase):
|
|||||||
dn_2 = create_delivery_note(
|
dn_2 = create_delivery_note(
|
||||||
item_code="_Test Serialized Item With Series",
|
item_code="_Test Serialized Item With Series",
|
||||||
qty=1,
|
qty=1,
|
||||||
serial_no=serial_nos[0],
|
serial_no=[serial_nos[0]],
|
||||||
company="_Test Company 1",
|
company="_Test Company 1",
|
||||||
warehouse=wh,
|
warehouse=wh,
|
||||||
)
|
)
|
||||||
sn_doc.reload()
|
sn_doc.reload()
|
||||||
|
|
||||||
# check Serial No details after delivery from **second** company
|
# check Serial No details after delivery from **second** company
|
||||||
self.assertEqual(sn_doc.status, "Delivered")
|
|
||||||
self.assertEqual(sn_doc.company, "_Test Company 1")
|
|
||||||
self.assertEqual(sn_doc.warehouse, None)
|
self.assertEqual(sn_doc.warehouse, None)
|
||||||
self.assertEqual(sn_doc.delivery_document_no, dn_2.name)
|
|
||||||
|
|
||||||
# cannot cancel any intermediate document before last Delivery Note
|
# cannot cancel any intermediate document before last Delivery Note
|
||||||
self.assertRaises(frappe.ValidationError, se.cancel)
|
self.assertRaises(frappe.ValidationError, se.cancel)
|
||||||
@ -158,12 +143,12 @@ class TestSerialNo(FrappeTestCase):
|
|||||||
"""
|
"""
|
||||||
# Receipt in **first** company
|
# Receipt in **first** company
|
||||||
se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
|
se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
|
||||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||||
sn_doc = frappe.get_doc("Serial No", serial_nos[0])
|
sn_doc = frappe.get_doc("Serial No", serial_nos[0])
|
||||||
|
|
||||||
# Delivery from first company
|
# Delivery from first company
|
||||||
dn = create_delivery_note(
|
dn = create_delivery_note(
|
||||||
item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0]
|
item_code="_Test Serialized Item With Series", qty=1, serial_no=[serial_nos[0]]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Receipt in **second** company
|
# Receipt in **second** company
|
||||||
@ -171,7 +156,7 @@ class TestSerialNo(FrappeTestCase):
|
|||||||
pr = make_purchase_receipt(
|
pr = make_purchase_receipt(
|
||||||
item_code="_Test Serialized Item With Series",
|
item_code="_Test Serialized Item With Series",
|
||||||
qty=1,
|
qty=1,
|
||||||
serial_no=serial_nos[0],
|
serial_no=[serial_nos[0]],
|
||||||
company="_Test Company 1",
|
company="_Test Company 1",
|
||||||
warehouse=wh,
|
warehouse=wh,
|
||||||
)
|
)
|
||||||
@ -180,55 +165,29 @@ class TestSerialNo(FrappeTestCase):
|
|||||||
dn_2 = create_delivery_note(
|
dn_2 = create_delivery_note(
|
||||||
item_code="_Test Serialized Item With Series",
|
item_code="_Test Serialized Item With Series",
|
||||||
qty=1,
|
qty=1,
|
||||||
serial_no=serial_nos[0],
|
serial_no=[serial_nos[0]],
|
||||||
company="_Test Company 1",
|
company="_Test Company 1",
|
||||||
warehouse=wh,
|
warehouse=wh,
|
||||||
)
|
)
|
||||||
sn_doc.reload()
|
sn_doc.reload()
|
||||||
|
|
||||||
self.assertEqual(sn_doc.status, "Delivered")
|
self.assertEqual(sn_doc.warehouse, None)
|
||||||
self.assertEqual(sn_doc.company, "_Test Company 1")
|
|
||||||
self.assertEqual(sn_doc.delivery_document_no, dn_2.name)
|
|
||||||
|
|
||||||
dn_2.cancel()
|
dn_2.cancel()
|
||||||
sn_doc.reload()
|
sn_doc.reload()
|
||||||
# Fallback on Purchase Receipt if Delivery is cancelled
|
# Fallback on Purchase Receipt if Delivery is cancelled
|
||||||
self.assertEqual(sn_doc.status, "Active")
|
|
||||||
self.assertEqual(sn_doc.company, "_Test Company 1")
|
|
||||||
self.assertEqual(sn_doc.warehouse, wh)
|
self.assertEqual(sn_doc.warehouse, wh)
|
||||||
self.assertEqual(sn_doc.purchase_document_no, pr.name)
|
|
||||||
|
|
||||||
pr.cancel()
|
pr.cancel()
|
||||||
sn_doc.reload()
|
sn_doc.reload()
|
||||||
# Inactive in same company if Receipt cancelled
|
# Inactive in same company if Receipt cancelled
|
||||||
self.assertEqual(sn_doc.status, "Inactive")
|
|
||||||
self.assertEqual(sn_doc.company, "_Test Company 1")
|
|
||||||
self.assertEqual(sn_doc.warehouse, None)
|
self.assertEqual(sn_doc.warehouse, None)
|
||||||
|
|
||||||
dn.cancel()
|
dn.cancel()
|
||||||
sn_doc.reload()
|
sn_doc.reload()
|
||||||
# Fallback on Purchase Receipt in FIRST company if
|
# Fallback on Purchase Receipt in FIRST company if
|
||||||
# Delivery from FIRST company is cancelled
|
# Delivery from FIRST company is cancelled
|
||||||
self.assertEqual(sn_doc.status, "Active")
|
|
||||||
self.assertEqual(sn_doc.company, "_Test Company")
|
|
||||||
self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
|
self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
|
||||||
self.assertEqual(sn_doc.purchase_document_no, se.name)
|
|
||||||
|
|
||||||
def test_serial_no_sanitation(self):
|
|
||||||
"Test if Serial No input is sanitised before entering the DB."
|
|
||||||
item_code = "_Test Serialized Item"
|
|
||||||
test_records = frappe.get_test_records("Stock Entry")
|
|
||||||
|
|
||||||
se = frappe.copy_doc(test_records[0])
|
|
||||||
se.get("items")[0].item_code = item_code
|
|
||||||
se.get("items")[0].qty = 4
|
|
||||||
se.get("items")[0].serial_no = " _TS1, _TS2 , _TS3 , _TS4 - 2021"
|
|
||||||
se.get("items")[0].transfer_qty = 4
|
|
||||||
se.set_stock_entry_type()
|
|
||||||
se.insert()
|
|
||||||
se.submit()
|
|
||||||
|
|
||||||
self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3\n_TS4 - 2021")
|
|
||||||
|
|
||||||
def test_correct_serial_no_incoming_rate(self):
|
def test_correct_serial_no_incoming_rate(self):
|
||||||
"""Check correct consumption rate based on serial no record."""
|
"""Check correct consumption rate based on serial no record."""
|
||||||
@ -236,19 +195,28 @@ class TestSerialNo(FrappeTestCase):
|
|||||||
warehouse = "_Test Warehouse - _TC"
|
warehouse = "_Test Warehouse - _TC"
|
||||||
serial_nos = ["LOWVALUATION", "HIGHVALUATION"]
|
serial_nos = ["LOWVALUATION", "HIGHVALUATION"]
|
||||||
|
|
||||||
|
for serial_no in serial_nos:
|
||||||
|
if not frappe.db.exists("Serial No", serial_no):
|
||||||
|
frappe.get_doc(
|
||||||
|
{"doctype": "Serial No", "item_code": item_code, "serial_no": serial_no}
|
||||||
|
).insert()
|
||||||
|
|
||||||
in1 = make_stock_entry(
|
in1 = make_stock_entry(
|
||||||
item_code=item_code, to_warehouse=warehouse, qty=1, rate=42, serial_no=serial_nos[0]
|
item_code=item_code, to_warehouse=warehouse, qty=1, rate=42, serial_no=[serial_nos[0]]
|
||||||
)
|
)
|
||||||
in2 = make_stock_entry(
|
in2 = make_stock_entry(
|
||||||
item_code=item_code, to_warehouse=warehouse, qty=1, rate=113, serial_no=serial_nos[1]
|
item_code=item_code, to_warehouse=warehouse, qty=1, rate=113, serial_no=[serial_nos[1]]
|
||||||
)
|
)
|
||||||
|
|
||||||
out = create_delivery_note(
|
out = create_delivery_note(
|
||||||
item_code=item_code, qty=1, serial_no=serial_nos[0], do_not_submit=True
|
item_code=item_code, qty=1, serial_no=[serial_nos[0]], do_not_submit=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# change serial no
|
bundle = out.items[0].serial_and_batch_bundle
|
||||||
out.items[0].serial_no = serial_nos[1]
|
doc = frappe.get_doc("Serial and Batch Bundle", bundle)
|
||||||
|
doc.entries[0].serial_no = serial_nos[1]
|
||||||
|
doc.save()
|
||||||
|
|
||||||
out.save()
|
out.save()
|
||||||
out.submit()
|
out.submit()
|
||||||
|
|
||||||
@ -285,40 +253,90 @@ class TestSerialNo(FrappeTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Test FIFO
|
# Test FIFO
|
||||||
first_fetch = auto_fetch_serial_number(5, item_code, warehouse)
|
first_fetch = get_auto_serial_nos(
|
||||||
|
_dict(
|
||||||
|
{
|
||||||
|
"qty": 5,
|
||||||
|
"item_code": item_code,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(first_fetch, batch_wise_serials[batch1])
|
self.assertEqual(first_fetch, batch_wise_serials[batch1])
|
||||||
|
|
||||||
# partial FIFO
|
# partial FIFO
|
||||||
partial_fetch = auto_fetch_serial_number(2, item_code, warehouse)
|
partial_fetch = get_auto_serial_nos(
|
||||||
|
_dict(
|
||||||
|
{
|
||||||
|
"qty": 2,
|
||||||
|
"item_code": item_code,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
set(partial_fetch).issubset(set(first_fetch)),
|
set(partial_fetch).issubset(set(first_fetch)),
|
||||||
msg=f"{partial_fetch} should be subset of {first_fetch}",
|
msg=f"{partial_fetch} should be subset of {first_fetch}",
|
||||||
)
|
)
|
||||||
|
|
||||||
# exclusion
|
# exclusion
|
||||||
remaining = auto_fetch_serial_number(
|
remaining = get_auto_serial_nos(
|
||||||
3, item_code, warehouse, exclude_sr_nos=json.dumps(partial_fetch)
|
_dict(
|
||||||
|
{
|
||||||
|
"qty": 3,
|
||||||
|
"item_code": item_code,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"ignore_serial_nos": partial_fetch,
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(sorted(remaining + partial_fetch), first_fetch)
|
self.assertEqual(sorted(remaining + partial_fetch), first_fetch)
|
||||||
|
|
||||||
# batchwise
|
# batchwise
|
||||||
for batch, expected_serials in batch_wise_serials.items():
|
for batch, expected_serials in batch_wise_serials.items():
|
||||||
fetched_sr = auto_fetch_serial_number(5, item_code, warehouse, batch_nos=batch)
|
fetched_sr = get_auto_serial_nos(
|
||||||
|
_dict({"qty": 5, "item_code": item_code, "warehouse": warehouse, "batches": [batch]})
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(fetched_sr, sorted(expected_serials))
|
self.assertEqual(fetched_sr, sorted(expected_serials))
|
||||||
|
|
||||||
# non existing warehouse
|
# non existing warehouse
|
||||||
self.assertEqual(auto_fetch_serial_number(10, item_code, warehouse="Nonexisting"), [])
|
self.assertFalse(
|
||||||
|
get_auto_serial_nos(
|
||||||
|
_dict({"qty": 10, "item_code": item_code, "warehouse": "Non Existing Warehouse"})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# multi batch
|
# multi batch
|
||||||
all_serials = [sr for sr_list in batch_wise_serials.values() for sr in sr_list]
|
all_serials = [sr for sr_list in batch_wise_serials.values() for sr in sr_list]
|
||||||
fetched_serials = auto_fetch_serial_number(
|
fetched_serials = get_auto_serial_nos(
|
||||||
10, item_code, warehouse, batch_nos=list(batch_wise_serials.keys())
|
_dict(
|
||||||
|
{
|
||||||
|
"qty": 10,
|
||||||
|
"item_code": item_code,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"batches": list(batch_wise_serials.keys()),
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
self.assertEqual(sorted(all_serials), fetched_serials)
|
self.assertEqual(sorted(all_serials), fetched_serials)
|
||||||
|
|
||||||
# expiry date
|
# expiry date
|
||||||
frappe.db.set_value("Batch", batch1, "expiry_date", "1980-01-01")
|
frappe.db.set_value("Batch", batch1, "expiry_date", "1980-01-01")
|
||||||
non_expired_serials = auto_fetch_serial_number(
|
non_expired_serials = get_auto_serial_nos(
|
||||||
5, item_code, warehouse, posting_date="2021-01-01", batch_nos=batch1
|
_dict({"qty": 5, "item_code": item_code, "warehouse": warehouse, "batches": [batch1]})
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(non_expired_serials, [])
|
self.assertEqual(non_expired_serials, [])
|
||||||
|
|
||||||
|
|
||||||
|
def get_auto_serial_nos(kwargs):
|
||||||
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
|
get_available_serial_nos,
|
||||||
|
)
|
||||||
|
|
||||||
|
serial_nos = get_available_serial_nos(kwargs)
|
||||||
|
return sorted([d.serial_no for d in serial_nos])
|
||||||
|
@ -15,9 +15,10 @@
|
|||||||
"voucher_type",
|
"voucher_type",
|
||||||
"voucher_no",
|
"voucher_no",
|
||||||
"voucher_detail_no",
|
"voucher_detail_no",
|
||||||
|
"serial_and_batch_bundle",
|
||||||
"dependant_sle_voucher_detail_no",
|
"dependant_sle_voucher_detail_no",
|
||||||
"recalculate_rate",
|
|
||||||
"section_break_11",
|
"section_break_11",
|
||||||
|
"recalculate_rate",
|
||||||
"actual_qty",
|
"actual_qty",
|
||||||
"qty_after_transaction",
|
"qty_after_transaction",
|
||||||
"incoming_rate",
|
"incoming_rate",
|
||||||
@ -31,15 +32,14 @@
|
|||||||
"company",
|
"company",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
"project",
|
"project",
|
||||||
"serial_and_batch_bundle",
|
|
||||||
"has_batch_no",
|
|
||||||
"batch_no",
|
|
||||||
"column_break_26",
|
"column_break_26",
|
||||||
"fiscal_year",
|
"fiscal_year",
|
||||||
|
"has_batch_no",
|
||||||
"has_serial_no",
|
"has_serial_no",
|
||||||
"serial_no",
|
|
||||||
"is_cancelled",
|
"is_cancelled",
|
||||||
"to_rename"
|
"to_rename",
|
||||||
|
"serial_no",
|
||||||
|
"batch_no"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -341,7 +341,7 @@
|
|||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-12-28 14:50:56.359348",
|
"modified": "2023-04-03 16:33:16.270722",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Ledger Entry",
|
"name": "Stock Ledger Entry",
|
||||||
|
@ -177,6 +177,11 @@ class SerialBatchBundle:
|
|||||||
{"is_cancelled": 1, "voucher_no": ""},
|
{"is_cancelled": 1, "voucher_no": ""},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.sle.serial_and_batch_bundle:
|
||||||
|
frappe.get_cached_doc(
|
||||||
|
"Serial and Batch Bundle", self.sle.serial_and_batch_bundle
|
||||||
|
).validate_serial_and_batch_inventory()
|
||||||
|
|
||||||
def post_process(self):
|
def post_process(self):
|
||||||
if not self.sle.serial_and_batch_bundle:
|
if not self.sle.serial_and_batch_bundle:
|
||||||
return
|
return
|
||||||
|
Loading…
x
Reference in New Issue
Block a user