Merge pull request #38560 from blaggacao/feat/so-po-advance-payment-status
feat!: advance payment status on advance payment doctypes
diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js
index cf7fab8..aacab0f 100644
--- a/erpnext/public/js/utils/barcode_scanner.js
+++ b/erpnext/public/js/utils/barcode_scanner.js
@@ -105,32 +105,47 @@
this.frm.has_items = false;
}
- if (serial_no && this.is_duplicate_serial_no(row, item_code, serial_no)) {
- this.clean_up();
- reject();
- return;
+ if (serial_no) {
+ this.is_duplicate_serial_no(row, item_code, serial_no)
+ .then((is_duplicate) => {
+ if (!is_duplicate) {
+ this.run_serially_tasks(row, data, resolve);
+ } else {
+ this.clean_up();
+ reject();
+ return;
+ }
+ });
+ } else {
+ this.run_serially_tasks(row, data, resolve);
}
- frappe.run_serially([
- () => this.set_serial_and_batch(row, item_code, serial_no, batch_no),
- () => this.set_barcode(row, barcode),
- () => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
- this.show_scan_message(row.idx, row.item_code, qty);
- }),
- () => this.set_barcode_uom(row, uom),
- () => this.clean_up(),
- () => resolve(row),
- () => {
- if (row.serial_and_batch_bundle && !this.frm.is_new()) {
- this.frm.save();
- }
- frappe.flags.trigger_from_barcode_scanner = false;
- }
- ]);
});
}
+ run_serially_tasks(row, data, resolve) {
+ const {item_code, barcode, batch_no, serial_no, uom} = data;
+
+ frappe.run_serially([
+ () => this.set_serial_and_batch(row, item_code, serial_no, batch_no),
+ () => this.set_barcode(row, barcode),
+ () => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
+ this.show_scan_message(row.idx, row.item_code, qty);
+ }),
+ () => this.set_barcode_uom(row, uom),
+ () => this.clean_up(),
+ () => {
+ if (row.serial_and_batch_bundle && !this.frm.is_new()) {
+ this.frm.save();
+ }
+
+ frappe.flags.trigger_from_barcode_scanner = false;
+ },
+ () => resolve(row),
+ ]);
+ }
+
set_item(row, item_code, barcode, batch_no, serial_no) {
return new Promise(resolve => {
const increment = async (value = 1) => {
@@ -475,26 +490,32 @@
}
}
- is_duplicate_serial_no(row, item_code, 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");
- }
-
- return is_duplicate;
- } else if (row.serial_and_batch_bundle) {
- this.check_duplicate_serial_no_in_db(row, serial_no, (r) => {
- if (r.message) {
+ async is_duplicate_serial_no(row, item_code, serial_no) {
+ let is_duplicate = false;
+ const promise = new Promise((resolve, reject) => {
+ if (this.frm.is_new() || !row.serial_and_batch_bundle) {
+ is_duplicate = this.check_duplicate_serial_no_in_localstorage(item_code, serial_no);
+ if (is_duplicate) {
this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
}
- return r.message;
- })
- }
+ resolve(is_duplicate);
+ } else if (row.serial_and_batch_bundle) {
+ this.check_duplicate_serial_no_in_db(row, serial_no, (r) => {
+ if (r.message) {
+ this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
+ }
+
+ is_duplicate = r.message;
+ resolve(is_duplicate);
+ })
+ }
+ });
+
+ return await promise;
}
- async check_duplicate_serial_no_in_db(row, serial_no, response) {
+ 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: {
@@ -504,7 +525,7 @@
callback(r) {
response(r);
}
- })
+ });
}
check_duplicate_serial_no_in_localstorage(item_code, serial_no) {
diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js
index b92b02e..b8ec77f 100644
--- a/erpnext/public/js/utils/sales_common.js
+++ b/erpnext/public/js/utils/sales_common.js
@@ -22,6 +22,15 @@
}
};
});
+
+ this.frm.set_query('project', function(doc) {
+ return {
+ query: "erpnext.controllers.queries.get_project_name",
+ filters: {
+ 'customer': doc.customer
+ }
+ }
+ });
}
setup_queries() {
@@ -439,4 +448,4 @@
}
});
}
-}
\ No newline at end of file
+}
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index bf362e3..44a4957 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -135,7 +135,7 @@
filters: this.get_serial_no_filters()
};
},
- onchange: () => this.update_serial_batch_no()
+ onchange: () => this.scan_barcode_data()
});
}
@@ -145,7 +145,7 @@
options: 'Barcode',
fieldname: 'scan_batch_no',
label: __('Scan Batch No'),
- onchange: () => this.update_serial_batch_no()
+ onchange: () => this.scan_barcode_data()
});
}
@@ -179,11 +179,54 @@
label = __('Serial Nos / Batch Nos');
}
- return [
+ let fields = [
{
fieldtype: 'Section Break',
label: __('{0} {1} via CSV File', [primary_label, label])
- },
+ }
+ ]
+
+ if (this.item?.has_serial_no) {
+ fields = [...fields,
+ {
+ fieldtype: 'Check',
+ label: __('Import Using CSV file'),
+ fieldname: 'import_using_csv_file',
+ default: 0,
+ },
+ {
+ fieldtype: 'Section Break',
+ label: __('{0} {1} Manually', [primary_label, label]),
+ depends_on: 'eval:doc.import_using_csv_file === 0',
+ },
+ {
+ fieldtype: 'Small Text',
+ label: __('Enter Serial Nos'),
+ fieldname: 'upload_serial_nos',
+ depends_on: 'eval:doc.import_using_csv_file === 0',
+ description: __('Enter each serial no in a new line'),
+ },
+ {
+ fieldtype: 'Column Break',
+ depends_on: 'eval:doc.import_using_csv_file === 0',
+ },
+ {
+ fieldtype: 'Button',
+ fieldname: 'make_serial_nos',
+ label: __('Create Serial Nos'),
+ depends_on: 'eval:doc.import_using_csv_file === 0',
+ click: () => {
+ this.create_serial_nos();
+ }
+ },
+ {
+ fieldtype: 'Section Break',
+ depends_on: 'eval:doc.import_using_csv_file === 1',
+ }
+ ];
+ }
+
+ fields = [...fields,
{
fieldtype: 'Button',
fieldname: 'download_csv',
@@ -199,7 +242,32 @@
label: __('Attach CSV File'),
onchange: () => this.upload_csv_file()
}
- ]
+ ];
+
+ return fields;
+ }
+
+ create_serial_nos() {
+ let {upload_serial_nos} = this.dialog.get_values();
+
+ if (!upload_serial_nos) {
+ frappe.throw(__('Please enter Serial Nos'));
+ }
+
+ frappe.call({
+ method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.create_serial_nos',
+ args: {
+ item_code: this.item.item_code,
+ serial_nos: upload_serial_nos
+ },
+ callback: (r) => {
+ if (r.message) {
+ this.dialog.fields_dict.entries.df.data = [];
+ this.set_data(r.message);
+ this.update_bundle_entries();
+ }
+ }
+ });
}
download_csv_file() {
@@ -374,6 +442,26 @@
}
}
+ scan_barcode_data() {
+ const { scan_serial_no, scan_batch_no } = this.dialog.get_values();
+
+ if (scan_serial_no || scan_batch_no) {
+ frappe.call({
+ method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.is_serial_batch_no_exists',
+ args: {
+ item_code: this.item.item_code,
+ type_of_transaction: this.item.type_of_transaction,
+ serial_no: scan_serial_no,
+ batch_no: scan_batch_no,
+ },
+ callback: (r) => {
+ this.update_serial_batch_no();
+ }
+
+ })
+ }
+ }
+
update_serial_batch_no() {
const { scan_serial_no, scan_batch_no } = this.dialog.get_values();
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index ab74f7f..654f297 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -127,7 +127,8 @@
def validate(self):
super(Quotation, self).validate()
self.set_status()
- self.validate_uom_is_integer("stock_uom", "qty")
+ self.validate_uom_is_integer("stock_uom", "stock_qty")
+ self.validate_uom_is_integer("uom", "qty")
self.validate_valid_till()
self.set_customer_name()
if self.items:
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index ecb7d09..2a4855e 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -593,6 +593,22 @@
quotation.reload()
self.assertEqual(quotation.status, "Ordered")
+ def test_uom_validation(self):
+ from erpnext.stock.doctype.item.test_item import make_item
+
+ item = "_Test Item FOR UOM Validation"
+ make_item(item, {"is_stock_item": 1})
+
+ if not frappe.db.exists("UOM", "lbs"):
+ frappe.get_doc({"doctype": "UOM", "uom_name": "lbs", "must_be_whole_number": 1}).insert()
+ else:
+ frappe.db.set_value("UOM", "lbs", "must_be_whole_number", 1)
+
+ quotation = make_quotation(item_code=item, qty=1, rate=100, do_not_submit=1)
+ quotation.items[0].uom = "lbs"
+ quotation.items[0].conversion_factor = 2.23
+ self.assertRaises(frappe.ValidationError, quotation.save)
+
test_records = frappe.get_test_records("Quotation")
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 56c745c..2bb093d 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -144,15 +144,6 @@
};
});
- frm.set_query('project', function(doc, cdt, cdn) {
- return {
- query: "erpnext.controllers.queries.get_project_name",
- filters: {
- 'customer': doc.customer
- }
- }
- });
-
frm.set_query('warehouse', 'items', function(doc, cdt, cdn) {
let row = locals[cdt][cdn];
let query = {
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index ec68549..14aedca 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -31,15 +31,6 @@
});
erpnext.queries.setup_warehouse_query(frm);
- frm.set_query('project', function(doc) {
- return {
- query: "erpnext.controllers.queries.get_project_name",
- filters: {
- 'customer': doc.customer
- }
- }
- })
-
frm.set_query('transporter', function() {
return {
filters: {
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
index 9f01ee9..91b7430 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
@@ -74,7 +74,7 @@
let fields = [
{
- "label": __("Using CSV File"),
+ "label": __("Import Using CSV file"),
"fieldname": "using_csv_file",
"default": 1,
"fieldtype": "Check",
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index 2b87fcd..63cc938 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -999,9 +999,25 @@
make_serial_nos(item_code, serial_nos)
+ if kwargs.get("_has_serial_nos"):
+ return serial_nos
+
return serial_nos, batch_nos
+@frappe.whitelist()
+def create_serial_nos(item_code, serial_nos):
+ serial_nos = get_serial_batch_from_data(
+ item_code,
+ {
+ "serial_nos": serial_nos,
+ "_has_serial_nos": True,
+ },
+ )
+
+ return serial_nos
+
+
def make_serial_nos(item_code, serial_nos):
item = frappe.get_cached_value("Item", item_code, ["description", "item_code"], as_dict=1)
@@ -2080,5 +2096,34 @@
@frappe.whitelist()
+def is_serial_batch_no_exists(item_code, type_of_transaction, serial_no=None, batch_no=None):
+ if serial_no and not frappe.db.exists("Serial No", serial_no):
+ if type_of_transaction != "Inward":
+ frappe.throw(_("Serial No {0} does not exists").format(serial_no))
+
+ make_serial_no(serial_no, item_code)
+
+ if batch_no and frappe.db.exists("Batch", batch_no):
+ if type_of_transaction != "Inward":
+ frappe.throw(_("Batch No {0} does not exists").format(batch_no))
+
+ make_batch_no(batch_no, item_code)
+
+
+def make_serial_no(serial_no, item_code):
+ serial_no_doc = frappe.new_doc("Serial No")
+ serial_no_doc.serial_no = serial_no
+ serial_no_doc.item_code = item_code
+ serial_no_doc.save(ignore_permissions=True)
+
+
+def make_batch_no(batch_no, item_code):
+ batch_doc = frappe.new_doc("Batch")
+ batch_doc.batch_id = batch_no
+ batch_doc.item = item_code
+ batch_doc.save(ignore_permissions=True)
+
+
+@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})