fix: Check for backdated validation only for transaction company (#23639)
* fix: Check SLE only for transaction company
* fix: Add tests
* fix: Move backdated entry validation from transaction base to stock ledger entry
* chore: Add tests
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 74a06d8..12c8906 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -6,7 +6,7 @@
import json
import frappe, erpnext
import frappe.defaults
-from frappe.utils import cint, flt, cstr, today, random_string
+from frappe.utils import cint, flt, cstr, today, random_string, add_days
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
from erpnext.stock.doctype.item.test_item import create_item
from erpnext import set_perpetual_inventory
@@ -665,6 +665,59 @@
warehouse.account = ''
warehouse.save()
+ def test_backdated_purchase_receipt(self):
+ # make purchase receipt for default company
+ make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4")
+
+ # try to make another backdated PR
+ posting_date = add_days(today(), -1)
+ pr = make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4",
+ do_not_submit=True)
+
+ pr.set_posting_time = 1
+ pr.posting_date = posting_date
+ pr.save()
+
+ self.assertRaises(frappe.ValidationError, pr.submit)
+
+ # make purchase receipt for other company backdated
+ pr = make_purchase_receipt(company="_Test Company 5", warehouse="Stores - _TC5",
+ do_not_submit=True)
+
+ pr.set_posting_time = 1
+ pr.posting_date = posting_date
+ pr.submit()
+
+ # Allowed to submit for other company's PR
+ self.assertEqual(pr.docstatus, 1)
+
+ def test_backdated_purchase_receipt_for_same_company_different_warehouse(self):
+ # make purchase receipt for default company
+ make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4")
+
+ # try to make another backdated PR
+ posting_date = add_days(today(), -1)
+ pr = make_purchase_receipt(company="_Test Company 4", warehouse="Stores - _TC4",
+ do_not_submit=True)
+
+ pr.set_posting_time = 1
+ pr.posting_date = posting_date
+ pr.save()
+
+ self.assertRaises(frappe.ValidationError, pr.submit)
+
+ # make purchase receipt for other company backdated
+ pr = make_purchase_receipt(company="_Test Company 4", warehouse="Finished Goods - _TC4",
+ do_not_submit=True)
+
+ pr.set_posting_time = 1
+ pr.posting_date = posting_date
+ pr.submit()
+
+ # Allowed to submit for other company's PR
+ self.assertEqual(pr.docstatus, 1)
+
+
def get_sl_entries(voucher_type, voucher_no):
return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference
from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 101c6e0..bb356f6 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -5,7 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import flt, getdate, add_days, formatdate
+from frappe.utils import flt, getdate, add_days, formatdate, get_datetime, date_diff
from frappe.model.document import Document
from datetime import date
from erpnext.controllers.item_variant import ItemTemplateCannotHaveStock
@@ -33,6 +33,8 @@
self.scrub_posting_time()
self.validate_and_set_fiscal_year()
self.block_transactions_against_group_warehouse()
+ self.validate_with_last_transaction_posting_time()
+ self.validate_future_posting()
def on_submit(self):
self.check_stock_frozen_date()
@@ -139,6 +141,30 @@
from erpnext.stock.utils import is_group_warehouse
is_group_warehouse(self.warehouse)
+ def validate_with_last_transaction_posting_time(self):
+ last_transaction_time = frappe.db.sql("""
+ select MAX(timestamp(posting_date, posting_time)) as posting_time
+ from `tabStock Ledger Entry`
+ where docstatus = 1 and item_code = %s
+ and warehouse = %s""", (self.item_code, self.warehouse))[0][0]
+
+ cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00")
+
+ if last_transaction_time and get_datetime(cur_doc_posting_datetime) < get_datetime(last_transaction_time):
+ msg = _("Last Stock Transaction for item {0} under warehouse {1} was on {2}.").format(frappe.bold(self.item_code),
+ frappe.bold(self.warehouse), frappe.bold(last_transaction_time))
+
+ msg += "<br><br>" + _("Stock Transactions for Item {0} under warehouse {1} cannot be posted before this time.").format(
+ frappe.bold(self.item_code), frappe.bold(self.warehouse))
+
+ msg += "<br><br>" + _("Please remove this item and try to submit again or update the posting time.")
+ frappe.throw(msg, title=_("Backdated Stock Entry"))
+
+ def validate_future_posting(self):
+ if date_diff(self.posting_date, getdate()) > 0:
+ msg = _("Posting future stock transactions are not allowed due to Immutable Ledger")
+ frappe.throw(msg, title=_("Future Posting Not Allowed"))
+
def on_doctype_update():
if not frappe.db.has_index('tabStock Ledger Entry', 'posting_sort_index'):
frappe.db.commit()
diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index 298e111..c8ae733 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -29,28 +29,6 @@
except ValueError:
frappe.throw(_('Invalid Posting Time'))
- self.validate_future_posting()
- self.validate_with_last_transaction_posting_time()
-
- def is_stock_transaction(self):
- if self.doctype not in ["Sales Invoice", "Purchase Invoice", "Stock Entry", "Stock Reconciliation",
- "Delivery Note", "Purchase Receipt", "Fees"]:
- return False
-
- if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
- if not (self.get("update_stock") or self.get("is_pos")):
- return False
-
- return True
-
- def validate_future_posting(self):
- if not self.is_stock_transaction():
- return
-
- if getattr(self, 'set_posting_time', None) and date_diff(self.posting_date, nowdate()) > 0:
- msg = _("Posting future transactions are not allowed due to Immutable Ledger")
- frappe.throw(msg, title=_("Future Posting Not Allowed"))
-
def add_calendar_event(self, opts, force=False):
if cstr(self.contact_by) != cstr(self._prev.contact_by) or \
cstr(self.contact_date) != cstr(self._prev.contact_date) or force or \
@@ -180,25 +158,6 @@
return ret
- def validate_with_last_transaction_posting_time(self):
-
- if not self.is_stock_transaction():
- return
-
- for item in self.get('items'):
- last_transaction_time = frappe.db.sql("""
- select MAX(timestamp(posting_date, posting_time)) as posting_time
- from `tabStock Ledger Entry`
- where docstatus = 1 and item_code = %s """, (item.item_code))[0][0]
-
- cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00")
-
- if last_transaction_time and get_datetime(cur_doc_posting_datetime) < get_datetime(last_transaction_time):
- msg = _("Last Stock Transaction for item {0} was on {1}.").format(frappe.bold(item.item_code), frappe.bold(last_transaction_time))
- msg += "<br><br>" + _("Stock Transactions for Item {0} cannot be posted before this time.").format(frappe.bold(item.item_code))
- msg += "<br><br>" + _("Please remove this item and try to submit again or update the posting time.")
- frappe.throw(msg, title=_("Backdated Stock Entry"))
-
def delete_events(ref_type, ref_name):
events = frappe.db.sql_list(""" SELECT
distinct `tabEvent`.name