test: Sales Order + fix: Mapping of Items from Quotation & SO

This commit is contained in:
marination
2025-03-14 18:54:11 +01:00
parent eea758f5b2
commit 55981c8358
5 changed files with 101 additions and 11 deletions

View File

@@ -1262,7 +1262,7 @@ class TestPurchaseOrder(IntegrationTestCase):
po.reload()
self.assertEqual(po.items[0].received_qty, 5)
# PO still has qty 0, so billed % should be unset
# PO still has qty 0, so received % should be unset
self.assertFalse(po.per_received)
self.assertEqual(po.status, "To Receive and Bill")

View File

@@ -381,6 +381,8 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
as_list=1,
)
)
# 0 qty is accepted, as the qty uncertain for some items
has_unit_price_items = frappe.db.get_value("Quotation", source_name, "has_unit_price_items")
selected_rows = [x.get("name") for x in frappe.flags.get("args", {}).get("selected_items", [])]
@@ -428,14 +430,12 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
2. If selections: Is Alternative Item/Has Alternative Item: Map if selected and adequate qty
3. If selections: Simple row: Map if adequate qty
"""
# has_unit_price_items = 0 is accepted as the qty uncertain for some items
has_unit_price_items = frappe.db.get_value("Quotation", source_name, "has_unit_price_items")
balance_qty = item.qty - ordered_items.get(item.item_code, 0.0)
if balance_qty <= 0 and not has_unit_price_items:
# False if qty <=0 in a 'normal' scenario
return False
has_qty = balance_qty or has_unit_price_items
has_qty: bool = (balance_qty > 0) or has_unit_price_items
if not selected_rows:
return not item.is_alternative

View File

@@ -618,10 +618,12 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
}
if (doc.status !== "Closed") {
if (doc.status !== "On Hold") {
const items_are_deliverable = this.frm.doc.items.some(
(item) => item.delivered_by_supplier === 0 && item.qty > flt(item.delivered_qty)
);
allow_delivery =
this.frm.doc.items.some(
(item) => item.delivered_by_supplier === 0 && item.qty > flt(item.delivered_qty)
) && !this.frm.doc.skip_delivery_note;
(this.frm.doc.has_unit_price_items || items_are_deliverable) &&
!this.frm.doc.skip_delivery_note;
if (this.frm.has_perm("submit")) {
if (flt(doc.per_delivered) < 100 || flt(doc.per_billed) < 100) {

View File

@@ -967,6 +967,9 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None):
kwargs = frappe._dict(kwargs)
# 0 qty is accepted, as the qty is uncertain for some items
has_unit_price_items = frappe.db.get_value("Sales Order", source_name, "has_unit_price_items")
sre_details = {}
if kwargs.for_reserved_stock:
sre_details = get_sre_reserved_qty_details_for_voucher("Sales Order", source_name)
@@ -1016,12 +1019,14 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None):
if cstr(doc.delivery_date) > frappe.flags.args.until_delivery_date:
return False
return abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier != 1
return (
(abs(doc.delivered_qty) < abs(doc.qty)) or has_unit_price_items
) and doc.delivered_by_supplier != 1
def update_item(source, target, source_parent):
target.base_amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.base_rate)
target.amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.rate)
target.qty = flt(source.qty) - flt(source.delivered_qty)
target.qty = flt(source.qty) - flt(source.delivered_qty) if not has_unit_price_items else 0
item = get_item_defaults(target.item_code, source_parent.company)
item_group = get_item_group_defaults(target.item_code, source_parent.company)

View File

@@ -7,7 +7,7 @@ from unittest.mock import patch
import frappe
import frappe.permissions
from frappe.core.doctype.user_permission.test_user_permission import create_user
from frappe.tests import IntegrationTestCase
from frappe.tests import IntegrationTestCase, change_settings
from frappe.utils import add_days, flt, getdate, nowdate, today
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
@@ -109,6 +109,13 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase):
so.save()
self.assertEqual(so.items[0].qty, 1)
def test_sales_order_zero_qty(self):
po = make_sales_order(qty=0, do_not_save=True)
with change_settings("Selling Settings", {"allow_zero_qty_in_sales_order": 1}):
po.save()
self.assertEqual(po.items[0].qty, 0)
def test_make_material_request(self):
so = make_sales_order(do_not_submit=True)
@@ -2321,6 +2328,82 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase):
sre_doc.reload()
self.assertTrue(sre_doc.status == "Delivered")
@IntegrationTestCase.change_settings("Selling Settings", {"allow_zero_qty_in_sales_order": 1})
def test_deliver_zero_qty_purchase_order(self):
"""
Test the flow of a Unit Price SO and DN creation against it until completion.
Flow:
SO Qty 0 -> Deliver +5 -> Deliver +5 -> Update SO Qty +10 -> SO is 100% delivered
"""
so = make_sales_order(qty=0)
dn = make_delivery_note(so.name)
self.assertEqual(dn.items[0].qty, 0)
dn.items[0].qty = 5
dn.submit()
so.reload()
self.assertEqual(so.items[0].delivered_qty, 5)
# SO still has qty 0, so delivered % should be unset
self.assertFalse(so.per_delivered)
self.assertEqual(so.status, "To Deliver and Bill")
# Test: DN can be made against SO as long SO qty is 0 OR SO qty > delivered qty
dn2 = make_delivery_note(so.name)
self.assertEqual(dn2.items[0].qty, 0)
dn2.items[0].qty = 5
dn2.submit()
so.reload()
self.assertEqual(so.items[0].delivered_qty, 10)
self.assertFalse(so.per_delivered)
self.assertEqual(so.status, "To Deliver and Bill")
# Update SO Item Qty to 10 after delivery of items
first_item_of_so = so.items[0]
trans_item = json.dumps(
[
{
"item_code": first_item_of_so.item_code,
"rate": first_item_of_so.rate,
"qty": 10,
"docname": first_item_of_so.name,
}
]
)
update_child_qty_rate("Sales Order", trans_item, so.name)
# SO should be updated to 100% delivered
so.reload()
self.assertEqual(so.items[0].qty, 10)
self.assertEqual(so.per_delivered, 100.0)
self.assertEqual(so.status, "To Bill")
@IntegrationTestCase.change_settings("Selling Settings", {"allow_zero_qty_in_sales_order": 1})
def test_bill_zero_qty_sales_order(self):
so = make_sales_order(qty=0)
self.assertEqual(so.grand_total, 0)
self.assertFalse(so.per_billed)
self.assertEqual(so.items[0].qty, 0)
self.assertEqual(so.items[0].rate, 100)
si = make_sales_invoice(so.name)
self.assertEqual(si.items[0].qty, 0)
self.assertEqual(si.items[0].rate, 100)
si.items[0].qty = 5
si.submit()
self.assertEqual(si.grand_total, 500)
so.reload()
self.assertEqual(so.items[0].amount, 0)
self.assertEqual(so.items[0].billed_amt, 500)
# SO still has qty 0, so billed % should be unset
self.assertFalse(so.per_billed)
self.assertEqual(so.status, "To Deliver and Bill")
def automatically_fetch_payment_terms(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")