mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-02 04:58:29 +00:00
fix: allow to fix negative stock for batch using stock reco
(cherry picked from commit 2e8cde3378)
This commit is contained in:
committed by
Mergify
parent
f9d96726f0
commit
69c5695f6e
@@ -54,7 +54,12 @@ frappe.ui.form.on("Batch", {
|
|||||||
|
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.stock.doctype.batch.batch.get_batch_qty",
|
method: "erpnext.stock.doctype.batch.batch.get_batch_qty",
|
||||||
args: { batch_no: frm.doc.name, item_code: frm.doc.item, for_stock_levels: for_stock_levels },
|
args: {
|
||||||
|
batch_no: frm.doc.name,
|
||||||
|
item_code: frm.doc.item,
|
||||||
|
for_stock_levels: for_stock_levels,
|
||||||
|
consider_negative_batches: 1,
|
||||||
|
},
|
||||||
callback: (r) => {
|
callback: (r) => {
|
||||||
if (!r.message) {
|
if (!r.message) {
|
||||||
return;
|
return;
|
||||||
@@ -71,7 +76,7 @@ frappe.ui.form.on("Batch", {
|
|||||||
|
|
||||||
// show
|
// show
|
||||||
(r.message || []).forEach(function (d) {
|
(r.message || []).forEach(function (d) {
|
||||||
if (d.qty > 0) {
|
if (d.qty != 0) {
|
||||||
$(`<div class='row' style='margin-bottom: 10px;'>
|
$(`<div class='row' style='margin-bottom: 10px;'>
|
||||||
<div class='col-sm-3 small' style='padding-top: 3px;'>${d.warehouse}</div>
|
<div class='col-sm-3 small' style='padding-top: 3px;'>${d.warehouse}</div>
|
||||||
<div class='col-sm-3 small text-right' style='padding-top: 3px;'>${d.qty}</div>
|
<div class='col-sm-3 small text-right' style='padding-top: 3px;'>${d.qty}</div>
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ def get_batch_qty(
|
|||||||
posting_time=None,
|
posting_time=None,
|
||||||
ignore_voucher_nos=None,
|
ignore_voucher_nos=None,
|
||||||
for_stock_levels=False,
|
for_stock_levels=False,
|
||||||
|
consider_negative_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
|
||||||
@@ -243,6 +244,7 @@ def get_batch_qty(
|
|||||||
"batch_no": batch_no,
|
"batch_no": batch_no,
|
||||||
"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,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -139,8 +139,8 @@ class StockReconciliation(StockController):
|
|||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"voucher_detail_no": row.name,
|
"voucher_detail_no": row.name,
|
||||||
"qty": row.current_qty,
|
"qty": row.current_qty * -1,
|
||||||
"type_of_transaction": "Outward",
|
"type_of_transaction": "Outward" if row.current_qty > 0 else "Inward",
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"is_rejected": 0,
|
"is_rejected": 0,
|
||||||
"serial_nos": get_serial_nos(row.current_serial_no)
|
"serial_nos": get_serial_nos(row.current_serial_no)
|
||||||
@@ -1367,6 +1367,7 @@ def get_stock_balance_for(
|
|||||||
posting_date=posting_date,
|
posting_date=posting_date,
|
||||||
posting_time=posting_time,
|
posting_time=posting_time,
|
||||||
for_stock_levels=True,
|
for_stock_levels=True,
|
||||||
|
consider_negative_batches=True,
|
||||||
)
|
)
|
||||||
or 0
|
or 0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1330,6 +1330,84 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
|||||||
|
|
||||||
self.assertEqual(stock_value_difference, 1500.00 * -1)
|
self.assertEqual(stock_value_difference, 1500.00 * -1)
|
||||||
|
|
||||||
|
def test_stock_reco_for_negative_batch(self):
|
||||||
|
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
|
||||||
|
item_code = self.make_item(
|
||||||
|
"Test Item For Negative Batch",
|
||||||
|
{
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "TEST-BATCH-NB-.###",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
|
se = make_stock_entry(
|
||||||
|
posting_date="2024-11-01",
|
||||||
|
posting_time="11:00",
|
||||||
|
item_code=item_code,
|
||||||
|
target=warehouse,
|
||||||
|
qty=10,
|
||||||
|
basic_rate=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
se = make_stock_entry(
|
||||||
|
posting_date="2024-11-01",
|
||||||
|
posting_time="11:00",
|
||||||
|
item_code=item_code,
|
||||||
|
source=warehouse,
|
||||||
|
qty=10,
|
||||||
|
basic_rate=100,
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
batch_no=batch_no,
|
||||||
|
)
|
||||||
|
|
||||||
|
sles = frappe.get_all(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
filters={"voucher_no": se.name, "is_cancelled": 0},
|
||||||
|
)
|
||||||
|
|
||||||
|
# intentionally setting negative qty
|
||||||
|
doc = frappe.get_doc("Stock Ledger Entry", sles[0].name)
|
||||||
|
doc.db_set(
|
||||||
|
{
|
||||||
|
"actual_qty": -20,
|
||||||
|
"qty_after_transaction": -10,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
sabb_doc = frappe.get_doc("Serial and Batch Bundle", doc.serial_and_batch_bundle)
|
||||||
|
for row in sabb_doc.entries:
|
||||||
|
row.db_set("qty", -20)
|
||||||
|
|
||||||
|
batch_qty = get_batch_qty(batch_no, warehouse, item_code, consider_negative_batches=True)
|
||||||
|
self.assertEqual(batch_qty, -10)
|
||||||
|
|
||||||
|
sr = create_stock_reconciliation(
|
||||||
|
posting_date="2024-11-02",
|
||||||
|
posting_time="11:00",
|
||||||
|
item_code=item_code,
|
||||||
|
warehouse=warehouse,
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
batch_no=batch_no,
|
||||||
|
qty=0,
|
||||||
|
rate=100,
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(sr.items[0].current_qty, -10)
|
||||||
|
sr.submit()
|
||||||
|
sr.reload()
|
||||||
|
|
||||||
|
self.assertTrue(sr.items[0].current_serial_and_batch_bundle)
|
||||||
|
self.assertFalse(sr.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -418,7 +418,13 @@ class SerialBatchBundle:
|
|||||||
batches = frappe._dict({self.sle.batch_no: self.sle.actual_qty})
|
batches = frappe._dict({self.sle.batch_no: self.sle.actual_qty})
|
||||||
|
|
||||||
batches_qty = get_available_batches(
|
batches_qty = get_available_batches(
|
||||||
frappe._dict({"item_code": self.item_code, "batch_no": list(batches.keys())})
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"item_code": self.item_code,
|
||||||
|
"batch_no": list(batches.keys()),
|
||||||
|
"consider_negative_batches": 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
for batch_no in batches:
|
for batch_no in batches:
|
||||||
|
|||||||
Reference in New Issue
Block a user