Skip to content

Commit

Permalink
fix: incorrect available qty for backdated stock reco with batch (#36581
Browse files Browse the repository at this point in the history
)
  • Loading branch information
rohitwaghchaure authored Aug 10, 2023
1 parent a864e07 commit 2800ad3
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 56 deletions.
62 changes: 38 additions & 24 deletions erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import frappe
from frappe import _, bold, msgprint
from frappe.query_builder.functions import CombineDatetime, Sum
from frappe.utils import cint, cstr, flt
from frappe.utils import add_to_date, cint, cstr, flt

import erpnext
from erpnext.accounts.utils import get_company_default
Expand Down Expand Up @@ -570,44 +570,58 @@ def cancel(self):
else:
self._cancel()

def recalculate_current_qty(self, item_code, batch_no):
def recalculate_current_qty(self, voucher_detail_no, sle_creation, add_new_sle=False):
from erpnext.stock.stock_ledger import get_valuation_rate

sl_entries = []
for row in self.items:
if not (row.item_code == item_code and row.batch_no == batch_no):
if voucher_detail_no != row.name:
continue

current_qty = get_batch_qty_for_stock_reco(
item_code, row.warehouse, batch_no, self.posting_date, self.posting_time, self.name
row.item_code, row.warehouse, row.batch_no, self.posting_date, self.posting_time, self.name
)

precesion = row.precision("current_qty")
if flt(current_qty, precesion) == flt(row.current_qty, precesion):
continue

val_rate = get_valuation_rate(
item_code, row.warehouse, self.doctype, self.name, company=self.company, batch_no=batch_no
)
if flt(current_qty, precesion) != flt(row.current_qty, precesion):
val_rate = get_valuation_rate(
row.item_code,
row.warehouse,
self.doctype,
self.name,
company=self.company,
batch_no=row.batch_no,
)

row.current_valuation_rate = val_rate
if not row.current_qty and current_qty:
sle = self.get_sle_for_items(row)
sle.actual_qty = current_qty * -1
sle.valuation_rate = val_rate
sl_entries.append(sle)
row.current_valuation_rate = val_rate
row.current_qty = current_qty
row.db_set(
{
"current_qty": row.current_qty,
"current_valuation_rate": row.current_valuation_rate,
"current_amount": flt(row.current_qty * row.current_valuation_rate),
}
)

row.current_qty = current_qty
row.db_set(
{
"current_qty": row.current_qty,
"current_valuation_rate": row.current_valuation_rate,
"current_amount": flt(row.current_qty * row.current_valuation_rate),
}
)
if (
add_new_sle
and not frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_detail_no": row.name, "actual_qty": ("<", 0), "is_cancelled": 0},
"name",
)
and current_qty
):
new_sle = self.get_sle_for_items(row)
new_sle.actual_qty = current_qty * -1
new_sle.valuation_rate = row.current_valuation_rate
new_sle.creation_time = add_to_date(sle_creation, seconds=-1)
sl_entries.append(new_sle)

if sl_entries:
self.make_sl_entries(sl_entries, allow_negative_stock=True)
if frappe.db.exists("Repost Item Valuation", {"voucher_no": self.name, "status": "Queued"}):
self.repost_future_sle_and_gle(force=True)


