mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-13 10:11:20 +00:00
test: Purchase Order with Unit Price Items
- chore: Fix error message in accounts controller
This commit is contained in:
@@ -745,8 +745,10 @@ def set_missing_values(source, target):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_purchase_receipt(source_name, target_doc=None):
|
def make_purchase_receipt(source_name, target_doc=None):
|
||||||
|
has_unit_price_items = frappe.db.get_value("Purchase Order", source_name, "has_unit_price_items")
|
||||||
|
|
||||||
def update_item(obj, target, source_parent):
|
def update_item(obj, target, source_parent):
|
||||||
target.qty = flt(obj.qty) - flt(obj.received_qty)
|
target.qty = flt(obj.qty) - flt(obj.received_qty) if not has_unit_price_items else 0
|
||||||
target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor)
|
target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor)
|
||||||
target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate)
|
target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate)
|
||||||
target.base_amount = (
|
target.base_amount = (
|
||||||
@@ -777,7 +779,9 @@ def make_purchase_receipt(source_name, target_doc=None):
|
|||||||
"wip_composite_asset": "wip_composite_asset",
|
"wip_composite_asset": "wip_composite_asset",
|
||||||
},
|
},
|
||||||
"postprocess": update_item,
|
"postprocess": update_item,
|
||||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
|
"condition": lambda doc: (
|
||||||
|
abs(doc.received_qty) < abs(doc.qty) if not has_unit_price_items else True
|
||||||
|
)
|
||||||
and doc.delivered_by_supplier != 1,
|
and doc.delivered_by_supplier != 1,
|
||||||
},
|
},
|
||||||
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "reset_value": True},
|
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "reset_value": True},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
from frappe.tests import IntegrationTestCase, UnitTestCase, change_settings
|
||||||
from frappe.utils import add_days, flt, getdate, nowdate
|
from frappe.utils import add_days, flt, getdate, nowdate
|
||||||
from frappe.utils.data import today
|
from frappe.utils.data import today
|
||||||
|
|
||||||
@@ -61,6 +61,13 @@ class TestPurchaseOrder(IntegrationTestCase):
|
|||||||
po.save()
|
po.save()
|
||||||
self.assertEqual(po.items[1].qty, 1)
|
self.assertEqual(po.items[1].qty, 1)
|
||||||
|
|
||||||
|
def test_purchase_order_zero_qty(self):
|
||||||
|
po = create_purchase_order(qty=0, do_not_save=True)
|
||||||
|
|
||||||
|
with change_settings("Buying Settings", {"allow_zero_qty_in_purchase_order": 1}):
|
||||||
|
po.save()
|
||||||
|
self.assertEqual(po.items[0].qty, 0)
|
||||||
|
|
||||||
def test_make_purchase_receipt(self):
|
def test_make_purchase_receipt(self):
|
||||||
po = create_purchase_order(do_not_submit=True)
|
po = create_purchase_order(do_not_submit=True)
|
||||||
self.assertRaises(frappe.ValidationError, make_purchase_receipt, po.name)
|
self.assertRaises(frappe.ValidationError, make_purchase_receipt, po.name)
|
||||||
@@ -1239,6 +1246,82 @@ class TestPurchaseOrder(IntegrationTestCase):
|
|||||||
po.reload()
|
po.reload()
|
||||||
self.assertEqual(po.per_billed, 100)
|
self.assertEqual(po.per_billed, 100)
|
||||||
|
|
||||||
|
@IntegrationTestCase.change_settings("Buying Settings", {"allow_zero_qty_in_purchase_order": 1})
|
||||||
|
def test_receive_zero_qty_purchase_order(self):
|
||||||
|
"""
|
||||||
|
Test the flow of a Unit Price PO and PR creation against it until completion.
|
||||||
|
Flow:
|
||||||
|
PO Qty 0 -> Receive +5 -> Receive +5 -> Update PO Qty +10 -> PO is 100% received
|
||||||
|
"""
|
||||||
|
po = create_purchase_order(qty=0)
|
||||||
|
pr = make_purchase_receipt(po.name)
|
||||||
|
|
||||||
|
self.assertEqual(pr.items[0].qty, 0)
|
||||||
|
pr.items[0].qty = 5
|
||||||
|
pr.submit()
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
self.assertEqual(po.items[0].received_qty, 5)
|
||||||
|
# PO still has qty 0, so billed % should be unset
|
||||||
|
self.assertFalse(po.per_received)
|
||||||
|
self.assertEqual(po.status, "To Receive and Bill")
|
||||||
|
|
||||||
|
# Test: PR can be made against PO as long PO qty is 0 OR PO qty > received qty
|
||||||
|
pr2 = make_purchase_receipt(po.name)
|
||||||
|
self.assertEqual(pr2.items[0].qty, 0)
|
||||||
|
pr2.items[0].qty = 5
|
||||||
|
pr2.submit()
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
self.assertEqual(po.items[0].received_qty, 10)
|
||||||
|
self.assertFalse(po.per_received)
|
||||||
|
self.assertEqual(po.status, "To Receive and Bill")
|
||||||
|
|
||||||
|
# Update PO Item Qty to 10 after receipt of items
|
||||||
|
first_item_of_po = po.items[0]
|
||||||
|
trans_item = json.dumps(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"item_code": first_item_of_po.item_code,
|
||||||
|
"rate": first_item_of_po.rate,
|
||||||
|
"qty": 10,
|
||||||
|
"docname": first_item_of_po.name,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
update_child_qty_rate("Purchase Order", trans_item, po.name)
|
||||||
|
|
||||||
|
# PO should be updated to 100% received
|
||||||
|
po.reload()
|
||||||
|
self.assertEqual(po.items[0].qty, 10)
|
||||||
|
self.assertEqual(po.per_received, 100.0)
|
||||||
|
self.assertEqual(po.status, "To Bill")
|
||||||
|
|
||||||
|
@IntegrationTestCase.change_settings("Buying Settings", {"allow_zero_qty_in_purchase_order": 1})
|
||||||
|
def test_bill_zero_qty_purchase_order(self):
|
||||||
|
po = create_purchase_order(qty=0)
|
||||||
|
|
||||||
|
self.assertEqual(po.grand_total, 0)
|
||||||
|
self.assertFalse(po.per_billed)
|
||||||
|
self.assertEqual(po.items[0].qty, 0)
|
||||||
|
self.assertEqual(po.items[0].rate, 500)
|
||||||
|
|
||||||
|
pi = make_pi_from_po(po.name)
|
||||||
|
self.assertEqual(pi.items[0].qty, 0)
|
||||||
|
self.assertEqual(pi.items[0].rate, 500)
|
||||||
|
|
||||||
|
pi.items[0].qty = 5
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
self.assertEqual(pi.grand_total, 2500)
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
self.assertEqual(po.items[0].amount, 0)
|
||||||
|
self.assertEqual(po.items[0].billed_amt, 2500)
|
||||||
|
# PO still has qty 0, so billed % should be unset
|
||||||
|
self.assertFalse(po.per_billed)
|
||||||
|
self.assertEqual(po.status, "To Receive and Bill")
|
||||||
|
|
||||||
|
|
||||||
def create_po_for_sc_testing():
|
def create_po_for_sc_testing():
|
||||||
from erpnext.controllers.tests.test_subcontracting_controller import (
|
from erpnext.controllers.tests.test_subcontracting_controller import (
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class TestRequestforQuotation(IntegrationTestCase):
|
|||||||
|
|
||||||
with change_settings("Buying Settings", {"allow_zero_qty_in_request_for_quotation": 1}):
|
with change_settings("Buying Settings", {"allow_zero_qty_in_request_for_quotation": 1}):
|
||||||
rfq.save()
|
rfq.save()
|
||||||
|
self.assertEqual(rfq.items[0].qty, 0)
|
||||||
|
|
||||||
def test_quote_status(self):
|
def test_quote_status(self):
|
||||||
rfq = make_request_for_quotation()
|
rfq = make_request_for_quotation()
|
||||||
@@ -190,8 +191,8 @@ class TestRequestforQuotation(IntegrationTestCase):
|
|||||||
supplier_doc.reload()
|
supplier_doc.reload()
|
||||||
self.assertTrue(supplier_doc.portal_users[0].user)
|
self.assertTrue(supplier_doc.portal_users[0].user)
|
||||||
|
|
||||||
@change_settings("Buying Settings", {"allow_zero_qty_in_request_for_quotation": 1})
|
@IntegrationTestCase.change_settings("Buying Settings", {"allow_zero_qty_in_request_for_quotation": 1})
|
||||||
def test_map_supplier_quotation_from_zero_qty_rfq(self):
|
def test_supplier_quotation_from_zero_qty_rfq(self):
|
||||||
rfq = make_request_for_quotation(qty=0)
|
rfq = make_request_for_quotation(qty=0)
|
||||||
sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get("suppliers")[0].supplier)
|
sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get("suppliers")[0].supplier)
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class TestPurchaseOrder(IntegrationTestCase):
|
|||||||
|
|
||||||
with change_settings("Buying Settings", {"allow_zero_qty_in_supplier_quotation": 1}):
|
with change_settings("Buying Settings", {"allow_zero_qty_in_supplier_quotation": 1}):
|
||||||
sq.save()
|
sq.save()
|
||||||
|
self.assertEqual(sq.items[0].qty, 0)
|
||||||
|
|
||||||
def test_make_purchase_order(self):
|
def test_make_purchase_order(self):
|
||||||
sq = frappe.copy_doc(self.globalTestRecords["Supplier Quotation"][0]).insert()
|
sq = frappe.copy_doc(self.globalTestRecords["Supplier Quotation"][0]).insert()
|
||||||
@@ -60,7 +61,7 @@ class TestPurchaseOrder(IntegrationTestCase):
|
|||||||
|
|
||||||
po.insert()
|
po.insert()
|
||||||
|
|
||||||
@change_settings("Buying Settings", {"allow_zero_qty_in_supplier_quotation": 1})
|
@IntegrationTestCase.change_settings("Buying Settings", {"allow_zero_qty_in_supplier_quotation": 1})
|
||||||
def test_map_purchase_order_from_zero_qty_supplier_quotation(self):
|
def test_map_purchase_order_from_zero_qty_supplier_quotation(self):
|
||||||
sq = frappe.copy_doc(self.globalTestRecords["Supplier Quotation"][0])
|
sq = frappe.copy_doc(self.globalTestRecords["Supplier Quotation"][0])
|
||||||
sq.items[0].qty = 0
|
sq.items[0].qty = 0
|
||||||
|
|||||||
@@ -3658,9 +3658,9 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
)
|
)
|
||||||
if amount_below_billed_amt and row_rate > 0.0:
|
if amount_below_billed_amt and row_rate > 0.0:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.").format(
|
_(
|
||||||
child_item.idx, child_item.item_code
|
"Row #{0}: Cannot set Rate if the billed amount is greater than the amount for Item {1}."
|
||||||
)
|
).format(child_item.idx, child_item.item_code)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
child_item.rate = row_rate
|
child_item.rate = row_rate
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from pypika import functions as fn
|
|||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
||||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
|
||||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||||
from erpnext.controllers.accounts_controller import merge_taxes
|
from erpnext.controllers.accounts_controller import merge_taxes
|
||||||
from erpnext.controllers.buying_controller import BuyingController
|
from erpnext.controllers.buying_controller import BuyingController
|
||||||
|
|||||||
Reference in New Issue
Block a user