feat: added negative inventory validation and restrict to make backdated entry for serial nos
This commit is contained in:
@@ -38,6 +38,7 @@ class BuyingController(SubcontractingController):
|
|||||||
self.set_supplier_address()
|
self.set_supplier_address()
|
||||||
self.validate_asset_return()
|
self.validate_asset_return()
|
||||||
self.validate_auto_repeat_subscription_dates()
|
self.validate_auto_repeat_subscription_dates()
|
||||||
|
self.create_package_for_transfer()
|
||||||
|
|
||||||
if self.doctype == "Purchase Invoice":
|
if self.doctype == "Purchase Invoice":
|
||||||
self.validate_purchase_receipt_if_update_stock()
|
self.validate_purchase_receipt_if_update_stock()
|
||||||
@@ -69,6 +70,36 @@ class BuyingController(SubcontractingController):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def create_package_for_transfer(self) -> None:
|
||||||
|
"""Create serial and batch package for Sourece Warehouse in case of inter transfer."""
|
||||||
|
|
||||||
|
if self.is_internal_transfer() and (
|
||||||
|
self.doctype == "Purchase Receipt" or (self.doctype == "Purchase Invoice" and self.update_stock)
|
||||||
|
):
|
||||||
|
field = "delivery_note_item" if self.doctype == "Purchase Receipt" else "sales_invoice_item"
|
||||||
|
|
||||||
|
doctype = "Delivery Note Item" if self.doctype == "Purchase Receipt" else "Sales Invoice Item"
|
||||||
|
|
||||||
|
ids = [d.get(field) for d in self.get("items") if d.get(field)]
|
||||||
|
bundle_ids = {}
|
||||||
|
if ids:
|
||||||
|
for bundle in frappe.get_all(
|
||||||
|
doctype, filters={"name": ("in", ids)}, fields=["serial_and_batch_bundle", "name"]
|
||||||
|
):
|
||||||
|
bundle_ids[bundle.name] = bundle.serial_and_batch_bundle
|
||||||
|
|
||||||
|
if not bundle_ids:
|
||||||
|
return
|
||||||
|
|
||||||
|
for item in self.get("items"):
|
||||||
|
if item.get(field) and not item.serial_and_batch_bundle:
|
||||||
|
item.serial_and_batch_bundle = self.make_package_for_transfer(
|
||||||
|
bundle_ids.get(item.get(field)),
|
||||||
|
item.from_warehouse,
|
||||||
|
type_of_transaction="Outward",
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
|
||||||
def set_missing_values(self, for_validate=False):
|
def set_missing_values(self, for_validate=False):
|
||||||
super(BuyingController, self).set_missing_values(for_validate)
|
super(BuyingController, self).set_missing_values(for_validate)
|
||||||
|
|
||||||
@@ -467,7 +498,11 @@ class BuyingController(SubcontractingController):
|
|||||||
{
|
{
|
||||||
"actual_qty": flt(pr_qty),
|
"actual_qty": flt(pr_qty),
|
||||||
"serial_no": cstr(d.serial_no).strip(),
|
"serial_no": cstr(d.serial_no).strip(),
|
||||||
"serial_and_batch_bundle": d.serial_and_batch_bundle,
|
"serial_and_batch_bundle": (
|
||||||
|
d.serial_and_batch_bundle
|
||||||
|
if not self.is_internal_transfer()
|
||||||
|
else self.get_package_for_target_warehouse(d)
|
||||||
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -494,7 +529,6 @@ class BuyingController(SubcontractingController):
|
|||||||
"recalculate_rate": 1
|
"recalculate_rate": 1
|
||||||
if (self.is_subcontracted and (d.bom or d.fg_item)) or d.from_warehouse
|
if (self.is_subcontracted and (d.bom or d.fg_item)) or d.from_warehouse
|
||||||
else 0,
|
else 0,
|
||||||
"serial_and_batch_bundle": d.serial_and_batch_bundle,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
sl_entries.append(sle)
|
sl_entries.append(sle)
|
||||||
@@ -531,6 +565,15 @@ class BuyingController(SubcontractingController):
|
|||||||
via_landed_cost_voucher=via_landed_cost_voucher,
|
via_landed_cost_voucher=via_landed_cost_voucher,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_package_for_target_warehouse(self, item) -> str:
|
||||||
|
if not item.serial_and_batch_bundle:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
return self.make_package_for_transfer(
|
||||||
|
item.serial_and_batch_bundle,
|
||||||
|
item.warehouse,
|
||||||
|
)
|
||||||
|
|
||||||
def update_ordered_and_reserved_qty(self):
|
def update_ordered_and_reserved_qty(self):
|
||||||
po_map = {}
|
po_map = {}
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
|
|||||||
@@ -531,6 +531,11 @@ class SellingController(StockController):
|
|||||||
if item_row.warehouse:
|
if item_row.warehouse:
|
||||||
sle.dependant_sle_voucher_detail_no = item_row.name
|
sle.dependant_sle_voucher_detail_no = item_row.name
|
||||||
|
|
||||||
|
if item_row.serial_and_batch_bundle:
|
||||||
|
sle["serial_and_batch_bundle"] = self.make_package_for_transfer(
|
||||||
|
item_row.serial_and_batch_bundle, item_row.target_warehouse
|
||||||
|
)
|
||||||
|
|
||||||
return sle
|
return sle
|
||||||
|
|
||||||
def set_po_nos(self, for_validate=False):
|
def set_po_nos(self, for_validate=False):
|
||||||
|
|||||||
@@ -372,6 +372,44 @@ class StockController(AccountsController):
|
|||||||
|
|
||||||
row.db_set("serial_and_batch_bundle", None)
|
row.db_set("serial_and_batch_bundle", None)
|
||||||
|
|
||||||
|
def make_package_for_transfer(
|
||||||
|
self, serial_and_batch_bundle, warehouse, type_of_transaction=None, do_not_submit=None
|
||||||
|
):
|
||||||
|
bundle_doc = frappe.get_doc("Serial and Batch Bundle", serial_and_batch_bundle)
|
||||||
|
|
||||||
|
if not type_of_transaction:
|
||||||
|
type_of_transaction = "Inward"
|
||||||
|
|
||||||
|
bundle_doc = frappe.copy_doc(bundle_doc)
|
||||||
|
bundle_doc.warehouse = warehouse
|
||||||
|
bundle_doc.type_of_transaction = type_of_transaction
|
||||||
|
bundle_doc.voucher_type = self.doctype
|
||||||
|
bundle_doc.voucher_no = self.name
|
||||||
|
bundle_doc.is_cancelled = 0
|
||||||
|
|
||||||
|
for row in bundle_doc.ledgers:
|
||||||
|
row.is_outward = 0
|
||||||
|
row.qty = abs(row.qty)
|
||||||
|
row.stock_value_difference = abs(row.stock_value_difference)
|
||||||
|
if type_of_transaction == "Outward":
|
||||||
|
row.qty *= -1
|
||||||
|
row.stock_value_difference *= row.stock_value_difference
|
||||||
|
row.is_outward = 1
|
||||||
|
|
||||||
|
row.warehouse = warehouse
|
||||||
|
|
||||||
|
bundle_doc.set_total_qty()
|
||||||
|
bundle_doc.set_avg_rate()
|
||||||
|
bundle_doc.flags.ignore_permissions = True
|
||||||
|
|
||||||
|
if not do_not_submit:
|
||||||
|
bundle_doc.submit()
|
||||||
|
else:
|
||||||
|
bundle_doc.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
print(bundle_doc.name)
|
||||||
|
return bundle_doc.name
|
||||||
|
|
||||||
def get_sl_entries(self, d, args):
|
def get_sl_entries(self, d, args):
|
||||||
sl_dict = frappe._dict(
|
sl_dict = frappe._dict(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ class DeprecatedBatchNoValuation:
|
|||||||
def calculate_avg_rate_from_deprecarated_ledgers(self):
|
def calculate_avg_rate_from_deprecarated_ledgers(self):
|
||||||
ledgers = self.get_sle_for_batches()
|
ledgers = self.get_sle_for_batches()
|
||||||
for ledger in ledgers:
|
for ledger in ledgers:
|
||||||
self.batch_avg_rate[ledger.batch_no] += flt(ledger.incoming_rate) / flt(ledger.qty)
|
self.batch_avg_rate[ledger.batch_no] += flt(ledger.batch_value) / flt(ledger.batch_qty)
|
||||||
|
self.available_qty[ledger.batch_no] += flt(ledger.batch_qty)
|
||||||
|
|
||||||
def get_sle_for_batches(self):
|
def get_sle_for_batches(self):
|
||||||
batch_nos = list(self.batch_nos.keys())
|
batch_nos = list(self.batch_nos.keys())
|
||||||
|
|||||||
@@ -1044,8 +1044,6 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
"field_map": {
|
"field_map": {
|
||||||
source_document_warehouse_field: target_document_warehouse_field,
|
source_document_warehouse_field: target_document_warehouse_field,
|
||||||
"name": "delivery_note_item",
|
"name": "delivery_note_item",
|
||||||
"batch_no": "batch_no",
|
|
||||||
"serial_no": "serial_no",
|
|
||||||
"purchase_order": "purchase_order",
|
"purchase_order": "purchase_order",
|
||||||
"purchase_order_item": "purchase_order_item",
|
"purchase_order_item": "purchase_order_item",
|
||||||
"material_request": "material_request",
|
"material_request": "material_request",
|
||||||
|
|||||||
@@ -7,19 +7,24 @@ from typing import Dict, List
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, bold
|
from frappe import _, bold
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||||
from frappe.utils import cint, flt, today
|
from frappe.utils import cint, flt, get_link_to_form, today
|
||||||
from pypika import Case
|
from pypika import Case
|
||||||
|
|
||||||
from erpnext.stock.serial_batch_bundle import BatchNoBundleValuation, SerialNoBundleValuation
|
from erpnext.stock.serial_batch_bundle import BatchNoBundleValuation, SerialNoBundleValuation
|
||||||
|
|
||||||
|
|
||||||
|
class SerialNoExistsInFutureTransactionError(frappe.ValidationError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SerialandBatchBundle(Document):
|
class SerialandBatchBundle(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_serial_and_batch_no()
|
self.validate_serial_and_batch_no()
|
||||||
self.validate_duplicate_serial_and_batch_no()
|
self.validate_duplicate_serial_and_batch_no()
|
||||||
# self.validate_voucher_no()
|
# self.validate_voucher_no()
|
||||||
self.validate_serial_nos()
|
self.check_future_entries_exists()
|
||||||
|
self.validate_serial_nos_inventory()
|
||||||
|
|
||||||
def before_save(self):
|
def before_save(self):
|
||||||
self.set_total_qty()
|
self.set_total_qty()
|
||||||
@@ -31,6 +36,26 @@ class SerialandBatchBundle(Document):
|
|||||||
if self.ledgers:
|
if self.ledgers:
|
||||||
self.set_avg_rate()
|
self.set_avg_rate()
|
||||||
|
|
||||||
|
def validate_serial_nos_inventory(self):
|
||||||
|
if not (self.has_serial_no and self.type_of_transaction == "Outward"):
|
||||||
|
return
|
||||||
|
|
||||||
|
serial_nos = [d.serial_no for d in self.ledgers if d.serial_no]
|
||||||
|
serial_no_warehouse = frappe._dict(
|
||||||
|
frappe.get_all(
|
||||||
|
"Serial No",
|
||||||
|
filters={"name": ("in", serial_nos)},
|
||||||
|
fields=["name", "warehouse"],
|
||||||
|
as_list=1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for serial_no in serial_nos:
|
||||||
|
if serial_no_warehouse.get(serial_no) != self.warehouse:
|
||||||
|
frappe.throw(
|
||||||
|
_(f"Serial No {bold(serial_no)} is not present in the warehouse {bold(self.warehouse)}.")
|
||||||
|
)
|
||||||
|
|
||||||
def set_incoming_rate(self, row=None, save=False):
|
def set_incoming_rate(self, row=None, save=False):
|
||||||
if self.type_of_transaction == "Outward":
|
if self.type_of_transaction == "Outward":
|
||||||
self.set_incoming_rate_for_outward_transaction(row, save)
|
self.set_incoming_rate_for_outward_transaction(row, save)
|
||||||
@@ -65,10 +90,14 @@ class SerialandBatchBundle(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for d in self.ledgers:
|
for d in self.ledgers:
|
||||||
|
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:
|
||||||
d.incoming_rate = abs(sn_obj.batch_avg_rate.get(d.batch_no))
|
d.incoming_rate = abs(sn_obj.batch_avg_rate.get(d.batch_no))
|
||||||
|
available_qty = sn_obj.batch_available_qty.get(d.batch_no) + d.qty
|
||||||
|
|
||||||
|
self.validate_negative_batch(d.batch_no, available_qty)
|
||||||
|
|
||||||
if self.has_batch_no:
|
if self.has_batch_no:
|
||||||
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
|
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
|
||||||
@@ -78,6 +107,14 @@ class SerialandBatchBundle(Document):
|
|||||||
{"incoming_rate": d.incoming_rate, "stock_value_difference": d.stock_value_difference}
|
{"incoming_rate": d.incoming_rate, "stock_value_difference": d.stock_value_difference}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_negative_batch(self, batch_no, available_qty):
|
||||||
|
if available_qty < 0:
|
||||||
|
msg = f"""Batch No {bold(batch_no)} has negative stock
|
||||||
|
of quantity {bold(available_qty)} in the
|
||||||
|
warehouse {self.warehouse}"""
|
||||||
|
|
||||||
|
frappe.throw(_(msg))
|
||||||
|
|
||||||
def get_sle_for_outward_transaction(self, row):
|
def get_sle_for_outward_transaction(self, row):
|
||||||
return frappe._dict(
|
return frappe._dict(
|
||||||
{
|
{
|
||||||
@@ -169,10 +206,54 @@ class SerialandBatchBundle(Document):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_serial_nos(self):
|
def check_future_entries_exists(self):
|
||||||
if not self.has_serial_no:
|
if not self.has_serial_no:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
serial_nos = [d.serial_no for d in self.ledgers if d.serial_no]
|
||||||
|
|
||||||
|
parent = frappe.qb.DocType("Serial and Batch Bundle")
|
||||||
|
child = frappe.qb.DocType("Serial and Batch Ledger")
|
||||||
|
|
||||||
|
timestamp_condition = CombineDatetime(
|
||||||
|
parent.posting_date, parent.posting_time
|
||||||
|
) > CombineDatetime(self.posting_date, self.posting_time)
|
||||||
|
|
||||||
|
future_entries = (
|
||||||
|
frappe.qb.from_(parent)
|
||||||
|
.inner_join(child)
|
||||||
|
.on(parent.name == child.parent)
|
||||||
|
.select(
|
||||||
|
child.serial_no,
|
||||||
|
parent.voucher_type,
|
||||||
|
parent.voucher_no,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(child.serial_no.isin(serial_nos))
|
||||||
|
& (child.parent != self.name)
|
||||||
|
& (parent.item_code == self.item_code)
|
||||||
|
& (parent.docstatus == 1)
|
||||||
|
& (parent.is_cancelled == 0)
|
||||||
|
)
|
||||||
|
.where(timestamp_condition)
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
if future_entries:
|
||||||
|
msg = """The serial nos has been used in the future
|
||||||
|
transactions so you need to cancel them first.
|
||||||
|
The list of serial nos and their respective
|
||||||
|
transactions are as below."""
|
||||||
|
|
||||||
|
msg += "<br><br><ul>"
|
||||||
|
|
||||||
|
for d in future_entries:
|
||||||
|
msg += f"<li>{d.serial_no} in {get_link_to_form(d.voucher_type, d.voucher_no)}</li>"
|
||||||
|
msg += "</li></ul>"
|
||||||
|
|
||||||
|
title = "Serial No Exists In Future Transaction(s)"
|
||||||
|
|
||||||
|
frappe.throw(_(msg), title=_(title), exc=SerialNoExistsInFutureTransactionError)
|
||||||
|
|
||||||
def validate_quantity(self, row):
|
def validate_quantity(self, row):
|
||||||
self.set_total_qty(save=True)
|
self.set_total_qty(save=True)
|
||||||
|
|
||||||
@@ -429,8 +510,19 @@ def update_serial_batch_no_ledgers(ledgers, child_row, parent_doc) -> object:
|
|||||||
doc.posting_date = parent_doc.posting_date
|
doc.posting_date = parent_doc.posting_date
|
||||||
doc.posting_time = parent_doc.posting_time
|
doc.posting_time = parent_doc.posting_time
|
||||||
doc.set("ledgers", [])
|
doc.set("ledgers", [])
|
||||||
doc.set("ledgers", ledgers)
|
|
||||||
doc.save()
|
for d in ledgers:
|
||||||
|
doc.append(
|
||||||
|
"ledgers",
|
||||||
|
{
|
||||||
|
"qty": 1 if doc.type_of_transaction == "Inward" else -1,
|
||||||
|
"warehouse": d.get("warehouse"),
|
||||||
|
"batch_no": d.get("batch_no"),
|
||||||
|
"serial_no": d.get("serial_no"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
doc.save(ignore_permissions=True)
|
||||||
|
|
||||||
frappe.msgprint(_("Serial and Batch Bundle updated"), alert=True)
|
frappe.msgprint(_("Serial and Batch Bundle updated"), alert=True)
|
||||||
|
|
||||||
|
|||||||
@@ -1219,8 +1219,16 @@ class StockEntry(StockController):
|
|||||||
if cstr(d.s_warehouse) or (finished_item_row and d.name == finished_item_row.name):
|
if cstr(d.s_warehouse) or (finished_item_row and d.name == finished_item_row.name):
|
||||||
sle.recalculate_rate = 1
|
sle.recalculate_rate = 1
|
||||||
|
|
||||||
if d.serial_and_batch_bundle and self.docstatus == 1:
|
allowed_types = [
|
||||||
d.serial_and_batch_bundle = self.copy_serial_and_batch_bundle(sle)
|
"Material Transfer",
|
||||||
|
"Send to Subcontractor",
|
||||||
|
"Material Transfer for Manufacture",
|
||||||
|
]
|
||||||
|
|
||||||
|
if self.purpose in allowed_types and d.serial_and_batch_bundle and self.docstatus == 1:
|
||||||
|
d.serial_and_batch_bundle = self.make_package_for_transfer(
|
||||||
|
d.serial_and_batch_bundle, d.t_warehouse
|
||||||
|
)
|
||||||
|
|
||||||
if d.serial_and_batch_bundle and self.docstatus == 2:
|
if d.serial_and_batch_bundle and self.docstatus == 2:
|
||||||
bundle_id = frappe.get_cached_value(
|
bundle_id = frappe.get_cached_value(
|
||||||
@@ -1239,36 +1247,6 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
sl_entries.append(sle)
|
sl_entries.append(sle)
|
||||||
|
|
||||||
def copy_serial_and_batch_bundle(self, child):
|
|
||||||
allowed_types = [
|
|
||||||
"Material Transfer",
|
|
||||||
"Send to Subcontractor",
|
|
||||||
"Material Transfer for Manufacture",
|
|
||||||
]
|
|
||||||
|
|
||||||
if self.purpose in allowed_types:
|
|
||||||
bundle_doc = frappe.get_doc("Serial and Batch Bundle", child.serial_and_batch_bundle)
|
|
||||||
|
|
||||||
bundle_doc = frappe.copy_doc(bundle_doc)
|
|
||||||
bundle_doc.warehouse = child.t_warehouse
|
|
||||||
bundle_doc.type_of_transaction = "Inward"
|
|
||||||
|
|
||||||
for row in bundle_doc.ledgers:
|
|
||||||
if row.qty < 0:
|
|
||||||
row.qty = abs(row.qty)
|
|
||||||
|
|
||||||
if row.stock_value_difference < 0:
|
|
||||||
row.stock_value_difference = abs(row.stock_value_difference)
|
|
||||||
|
|
||||||
row.warehouse = child.t_warehouse
|
|
||||||
row.is_outward = 0
|
|
||||||
|
|
||||||
bundle_doc.set_total_qty()
|
|
||||||
bundle_doc.set_avg_rate()
|
|
||||||
bundle_doc.flags.ignore_permissions = True
|
|
||||||
bundle_doc.submit()
|
|
||||||
return bundle_doc.name
|
|
||||||
|
|
||||||
def get_gl_entries(self, warehouse_account):
|
def get_gl_entries(self, warehouse_account):
|
||||||
gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account)
|
gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import List
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, bold
|
from frappe import _, bold
|
||||||
from frappe.model.naming import make_autoname
|
from frappe.model.naming import make_autoname
|
||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||||
from frappe.utils import cint, flt, now
|
from frappe.utils import cint, flt, now
|
||||||
|
|
||||||
from erpnext.stock.deprecated_serial_batch import (
|
from erpnext.stock.deprecated_serial_batch import (
|
||||||
@@ -255,7 +255,7 @@ class SerialBatchBundle:
|
|||||||
data = frappe.db.get_value(
|
data = frappe.db.get_value(
|
||||||
"Serial and Batch Bundle",
|
"Serial and Batch Bundle",
|
||||||
self.sle.serial_and_batch_bundle,
|
self.sle.serial_and_batch_bundle,
|
||||||
["item_code", "warehouse", "voucher_no"],
|
["item_code", "warehouse", "voucher_no", "name"],
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -408,7 +408,7 @@ class SerialNoBundleValuation(DeprecatedSerialNoValuation):
|
|||||||
parent.name = child.parent
|
parent.name = child.parent
|
||||||
AND child.serial_no IN ({', '.join([frappe.db.escape(s) for s in serial_nos])})
|
AND child.serial_no IN ({', '.join([frappe.db.escape(s) for s in serial_nos])})
|
||||||
AND child.is_outward = 0
|
AND child.is_outward = 0
|
||||||
AND parent.docstatus < 2
|
AND parent.docstatus = 1
|
||||||
AND parent.is_cancelled = 0
|
AND parent.is_cancelled = 0
|
||||||
AND child.warehouse = {frappe.db.escape(self.sle.warehouse)}
|
AND child.warehouse = {frappe.db.escape(self.sle.warehouse)}
|
||||||
AND parent.item_code = {frappe.db.escape(self.sle.item_code)}
|
AND parent.item_code = {frappe.db.escape(self.sle.item_code)}
|
||||||
@@ -511,8 +511,10 @@ class BatchNoBundleValuation(DeprecatedBatchNoValuation):
|
|||||||
ledgers = self.get_batch_no_ledgers()
|
ledgers = self.get_batch_no_ledgers()
|
||||||
|
|
||||||
self.batch_avg_rate = defaultdict(float)
|
self.batch_avg_rate = defaultdict(float)
|
||||||
|
self.available_qty = defaultdict(float)
|
||||||
for ledger in ledgers:
|
for ledger in ledgers:
|
||||||
self.batch_avg_rate[ledger.batch_no] += flt(ledger.incoming_rate) / flt(ledger.qty)
|
self.batch_avg_rate[ledger.batch_no] += flt(ledger.incoming_rate) / flt(ledger.qty)
|
||||||
|
self.available_qty[ledger.batch_no] += flt(ledger.qty)
|
||||||
|
|
||||||
self.calculate_avg_rate_from_deprecarated_ledgers()
|
self.calculate_avg_rate_from_deprecarated_ledgers()
|
||||||
self.set_stock_value_difference()
|
self.set_stock_value_difference()
|
||||||
@@ -523,6 +525,10 @@ class BatchNoBundleValuation(DeprecatedBatchNoValuation):
|
|||||||
|
|
||||||
batch_nos = list(self.batch_nos.keys())
|
batch_nos = list(self.batch_nos.keys())
|
||||||
|
|
||||||
|
timestamp_condition = CombineDatetime(
|
||||||
|
parent.posting_date, parent.posting_time
|
||||||
|
) < CombineDatetime(self.sle.posting_date, self.sle.posting_time)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
frappe.qb.from_(parent)
|
frappe.qb.from_(parent)
|
||||||
.inner_join(child)
|
.inner_join(child)
|
||||||
@@ -537,8 +543,10 @@ class BatchNoBundleValuation(DeprecatedBatchNoValuation):
|
|||||||
& (child.parent != self.sle.serial_and_batch_bundle)
|
& (child.parent != self.sle.serial_and_batch_bundle)
|
||||||
& (parent.warehouse == self.sle.warehouse)
|
& (parent.warehouse == self.sle.warehouse)
|
||||||
& (parent.item_code == self.sle.item_code)
|
& (parent.item_code == self.sle.item_code)
|
||||||
|
& (parent.docstatus == 1)
|
||||||
& (parent.is_cancelled == 0)
|
& (parent.is_cancelled == 0)
|
||||||
)
|
)
|
||||||
|
.where(timestamp_condition)
|
||||||
.groupby(child.batch_no)
|
.groupby(child.batch_no)
|
||||||
).run(as_dict=True)
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user