def get_batch_qty_for_stock_reco(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -759,13 +759,6 @@ def test_backdated_stock_reco_entry(self):

se2.cancel()

self.assertTrue(frappe.db.exists("Repost Item Valuation", {"voucher_no": stock_reco.name}))

self.assertEqual(
frappe.db.get_value("Repost Item Valuation", {"voucher_no": stock_reco.name}, "status"),
"Completed",
)

sle = frappe.get_all(
"Stock Ledger Entry",
filters={"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0},
Expand All @@ -775,6 +768,62 @@ def test_backdated_stock_reco_entry(self):

self.assertEqual(flt(sle[0].qty_after_transaction), flt(50.0))

def test_backdated_stock_reco_entry_with_batch(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

item_code = self.make_item(
"Test New Batch Item ABCVSD",
{
"is_stock_item": 1,
"has_batch_no": 1,
"batch_number_series": "BNS9.####",
"create_new_batch": 1,
},
).name

warehouse = "_Test Warehouse - _TC"

# Stock Reco for 100, Balace Qty 100
stock_reco = create_stock_reconciliation(
item_code=item_code,
posting_date=nowdate(),
posting_time="11:00:00",
warehouse=warehouse,
qty=100,
rate=100,
)

sles = frappe.get_all(
"Stock Ledger Entry",
fields=["actual_qty", "batch_no"],
filters={"voucher_no": stock_reco.name, "is_cancelled": 0},
)

self.assertEqual(len(sles), 1)

# Stock Reco for 100, Balace Qty 100
create_stock_reconciliation(
item_code=item_code,
posting_date=add_days(nowdate(), -1),
posting_time="11:00:00",
batch_no=sles[0].batch_no,
warehouse=warehouse,
qty=60,
rate=100,
)

sles = frappe.get_all(
"Stock Ledger Entry",
fields=["actual_qty"],
filters={"voucher_no": stock_reco.name, "is_cancelled": 0},
)

self.assertEqual(len(sles), 2)

for row in sles:
if row.actual_qty < 0:
self.assertEqual(row.actual_qty, -60)

def test_update_stock_reconciliation_while_reposting(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

Expand Down
41 changes: 16 additions & 25 deletions erpnext/stock/stock_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False):
sle.allow_negative_stock = allow_negative_stock
sle.via_landed_cost_voucher = via_landed_cost_voucher
sle.submit()

# Added to handle the case when the stock ledger entry is created from the repostig
if args.get("creation_time") and args.get("voucher_type") == "Stock Reconciliation":
sle.db_set("creation", args.get("creation_time"))

return sle


Expand Down Expand Up @@ -564,12 +569,7 @@ def process_sle(self, sle):
if not self.args.get("sle_id"):
self.get_dynamic_incoming_outgoing_rate(sle)

if (
sle.voucher_type == "Stock Reconciliation"
and sle.batch_no
and sle.voucher_detail_no
and sle.actual_qty < 0
):
if sle.voucher_type == "Stock Reconciliation" and sle.batch_no and sle.voucher_detail_no:
self.reset_actual_qty_for_stock_reco(sle)

if (
Expand Down Expand Up @@ -634,14 +634,17 @@ def process_sle(self, sle):
self.update_outgoing_rate_on_transaction(sle)

def reset_actual_qty_for_stock_reco(self, sle):
current_qty = frappe.get_cached_value(
"Stock Reconciliation Item", sle.voucher_detail_no, "current_qty"
)
doc = frappe.get_cached_doc("Stock Reconciliation", sle.voucher_no)
doc.recalculate_current_qty(sle.voucher_detail_no, sle.creation, sle.actual_qty > 0)

if current_qty:
sle.actual_qty = current_qty * -1
elif current_qty == 0:
sle.is_cancelled = 1
if sle.actual_qty < 0:
sle.actual_qty = (
flt(frappe.db.get_value("Stock Reconciliation Item", sle.voucher_detail_no, "current_qty"))
* -1
)

if abs(sle.actual_qty) == 0.0:
sle.is_cancelled = 1

def validate_negative_stock(self, sle):
"""
Expand Down Expand Up @@ -1433,8 +1436,6 @@ def update_qty_in_future_sle(args, allow_negative_stock=False):
next_stock_reco_detail = get_next_stock_reco(args)
if next_stock_reco_detail:
detail = next_stock_reco_detail[0]
if detail.batch_no:
regenerate_sle_for_batch_stock_reco(detail)

# add condition to update SLEs before this date & time
datetime_limit_condition = get_datetime_limit_condition(detail)
Expand Down Expand Up @@ -1463,16 +1464,6 @@ def update_qty_in_future_sle(args, allow_negative_stock=False):
validate_negative_qty_in_future_sle(args, allow_negative_stock)


def regenerate_sle_for_batch_stock_reco(detail):
doc = frappe.get_cached_doc("Stock Reconciliation", detail.voucher_no)
doc.recalculate_current_qty(detail.item_code, detail.batch_no)

if not frappe.db.exists(
"Repost Item Valuation", {"voucher_no": doc.name, "status": "Queued", "docstatus": "1"}
):
doc.repost_future_sle_and_gle(force=True)


def get_stock_reco_qty_shift(args):
stock_reco_qty_shift = 0
if args.get("is_cancelled"):
Expand Down

0 comments on commit 2800ad3

Please sign in to comment.