mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-14 04:15:10 +00:00
fix: stock reco recalculate qty not works for opening stock reco
(cherry picked from commit 97095c7d24)
This commit is contained in:
committed by
Mergify
parent
fb56db1166
commit
2bd30e3c46
@@ -223,6 +223,7 @@ def get_batch_qty(
|
|||||||
ignore_voucher_nos=None,
|
ignore_voucher_nos=None,
|
||||||
for_stock_levels=False,
|
for_stock_levels=False,
|
||||||
consider_negative_batches=False,
|
consider_negative_batches=False,
|
||||||
|
do_not_check_future_batches=False,
|
||||||
):
|
):
|
||||||
"""Returns batch actual qty if warehouse is passed,
|
"""Returns batch actual qty if warehouse is passed,
|
||||||
or returns dict of qty by warehouse if warehouse is None
|
or returns dict of qty by warehouse if warehouse is None
|
||||||
@@ -249,6 +250,7 @@ def get_batch_qty(
|
|||||||
"ignore_voucher_nos": ignore_voucher_nos,
|
"ignore_voucher_nos": ignore_voucher_nos,
|
||||||
"for_stock_levels": for_stock_levels,
|
"for_stock_levels": for_stock_levels,
|
||||||
"consider_negative_batches": consider_negative_batches,
|
"consider_negative_batches": consider_negative_batches,
|
||||||
|
"do_not_check_future_batches": do_not_check_future_batches,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, bold, json, msgprint
|
from frappe import _, bold, json, msgprint
|
||||||
from frappe.query_builder.functions import CombineDatetime, Sum
|
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||||
from frappe.utils import add_to_date, cint, cstr, flt
|
from frappe.utils import add_to_date, cint, cstr, flt, get_datetime
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.utils import get_company_default
|
from erpnext.accounts.utils import get_company_default
|
||||||
from erpnext.controllers.stock_controller import StockController
|
from erpnext.controllers.stock_controller import StockController, create_repost_item_valuation_entry
|
||||||
from erpnext.stock.doctype.batch.batch import get_available_batches, get_batch_qty
|
from erpnext.stock.doctype.batch.batch import get_available_batches, get_batch_qty
|
||||||
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions
|
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
@@ -186,9 +186,35 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
if not item.reconcile_all_serial_batch and item.serial_and_batch_bundle:
|
if not item.reconcile_all_serial_batch and item.serial_and_batch_bundle:
|
||||||
bundle = self.get_bundle_for_specific_serial_batch(item)
|
bundle = self.get_bundle_for_specific_serial_batch(item)
|
||||||
|
if not bundle:
|
||||||
|
continue
|
||||||
|
|
||||||
item.current_serial_and_batch_bundle = bundle.name
|
item.current_serial_and_batch_bundle = bundle.name
|
||||||
item.current_valuation_rate = abs(bundle.avg_rate)
|
item.current_valuation_rate = abs(bundle.avg_rate)
|
||||||
|
|
||||||
|
if bundle.total_qty:
|
||||||
|
item.current_qty = abs(bundle.total_qty)
|
||||||
|
|
||||||
|
if save:
|
||||||
|
if not item.current_qty:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row # {0}: Please enter quantity for Item {1} as it is not zero.").format(
|
||||||
|
item.idx, item.item_code
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.docstatus == 1:
|
||||||
|
bundle.voucher_no = self.name
|
||||||
|
bundle.submit()
|
||||||
|
|
||||||
|
item.db_set(
|
||||||
|
{
|
||||||
|
"current_serial_and_batch_bundle": item.current_serial_and_batch_bundle,
|
||||||
|
"current_qty": item.current_qty,
|
||||||
|
"current_valuation_rate": item.current_valuation_rate,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if not item.valuation_rate:
|
if not item.valuation_rate:
|
||||||
item.valuation_rate = item.current_valuation_rate
|
item.valuation_rate = item.current_valuation_rate
|
||||||
continue
|
continue
|
||||||
@@ -333,20 +359,26 @@ class StockReconciliation(StockController):
|
|||||||
entry.batch_no,
|
entry.batch_no,
|
||||||
row.warehouse,
|
row.warehouse,
|
||||||
row.item_code,
|
row.item_code,
|
||||||
|
ignore_voucher_nos=[self.name],
|
||||||
posting_date=self.posting_date,
|
posting_date=self.posting_date,
|
||||||
posting_time=self.posting_time,
|
posting_time=self.posting_time,
|
||||||
for_stock_levels=True,
|
for_stock_levels=True,
|
||||||
consider_negative_batches=True,
|
consider_negative_batches=True,
|
||||||
|
do_not_check_future_batches=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not current_qty:
|
||||||
|
continue
|
||||||
|
|
||||||
total_current_qty += current_qty
|
total_current_qty += current_qty
|
||||||
entry.qty = current_qty * -1
|
entry.qty = current_qty * -1
|
||||||
|
|
||||||
reco_obj.save()
|
if total_current_qty:
|
||||||
|
reco_obj.save()
|
||||||
|
|
||||||
row.current_qty = total_current_qty
|
row.current_qty = total_current_qty
|
||||||
|
|
||||||
return reco_obj
|
return reco_obj
|
||||||
|
|
||||||
def has_change_in_serial_batch(self, row) -> bool:
|
def has_change_in_serial_batch(self, row) -> bool:
|
||||||
bundles = {row.serial_and_batch_bundle: [], row.current_serial_and_batch_bundle: []}
|
bundles = {row.serial_and_batch_bundle: [], row.current_serial_and_batch_bundle: []}
|
||||||
@@ -968,7 +1000,7 @@ class StockReconciliation(StockController):
|
|||||||
else:
|
else:
|
||||||
self._cancel()
|
self._cancel()
|
||||||
|
|
||||||
def recalculate_current_qty(self, voucher_detail_no):
|
def recalculate_current_qty(self, voucher_detail_no, sle_creation, add_new_sle=False):
|
||||||
from erpnext.stock.stock_ledger import get_valuation_rate
|
from erpnext.stock.stock_ledger import get_valuation_rate
|
||||||
|
|
||||||
for row in self.items:
|
for row in self.items:
|
||||||
@@ -1036,6 +1068,49 @@ class StockReconciliation(StockController):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 not row.current_serial_and_batch_bundle
|
||||||
|
):
|
||||||
|
self.set_current_serial_and_batch_bundle(voucher_detail_no, save=True)
|
||||||
|
row.reload()
|
||||||
|
|
||||||
|
self.add_missing_stock_ledger_entry(row, voucher_detail_no, sle_creation)
|
||||||
|
|
||||||
|
def add_missing_stock_ledger_entry(self, row, voucher_detail_no, sle_creation):
|
||||||
|
if row.current_qty == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
new_sle = frappe.get_doc(self.get_sle_for_items(row))
|
||||||
|
new_sle.actual_qty = row.current_qty * -1
|
||||||
|
new_sle.valuation_rate = row.current_valuation_rate
|
||||||
|
new_sle.serial_and_batch_bundle = row.current_serial_and_batch_bundle
|
||||||
|
new_sle.submit()
|
||||||
|
|
||||||
|
creation = add_to_date(sle_creation, seconds=-1)
|
||||||
|
new_sle.db_set("creation", creation)
|
||||||
|
|
||||||
|
if not frappe.db.exists(
|
||||||
|
"Repost Item Valuation",
|
||||||
|
{"item": row.item_code, "warehouse": row.warehouse, "docstatus": 1, "status": "Queued"},
|
||||||
|
):
|
||||||
|
create_repost_item_valuation_entry(
|
||||||
|
{
|
||||||
|
"based_on": "Item and Warehouse",
|
||||||
|
"item_code": row.item_code,
|
||||||
|
"warehouse": row.warehouse,
|
||||||
|
"company": self.company,
|
||||||
|
"allow_negative_stock": 1,
|
||||||
|
"posting_date": self.posting_date,
|
||||||
|
"posting_time": self.posting_time,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def has_negative_stock_allowed(self):
|
def has_negative_stock_allowed(self):
|
||||||
allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
|
allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
|
||||||
if allow_negative_stock:
|
if allow_negative_stock:
|
||||||
@@ -1109,6 +1184,7 @@ class StockReconciliation(StockController):
|
|||||||
ignore_voucher_nos=[doc.voucher_no],
|
ignore_voucher_nos=[doc.voucher_no],
|
||||||
for_stock_levels=True,
|
for_stock_levels=True,
|
||||||
consider_negative_batches=True,
|
consider_negative_batches=True,
|
||||||
|
do_not_check_future_batches=True,
|
||||||
)
|
)
|
||||||
or 0
|
or 0
|
||||||
) * -1
|
) * -1
|
||||||
|
|||||||
@@ -1069,7 +1069,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
|||||||
|
|
||||||
sr.reload()
|
sr.reload()
|
||||||
self.assertTrue(sr.items[0].serial_and_batch_bundle)
|
self.assertTrue(sr.items[0].serial_and_batch_bundle)
|
||||||
self.assertFalse(sr.items[0].current_serial_and_batch_bundle)
|
self.assertTrue(sr.items[0].current_serial_and_batch_bundle)
|
||||||
|
|
||||||
def test_not_reconcile_all_batch(self):
|
def test_not_reconcile_all_batch(self):
|
||||||
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||||
@@ -1446,6 +1446,74 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
|||||||
self.assertEqual(sr.difference_amount, 100 * -1)
|
self.assertEqual(sr.difference_amount, 100 * -1)
|
||||||
self.assertTrue(sr.items[0].qty == 0)
|
self.assertTrue(sr.items[0].qty == 0)
|
||||||
|
|
||||||
|
def test_stock_reco_recalculate_qty_for_backdated_entry(self):
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
|
||||||
|
item_code = self.make_item(
|
||||||
|
"Test Batch Item Stock Reco Recalculate Qty",
|
||||||
|
{
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "TEST-BATCH-RRQ-.###",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
|
sr = create_stock_reconciliation(
|
||||||
|
item_code=item_code,
|
||||||
|
warehouse=warehouse,
|
||||||
|
qty=10,
|
||||||
|
rate=100,
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
sr.reload()
|
||||||
|
self.assertEqual(sr.items[0].current_qty, 0)
|
||||||
|
self.assertEqual(sr.items[0].current_valuation_rate, 0)
|
||||||
|
|
||||||
|
batch_no = get_batch_from_bundle(sr.items[0].serial_and_batch_bundle)
|
||||||
|
stock_ledgers = frappe.get_all(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
filters={"voucher_no": sr.name, "is_cancelled": 0},
|
||||||
|
pluck="name",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(len(stock_ledgers) == 1)
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
target=warehouse,
|
||||||
|
qty=10,
|
||||||
|
basic_rate=100,
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
batch_no=batch_no,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make backdated stock reconciliation entry
|
||||||
|
create_stock_reconciliation(
|
||||||
|
item_code=item_code,
|
||||||
|
warehouse=warehouse,
|
||||||
|
qty=10,
|
||||||
|
rate=100,
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
batch_no=batch_no,
|
||||||
|
posting_date=add_days(nowdate(), -1),
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_ledgers = frappe.get_all(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
filters={"voucher_no": sr.name, "is_cancelled": 0},
|
||||||
|
pluck="name",
|
||||||
|
)
|
||||||
|
|
||||||
|
sr.reload()
|
||||||
|
self.assertEqual(sr.items[0].current_qty, 10)
|
||||||
|
self.assertEqual(sr.items[0].current_valuation_rate, 100)
|
||||||
|
|
||||||
|
self.assertTrue(len(stock_ledgers) == 2)
|
||||||
|
|
||||||
|
|
||||||
def create_batch_item_with_batch(item_name, batch_id):
|
def create_batch_item_with_batch(item_name, batch_id):
|
||||||
batch_item_doc = create_item(item_name, is_stock_item=1)
|
batch_item_doc = create_item(item_name, is_stock_item=1)
|
||||||
|
|||||||
@@ -743,6 +743,9 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
|||||||
if not self.sle.actual_qty:
|
if not self.sle.actual_qty:
|
||||||
self.sle.actual_qty = self.get_actual_qty()
|
self.sle.actual_qty = self.get_actual_qty()
|
||||||
|
|
||||||
|
if not self.sle.actual_qty:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
return abs(flt(self.stock_value_change) / flt(self.sle.actual_qty))
|
return abs(flt(self.stock_value_change) / flt(self.sle.actual_qty))
|
||||||
|
|
||||||
def get_actual_qty(self):
|
def get_actual_qty(self):
|
||||||
|
|||||||
@@ -962,7 +962,7 @@ class update_entries_after:
|
|||||||
|
|
||||||
def reset_actual_qty_for_stock_reco(self, sle):
|
def reset_actual_qty_for_stock_reco(self, sle):
|
||||||
doc = frappe.get_cached_doc("Stock Reconciliation", sle.voucher_no)
|
doc = frappe.get_cached_doc("Stock Reconciliation", sle.voucher_no)
|
||||||
doc.recalculate_current_qty(sle.voucher_detail_no)
|
doc.recalculate_current_qty(sle.voucher_detail_no, sle.creation, sle.actual_qty > 0)
|
||||||
|
|
||||||
if sle.actual_qty < 0:
|
if sle.actual_qty < 0:
|
||||||
sle.actual_qty = (
|
sle.actual_qty = (
|
||||||
|
|||||||
Reference in New Issue
Block a user