feat(dn): 添加 is_exchange 字段支持换货场景库存更新
问题描述: - DN 收退货时,明细行有正负数量导致总数量=0时,提交后库存未更新 - 原因:代码中多处过滤条件会跳过 actual_qty=0 的记录 解决方案: 1. 在 Delivery Note DocType 中添加内置字段 is_exchange (Check) 2. 替换所有 custom_is_exchange 自定义字段引用为 is_exchange 3. 修改库存更新逻辑,在 is_exchange=1 时允许数量为0的明细行更新库存 4. 在 SLE 生成时传递 is_exchange 字段到 Stock Ledger Entry 修改文件: - delivery_note.json: 添加 is_exchange 字段定义 - delivery_note.py: 替换字段引用 - selling_controller.py: 修改库存更新条件并传递 is_exchange 到 SLE - stock_ledger.py: 修改 SLE 创建和过账条件 - accounts_controller.py: 替换字段引用 - status_updater.py: 替换字段引用 影响范围: - 仅当 DN 的 is_exchange=1 时生效 - 不影响其他单据类型的校验逻辑 - 正常 DN (is_exchange=0) 保持原有行为
This commit is contained in:
@@ -249,7 +249,7 @@ class AccountsController(TransactionBase):
|
||||
behavior unless the document is explicitly an exception such as a return.
|
||||
"""
|
||||
if self.doctype == "Delivery Note":
|
||||
return cint(getattr(self, "custom_is_exchange", 0)) == 1
|
||||
return cint(self.is_exchange) == 1
|
||||
|
||||
if self.doctype == "Sales Order":
|
||||
return bool(frappe.get_single_value("Selling Settings", "allow_negative_rates_for_items"))
|
||||
|
||||
@@ -656,7 +656,16 @@ class SellingController(StockController):
|
||||
sl_entries = []
|
||||
# Loop over items and packed items table
|
||||
for d in self.get_item_list():
|
||||
if frappe.get_cached_value("Item", d.item_code, "is_stock_item") == 1 and flt(d.qty):
|
||||
item_is_stock = frappe.get_cached_value("Item", d.item_code, "is_stock_item") == 1
|
||||
qty_value = flt(d.qty)
|
||||
|
||||
# 对于换货(is_exchange=1)或退货(is_return=1)场景,即使单行数量为0也要处理
|
||||
is_exchange_or_return = (
|
||||
(self.doctype == "Delivery Note" and cint(self.is_exchange) == 1)
|
||||
or self.is_return
|
||||
)
|
||||
|
||||
if item_is_stock and (qty_value != 0 or is_exchange_or_return):
|
||||
if flt(d.conversion_factor) == 0.0:
|
||||
d.conversion_factor = (
|
||||
get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0
|
||||
@@ -708,6 +717,7 @@ class SellingController(StockController):
|
||||
"incoming_rate": item_row.incoming_rate,
|
||||
"recalculate_rate": cint(self.is_return),
|
||||
"serial_and_batch_bundle": serial_and_batch_bundle,
|
||||
"is_exchange": cint(self.is_exchange),
|
||||
},
|
||||
)
|
||||
if item_row.target_warehouse and not cint(self.is_return):
|
||||
@@ -717,7 +727,12 @@ class SellingController(StockController):
|
||||
|
||||
def get_sle_for_target_warehouse(self, item_row):
|
||||
sle = self.get_sl_entries(
|
||||
item_row, {"actual_qty": flt(item_row.qty), "warehouse": item_row.target_warehouse}
|
||||
item_row,
|
||||
{
|
||||
"actual_qty": flt(item_row.qty),
|
||||
"warehouse": item_row.target_warehouse,
|
||||
"is_exchange": cint(self.is_exchange),
|
||||
},
|
||||
)
|
||||
|
||||
if self.docstatus == 1:
|
||||
|
||||
@@ -288,7 +288,7 @@ class StatusUpdater(Document):
|
||||
|
||||
# get unique transactions to update
|
||||
for d in self.get_all_children():
|
||||
is_dn_exchange = self.doctype == "Delivery Note" and cint(getattr(self, "custom_is_exchange", 0)) == 1
|
||||
is_dn_exchange = self.doctype == "Delivery Note" and cint(self.is_exchange) == 1
|
||||
if not is_dn_exchange:
|
||||
if hasattr(d, "qty") and flt(d.qty) < 0 and not self.get("is_return"):
|
||||
frappe.throw(
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"company",
|
||||
"amended_from",
|
||||
"is_return",
|
||||
"is_exchange",
|
||||
"issue_credit_note",
|
||||
"return_against",
|
||||
"accounting_dimensions_section",
|
||||
@@ -285,6 +286,13 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_exchange",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Exchange",
|
||||
"description": "Enable exchange scenario where quantities can be mixed (positive and negative)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "is_return",
|
||||
|
||||
@@ -411,7 +411,7 @@ class DeliveryNote(SellingController):
|
||||
def validate_warehouse(self):
|
||||
super().validate_warehouse()
|
||||
|
||||
if self.doctype == "Delivery Note" and cint(getattr(self, "custom_is_exchange", 0)) == 1:
|
||||
if self.doctype == "Delivery Note" and cint(self.is_exchange) == 1:
|
||||
return
|
||||
|
||||
for d in self.get_item_list():
|
||||
|
||||
@@ -121,7 +121,13 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc
|
||||
)
|
||||
sle["outgoing_rate"] = 0.0
|
||||
|
||||
if sle.get("actual_qty") or sle.get("voucher_type") == "Stock Reconciliation":
|
||||
# 对于换货(is_exchange=1)或退货(is_return=1)场景,即使actual_qty为0也要创建记录
|
||||
is_exchange_or_return = (
|
||||
sle.get("voucher_type") == "Delivery Note"
|
||||
and (cint(sle.get("is_exchange")) == 1 or sle.get("is_return"))
|
||||
)
|
||||
|
||||
if sle.get("actual_qty") or sle.get("voucher_type") == "Stock Reconciliation" or is_exchange_or_return:
|
||||
sle_doc = make_entry(sle, allow_negative_stock, via_landed_cost_voucher)
|
||||
|
||||
args = sle_doc.as_dict()
|
||||
@@ -148,7 +154,13 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc
|
||||
|
||||
|
||||
def repost_current_voucher(args, allow_negative_stock=False, via_landed_cost_voucher=False, cancelled=False):
|
||||
if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation":
|
||||
# 对于换货(is_exchange=1)或退货(is_return=1)场景,即使actual_qty为0也要重新过账
|
||||
is_exchange_or_return = (
|
||||
args.get("voucher_type") == "Delivery Note"
|
||||
and (cint(args.get("is_exchange")) == 1 or args.get("is_return"))
|
||||
)
|
||||
|
||||
if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation" or is_exchange_or_return:
|
||||
if not args.get("posting_date"):
|
||||
args["posting_date"] = nowdate()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user