From 73c23c51160e465cd411eb073d60633267e6eca1 Mon Sep 17 00:00:00 2001 From: sayanthns Date: Thu, 15 Oct 2026 12:34:52 +0300 Subject: [PATCH] fix(po-print): VAT rate and amount follow the document, not a hard-coded 15% Two custom Purchase Order print formats printed "15%" and amount * 0.15 on every line, so a zero-rated import still showed VAT per line while the total said 0.00. Patch rewrites those lines to use the document's "On Net Total" tax rate. Idempotent; tests added. Co-Authored-By: Claude Sonnet 5.5 --- ram_custom/patches.txt | 1 + ram_custom/patches/v0_0_4/__init__.py | 0 .../patches/v0_0_4/fix_po_print_vat_rate.py | 37 ++++++++++++++ ram_custom/tests/test_po_print_vat.py | 48 +++++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 ram_custom/patches/v0_0_4/__init__.py create mode 100644 ram_custom/patches/v0_0_4/fix_po_print_vat_rate.py create mode 100644 ram_custom/tests/test_po_print_vat.py diff --git a/ram_custom/patches.txt b/ram_custom/patches.txt index 0c3c3b5..47df9c5 100644 --- a/ram_custom/patches.txt +++ b/ram_custom/patches.txt @@ -8,3 +8,4 @@ ram_custom.patches.v0_0_2.seed_ict_reconciled_cost_value ram_custom.patches.v0_0_2.seed_ict_uom_split_rates ram_custom.patches.v0_0_3.fix_delivery_note_return_status ram_custom.patches.v0_0_3.merge_duplicate_address_arabic_fields +ram_custom.patches.v0_0_4.fix_po_print_vat_rate diff --git a/ram_custom/patches/v0_0_4/__init__.py b/ram_custom/patches/v0_0_4/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ram_custom/patches/v0_0_4/fix_po_print_vat_rate.py b/ram_custom/patches/v0_0_4/fix_po_print_vat_rate.py new file mode 100644 index 0000000..cc483da --- /dev/null +++ b/ram_custom/patches/v0_0_4/fix_po_print_vat_rate.py @@ -0,0 +1,37 @@ +# Purchase Order prints hard-coded "15%" and amount * 0.15 on every line, so a +# zero-rated purchase (e.g. an import taxed with "VAT 0%") still printed 15% and a +# VAT amount per line while the document total correctly said 0.00. +# +# The two custom PO print formats now take the rate from the document's own +# "On Net Total" tax rows, so 15% stays 15% and a zero-rated order prints 0%. +# Only those two lines and one added variable are touched; idempotent, and +# `modified` is left alone. + +import frappe + +FORMATS = ("Purchase order new format", "Purchase Order Print Format") + +RATE_SET = ( + '{% set vat = namespace(rate=0) %}' + '{% for t in doc.taxes %}{% if t.charge_type == "On Net Total" %}' + '{% set vat.rate = vat.rate + (t.rate or 0) %}{% endif %}{% endfor %}\n' + " {% for item in doc.items %}" +) +OLD_LOOP = " {% for item in doc.items %}" +OLD_RATE = '15%' +NEW_RATE = '{{ "%g"|format(vat.rate) }}%' +OLD_AMOUNT = '"%.2f"|format((item.amount or 0) * 0.15)' +NEW_AMOUNT = '"%.2f"|format((item.amount or 0) * vat.rate / 100)' + + +def execute(): + for name in FORMATS: + html = frappe.db.get_value("Print Format", name, "html") + if not html or OLD_RATE not in html or OLD_AMOUNT not in html or "namespace(rate=0)" in html: + continue + if html.count(OLD_LOOP) != 1: + frappe.log_error(f"{name}: unexpected item loop count, left unchanged", "PO print VAT patch") + continue + html = html.replace(OLD_LOOP, " " + RATE_SET.lstrip(), 1) + html = html.replace(OLD_RATE, NEW_RATE).replace(OLD_AMOUNT, NEW_AMOUNT) + frappe.db.set_value("Print Format", name, "html", html, update_modified=False) diff --git a/ram_custom/tests/test_po_print_vat.py b/ram_custom/tests/test_po_print_vat.py new file mode 100644 index 0000000..8e60bf8 --- /dev/null +++ b/ram_custom/tests/test_po_print_vat.py @@ -0,0 +1,48 @@ +# Copyright (c) 2026, Enfono Technologies and contributors +# For license information, please see license.txt + +import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPurchaseOrderPrintVat(FrappeTestCase): + """The PO print follows the document's own tax rate, not a hard-coded 15%.""" + + FORMAT = "Purchase order new format" + + def _po(self, zero_rated): + rate_filter = ["=", 0] if zero_rated else [">", 0] + rows = frappe.get_all( + "Purchase Taxes and Charges", + filters={"parenttype": "Purchase Order", "charge_type": "On Net Total", "rate": rate_filter, "docstatus": ["<", 2]}, + fields=["parent"], + limit=1, + ) + return rows[0].parent if rows else None + + def _html(self, po): + if not frappe.db.exists("Print Format", self.FORMAT): + self.skipTest(f"{self.FORMAT} is not installed on this site") + return frappe.get_print("Purchase Order", po, print_format=self.FORMAT) + + def test_no_hardcoded_rate_left(self): + html = frappe.db.get_value("Print Format", self.FORMAT, "html") or "" + self.assertNotIn("* 0.15", html) + self.assertNotIn('15%', html) + + def test_zero_rated_order_prints_zero(self): + po = self._po(zero_rated=True) + if not po: + self.skipTest("no zero-rated Purchase Order") + html = self._html(po) + self.assertIn("0%", html) + self.assertNotIn("15%", html) + + def test_standard_rated_order_still_prints_fifteen(self): + po = self._po(zero_rated=False) + if not po: + self.skipTest("no standard-rated Purchase Order") + rate = frappe.db.get_value( + "Purchase Taxes and Charges", {"parent": po, "charge_type": "On Net Total"}, "rate" + ) + self.assertIn(f"{rate:g}%", self._html(po))