fix(credit-note): set incoming rate as zero for expired batch

(cherry picked from commit e78c750b4e)
This commit is contained in:
kavin-114
2026-01-23 02:12:00 +05:30
committed by Mergify
parent b84fd46841
commit 0f9bf08685
3 changed files with 57 additions and 5 deletions

View File

@@ -12,7 +12,7 @@ from frappe.utils import cint, flt, format_datetime, get_datetime
import erpnext import erpnext
from erpnext.stock.serial_batch_bundle import get_batches_from_bundle from erpnext.stock.serial_batch_bundle import get_batches_from_bundle
from erpnext.stock.utils import get_combine_datetime, get_incoming_rate, get_valuation_method from erpnext.stock.utils import get_combine_datetime, get_incoming_rate, get_valuation_method, getdate
class StockOverReturnError(frappe.ValidationError): class StockOverReturnError(frappe.ValidationError):
@@ -759,6 +759,29 @@ def get_rate_for_return(
StockLedgerEntry = frappe.qb.DocType("Stock Ledger Entry") StockLedgerEntry = frappe.qb.DocType("Stock Ledger Entry")
select_field = Abs(StockLedgerEntry.stock_value_difference / StockLedgerEntry.actual_qty) select_field = Abs(StockLedgerEntry.stock_value_difference / StockLedgerEntry.actual_qty)
item_details = frappe.get_cached_value("Item", item_code, ["has_batch_no", "has_expiry_date"], as_dict=1)
set_zero_rate_for_expired_batch = frappe.db.get_single_value(
"Selling Settings", "set_zero_rate_for_expired_batch"
)
if (
set_zero_rate_for_expired_batch
and item_details.has_batch_no
and item_details.has_expiry_date
and not return_against
and voucher_type in ["Sales Invoice", "Delivery Note"]
):
# set incoming_rate zero explicitly for standalone credit note with expired batch
batch_no = frappe.db.get_value(f"{voucher_type} Item", voucher_detail_no, "batch_no")
if batch_no and is_batch_expired(batch_no, sle.get("posting_date")):
frappe.db.set_value(
voucher_type + " Item",
voucher_detail_no,
"incoming_rate",
0,
)
return 0
rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field)) rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
if not (rate and return_against) and voucher_type in ["Sales Invoice", "Delivery Note"]: if not (rate and return_against) and voucher_type in ["Sales Invoice", "Delivery Note"]:
rate = frappe.db.get_value(f"{voucher_type} Item", voucher_detail_no, "incoming_rate") rate = frappe.db.get_value(f"{voucher_type} Item", voucher_detail_no, "incoming_rate")
@@ -1276,3 +1299,17 @@ def get_sales_invoice_item_from_consolidated_invoice(return_against_pos_invoice,
return result[0].name if result else None return result[0].name if result else None
except Exception: except Exception:
return None return None
def is_batch_expired(batch_no, posting_date):
"""
To check whether the batch is expired or not based on the posting date.
"""
expiry_date = frappe.db.get_value("Batch", batch_no, "expiry_date")
if not expiry_date:
return
if getdate(posting_date) > getdate(expiry_date):
return True
return False

View File

@@ -8,7 +8,7 @@ from frappe.utils import cint, flt, get_link_to_form, nowtime
from erpnext.accounts.party import render_address from erpnext.accounts.party import render_address
from erpnext.controllers.accounts_controller import get_taxes_and_charges from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return from erpnext.controllers.sales_and_purchase_return import get_rate_for_return, is_batch_expired
from erpnext.controllers.stock_controller import StockController from erpnext.controllers.stock_controller import StockController
from erpnext.stock.doctype.item.item import set_item_default from erpnext.stock.doctype.item.item import set_item_default
from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor
@@ -536,16 +536,31 @@ class SellingController(StockController):
allow_at_arms_length_price = frappe.get_cached_value( allow_at_arms_length_price = frappe.get_cached_value(
"Stock Settings", None, "allow_internal_transfer_at_arms_length_price" "Stock Settings", None, "allow_internal_transfer_at_arms_length_price"
) )
set_zero_rate_for_expired_batch = frappe.db.get_single_value(
"Selling Settings", "set_zero_rate_for_expired_batch"
)
items = self.get("items") + (self.get("packed_items") or []) items = self.get("items") + (self.get("packed_items") or [])
for d in items: for d in items:
if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"): if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
continue continue
item_details = frappe.get_cached_value( item_details = frappe.get_cached_value(
"Item", d.item_code, ["has_serial_no", "has_batch_no"], as_dict=1 "Item", d.item_code, ["has_serial_no", "has_batch_no", "has_expiry_date"], as_dict=1
) )
if not self.get("return_against") or ( if (
set_zero_rate_for_expired_batch
and item_details.has_batch_no
and item_details.has_expiry_date
and self.get("is_return")
and not self.get("return_against")
and is_batch_expired(d.batch_no, self.get("posting_date"))
):
# set incoming rate as zero for stand-lone credit note with expired batch
d.incoming_rate = 0
elif not self.get("return_against") or (
get_valuation_method(d.item_code, self.company) == "Moving Average" get_valuation_method(d.item_code, self.company) == "Moving Average"
and self.get("is_return") and self.get("is_return")
and not item_details.has_serial_no and not item_details.has_serial_no

View File

@@ -44,7 +44,7 @@ class SellingSettings(Document):
role_to_override_stop_action: DF.Link | None role_to_override_stop_action: DF.Link | None
sales_update_frequency: DF.Literal["Monthly", "Each Transaction", "Daily"] sales_update_frequency: DF.Literal["Monthly", "Each Transaction", "Daily"]
selling_price_list: DF.Link | None selling_price_list: DF.Link | None
set_zero_rate_for_expired: DF.Check set_zero_rate_for_expired_batch: DF.Check
so_required: DF.Literal["No", "Yes"] so_required: DF.Literal["No", "Yes"]
territory: DF.Link | None territory: DF.Link | None
use_legacy_js_reactivity: DF.Check use_legacy_js_reactivity: DF.Check