fix(selling): guard proforma against unsubmitted SO and missing PDF

Address review findings:
- make_proforma_invoice: reject a non-submitted Sales Order (the whitelisted
  endpoint was previously only JS-gated on docstatus)
- send_proforma_email: throw a clear error when the attached PDF File is missing
  instead of passing a null fid to sendmail
This commit is contained in:
Nabin Hait
2026-07-23 18:59:08 +05:30
parent e932105ee3
commit e4fb5ed3c4
2 changed files with 15 additions and 0 deletions

View File

@@ -152,6 +152,8 @@ def make_proforma_invoice(
validate_feature_enabled()
selected = frappe.parse_json(items)
sales_order_doc = frappe.get_doc("Sales Order", sales_order)
if sales_order_doc.docstatus != 1:
frappe.throw(_("A Proforma Invoice can only be created against a submitted Sales Order."))
so_items = {item.name: item for item in sales_order_doc.items}
proforma = frappe.new_doc("Proforma Invoice")
@@ -214,6 +216,8 @@ def send_proforma_email(proforma_name: str, recipients: str) -> None:
frappe.throw(_("This Proforma Invoice has no PDF to send."))
file_name = frappe.db.get_value("File", {"file_url": proforma.proforma_pdf}, "name")
if not file_name:
frappe.throw(_("The attached PDF file could not be found."))
frappe.sendmail(
recipients=[email.strip() for email in recipients.split(",") if email.strip()],
subject=_("Proforma Invoice {0}").format(proforma.name),

View File

@@ -143,3 +143,14 @@ class TestProformaInvoice(ERPNextTestSuite):
sales_order,
[(sales_order.items[0].name, 4)],
)
def test_requires_submitted_sales_order(self):
"""The server rejects a proforma against a draft Sales Order (the button is JS-gated only)."""
sales_order = make_sales_order(qty=10, do_not_submit=True)
self.assertRaises(
frappe.ValidationError,
self.create_proforma,
sales_order,
[(sales_order.items[0].name, 4)],
)