fix: system was allowing credit notes with serial numbers for any customer

(cherry picked from commit e073075834)

# Conflicts:
#	erpnext/stock/doctype/delivery_note/delivery_note.py
#	erpnext/stock/doctype/serial_no/serial_no.json
This commit is contained in:
Rohit Waghchaure
2025-07-15 14:00:04 +05:30
committed by Mergify
parent 0151733a25
commit 4b6444e93b
7 changed files with 69 additions and 1 deletions

View File

@@ -461,6 +461,7 @@ class SalesInvoice(SellingController):
self.make_bundle_for_sales_purchase_return(table_name) self.make_bundle_for_sales_purchase_return(table_name)
self.make_bundle_using_old_serial_batch_fields(table_name) self.make_bundle_using_old_serial_batch_fields(table_name)
self.validate_standalone_serial_nos_customer()
self.update_stock_reservation_entries() self.update_stock_reservation_entries()
self.update_stock_ledger() self.update_stock_ledger()

View File

@@ -36,6 +36,17 @@ def validate_return_against(doc):
party_type = "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier" party_type = "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier"
if ref_doc.get(party_type) != doc.get(party_type):
frappe.throw(
_("The {0} {1} does not match with the {0} {2} in the {3} {4}").format(
doc.meta.get_label(party_type),
doc.get(party_type),
ref_doc.get(party_type),
ref_doc.doctype,
ref_doc.name,
)
)
if ( if (
ref_doc.company == doc.company ref_doc.company == doc.company
and ref_doc.get(party_type) == doc.get(party_type) and ref_doc.get(party_type) == doc.get(party_type)

View File

@@ -57,6 +57,35 @@ class SellingController(StockController):
if self.get(table_field): if self.get(table_field):
self.set_serial_and_batch_bundle(table_field) self.set_serial_and_batch_bundle(table_field)
def validate_standalone_serial_nos_customer(self):
if not self.is_return or self.return_against:
return
if self.doctype in ["Sales Invoice", "Delivery Note"]:
bundle_ids = [d.serial_and_batch_bundle for d in self.get("items") if d.serial_and_batch_bundle]
if not bundle_ids:
return
serial_nos = frappe.get_all(
"Serial and Batch Entry",
filters={"parent": ("in", bundle_ids)},
pluck="serial_no",
)
if serial_nos := frappe.get_all(
"Serial No",
filters={"name": ("in", serial_nos), "customer": ("is", "set")},
fields=["name", "customer"],
):
for sn in serial_nos:
if sn.customer and sn.customer != self.customer:
frappe.throw(
_(
"Serial No {0} is already assigned to customer {1}. Can only be returned against the customer {1}"
).format(frappe.bold(sn.name), frappe.bold(sn.customer)),
title=_("Serial No Already Assigned"),
)
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
super().set_missing_values(for_validate) super().set_missing_values(for_validate)

View File

@@ -466,6 +466,12 @@ class DeliveryNote(SellingController):
self.make_bundle_for_sales_purchase_return(table_name) self.make_bundle_for_sales_purchase_return(table_name)
self.make_bundle_using_old_serial_batch_fields(table_name) self.make_bundle_using_old_serial_batch_fields(table_name)
<<<<<<< HEAD
=======
self.validate_standalone_serial_nos_customer()
self.update_stock_reservation_entries()
>>>>>>> e073075834 (fix: system was allowing credit notes with serial numbers for any customer)
# Updating stock ledger should always be called after updating prevdoc status, # Updating stock ledger should always be called after updating prevdoc status,
# because updating reserved qty in bin depends upon updated delivered qty in SO # because updating reserved qty in bin depends upon updated delivered qty in SO
self.update_stock_ledger() self.update_stock_ledger()

View File

@@ -15,6 +15,7 @@
"batch_no", "batch_no",
"warehouse", "warehouse",
"purchase_rate", "purchase_rate",
"customer",
"column_break1", "column_break1",
"status", "status",
"item_name", "item_name",
@@ -267,12 +268,25 @@
"label": "Creation Document No", "label": "Creation Document No",
"no_copy": 1, "no_copy": 1,
"read_only": 1 "read_only": 1
},
{
"fieldname": "customer",
"fieldtype": "Link",
"label": "Customer",
"no_copy": 1,
"options": "Customer",
"print_hide": 1,
"read_only": 1
} }
], ],
"icon": "fa fa-barcode", "icon": "fa fa-barcode",
"idx": 1, "idx": 1,
"links": [], "links": [],
<<<<<<< HEAD
"modified": "2025-01-15 16:22:49.873889", "modified": "2025-01-15 16:22:49.873889",
=======
"modified": "2025-07-15 13:36:21.938700",
>>>>>>> e073075834 (fix: system was allowing credit notes with serial numbers for any customer)
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Serial No", "name": "Serial No",
@@ -310,6 +324,7 @@
"role": "Stock User" "role": "Stock User"
} }
], ],
"row_format": "Dynamic",
"search_fields": "item_code", "search_fields": "item_code",
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",

View File

@@ -40,6 +40,7 @@ class SerialNo(StockController):
batch_no: DF.Link | None batch_no: DF.Link | None
brand: DF.Link | None brand: DF.Link | None
company: DF.Link company: DF.Link
customer: DF.Link | None
description: DF.Text | None description: DF.Text | None
employee: DF.Link | None employee: DF.Link | None
item_code: DF.Link item_code: DF.Link

View File

@@ -377,6 +377,10 @@ class SerialBatchBundle:
]: ]:
status = "Consumed" status = "Consumed"
customer = None
if sle.voucher_type in ["Sales Invoice", "Delivery Note"] and sle.actual_qty < 0:
customer = frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "customer")
sn_table = frappe.qb.DocType("Serial No") sn_table = frappe.qb.DocType("Serial No")
query = ( query = (
@@ -387,10 +391,11 @@ class SerialBatchBundle:
"Active" "Active"
if warehouse if warehouse
else status else status
if (sn_table.purchase_document_no != sle.voucher_no and sle.is_cancelled != 1) if (sn_table.purchase_document_no != sle.voucher_no or sle.is_cancelled != 1)
else "Inactive", else "Inactive",
) )
.set(sn_table.company, sle.company) .set(sn_table.company, sle.company)
.set(sn_table.customer, customer)
.where(sn_table.name.isin(serial_nos)) .where(sn_table.name.isin(serial_nos))
) )