mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-14 02:04:17 +00:00
* fix: validate available stock with multiple dimensions * test: validate negative stock with multiple inventory dimensions * chore: reset document_wise_inventory_dimensions
This commit is contained in:
@@ -504,6 +504,61 @@ class TestInventoryDimension(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(site_name, "Site 1")
|
self.assertEqual(site_name, "Site 1")
|
||||||
|
|
||||||
|
def test_validate_negative_stock_with_multiple_dimension(self):
|
||||||
|
frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 0)
|
||||||
|
item_code = "Test Negative Multi Inventory Dimension Item"
|
||||||
|
create_item(item_code)
|
||||||
|
|
||||||
|
inv_dimension_1 = create_inventory_dimension(
|
||||||
|
apply_to_all_doctypes=1,
|
||||||
|
dimension_name="Inv Site",
|
||||||
|
reference_document="Inv Site",
|
||||||
|
document_type="Inv Site",
|
||||||
|
validate_negative_stock=1,
|
||||||
|
)
|
||||||
|
inv_dimension_1.db_set("validate_negative_stock", 1)
|
||||||
|
|
||||||
|
inv_dimension_2 = create_inventory_dimension(
|
||||||
|
apply_to_all_doctypes=1,
|
||||||
|
dimension_name="Rack",
|
||||||
|
reference_document="Rack",
|
||||||
|
document_type="Rack",
|
||||||
|
validate_negative_stock=1,
|
||||||
|
)
|
||||||
|
inv_dimension_2.db_set("validate_negative_stock", 1)
|
||||||
|
frappe.local.inventory_dimensions = {}
|
||||||
|
frappe.local.document_wise_inventory_dimensions = {}
|
||||||
|
|
||||||
|
pr_doc = make_purchase_receipt(item_code=item_code, qty=30, do_not_submit=True)
|
||||||
|
pr_doc.items[0].inv_site = "Site 1"
|
||||||
|
pr_doc.items[0].rack = "Rack 1"
|
||||||
|
pr_doc.save()
|
||||||
|
pr_doc.submit()
|
||||||
|
|
||||||
|
pr_doc = make_purchase_receipt(item_code=item_code, qty=15, do_not_submit=True)
|
||||||
|
pr_doc.items[0].inv_site = "Site 1"
|
||||||
|
pr_doc.items[0].rack = "Rack 2"
|
||||||
|
pr_doc.save()
|
||||||
|
pr_doc.submit()
|
||||||
|
|
||||||
|
pr_doc = make_purchase_receipt(item_code=item_code, qty=30, do_not_submit=True)
|
||||||
|
pr_doc.items[0].inv_site = "Site 2"
|
||||||
|
pr_doc.items[0].rack = "Rack 1"
|
||||||
|
pr_doc.save()
|
||||||
|
pr_doc.submit()
|
||||||
|
|
||||||
|
pr_doc = make_purchase_receipt(item_code=item_code, qty=25, do_not_submit=True)
|
||||||
|
pr_doc.items[0].inv_site = "Site 2"
|
||||||
|
pr_doc.items[0].rack = "Rack 2"
|
||||||
|
pr_doc.save()
|
||||||
|
pr_doc.submit()
|
||||||
|
|
||||||
|
dn_doc = create_delivery_note(item_code=item_code, qty=35, do_not_submit=True)
|
||||||
|
dn_doc.items[0].inv_site = "Site 2"
|
||||||
|
dn_doc.items[0].rack = "Rack 1"
|
||||||
|
dn_doc.save()
|
||||||
|
self.assertRaises(InventoryDimensionNegativeStockError, dn_doc.submit)
|
||||||
|
|
||||||
|
|
||||||
def get_voucher_sl_entries(voucher_no, fields):
|
def get_voucher_sl_entries(voucher_no, fields):
|
||||||
return frappe.get_all(
|
return frappe.get_all(
|
||||||
@@ -593,7 +648,7 @@ def prepare_test_data():
|
|||||||
}
|
}
|
||||||
).insert(ignore_permissions=True)
|
).insert(ignore_permissions=True)
|
||||||
|
|
||||||
for rack in ["Rack 1"]:
|
for rack in ["Rack 1", "Rack 2"]:
|
||||||
if not frappe.db.exists("Rack", rack):
|
if not frappe.db.exists("Rack", rack):
|
||||||
frappe.get_doc({"doctype": "Rack", "rack_name": rack}).insert(ignore_permissions=True)
|
frappe.get_doc({"doctype": "Rack", "rack_name": rack}).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
|||||||
@@ -113,17 +113,15 @@ class StockLedgerEntry(Document):
|
|||||||
return
|
return
|
||||||
|
|
||||||
flt_precision = cint(frappe.db.get_default("float_precision")) or 2
|
flt_precision = cint(frappe.db.get_default("float_precision")) or 2
|
||||||
for dimension, values in dimensions.items():
|
available_qty = self.get_available_qty_after_prev_transaction(dimensions)
|
||||||
dimension_value = values.get("value")
|
|
||||||
available_qty = self.get_available_qty_after_prev_transaction(dimension, dimension_value)
|
|
||||||
|
|
||||||
diff = flt(available_qty + flt(self.actual_qty), flt_precision) # qty after current transaction
|
diff = flt(available_qty + flt(self.actual_qty), flt_precision) # qty after current transaction
|
||||||
if diff < 0 and abs(diff) > 0.0001:
|
if diff < 0 and abs(diff) > 0.0001:
|
||||||
self.throw_validation_error(diff, dimension, dimension_value)
|
self.throw_validation_error(diff, dimensions)
|
||||||
|
|
||||||
def get_available_qty_after_prev_transaction(self, dimension, dimension_value):
|
def get_available_qty_after_prev_transaction(self, dimensions):
|
||||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
available_qty = (
|
available_qty_query = (
|
||||||
frappe.qb.from_(sle)
|
frappe.qb.from_(sle)
|
||||||
.select(Sum(sle.actual_qty))
|
.select(Sum(sle.actual_qty))
|
||||||
.where(
|
.where(
|
||||||
@@ -132,21 +130,27 @@ class StockLedgerEntry(Document):
|
|||||||
& (sle.posting_datetime < self.posting_datetime)
|
& (sle.posting_datetime < self.posting_datetime)
|
||||||
& (sle.company == self.company)
|
& (sle.company == self.company)
|
||||||
& (sle.is_cancelled == 0)
|
& (sle.is_cancelled == 0)
|
||||||
& (sle[dimension] == dimension_value)
|
|
||||||
)
|
)
|
||||||
).run()
|
)
|
||||||
|
|
||||||
|
for dimension, values in dimensions.items():
|
||||||
|
dimension_value = values.get("value")
|
||||||
|
available_qty_query = available_qty_query.where(sle[dimension] == dimension_value)
|
||||||
|
|
||||||
|
available_qty = available_qty_query.run()
|
||||||
|
|
||||||
return available_qty[0][0] or 0
|
return available_qty[0][0] or 0
|
||||||
|
|
||||||
def throw_validation_error(self, diff, dimension, dimension_value):
|
def throw_validation_error(self, diff, dimensions):
|
||||||
msg = _(
|
msg = _(
|
||||||
"{0} units of {1} are required in {2} with the inventory dimension: {3} ({4}) on {5} {6} for {7} to complete the transaction."
|
"{0} units of {1} are required in {2} with the inventory dimension: {3} on {4} {5} for {6} to complete the transaction."
|
||||||
).format(
|
).format(
|
||||||
abs(diff),
|
abs(diff),
|
||||||
frappe.get_desk_link("Item", self.item_code),
|
frappe.get_desk_link("Item", self.item_code),
|
||||||
frappe.get_desk_link("Warehouse", self.warehouse),
|
frappe.get_desk_link("Warehouse", self.warehouse),
|
||||||
frappe.bold(dimension),
|
frappe.bold(
|
||||||
frappe.bold(dimension_value),
|
", ".join([f"{dimension}: {values.get('value')}" for dimension, values in dimensions.items()])
|
||||||
|
),
|
||||||
self.posting_date,
|
self.posting_date,
|
||||||
self.posting_time,
|
self.posting_time,
|
||||||
frappe.get_desk_link(self.voucher_type, self.voucher_no),
|
frappe.get_desk_link(self.voucher_type, self.voucher_no),
|
||||||
|
|||||||
Reference in New Issue
Block a user