mirror of
https://github.com/frappe/erpnext.git
synced 2026-10-29 22:57:42 +00:00
Merge pull request #62636 from aerele/fix/cheque-print-format-collision
fix(accounts): prevent cheque print format collisions
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
"field_order": [
|
||||
"settings",
|
||||
"has_print_format",
|
||||
"print_format_creation",
|
||||
"primary_settings",
|
||||
"bank_name",
|
||||
"cheque_size",
|
||||
@@ -61,6 +62,17 @@
|
||||
"hidden": 1,
|
||||
"label": "Has Print Format",
|
||||
"no_copy": 1,
|
||||
"permlevel": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "print_format_creation",
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 1,
|
||||
"label": "Print Format Creation",
|
||||
"no_copy": 1,
|
||||
"permlevel": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -298,7 +310,7 @@
|
||||
"icon": "printer",
|
||||
"links": [],
|
||||
"max_attachments": 1,
|
||||
"modified": "2026-06-08 12:10:35.829531",
|
||||
"modified": "2026-10-29 12:48:46.566372",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cheque Print Template",
|
||||
@@ -316,6 +328,12 @@
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"permlevel": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
|
||||
@@ -6,6 +6,7 @@ import re
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import get_datetime
|
||||
|
||||
|
||||
class ChequePrintTemplate(Document):
|
||||
@@ -38,6 +39,7 @@ class ChequePrintTemplate(Document):
|
||||
message_to_show: DF.Data | None
|
||||
payer_name_from_left_edge: DF.Float
|
||||
payer_name_from_top_edge: DF.Float
|
||||
print_format_creation: DF.Datetime | None
|
||||
scanned_cheque: DF.Attach | None
|
||||
signatory_from_left_edge: DF.Float
|
||||
signatory_from_top_edge: DF.Float
|
||||
@@ -50,6 +52,7 @@ class ChequePrintTemplate(Document):
|
||||
@frappe.whitelist(methods=["POST"])
|
||||
def create_or_update_cheque_print_format(template_name: str):
|
||||
frappe.only_for("System Manager")
|
||||
doc = frappe.get_doc("Cheque Print Template", template_name)
|
||||
|
||||
if not frappe.db.exists("Print Format", template_name):
|
||||
cheque_print = frappe.new_doc("Print Format")
|
||||
@@ -65,8 +68,6 @@ def create_or_update_cheque_print_format(template_name: str):
|
||||
else:
|
||||
cheque_print = frappe.get_doc("Print Format", template_name)
|
||||
|
||||
doc = frappe.get_doc("Cheque Print Template", template_name)
|
||||
|
||||
account_payee = ""
|
||||
if doc.is_account_payable:
|
||||
account_payee = """
|
||||
@@ -79,7 +80,7 @@ def create_or_update_cheque_print_format(template_name: str):
|
||||
message_to_show=frappe.as_json(doc.message_to_show or _("Account Pay Only"), ensure_ascii=False),
|
||||
)
|
||||
|
||||
cheque_print.html = """
|
||||
cheque_html = """
|
||||
<style>
|
||||
.print-format {{
|
||||
padding: 0px;
|
||||
@@ -145,9 +146,32 @@ def create_or_update_cheque_print_format(template_name: str):
|
||||
signatory_from_left_edge=doc.signatory_from_left_edge,
|
||||
)
|
||||
|
||||
if not cheque_print.is_new():
|
||||
if doc.print_format_creation:
|
||||
is_generated_format = get_datetime(doc.print_format_creation) == get_datetime(
|
||||
cheque_print.creation
|
||||
)
|
||||
else:
|
||||
# Adopt an older format only when updating it would preserve its exact HTML.
|
||||
is_generated_format = cheque_print.html == cheque_html
|
||||
|
||||
if not doc.has_print_format or cheque_print.doc_type != "Payment Entry" or not is_generated_format:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Print Format {0} already exists and could not be identified as generated by this template. "
|
||||
"Rename the existing Print Format before continuing."
|
||||
).format(frappe.bold(template_name)),
|
||||
title=_("Print Format Conflict"),
|
||||
)
|
||||
|
||||
cheque_print.html = cheque_html
|
||||
cheque_print.save(ignore_permissions=True)
|
||||
|
||||
frappe.db.set_value("Cheque Print Template", template_name, "has_print_format", 1)
|
||||
frappe.db.set_value(
|
||||
"Cheque Print Template",
|
||||
template_name,
|
||||
{"has_print_format": 1, "print_format_creation": cheque_print.creation},
|
||||
)
|
||||
|
||||
return cheque_print
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import frappe
|
||||
from frappe.desk.form.load import get_meta_bundle
|
||||
from frappe.translate import print_language
|
||||
from frappe.utils import fmt_money, money_in_words, nowdate
|
||||
from frappe.utils import fmt_money, get_datetime, money_in_words, nowdate
|
||||
from frappe.www.printview import get_rendered_template
|
||||
|
||||
from erpnext.accounts.doctype.cheque_print_template.cheque_print_template import (
|
||||
@@ -65,3 +68,124 @@ class TestChequePrintTemplate(ERPNextTestSuite):
|
||||
self.assertIn(money_in_words(100.25, "USD"), rendered)
|
||||
self.assertIn(fmt_money(100.25, currency="USD"), rendered)
|
||||
self.assertEqual(payment.reload().in_words, money_in_words(100.25, "USD"))
|
||||
|
||||
def test_generated_print_format_ownership(self):
|
||||
template = frappe.get_doc(
|
||||
doctype="Cheque Print Template", bank_name="_Test Cheque Format Ownership"
|
||||
).insert()
|
||||
|
||||
for operation in ("rename", "delete"):
|
||||
with self.subTest(operation=operation):
|
||||
generated = create_or_update_cheque_print_format(template.name)
|
||||
template.reload()
|
||||
if operation == "rename":
|
||||
frappe.rename_doc("Print Format", generated.name, generated.name + " Archived")
|
||||
else:
|
||||
frappe.delete_doc("Print Format", generated.name)
|
||||
|
||||
self.assertEqual(template.has_print_format, 1)
|
||||
replacement = frappe.get_doc(
|
||||
doctype="Print Format",
|
||||
name=template.name,
|
||||
doc_type="Payment Entry",
|
||||
standard="No",
|
||||
custom_format=1,
|
||||
print_format_type="Jinja",
|
||||
html="<h1>Payment remittance advice</h1>",
|
||||
).insert()
|
||||
with self.assertRaises(frappe.ValidationError):
|
||||
create_or_update_cheque_print_format(template.name)
|
||||
self.assertEqual(replacement.reload().html, "<h1>Payment remittance advice</h1>")
|
||||
# Reusing the generated HTML must not transfer ownership to the replacement record.
|
||||
replacement.html = generated.html
|
||||
replacement.save()
|
||||
with self.assertRaises(frappe.ValidationError):
|
||||
create_or_update_cheque_print_format(template.name)
|
||||
frappe.delete_doc("Print Format", replacement.name)
|
||||
|
||||
generated = create_or_update_cheque_print_format(template.name)
|
||||
template.reload()
|
||||
template.payer_name_from_left_edge = 5
|
||||
template.save()
|
||||
updated = create_or_update_cheque_print_format(template.name)
|
||||
self.assertEqual(generated.name, updated.name)
|
||||
self.assertNotEqual(generated.html, updated.html)
|
||||
self.assertIn("left: 5.0cm", updated.html)
|
||||
|
||||
# Existing generated formats can acquire an identity without changing their HTML.
|
||||
template.db_set("print_format_creation", None)
|
||||
self.assertEqual(create_or_update_cheque_print_format(template.name).html, updated.html)
|
||||
self.assertEqual(get_datetime(template.reload().print_format_creation), updated.creation)
|
||||
|
||||
# An untracked customized format must not be adopted based on the stale checkbox.
|
||||
template.db_set("print_format_creation", None)
|
||||
updated.reload()
|
||||
updated.html = "<h1>Customized payment layout</h1>"
|
||||
updated.save()
|
||||
with self.assertRaises(frappe.ValidationError):
|
||||
create_or_update_cheque_print_format(template.name)
|
||||
self.assertEqual(updated.reload().html, "<h1>Customized payment layout</h1>")
|
||||
|
||||
with self.set_user("Guest"), patch.dict(frappe.flags, {"in_test": False}):
|
||||
with self.assertRaises(frappe.PermissionError):
|
||||
create_or_update_cheque_print_format(template.name)
|
||||
self.assertEqual(updated.reload().html, "<h1>Customized payment layout</h1>")
|
||||
|
||||
def test_print_format_identity_permissions(self):
|
||||
manager = frappe.get_doc(
|
||||
doctype="User",
|
||||
email="cheque-accounts-manager@example.com",
|
||||
first_name="Cheque Accounts Manager",
|
||||
enabled=1,
|
||||
send_welcome_email=0,
|
||||
roles=[{"role": "Accounts Manager"}],
|
||||
).insert()
|
||||
print_format = frappe.get_doc(
|
||||
doctype="Print Format",
|
||||
name="_Test Cheque Format Permissions",
|
||||
doc_type="Payment Entry",
|
||||
standard="No",
|
||||
custom_format=1,
|
||||
print_format_type="Jinja",
|
||||
html="<h1>Payment remittance advice</h1>",
|
||||
).insert()
|
||||
|
||||
with self.set_user(manager.name):
|
||||
self.assertFalse(frappe.has_permission("Print Format", "write", doc=print_format))
|
||||
creation = next(
|
||||
row["creation"]
|
||||
for row in get_meta_bundle("Payment Entry")[0]["__print_formats"]
|
||||
if row["name"] == print_format.name
|
||||
)
|
||||
template = frappe.get_doc(
|
||||
doctype="Cheque Print Template",
|
||||
bank_name=print_format.name,
|
||||
has_print_format=1,
|
||||
print_format_creation=creation,
|
||||
).insert()
|
||||
self.assertFalse(template.reload().has_print_format)
|
||||
self.assertIsNone(template.print_format_creation)
|
||||
template.has_print_format = 1
|
||||
template.print_format_creation = creation
|
||||
template.save()
|
||||
self.assertFalse(template.reload().has_print_format)
|
||||
self.assertIsNone(template.print_format_creation)
|
||||
|
||||
with self.set_user("test@example.com"):
|
||||
with self.assertRaises(frappe.ValidationError):
|
||||
create_or_update_cheque_print_format(template.name)
|
||||
self.assertEqual(print_format.reload().html, "<h1>Payment remittance advice</h1>")
|
||||
|
||||
frappe.delete_doc("Print Format", print_format.name)
|
||||
with self.set_user("test@example.com"):
|
||||
generated = create_or_update_cheque_print_format(template.name)
|
||||
with self.set_user(manager.name):
|
||||
template.reload()
|
||||
template.payer_name_from_left_edge = 5
|
||||
template.has_print_format = 0
|
||||
template.print_format_creation = None
|
||||
template.save()
|
||||
self.assertTrue(template.reload().has_print_format)
|
||||
self.assertEqual(get_datetime(template.print_format_creation), get_datetime(generated.creation))
|
||||
with self.set_user("test@example.com"):
|
||||
self.assertIn("left: 5.0cm", create_or_update_cheque_print_format(template.name).html)
|
||||
|
||||
Reference in New Issue
Block a user