Merge pull request #48290 from frappe/mergify/bp/version-14/pr-48281
fix: do not allow backdated transactions against serial numbers. (backport #48281)
This commit is contained in:
@@ -2761,6 +2761,63 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
pr.reload()
|
pr.reload()
|
||||||
self.assertEqual(pr.status, "To Bill")
|
self.assertEqual(pr.status, "To Bill")
|
||||||
|
|
||||||
|
def test_serial_no_exists_in_future(self):
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
|
||||||
|
item_doc = make_item(
|
||||||
|
"Test Serial No Item Exists in Future",
|
||||||
|
{
|
||||||
|
"is_purchase_item": 1,
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"serial_no_series": "SN-SBNS-.#####",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
source_warehouse = "_Test Warehouse - _TC"
|
||||||
|
target_warehouse = "_Test Warehouse 1 - _TC"
|
||||||
|
if not frappe.db.exists("Warehouse", target_warehouse):
|
||||||
|
create_warehouse("_Test Warehouse 1")
|
||||||
|
|
||||||
|
make_purchase_receipt(
|
||||||
|
item_code=item_doc.name,
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
serial_no="SN-SBNS-00001",
|
||||||
|
posting_date=add_days(today(), -2),
|
||||||
|
)
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_doc.name,
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
to_warehouse=target_warehouse,
|
||||||
|
serial_no="SN-SBNS-00002",
|
||||||
|
posting_date=add_days(today(), -1),
|
||||||
|
)
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_doc.name,
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
from_warehouse=source_warehouse,
|
||||||
|
to_warehouse=target_warehouse,
|
||||||
|
serial_no="SN-SBNS-00001",
|
||||||
|
posting_date=today(),
|
||||||
|
)
|
||||||
|
|
||||||
|
se = make_stock_entry(
|
||||||
|
item_code=item_doc.name,
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
from_warehouse=target_warehouse,
|
||||||
|
serial_no="SN-SBNS-00001",
|
||||||
|
posting_date=add_days(today(), -1),
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, se.submit)
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_internal_transfer():
|
def prepare_data_for_internal_transfer():
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
|||||||
@@ -8,7 +8,18 @@ import frappe
|
|||||||
from frappe import ValidationError, _
|
from frappe import ValidationError, _
|
||||||
from frappe.model.naming import make_autoname
|
from frappe.model.naming import make_autoname
|
||||||
from frappe.query_builder.functions import Coalesce
|
from frappe.query_builder.functions import Coalesce
|
||||||
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, now, nowdate, safe_json_loads
|
from frappe.utils import (
|
||||||
|
add_days,
|
||||||
|
cint,
|
||||||
|
cstr,
|
||||||
|
flt,
|
||||||
|
get_datetime,
|
||||||
|
get_link_to_form,
|
||||||
|
getdate,
|
||||||
|
now,
|
||||||
|
nowdate,
|
||||||
|
safe_json_loads,
|
||||||
|
)
|
||||||
|
|
||||||
from erpnext.controllers.stock_controller import StockController
|
from erpnext.controllers.stock_controller import StockController
|
||||||
from erpnext.stock.get_item_details import get_reserved_qty_for_so
|
from erpnext.stock.get_item_details import get_reserved_qty_for_so
|
||||||
@@ -197,7 +208,7 @@ class SerialNo(StockController):
|
|||||||
for sle in frappe.db.sql(
|
for sle in frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
SELECT voucher_type, voucher_no,
|
SELECT voucher_type, voucher_no,
|
||||||
posting_date, posting_time, incoming_rate, actual_qty, serial_no
|
posting_date, posting_time, incoming_rate, actual_qty, serial_no, posting_datetime
|
||||||
FROM
|
FROM
|
||||||
`tabStock Ledger Entry`
|
`tabStock Ledger Entry`
|
||||||
WHERE
|
WHERE
|
||||||
@@ -251,8 +262,23 @@ class SerialNo(StockController):
|
|||||||
_("Cannot delete Serial No {0}, as it is used in stock transactions").format(self.name)
|
_("Cannot delete Serial No {0}, as it is used in stock transactions").format(self.name)
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_serial_no_reference(self, serial_no=None):
|
def update_serial_no_reference(self, serial_no=None, sle=None):
|
||||||
last_sle = self.get_last_sle(serial_no)
|
last_sle = self.get_last_sle(serial_no)
|
||||||
|
|
||||||
|
_last_sle_dict = last_sle.get("last_sle")
|
||||||
|
if (
|
||||||
|
_last_sle_dict
|
||||||
|
and sle.get("voucher_type") != "Stock Reconciliation"
|
||||||
|
and sle.get("voucher_no") != _last_sle_dict.get("voucher_no")
|
||||||
|
and get_datetime(sle.get("posting_datetime"))
|
||||||
|
< get_datetime(_last_sle_dict.get("posting_datetime"))
|
||||||
|
):
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"You can not complete this transaction because a future transaction exists for the serial number {0}"
|
||||||
|
).format(serial_no)
|
||||||
|
)
|
||||||
|
|
||||||
self.set_purchase_details(last_sle.get("purchase_sle"))
|
self.set_purchase_details(last_sle.get("purchase_sle"))
|
||||||
self.set_sales_details(last_sle.get("delivery_sle"))
|
self.set_sales_details(last_sle.get("delivery_sle"))
|
||||||
self.set_maintenance_status()
|
self.set_maintenance_status()
|
||||||
@@ -770,7 +796,7 @@ def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False):
|
|||||||
serial_no_doc.sales_order = None
|
serial_no_doc.sales_order = None
|
||||||
|
|
||||||
serial_no_doc.validate_item()
|
serial_no_doc.validate_item()
|
||||||
serial_no_doc.update_serial_no_reference(serial_no)
|
serial_no_doc.update_serial_no_reference(serial_no, sle=args)
|
||||||
|
|
||||||
if is_new:
|
if is_new:
|
||||||
serial_no_doc.db_insert()
|
serial_no_doc.db_insert()
|
||||||
|
|||||||
Reference in New Issue
Block a user