test(selling): add Proforma Invoice tests

Cover partial proforma being non-blocking on delivery/billing,
pending-qty aggregation with cancelled proformas excluded, tax scaling
to the partial qty, over-qty as a soft warning, and the settings gate.
This commit is contained in:
Nabin Hait
2026-07-16 16:52:17 +05:30
parent fdc8879ce1
commit b0c53ac5a4

View File

@@ -0,0 +1,103 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import json
import frappe
from frappe.utils import flt
from erpnext.selling.doctype.proforma_invoice.proforma_invoice import (
get_pending_proforma_qty,
make_proforma_invoice,
)
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.tests.utils import ERPNextTestSuite
class TestProformaInvoice(ERPNextTestSuite):
def setUp(self):
frappe.db.set_single_value("Selling Settings", "enable_proforma_invoice", 1)
def create_proforma(self, sales_order, lines, **kwargs):
items = [{"so_detail": so_detail, "qty": qty} for so_detail, qty in lines]
name = make_proforma_invoice(sales_order.name, json.dumps(items), **kwargs)
return frappe.get_doc("Proforma Invoice", name)
def test_partial_proforma_is_non_blocking(self):
"""A proforma tracks its own qty but must not touch delivery/billing or the source SO."""
sales_order = make_sales_order(qty=10)
so_detail = sales_order.items[0].name
proforma = self.create_proforma(sales_order, [(so_detail, 4)])
self.assertEqual(proforma.status, "Issued")
self.assertEqual(proforma.docstatus, 1)
self.assertTrue(proforma.proforma_pdf, "PDF should be generated and attached")
sales_order.reload()
item = sales_order.items[0]
# fulfillment untouched
self.assertEqual(flt(item.delivered_qty), 0)
self.assertEqual(flt(item.billed_amt), 0)
self.assertEqual(flt(sales_order.per_delivered), 0)
self.assertEqual(flt(sales_order.per_billed), 0)
# cosmetic counter set, ordered qty untouched (in-memory SO copy never persisted)
self.assertEqual(flt(item.proforma_qty), 4)
self.assertEqual(flt(item.qty), 10)
def test_pending_qty_aggregates_and_excludes_cancelled(self):
sales_order = make_sales_order(qty=10)
so_detail = sales_order.items[0].name
self.assertEqual(get_pending_proforma_qty(sales_order.name)[0]["pending_qty"], 10)
first = self.create_proforma(sales_order, [(so_detail, 4)])
self.assertEqual(get_pending_proforma_qty(sales_order.name)[0]["pending_qty"], 6)
self.create_proforma(sales_order, [(so_detail, 3)])
self.assertEqual(get_pending_proforma_qty(sales_order.name)[0]["pending_qty"], 3)
# cancelling the first proforma reverses its contribution
first.cancel()
self.assertEqual(first.status, "Cancelled")
self.assertEqual(flt(frappe.db.get_value("Sales Order Item", so_detail, "proforma_qty")), 3)
self.assertEqual(get_pending_proforma_qty(sales_order.name)[0]["pending_qty"], 7)
def test_over_qty_is_allowed_with_warning(self):
"""Proforma qty above the pending qty is a soft warning, never a hard block."""
sales_order = make_sales_order(qty=10)
so_detail = sales_order.items[0].name
proforma = self.create_proforma(sales_order, [(so_detail, 15)])
self.assertEqual(flt(proforma.items[0].qty), 15)
def test_taxes_scale_to_partial_qty(self):
sales_order = make_sales_order(qty=10, do_not_submit=True)
sales_order.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "_Test Account CST - _TC",
"description": "CST",
"rate": 10,
},
)
sales_order.submit()
# full order: net 1000 + 10% tax = 1100
self.assertEqual(flt(sales_order.grand_total), 1100)
proforma = self.create_proforma(sales_order, [(sales_order.items[0].name, 4)])
# partial (4 of 10): net 400 + 10% tax = 440
self.assertEqual(flt(proforma.grand_total), 440)
def test_feature_toggle_is_enforced(self):
sales_order = make_sales_order(qty=10)
frappe.db.set_single_value("Selling Settings", "enable_proforma_invoice", 0)
self.assertRaises(
frappe.ValidationError,
self.create_proforma,
sales_order,
[(sales_order.items[0].name, 4)],
)