diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index 38e820c4c4f..5ab862b51b3 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -169,7 +169,7 @@ class DeprecatedBatchNoValuation: if not self.non_batchwise_balance_qty: continue - if self.non_batchwise_balance_qty.get(batch_no) == 0: + if not self.non_batchwise_balance_qty.get(batch_no): self.batch_avg_rate[batch_no] = 0.0 self.stock_value_differece[batch_no] = 0.0 else: diff --git a/erpnext/stock/report/incorrect_serial_and_batch_bundle/__init__.py b/erpnext/stock/report/incorrect_serial_and_batch_bundle/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/report/incorrect_serial_and_batch_bundle/incorrect_serial_and_batch_bundle.js b/erpnext/stock/report/incorrect_serial_and_batch_bundle/incorrect_serial_and_batch_bundle.js new file mode 100644 index 00000000000..dccb543115e --- /dev/null +++ b/erpnext/stock/report/incorrect_serial_and_batch_bundle/incorrect_serial_and_batch_bundle.js @@ -0,0 +1,47 @@ +// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.query_reports["Incorrect Serial and Batch Bundle"] = { + filters: [ + { + fieldname: "item_code", + label: __("Item Code"), + fieldtype: "Link", + options: "Item", + }, + { + fieldname: "warehouse", + label: __("Warehouse"), + fieldtype: "Link", + options: "Warehouse", + }, + ], + + get_datatable_options(options) { + return Object.assign(options, { + checkboxColumn: true, + }); + }, + + onload(report) { + report.page.add_inner_button(__("Remove SABB Entry"), () => { + let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows(); + let selected_rows = indexes.map((i) => frappe.query_report.data[i]); + + if (!selected_rows.length) { + frappe.throw(__("Please select a row to create a Reposting Entry")); + } else { + frappe.call({ + method: "erpnext.stock.report.incorrect_serial_and_batch_bundle.incorrect_serial_and_batch_bundle.remove_sabb_entry", + freeze: true, + args: { + selected_rows: selected_rows, + }, + callback: function (r) { + frappe.query_report.refresh(); + }, + }); + } + }); + }, +}; diff --git a/erpnext/stock/report/incorrect_serial_and_batch_bundle/incorrect_serial_and_batch_bundle.json b/erpnext/stock/report/incorrect_serial_and_batch_bundle/incorrect_serial_and_batch_bundle.json new file mode 100644 index 00000000000..11e6e4ea3d4 --- /dev/null +++ b/erpnext/stock/report/incorrect_serial_and_batch_bundle/incorrect_serial_and_batch_bundle.json @@ -0,0 +1,56 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2025-02-03 15:39:44.521366", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "letter_head": "Test", + "letterhead": null, + "modified": "2025-02-03 15:39:47.613040", + "modified_by": "Administrator", + "module": "Stock", + "name": "Incorrect Serial and Batch Bundle", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Serial and Batch Bundle", + "report_name": "Incorrect Serial and Batch Bundle", + "report_type": "Script Report", + "roles": [ + { + "role": "Stock User" + }, + { + "role": "Purchase Manager" + }, + { + "role": "Delivery Manager" + }, + { + "role": "System Manager" + }, + { + "role": "Delivery User" + }, + { + "role": "Manufacturing User" + }, + { + "role": "Purchase User" + }, + { + "role": "Stock Manager" + }, + { + "role": "Manufacturing Manager" + }, + { + "role": "Maintenance User" + } + ], + "timeout": 0 +} \ No newline at end of file diff --git a/erpnext/stock/report/incorrect_serial_and_batch_bundle/incorrect_serial_and_batch_bundle.py b/erpnext/stock/report/incorrect_serial_and_batch_bundle/incorrect_serial_and_batch_bundle.py new file mode 100644 index 00000000000..2e954ef9190 --- /dev/null +++ b/erpnext/stock/report/incorrect_serial_and_batch_bundle/incorrect_serial_and_batch_bundle.py @@ -0,0 +1,107 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ + + +def execute(filters: dict | None = None): + """Return columns and data for the report. + + This is the main entry point for the report. It accepts the filters as a + dictionary and should return columns and data. It is called by the framework + every time the report is refreshed or a filter is updated. + """ + columns = get_columns() + data = get_data(filters) + + return columns, data + + +def get_columns() -> list[dict]: + """Return columns for the report. + + One field definition per column, just like a DocType field definition. + """ + return [ + { + "label": _("Serial and Batch Bundle"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Serial and Batch Bundle", + "width": 200, + }, + { + "label": _("Voucher Type"), + "fieldname": "voucher_type", + "fieldtype": "Data", + "width": 200, + }, + { + "label": _("Voucher No"), + "fieldname": "voucher_no", + "fieldtype": "Dynamic Link", + "options": "voucher_type", + "width": 200, + }, + { + "label": _("Voucher Detail No"), + "fieldname": "voucher_detail_no", + "fieldtype": "Data", + "width": 200, + }, + ] + + +def get_data(filters) -> list[list]: + """Return data for the report. + + The report data is a list of rows, with each row being a list of cell values. + """ + + SABB = frappe.qb.DocType("Serial And Batch Bundle") + SLE = frappe.qb.DocType("Stock Ledger Entry") + ignore_voycher_types = [ + "Installation Note", + "Job Card", + "Maintenance Schedule", + "Pick List", + ] + + query = ( + frappe.qb.from_(SABB) + .left_join(SLE) + .on(SABB.name == SLE.serial_and_batch_bundle) + .select( + SABB.name, + SABB.voucher_type, + SABB.voucher_no, + SABB.voucher_detail_no, + ) + .where( + (SLE.serial_and_batch_bundle.isnull()) + & (SABB.docstatus == 1) + & (SABB.is_cancelled == 0) + & (SABB.voucher_type.notin(ignore_voycher_types)) + ) + ) + + for field in filters: + query = query.where(SABB[field] == filters[field]) + + data = query.run(as_dict=1) + + return data + + +@frappe.whitelist() +def remove_sabb_entry(selected_rows): + if isinstance(selected_rows, str): + selected_rows = frappe.parse_json(selected_rows) + + for row in selected_rows: + doc = frappe.get_doc("Serial and Batch Bundle", row.get("name")) + doc.cancel() + doc.delete() + + frappe.msgprint(_("Selected Serial and Batch Bundle entries have been removed."))