mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-11 21:51:48 +00:00
Merge pull request #56717 from frappe/chore/test-calculated-discount-mismatch
test: Calculated Discount Mismatch report coverage
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.utils.formatters import format_value
|
||||
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.report.calculated_discount_mismatch.calculated_discount_mismatch import execute
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestCalculatedDiscountMismatch(ERPNextTestSuite):
|
||||
"""Integrity detector: flag transactions whose stored ``discount_amount`` was tampered
|
||||
after the fact (a Version records the change) while ``additional_discount_percentage``
|
||||
stayed the same, so the stored amount no longer matches the percentage-derived value.
|
||||
"""
|
||||
|
||||
def run_report(self, docname: str) -> dict | None:
|
||||
"""Run the (filter-less) report and return the row for ``docname``, if any."""
|
||||
_columns, data = execute(frappe._dict({}))
|
||||
return next((row for row in data if row["docname"] == docname), None)
|
||||
|
||||
def create_discounted_invoice(self) -> "frappe.Document":
|
||||
"""Draft Sales Invoice (rate 1000) with a 10% additional discount.
|
||||
|
||||
The controller derives ``discount_amount`` = 10% of the grand total = 100.00,
|
||||
so the stored amount is consistent with the percentage.
|
||||
"""
|
||||
invoice = create_sales_invoice(rate=1000, qty=1, do_not_submit=1)
|
||||
invoice.additional_discount_percentage = 10
|
||||
invoice.save()
|
||||
invoice.reload()
|
||||
return invoice
|
||||
|
||||
def test_consistent_discount_is_not_flagged(self):
|
||||
"""A submitted invoice whose discount_amount matches its percentage is not reported."""
|
||||
invoice = self.create_discounted_invoice()
|
||||
invoice.submit()
|
||||
invoice.reload()
|
||||
|
||||
self.assertEqual(invoice.discount_amount, 100.0)
|
||||
self.assertIsNone(self.run_report(invoice.name))
|
||||
|
||||
def test_tampered_discount_is_flagged(self):
|
||||
"""Directly overwriting discount_amount (leaving the percentage intact) is reported.
|
||||
|
||||
This reproduces the real-world integrity breach: a Version records the
|
||||
``discount_amount`` change, its ``new`` value equals the current stored amount, and
|
||||
``additional_discount_percentage`` was not touched -- exactly the shape the report
|
||||
queries for.
|
||||
"""
|
||||
invoice = self.create_discounted_invoice()
|
||||
consistent_amount = invoice.discount_amount # 100.00, matches the 10% percentage
|
||||
tampered_amount = 250.0
|
||||
|
||||
discount_field = frappe.get_meta("Sales Invoice").get_field("discount_amount")
|
||||
# Format exactly as the report does so version.new == format_value(current amount).
|
||||
suspected = format_value(consistent_amount, df=discount_field, currency=invoice.currency)
|
||||
actual = format_value(tampered_amount, df=discount_field, currency=invoice.currency)
|
||||
|
||||
# Tamper the stored amount directly, bypassing the controller that would recompute it.
|
||||
frappe.db.set_value("Sales Invoice", invoice.name, "discount_amount", tampered_amount)
|
||||
self.record_discount_change(invoice.name, suspected, actual)
|
||||
|
||||
row = self.run_report(invoice.name)
|
||||
|
||||
self.assertIsNotNone(row)
|
||||
self.assertEqual(row["doctype"], "Sales Invoice")
|
||||
self.assertEqual(row["actual_discount_percentage"], 10.0)
|
||||
self.assertEqual(row["actual_discount_amount"], actual)
|
||||
self.assertEqual(row["suspected_discount_amount"], suspected)
|
||||
|
||||
def record_discount_change(self, docname: str, old: str, new: str) -> None:
|
||||
"""Insert the Version audit row a direct discount_amount edit would have produced."""
|
||||
version = frappe.new_doc("Version")
|
||||
version.ref_doctype = "Sales Invoice"
|
||||
version.docname = docname
|
||||
version.data = json.dumps({"changed": [["discount_amount", old, new]]}, separators=(",", ":"))
|
||||
version.flags.ignore_version = True
|
||||
version.insert(ignore_permissions=True)
|
||||
Reference in New Issue
Block a user