mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-02 04:58:29 +00:00
test: Sales Order + fix: Mapping of Items from Quotation & SO
This commit is contained in:
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user