mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 08:24:47 +00:00
Merge pull request #28963 from frappe/mergify/bp/version-13-hotfix/pr-28588
fix: incorrect bin qty on backdated reconciliation (backport #28588)
This commit is contained in:
@@ -196,8 +196,6 @@ class TestWorkOrder(ERPNextTestCase):
|
|||||||
# no change in reserved / projected
|
# no change in reserved / projected
|
||||||
self.assertEqual(cint(bin1_on_end_production.reserved_qty_for_production),
|
self.assertEqual(cint(bin1_on_end_production.reserved_qty_for_production),
|
||||||
cint(bin1_on_start_production.reserved_qty_for_production))
|
cint(bin1_on_start_production.reserved_qty_for_production))
|
||||||
self.assertEqual(cint(bin1_on_end_production.projected_qty),
|
|
||||||
cint(bin1_on_end_production.projected_qty))
|
|
||||||
|
|
||||||
def test_backflush_qty_for_overpduction_manufacture(self):
|
def test_backflush_qty_for_overpduction_manufacture(self):
|
||||||
cancel_stock_entry = []
|
cancel_stock_entry = []
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ def update_stock(bin_name, args, allow_negative_stock=False, via_landed_cost_vou
|
|||||||
"""WARNING: This function is deprecated. Inline this function instead of using it."""
|
"""WARNING: This function is deprecated. Inline this function instead of using it."""
|
||||||
from erpnext.stock.stock_ledger import repost_current_voucher
|
from erpnext.stock.stock_ledger import repost_current_voucher
|
||||||
|
|
||||||
update_qty(bin_name, args)
|
|
||||||
repost_current_voucher(args, allow_negative_stock, via_landed_cost_voucher)
|
repost_current_voucher(args, allow_negative_stock, via_landed_cost_voucher)
|
||||||
|
update_qty(bin_name, args)
|
||||||
|
|
||||||
def get_bin_details(bin_name):
|
def get_bin_details(bin_name):
|
||||||
return frappe.db.get_value('Bin', bin_name, ['actual_qty', 'ordered_qty',
|
return frappe.db.get_value('Bin', bin_name, ['actual_qty', 'ordered_qty',
|
||||||
@@ -112,13 +112,23 @@ def get_bin_details(bin_name):
|
|||||||
'reserved_qty_for_sub_contract'], as_dict=1)
|
'reserved_qty_for_sub_contract'], as_dict=1)
|
||||||
|
|
||||||
def update_qty(bin_name, args):
|
def update_qty(bin_name, args):
|
||||||
bin_details = get_bin_details(bin_name)
|
from erpnext.controllers.stock_controller import future_sle_exists
|
||||||
|
|
||||||
# update the stock values (for current quantities)
|
bin_details = get_bin_details(bin_name)
|
||||||
if args.get("voucher_type")=="Stock Reconciliation":
|
# actual qty is already updated by processing current voucher
|
||||||
actual_qty = args.get('qty_after_transaction')
|
actual_qty = bin_details.actual_qty
|
||||||
else:
|
|
||||||
actual_qty = bin_details.actual_qty + flt(args.get("actual_qty"))
|
# actual qty is not up to date in case of backdated transaction
|
||||||
|
if future_sle_exists(args):
|
||||||
|
actual_qty = frappe.db.get_value("Stock Ledger Entry",
|
||||||
|
filters={
|
||||||
|
"item_code": args.get("item_code"),
|
||||||
|
"warehouse": args.get("warehouse"),
|
||||||
|
"is_cancelled": 0
|
||||||
|
},
|
||||||
|
fieldname="qty_after_transaction",
|
||||||
|
order_by="posting_date desc, posting_time desc, creation desc",
|
||||||
|
) or 0.0
|
||||||
|
|
||||||
ordered_qty = flt(bin_details.ordered_qty) + flt(args.get("ordered_qty"))
|
ordered_qty = flt(bin_details.ordered_qty) + flt(args.get("ordered_qty"))
|
||||||
reserved_qty = flt(bin_details.reserved_qty) + flt(args.get("reserved_qty"))
|
reserved_qty = flt(bin_details.reserved_qty) + flt(args.get("reserved_qty"))
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class RepostItemValuation(Document):
|
|||||||
self.db_set('status', self.status)
|
self.db_set('status', self.status)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if not frappe.flags.in_test or self.flags.dont_run_in_test:
|
if not frappe.flags.in_test or self.flags.dont_run_in_test or frappe.flags.dont_execute_stock_reposts:
|
||||||
return
|
return
|
||||||
|
|
||||||
frappe.enqueue(repost, timeout=1800, queue='long',
|
frappe.enqueue(repost, timeout=1800, queue='long',
|
||||||
@@ -97,7 +97,8 @@ def repost(doc):
|
|||||||
return
|
return
|
||||||
|
|
||||||
doc.set_status('In Progress')
|
doc.set_status('In Progress')
|
||||||
frappe.db.commit()
|
if not frappe.flags.in_test:
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
repost_sl_entries(doc)
|
repost_sl_entries(doc)
|
||||||
repost_gl_entries(doc)
|
repost_gl_entries(doc)
|
||||||
|
|||||||
@@ -24,11 +24,15 @@ from erpnext.tests.utils import ERPNextTestCase, change_settings
|
|||||||
|
|
||||||
class TestStockReconciliation(ERPNextTestCase):
|
class TestStockReconciliation(ERPNextTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
create_batch_or_serial_no_items()
|
create_batch_or_serial_no_items()
|
||||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
|
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.flags.dont_execute_stock_reposts = None
|
||||||
|
|
||||||
|
|
||||||
def test_reco_for_fifo(self):
|
def test_reco_for_fifo(self):
|
||||||
self._test_reco_sle_gle("FIFO")
|
self._test_reco_sle_gle("FIFO")
|
||||||
|
|
||||||
@@ -392,6 +396,41 @@ class TestStockReconciliation(ERPNextTestCase):
|
|||||||
repost_exists = bool(frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name}))
|
repost_exists = bool(frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name}))
|
||||||
self.assertFalse(repost_exists, msg="Negative stock validation not working on reco cancellation")
|
self.assertFalse(repost_exists, msg="Negative stock validation not working on reco cancellation")
|
||||||
|
|
||||||
|
def test_intermediate_sr_bin_update(self):
|
||||||
|
"""Bin should show correct qty even for backdated entries.
|
||||||
|
|
||||||
|
-------------------------------------------
|
||||||
|
| creation | Var | Doc | Qty | balance qty
|
||||||
|
-------------------------------------------
|
||||||
|
| 1 | SR | Reco | 10 | 10 (posting date: today+10)
|
||||||
|
| 3 | SR2 | Reco | 11 | 11 (posting date: today+11)
|
||||||
|
| 2 | DN | DN | 5 | 6 <-- assert in BIN (posting date: today+12)
|
||||||
|
"""
|
||||||
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
|
|
||||||
|
# repost will make this test useless, qty should update in realtime without reposts
|
||||||
|
frappe.flags.dont_execute_stock_reposts = True
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
item_code = "Backdated-Reco-Cancellation-Item"
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
create_item(item_code)
|
||||||
|
|
||||||
|
sr = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=10, rate=100,
|
||||||
|
posting_date=add_days(nowdate(), 10))
|
||||||
|
|
||||||
|
dn = create_delivery_note(item_code=item_code, warehouse=warehouse, qty=5, rate=120,
|
||||||
|
posting_date=add_days(nowdate(), 12))
|
||||||
|
old_bin_qty = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty")
|
||||||
|
|
||||||
|
sr2 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=11, rate=100,
|
||||||
|
posting_date=add_days(nowdate(), 11))
|
||||||
|
new_bin_qty = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty")
|
||||||
|
|
||||||
|
self.assertEqual(old_bin_qty + 1, new_bin_qty)
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
|
||||||
def test_valid_batch(self):
|
def test_valid_batch(self):
|
||||||
create_batch_item_with_batch("Testing Batch Item 1", "001")
|
create_batch_item_with_batch("Testing Batch Item 1", "001")
|
||||||
create_batch_item_with_batch("Testing Batch Item 2", "002")
|
create_batch_item_with_batch("Testing Batch Item 2", "002")
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc
|
|||||||
is_stock_item = frappe.get_cached_value('Item', args.get("item_code"), 'is_stock_item')
|
is_stock_item = frappe.get_cached_value('Item', args.get("item_code"), 'is_stock_item')
|
||||||
if is_stock_item:
|
if is_stock_item:
|
||||||
bin_name = get_or_make_bin(args.get("item_code"), args.get("warehouse"))
|
bin_name = get_or_make_bin(args.get("item_code"), args.get("warehouse"))
|
||||||
update_bin_qty(bin_name, args)
|
|
||||||
repost_current_voucher(args, allow_negative_stock, via_landed_cost_voucher)
|
repost_current_voucher(args, allow_negative_stock, via_landed_cost_voucher)
|
||||||
|
update_bin_qty(bin_name, args)
|
||||||
else:
|
else:
|
||||||
frappe.msgprint(_("Item {0} ignored since it is not a stock item").format(args.get("item_code")))
|
frappe.msgprint(_("Item {0} ignored since it is not a stock item").format(args.get("item_code")))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user