diff --git a/erpnext/accounts/report/calculated_discount_mismatch/__init__.py b/erpnext/accounts/report/calculated_discount_mismatch/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/report/calculated_discount_mismatch/calculated_discount_mismatch.js b/erpnext/accounts/report/calculated_discount_mismatch/calculated_discount_mismatch.js new file mode 100644 index 00000000000..b17e1e335c3 --- /dev/null +++ b/erpnext/accounts/report/calculated_discount_mismatch/calculated_discount_mismatch.js @@ -0,0 +1,13 @@ +// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.query_reports["Calculated Discount Mismatch"] = { + filters: [ + // { + // "fieldname": "my_filter", + // "label": __("My Filter"), + // "fieldtype": "Data", + // "reqd": 1, + // }, + ], +}; diff --git a/erpnext/accounts/report/calculated_discount_mismatch/calculated_discount_mismatch.json b/erpnext/accounts/report/calculated_discount_mismatch/calculated_discount_mismatch.json new file mode 100644 index 00000000000..84f081303e0 --- /dev/null +++ b/erpnext/accounts/report/calculated_discount_mismatch/calculated_discount_mismatch.json @@ -0,0 +1,38 @@ +{ + "add_total_row": 0, + "add_translate_data": 0, + "columns": [], + "creation": "2025-06-06 17:09:50.681090", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "letter_head": "", + "letterhead": null, + "modified": "2025-06-06 18:09:18.221911", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Calculated Discount Mismatch", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Version", + "report_name": "Calculated Discount Mismatch", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Administrator" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ], + "timeout": 0 +} diff --git a/erpnext/accounts/report/calculated_discount_mismatch/calculated_discount_mismatch.py b/erpnext/accounts/report/calculated_discount_mismatch/calculated_discount_mismatch.py new file mode 100644 index 00000000000..a51a3c57071 --- /dev/null +++ b/erpnext/accounts/report/calculated_discount_mismatch/calculated_discount_mismatch.py @@ -0,0 +1,86 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ + +DISCOUNT_DOCTYPES = frozenset( + ( + "POS Invoice", + "Purchase Invoice", + "Sales Invoice", + "Purchase Order", + "Supplier Quotation", + "Quotation", + "Sales Order", + "Delivery Note", + "Purchase Receipt", + ) +) +LAST_MODIFIED_DATE_THRESHOLD = "2025-05-30" + + +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() + + 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": _("Doctype"), + "fieldname": "doctype", + "fieldtype": "Data", + "width": 150, + }, + { + "label": _("Document Name"), + "fieldname": "document_name", + "fieldtype": "Dynamic Link", + "options": "doctype", + "width": 200, + }, + ] + + +def get_data() -> list[list]: + """Return data for the report. + + The report data is a list of rows, with each row being a list of cell values. + """ + data = [] + VERSION = frappe.qb.DocType("Version") + + result = ( + frappe.qb.from_(VERSION) + .select(VERSION.ref_doctype, VERSION.docname, VERSION.data, VERSION.name) + .where(VERSION.modified > LAST_MODIFIED_DATE_THRESHOLD) + .where(VERSION.ref_doctype.isin(list(DISCOUNT_DOCTYPES))) + .run(as_dict=True) + ) + + for row in result: + changed_data = {entry[0]: entry for entry in frappe.parse_json(row.data).get("changed", [])} + + docstatus = changed_data.get("docstatus") + if not docstatus or docstatus[2] != 1: + continue + + if "discount_amount" not in changed_data: + continue + + data.append({"doctype": row.ref_doctype, "document_name": row.docname}) + + return data diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 57e5465f8d0..edf91c225d7 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -408,3 +408,4 @@ erpnext.patches.v15_0.set_cancelled_status_to_cancelled_pos_invoice erpnext.patches.v15_0.rename_group_by_to_categorize_by_in_custom_reports erpnext.patches.v14_0.update_full_name_in_contract erpnext.patches.v15_0.drop_sle_indexes +erpnext.patches.v15_0.set_additional_discount_percentage \ No newline at end of file