Skip to content

Commit

Permalink
fix: incorrect available qty for backdated stock reco with batch
Browse files Browse the repository at this point in the history
  • Loading branch information
rohitwaghchaure committed Nov 2, 2023
1 parent fb9a809 commit b6b01b7
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 65 deletions.
89 changes: 52 additions & 37 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 @@ -689,56 +689,69 @@ 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)
and not row.serial_and_batch_bundle
):
if voucher_detail_no != row.name:
continue

if row.current_serial_and_batch_bundle:
self.recalculate_qty_for_serial_and_batch_bundle(row)
continue

current_qty = get_batch_qty_for_stock_reco(
item_code, row.warehouse, batch_no, self.posting_date, self.posting_time, self.name
)
current_qty = self.get_qty_for_serial_and_batch_bundle(row)
else:
current_qty = get_batch_qty_for_stock_reco(
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,
serial_and_batch_bundle=row.current_serial_and_batch_bundle,
)

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 > 0
):
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)
self.make_sl_entries(sl_entries, allow_negative_stock=self.has_negative_stock_allowed())
if not frappe.db.exists("Repost Item Valuation", {"voucher_no": self.name, "status": "Queued"}):
self.repost_future_sle_and_gle(force=True)

def recalculate_qty_for_serial_and_batch_bundle(self, row):
def get_qty_for_serial_and_batch_bundle(self, row):
doc = frappe.get_doc("Serial and Batch Bundle", row.current_serial_and_batch_bundle)
precision = doc.entries[0].precision("qty")

current_qty = 0
for d in doc.entries:
qty = (
get_batch_qty(
Expand All @@ -751,10 +764,12 @@ def recalculate_qty_for_serial_and_batch_bundle(self, row):
or 0
) * -1

if flt(d.qty, precision) == flt(qty, precision):
continue
if flt(d.qty, precision) != flt(qty, precision):
d.db_set("qty", qty)

current_qty += qty

d.db_set("qty", qty)
return abs(current_qty)


def get_batch_qty_for_stock_reco(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@
"fieldname": "current_serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Current Serial / Batch Bundle",
"no_copy": 1,
"options": "Serial and Batch Bundle",
"read_only": 1
},
Expand All @@ -216,7 +217,7 @@
],
"istable": 1,
"links": [],
"modified": "2023-07-26 12:54:34.011915",
"modified": "2023-11-02 15:47:07.929550",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation Item",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"stock_value_difference",
"valuation_rate",
"voucher_detail_no",
"serial_and_batch_bundle",
)


Expand Down Expand Up @@ -64,7 +65,11 @@ def add_invariant_check_fields(sles):

balance_qty += sle.actual_qty
balance_stock_value += sle.stock_value_difference
if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no:
if (
sle.voucher_type == "Stock Reconciliation"
and not sle.batch_no
and not sle.serial_and_batch_bundle
):
balance_qty = frappe.db.get_value("Stock Reconciliation Item", sle.voucher_detail_no, "qty")
if balance_qty is None:
balance_qty = sle.qty_after_transaction
Expand Down Expand Up @@ -143,6 +148,12 @@ def get_columns():
"label": _("Batch"),
"options": "Batch",
},
{
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": _("Serial and Batch Bundle"),
"options": "Serial and Batch Bundle",
},
{
"fieldname": "use_batchwise_valuation",
"fieldtype": "Check",
Expand Down
50 changes: 24 additions & 26 deletions erpnext/stock/stock_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,6 @@ def process_sle(self, sle):
sle.voucher_type == "Stock Reconciliation"
and (sle.batch_no or (sle.has_batch_no and sle.serial_and_batch_bundle))
and sle.voucher_detail_no
and sle.actual_qty < 0
):
self.reset_actual_qty_for_stock_reco(sle)

Expand Down Expand Up @@ -754,22 +753,17 @@ def process_sle(self, sle):
self.update_outgoing_rate_on_transaction(sle)

def reset_actual_qty_for_stock_reco(self, sle):
if sle.serial_and_batch_bundle:
current_qty = frappe.get_cached_value(
"Serial and Batch Bundle", sle.serial_and_batch_bundle, "total_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 is not None:
current_qty = abs(current_qty)
else:
current_qty = frappe.get_cached_value(
"Stock Reconciliation Item", sle.voucher_detail_no, "current_qty"
if sle.actual_qty < 0:
sle.actual_qty = (
flt(frappe.db.get_value("Stock Reconciliation Item", sle.voucher_detail_no, "current_qty"))
* -1
)

if current_qty:
sle.actual_qty = current_qty * -1
elif current_qty == 0:
sle.is_cancelled = 1
if abs(sle.actual_qty) == 0.0:
sle.is_cancelled = 1

def calculate_valuation_for_serial_batch_bundle(self, sle):
doc = frappe.get_cached_doc("Serial and Batch Bundle", sle.serial_and_batch_bundle)
Expand Down Expand Up @@ -1461,6 +1455,7 @@ def get_valuation_rate(
currency=None,
company=None,
raise_error_if_no_rate=True,
batch_no=None,
serial_and_batch_bundle=None,
):

Expand All @@ -1469,6 +1464,21 @@ def get_valuation_rate(
if not company:
company = frappe.get_cached_value("Warehouse", warehouse, "company")

if warehouse and batch_no and frappe.db.get_value("Batch", batch_no, "use_batchwise_valuation"):
last_valuation_rate = frappe.db.sql(
"""
select sum(stock_value_difference) / sum(actual_qty)
from `tabStock Ledger Entry`
where
item_code = %s
AND warehouse = %s
AND batch_no = %s
AND is_cancelled = 0
AND NOT (voucher_no = %s AND voucher_type = %s)
""",
(item_code, warehouse, batch_no, voucher_no, voucher_type),
)

# Get moving average rate of a specific batch number
if warehouse and serial_and_batch_bundle:
batch_obj = BatchNoValuation(
Expand Down Expand Up @@ -1563,8 +1573,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 or (detail.serial_and_batch_bundle and detail.has_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 @@ -1593,16 +1601,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 b6b01b7

Please sign in to comment.