mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-13 22:51:49 +00:00
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:
@@ -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)],
|
||||
)
|
||||
Reference in New Issue
Block a user