From 8859b71956afea1a0d8c5d0fc7e43ec3bba6feb3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 19 Oct 2020 16:56:45 +0530 Subject: [PATCH] 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 --- .../purchase_receipt/test_purchase_receipt.py | 55 ++++++++++++++++++- .../stock_ledger_entry/stock_ledger_entry.py | 28 +++++++++- erpnext/utilities/transaction_base.py | 41 -------------- 3 files changed, 81 insertions(+), 43 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 74a06d8585..12c89066a7 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 unittest 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 @@ class TestPurchaseReceipt(unittest.TestCase): 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 101c6e099e..bb356f694a 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 @@ class StockLedgerEntry(Document): 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 @@ class StockLedgerEntry(Document): 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 += "

" + _("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 += "

" + _("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 298e1110d3..c8ae73365b 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -29,28 +29,6 @@ class TransactionBase(StatusUpdater): 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 @@ class TransactionBase(StatusUpdater): 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 += "

" + _("Stock Transactions for Item {0} cannot be posted before this time.").format(frappe.bold(item.item_code)) - msg += "

" + _("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