fix: incorrect active serial nos (cherry picked from commit 64cb1153de2d09883234e4e80e5306de27db328f) Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
This commit is contained in:
parent
ed06f0cd1b
commit
7def475eb1
@ -71,6 +71,10 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
let warehouse = this.item?.type_of_transaction === "Outward" ?
|
let warehouse = this.item?.type_of_transaction === "Outward" ?
|
||||||
(this.item.warehouse || this.item.s_warehouse) : "";
|
(this.item.warehouse || this.item.s_warehouse) : "";
|
||||||
|
|
||||||
|
if (!warehouse && this.frm.doc.doctype === 'Stock Reconciliation') {
|
||||||
|
warehouse = this.get_warehouse();
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'item_code': this.item.item_code,
|
'item_code': this.item.item_code,
|
||||||
'warehouse': ["=", warehouse]
|
'warehouse': ["=", warehouse]
|
||||||
|
@ -250,6 +250,7 @@ class SerialandBatchBundle(Document):
|
|||||||
|
|
||||||
for d in self.entries:
|
for d in self.entries:
|
||||||
available_qty = 0
|
available_qty = 0
|
||||||
|
|
||||||
if self.has_serial_no:
|
if self.has_serial_no:
|
||||||
d.incoming_rate = abs(sn_obj.serial_no_incoming_rate.get(d.serial_no, 0.0))
|
d.incoming_rate = abs(sn_obj.serial_no_incoming_rate.get(d.serial_no, 0.0))
|
||||||
else:
|
else:
|
||||||
@ -892,6 +893,13 @@ class SerialandBatchBundle(Document):
|
|||||||
elif batch_nos:
|
elif batch_nos:
|
||||||
self.set("entries", batch_nos)
|
self.set("entries", batch_nos)
|
||||||
|
|
||||||
|
def delete_serial_batch_entries(self):
|
||||||
|
SBBE = frappe.qb.DocType("Serial and Batch Entry")
|
||||||
|
|
||||||
|
frappe.qb.from_(SBBE).delete().where(SBBE.parent == self.name).run()
|
||||||
|
|
||||||
|
self.set("entries", [])
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def download_blank_csv_template(content):
|
def download_blank_csv_template(content):
|
||||||
@ -1374,10 +1382,12 @@ def get_available_serial_nos(kwargs):
|
|||||||
elif kwargs.based_on == "Expiry":
|
elif kwargs.based_on == "Expiry":
|
||||||
order_by = "amc_expiry_date asc"
|
order_by = "amc_expiry_date asc"
|
||||||
|
|
||||||
filters = {"item_code": kwargs.item_code, "warehouse": ("is", "set")}
|
filters = {"item_code": kwargs.item_code}
|
||||||
|
|
||||||
if kwargs.warehouse:
|
if not kwargs.get("ignore_warehouse"):
|
||||||
filters["warehouse"] = kwargs.warehouse
|
filters["warehouse"] = ("is", "set")
|
||||||
|
if kwargs.warehouse:
|
||||||
|
filters["warehouse"] = kwargs.warehouse
|
||||||
|
|
||||||
# Since SLEs are not present against Reserved Stock [POS invoices, SRE], need to ignore reserved serial nos.
|
# Since SLEs are not present against Reserved Stock [POS invoices, SRE], need to ignore reserved serial nos.
|
||||||
ignore_serial_nos = get_reserved_serial_nos(kwargs)
|
ignore_serial_nos = get_reserved_serial_nos(kwargs)
|
||||||
|
@ -156,6 +156,7 @@ class StockReconciliation(StockController):
|
|||||||
"warehouse": item.warehouse,
|
"warehouse": item.warehouse,
|
||||||
"posting_date": self.posting_date,
|
"posting_date": self.posting_date,
|
||||||
"posting_time": self.posting_time,
|
"posting_time": self.posting_time,
|
||||||
|
"ignore_warehouse": 1,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -780,7 +781,20 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
current_qty = 0.0
|
current_qty = 0.0
|
||||||
if row.current_serial_and_batch_bundle:
|
if row.current_serial_and_batch_bundle:
|
||||||
current_qty = self.get_qty_for_serial_and_batch_bundle(row)
|
current_qty = self.get_current_qty_for_serial_or_batch(row)
|
||||||
|
elif row.serial_no:
|
||||||
|
item_dict = get_stock_balance_for(
|
||||||
|
row.item_code,
|
||||||
|
row.warehouse,
|
||||||
|
self.posting_date,
|
||||||
|
self.posting_time,
|
||||||
|
voucher_no=self.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
current_qty = item_dict.get("qty")
|
||||||
|
row.current_serial_no = item_dict.get("serial_nos")
|
||||||
|
row.current_valuation_rate = item_dict.get("rate")
|
||||||
|
val_rate = item_dict.get("rate")
|
||||||
elif row.batch_no:
|
elif row.batch_no:
|
||||||
current_qty = get_batch_qty_for_stock_reco(
|
current_qty = get_batch_qty_for_stock_reco(
|
||||||
row.item_code, row.warehouse, row.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
|
||||||
@ -788,15 +802,16 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
precesion = row.precision("current_qty")
|
precesion = row.precision("current_qty")
|
||||||
if flt(current_qty, precesion) != flt(row.current_qty, precesion):
|
if flt(current_qty, precesion) != flt(row.current_qty, precesion):
|
||||||
val_rate = get_valuation_rate(
|
if not row.serial_no:
|
||||||
row.item_code,
|
val_rate = get_valuation_rate(
|
||||||
row.warehouse,
|
row.item_code,
|
||||||
self.doctype,
|
row.warehouse,
|
||||||
self.name,
|
self.doctype,
|
||||||
company=self.company,
|
self.name,
|
||||||
batch_no=row.batch_no,
|
company=self.company,
|
||||||
serial_and_batch_bundle=row.current_serial_and_batch_bundle,
|
batch_no=row.batch_no,
|
||||||
)
|
serial_and_batch_bundle=row.current_serial_and_batch_bundle,
|
||||||
|
)
|
||||||
|
|
||||||
row.current_valuation_rate = val_rate
|
row.current_valuation_rate = val_rate
|
||||||
row.current_qty = current_qty
|
row.current_qty = current_qty
|
||||||
@ -842,11 +857,56 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
return allow_negative_stock
|
return allow_negative_stock
|
||||||
|
|
||||||
def get_qty_for_serial_and_batch_bundle(self, row):
|
def get_current_qty_for_serial_or_batch(self, row):
|
||||||
doc = frappe.get_doc("Serial and Batch Bundle", row.current_serial_and_batch_bundle)
|
doc = frappe.get_doc("Serial and Batch Bundle", row.current_serial_and_batch_bundle)
|
||||||
precision = doc.entries[0].precision("qty")
|
current_qty = 0.0
|
||||||
|
if doc.has_serial_no:
|
||||||
|
current_qty = self.get_current_qty_for_serial_nos(doc)
|
||||||
|
elif doc.has_batch_no:
|
||||||
|
current_qty = self.get_current_qty_for_batch_nos(doc)
|
||||||
|
|
||||||
current_qty = 0
|
return abs(current_qty)
|
||||||
|
|
||||||
|
def get_current_qty_for_serial_nos(self, doc):
|
||||||
|
serial_nos_details = get_available_serial_nos(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"item_code": doc.item_code,
|
||||||
|
"warehouse": doc.warehouse,
|
||||||
|
"posting_date": self.posting_date,
|
||||||
|
"posting_time": self.posting_time,
|
||||||
|
"voucher_no": self.name,
|
||||||
|
"ignore_warehouse": 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not serial_nos_details:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
doc.delete_serial_batch_entries()
|
||||||
|
current_qty = 0.0
|
||||||
|
for serial_no_row in serial_nos_details:
|
||||||
|
current_qty += 1
|
||||||
|
doc.append(
|
||||||
|
"entries",
|
||||||
|
{
|
||||||
|
"serial_no": serial_no_row.serial_no,
|
||||||
|
"qty": -1,
|
||||||
|
"warehouse": doc.warehouse,
|
||||||
|
"batch_no": serial_no_row.batch_no,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
doc.set_incoming_rate(save=True)
|
||||||
|
doc.calculate_qty_and_amount(save=True)
|
||||||
|
doc.db_update_all()
|
||||||
|
|
||||||
|
return current_qty
|
||||||
|
|
||||||
|
def get_current_qty_for_batch_nos(self, doc):
|
||||||
|
current_qty = 0.0
|
||||||
|
precision = doc.entries[0].precision("qty")
|
||||||
for d in doc.entries:
|
for d in doc.entries:
|
||||||
qty = (
|
qty = (
|
||||||
get_batch_qty(
|
get_batch_qty(
|
||||||
@ -864,7 +924,7 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
current_qty += qty
|
current_qty += qty
|
||||||
|
|
||||||
return abs(current_qty)
|
return current_qty
|
||||||
|
|
||||||
|
|
||||||
def get_batch_qty_for_stock_reco(
|
def get_batch_qty_for_stock_reco(
|
||||||
|
@ -925,6 +925,74 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
|||||||
|
|
||||||
self.assertEqual(len(serial_batch_bundle), 0)
|
self.assertEqual(len(serial_batch_bundle), 0)
|
||||||
|
|
||||||
|
def test_backdated_purchase_receipt_with_stock_reco(self):
|
||||||
|
item_code = self.make_item(
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"serial_no_series": "TEST-SERIAL-.###",
|
||||||
|
}
|
||||||
|
).name
|
||||||
|
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
|
# Step - 1: Create a Backdated Purchase Receipt
|
||||||
|
|
||||||
|
pr1 = make_purchase_receipt(
|
||||||
|
item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), -3)
|
||||||
|
)
|
||||||
|
pr1.reload()
|
||||||
|
|
||||||
|
serial_nos = sorted(get_serial_nos_from_bundle(pr1.items[0].serial_and_batch_bundle))[:5]
|
||||||
|
|
||||||
|
# Step - 2: Create a Stock Reconciliation
|
||||||
|
sr1 = create_stock_reconciliation(
|
||||||
|
item_code=item_code,
|
||||||
|
warehouse=warehouse,
|
||||||
|
qty=5,
|
||||||
|
serial_no=serial_nos,
|
||||||
|
)
|
||||||
|
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
fields=["serial_no", "actual_qty", "stock_value_difference"],
|
||||||
|
filters={"voucher_no": sr1.name, "is_cancelled": 0},
|
||||||
|
order_by="creation",
|
||||||
|
)
|
||||||
|
|
||||||
|
for d in data:
|
||||||
|
if d.actual_qty < 0:
|
||||||
|
self.assertEqual(d.actual_qty, -10.0)
|
||||||
|
self.assertAlmostEqual(d.stock_value_difference, -1000.0)
|
||||||
|
else:
|
||||||
|
self.assertEqual(d.actual_qty, 5.0)
|
||||||
|
self.assertAlmostEqual(d.stock_value_difference, 500.0)
|
||||||
|
|
||||||
|
# Step - 3: Create a Purchase Receipt before the first Purchase Receipt
|
||||||
|
make_purchase_receipt(
|
||||||
|
item_code=item_code, warehouse=warehouse, qty=10, rate=200, posting_date=add_days(nowdate(), -5)
|
||||||
|
)
|
||||||
|
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
fields=["serial_no", "actual_qty", "stock_value_difference"],
|
||||||
|
filters={"voucher_no": sr1.name, "is_cancelled": 0},
|
||||||
|
order_by="creation",
|
||||||
|
)
|
||||||
|
|
||||||
|
for d in data:
|
||||||
|
if d.actual_qty < 0:
|
||||||
|
self.assertEqual(d.actual_qty, -20.0)
|
||||||
|
self.assertAlmostEqual(d.stock_value_difference, -3000.0)
|
||||||
|
else:
|
||||||
|
self.assertEqual(d.actual_qty, 5.0)
|
||||||
|
self.assertAlmostEqual(d.stock_value_difference, 500.0)
|
||||||
|
|
||||||
|
active_serial_no = frappe.get_all(
|
||||||
|
"Serial No", filters={"status": "Active", "item_code": item_code}
|
||||||
|
)
|
||||||
|
self.assertEqual(len(active_serial_no), 5)
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
|
@ -9,9 +9,18 @@ from typing import Optional, Set, Tuple
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, scrub
|
from frappe import _, scrub
|
||||||
from frappe.model.meta import get_field_precision
|
from frappe.model.meta import get_field_precision
|
||||||
from frappe.query_builder import Case
|
|
||||||
from frappe.query_builder.functions import CombineDatetime, Sum
|
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||||
from frappe.utils import cint, flt, get_link_to_form, getdate, now, nowdate, nowtime, parse_json
|
from frappe.utils import (
|
||||||
|
cint,
|
||||||
|
cstr,
|
||||||
|
flt,
|
||||||
|
get_link_to_form,
|
||||||
|
getdate,
|
||||||
|
now,
|
||||||
|
nowdate,
|
||||||
|
nowtime,
|
||||||
|
parse_json,
|
||||||
|
)
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty
|
from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty
|
||||||
@ -712,11 +721,10 @@ class update_entries_after(object):
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
sle.voucher_type == "Stock Reconciliation"
|
sle.voucher_type == "Stock Reconciliation"
|
||||||
and (
|
and (sle.batch_no or sle.serial_no or sle.serial_and_batch_bundle)
|
||||||
sle.batch_no or (sle.has_batch_no and sle.serial_and_batch_bundle and not sle.has_serial_no)
|
|
||||||
)
|
|
||||||
and sle.voucher_detail_no
|
and sle.voucher_detail_no
|
||||||
and not self.args.get("sle_id")
|
and not self.args.get("sle_id")
|
||||||
|
and sle.is_cancelled == 0
|
||||||
):
|
):
|
||||||
self.reset_actual_qty_for_stock_reco(sle)
|
self.reset_actual_qty_for_stock_reco(sle)
|
||||||
|
|
||||||
@ -737,6 +745,23 @@ class update_entries_after(object):
|
|||||||
|
|
||||||
if sle.serial_and_batch_bundle:
|
if sle.serial_and_batch_bundle:
|
||||||
self.calculate_valuation_for_serial_batch_bundle(sle)
|
self.calculate_valuation_for_serial_batch_bundle(sle)
|
||||||
|
elif sle.serial_no and not self.args.get("sle_id"):
|
||||||
|
# Only run in reposting
|
||||||
|
self.get_serialized_values(sle)
|
||||||
|
self.wh_data.qty_after_transaction += flt(sle.actual_qty)
|
||||||
|
if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no:
|
||||||
|
self.wh_data.qty_after_transaction = sle.qty_after_transaction
|
||||||
|
|
||||||
|
self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(
|
||||||
|
self.wh_data.valuation_rate
|
||||||
|
)
|
||||||
|
elif (
|
||||||
|
sle.batch_no
|
||||||
|
and frappe.db.get_value("Batch", sle.batch_no, "use_batchwise_valuation", cache=True)
|
||||||
|
and not self.args.get("sle_id")
|
||||||
|
):
|
||||||
|
# Only run in reposting
|
||||||
|
self.update_batched_values(sle)
|
||||||
else:
|
else:
|
||||||
if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no and not has_dimensions:
|
if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no and not has_dimensions:
|
||||||
# assert
|
# assert
|
||||||
@ -782,6 +807,45 @@ class update_entries_after(object):
|
|||||||
):
|
):
|
||||||
self.update_outgoing_rate_on_transaction(sle)
|
self.update_outgoing_rate_on_transaction(sle)
|
||||||
|
|
||||||
|
def get_serialized_values(self, sle):
|
||||||
|
incoming_rate = flt(sle.incoming_rate)
|
||||||
|
actual_qty = flt(sle.actual_qty)
|
||||||
|
serial_nos = cstr(sle.serial_no).split("\n")
|
||||||
|
|
||||||
|
if incoming_rate < 0:
|
||||||
|
# wrong incoming rate
|
||||||
|
incoming_rate = self.wh_data.valuation_rate
|
||||||
|
|
||||||
|
stock_value_change = 0
|
||||||
|
if actual_qty > 0:
|
||||||
|
stock_value_change = actual_qty * incoming_rate
|
||||||
|
else:
|
||||||
|
# In case of delivery/stock issue, get average purchase rate
|
||||||
|
# of serial nos of current entry
|
||||||
|
if not sle.is_cancelled:
|
||||||
|
outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos)
|
||||||
|
stock_value_change = -1 * outgoing_value
|
||||||
|
else:
|
||||||
|
stock_value_change = actual_qty * sle.outgoing_rate
|
||||||
|
|
||||||
|
new_stock_qty = self.wh_data.qty_after_transaction + actual_qty
|
||||||
|
|
||||||
|
if new_stock_qty > 0:
|
||||||
|
new_stock_value = (
|
||||||
|
self.wh_data.qty_after_transaction * self.wh_data.valuation_rate
|
||||||
|
) + stock_value_change
|
||||||
|
if new_stock_value >= 0:
|
||||||
|
# calculate new valuation rate only if stock value is positive
|
||||||
|
# else it remains the same as that of previous entry
|
||||||
|
self.wh_data.valuation_rate = new_stock_value / new_stock_qty
|
||||||
|
|
||||||
|
if not self.wh_data.valuation_rate and sle.voucher_detail_no:
|
||||||
|
allow_zero_rate = self.check_if_allow_zero_valuation_rate(
|
||||||
|
sle.voucher_type, sle.voucher_detail_no
|
||||||
|
)
|
||||||
|
if not allow_zero_rate:
|
||||||
|
self.wh_data.valuation_rate = self.get_fallback_rate(sle)
|
||||||
|
|
||||||
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, sle.creation, sle.actual_qty > 0)
|
doc.recalculate_current_qty(sle.voucher_detail_no, sle.creation, sle.actual_qty > 0)
|
||||||
@ -795,6 +859,36 @@ class update_entries_after(object):
|
|||||||
if abs(sle.actual_qty) == 0.0:
|
if abs(sle.actual_qty) == 0.0:
|
||||||
sle.is_cancelled = 1
|
sle.is_cancelled = 1
|
||||||
|
|
||||||
|
if sle.serial_and_batch_bundle and frappe.get_cached_value(
|
||||||
|
"Item", sle.item_code, "has_serial_no"
|
||||||
|
):
|
||||||
|
self.update_serial_no_status(sle)
|
||||||
|
|
||||||
|
def update_serial_no_status(self, sle):
|
||||||
|
from erpnext.stock.serial_batch_bundle import get_serial_nos
|
||||||
|
|
||||||
|
serial_nos = get_serial_nos(sle.serial_and_batch_bundle)
|
||||||
|
if not serial_nos:
|
||||||
|
return
|
||||||
|
|
||||||
|
warehouse = None
|
||||||
|
status = "Inactive"
|
||||||
|
|
||||||
|
if sle.actual_qty > 0:
|
||||||
|
warehouse = sle.warehouse
|
||||||
|
status = "Active"
|
||||||
|
|
||||||
|
sn_table = frappe.qb.DocType("Serial No")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.update(sn_table)
|
||||||
|
.set(sn_table.warehouse, warehouse)
|
||||||
|
.set(sn_table.status, status)
|
||||||
|
.where(sn_table.name.isin(serial_nos))
|
||||||
|
)
|
||||||
|
|
||||||
|
query.run()
|
||||||
|
|
||||||
def calculate_valuation_for_serial_batch_bundle(self, sle):
|
def calculate_valuation_for_serial_batch_bundle(self, sle):
|
||||||
doc = frappe.get_cached_doc("Serial and Batch Bundle", sle.serial_and_batch_bundle)
|
doc = frappe.get_cached_doc("Serial and Batch Bundle", sle.serial_and_batch_bundle)
|
||||||
|
|
||||||
@ -1171,11 +1265,12 @@ class update_entries_after(object):
|
|||||||
outgoing_rate = get_batch_incoming_rate(
|
outgoing_rate = get_batch_incoming_rate(
|
||||||
item_code=sle.item_code,
|
item_code=sle.item_code,
|
||||||
warehouse=sle.warehouse,
|
warehouse=sle.warehouse,
|
||||||
serial_and_batch_bundle=sle.serial_and_batch_bundle,
|
batch_no=sle.batch_no,
|
||||||
posting_date=sle.posting_date,
|
posting_date=sle.posting_date,
|
||||||
posting_time=sle.posting_time,
|
posting_time=sle.posting_time,
|
||||||
creation=sle.creation,
|
creation=sle.creation,
|
||||||
)
|
)
|
||||||
|
|
||||||
if outgoing_rate is None:
|
if outgoing_rate is None:
|
||||||
# This can *only* happen if qty available for the batch is zero.
|
# This can *only* happen if qty available for the batch is zero.
|
||||||
# in such case fall back various other rates.
|
# in such case fall back various other rates.
|
||||||
@ -1449,11 +1544,10 @@ def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None):
|
|||||||
|
|
||||||
|
|
||||||
def get_batch_incoming_rate(
|
def get_batch_incoming_rate(
|
||||||
item_code, warehouse, serial_and_batch_bundle, posting_date, posting_time, creation=None
|
item_code, warehouse, batch_no, posting_date, posting_time, creation=None
|
||||||
):
|
):
|
||||||
|
|
||||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
batch_ledger = frappe.qb.DocType("Serial and Batch Entry")
|
|
||||||
|
|
||||||
timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime(
|
timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime(
|
||||||
posting_date, posting_time
|
posting_date, posting_time
|
||||||
@ -1464,28 +1558,13 @@ def get_batch_incoming_rate(
|
|||||||
== CombineDatetime(posting_date, posting_time)
|
== CombineDatetime(posting_date, posting_time)
|
||||||
) & (sle.creation < creation)
|
) & (sle.creation < creation)
|
||||||
|
|
||||||
batches = frappe.get_all(
|
|
||||||
"Serial and Batch Entry", fields=["batch_no"], filters={"parent": serial_and_batch_bundle}
|
|
||||||
)
|
|
||||||
|
|
||||||
batch_details = (
|
batch_details = (
|
||||||
frappe.qb.from_(sle)
|
frappe.qb.from_(sle)
|
||||||
.inner_join(batch_ledger)
|
.select(Sum(sle.stock_value_difference).as_("batch_value"), Sum(sle.actual_qty).as_("batch_qty"))
|
||||||
.on(sle.serial_and_batch_bundle == batch_ledger.parent)
|
|
||||||
.select(
|
|
||||||
Sum(
|
|
||||||
Case()
|
|
||||||
.when(sle.actual_qty > 0, batch_ledger.qty * batch_ledger.incoming_rate)
|
|
||||||
.else_(batch_ledger.qty * batch_ledger.outgoing_rate * -1)
|
|
||||||
).as_("batch_value"),
|
|
||||||
Sum(Case().when(sle.actual_qty > 0, batch_ledger.qty).else_(batch_ledger.qty * -1)).as_(
|
|
||||||
"batch_qty"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.where(
|
.where(
|
||||||
(sle.item_code == item_code)
|
(sle.item_code == item_code)
|
||||||
& (sle.warehouse == warehouse)
|
& (sle.warehouse == warehouse)
|
||||||
& (batch_ledger.batch_no.isin([row.batch_no for row in batches]))
|
& (sle.batch_no == batch_no)
|
||||||
& (sle.is_cancelled == 0)
|
& (sle.is_cancelled == 0)
|
||||||
)
|
)
|
||||||
.where(timestamp_condition)
|
.where(timestamp_condition)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user