mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-17 10:36:31 +00:00
Merge pull request #57846 from diptanilsaha/backport/57825
refactor(accounts)!: rework Purchase Invoice hold actions and enforce them on Journal Entry (backport #57825)
This commit is contained in:
@@ -906,6 +906,18 @@ class JournalEntry(AccountsController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if reference_type == "Purchase Invoice" and invoice.invoice_is_blocked():
|
||||||
|
msg = (
|
||||||
|
_("{0} {1} is blocked and on hold until {2}.").format(
|
||||||
|
invoice.doctype, invoice.name, invoice.release_date
|
||||||
|
)
|
||||||
|
if invoice.release_date
|
||||||
|
else _("{0} {1} is blocked.").format(
|
||||||
|
invoice.doctype, invoice.name, invoice.release_date
|
||||||
|
)
|
||||||
|
)
|
||||||
|
frappe.throw(msg)
|
||||||
|
|
||||||
def set_against_account(self):
|
def set_against_account(self):
|
||||||
accounts_debited, accounts_credited = [], []
|
accounts_debited, accounts_credited = [], []
|
||||||
if self.voucher_type in ("Deferred Revenue", "Deferred Expense"):
|
if self.voucher_type in ("Deferred Revenue", "Deferred Expense"):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import flt, nowdate
|
from frappe.utils import add_days, flt, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||||
from erpnext.accounts.doctype.journal_entry.journal_entry import StockAccountInvalidTransaction
|
from erpnext.accounts.doctype.journal_entry.journal_entry import StockAccountInvalidTransaction
|
||||||
@@ -609,6 +609,69 @@ class TestJournalEntry(ERPNextTestSuite):
|
|||||||
jv.save()
|
jv.save()
|
||||||
self.assertRaises(frappe.ValidationError, jv.submit)
|
self.assertRaises(frappe.ValidationError, jv.submit)
|
||||||
|
|
||||||
|
def make_jv_against_purchase_invoice(self, invoice, amount=100):
|
||||||
|
jv = make_journal_entry("Creditors - _TC", "_Test Cash - _TC", amount, save=False)
|
||||||
|
jv.accounts[0].party_type = "Supplier"
|
||||||
|
jv.accounts[0].party = invoice.supplier
|
||||||
|
jv.accounts[0].reference_type = "Purchase Invoice"
|
||||||
|
jv.accounts[0].reference_name = invoice.name
|
||||||
|
return jv
|
||||||
|
|
||||||
|
def test_jv_against_purchase_invoice_respects_hold_state(self):
|
||||||
|
"""Payment can be booked against a Purchase Invoice only while it is not on hold."""
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
|
|
||||||
|
release_date = add_days(nowdate(), 10)
|
||||||
|
|
||||||
|
def never_held():
|
||||||
|
return make_purchase_invoice()
|
||||||
|
|
||||||
|
def held_until_a_future_date():
|
||||||
|
invoice = make_purchase_invoice()
|
||||||
|
invoice.block_invoice(hold_comment="Waiting for the goods", release_date=release_date)
|
||||||
|
return invoice
|
||||||
|
|
||||||
|
def held_without_a_release_date():
|
||||||
|
invoice = make_purchase_invoice()
|
||||||
|
invoice.block_invoice(hold_comment="Under dispute")
|
||||||
|
return invoice
|
||||||
|
|
||||||
|
def held_until_a_date_that_has_passed():
|
||||||
|
invoice = held_until_a_future_date()
|
||||||
|
frappe.db.set_value("Purchase Invoice", invoice.name, "release_date", add_days(nowdate(), -1))
|
||||||
|
return invoice
|
||||||
|
|
||||||
|
def unblocked_again():
|
||||||
|
invoice = held_until_a_future_date()
|
||||||
|
invoice.unblock_invoice()
|
||||||
|
return invoice
|
||||||
|
|
||||||
|
for build_invoice in (held_until_a_future_date, held_without_a_release_date):
|
||||||
|
with self.subTest(build_invoice.__name__):
|
||||||
|
jv = self.make_jv_against_purchase_invoice(build_invoice())
|
||||||
|
self.assertRaisesRegex(frappe.ValidationError, "is blocked", jv.insert)
|
||||||
|
|
||||||
|
for build_invoice in (never_held, held_until_a_date_that_has_passed, unblocked_again):
|
||||||
|
with self.subTest(build_invoice.__name__):
|
||||||
|
invoice = build_invoice()
|
||||||
|
jv = self.make_jv_against_purchase_invoice(invoice)
|
||||||
|
jv.insert()
|
||||||
|
self.assertEqual(jv.reference_types[invoice.name], "Purchase Invoice")
|
||||||
|
|
||||||
|
def test_jv_against_blocked_sales_invoice_reference_is_not_checked(self):
|
||||||
|
"""A Sales Invoice has no hold state, so the check must skip it rather than fail."""
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
|
||||||
|
invoice = create_sales_invoice(rate=500)
|
||||||
|
jv = make_journal_entry("_Test Cash - _TC", "Debtors - _TC", 100, save=False)
|
||||||
|
jv.accounts[1].party_type = "Customer"
|
||||||
|
jv.accounts[1].party = "_Test Customer"
|
||||||
|
jv.accounts[1].reference_type = "Sales Invoice"
|
||||||
|
jv.accounts[1].reference_name = invoice.name
|
||||||
|
jv.insert()
|
||||||
|
|
||||||
|
self.assertEqual(jv.reference_types[invoice.name], "Sales Invoice")
|
||||||
|
|
||||||
|
|
||||||
def make_journal_entry(
|
def make_journal_entry(
|
||||||
account1,
|
account1,
|
||||||
|
|||||||
@@ -240,10 +240,8 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
|
|
||||||
unblock_invoice() {
|
unblock_invoice() {
|
||||||
const me = this;
|
const me = this;
|
||||||
frappe.call({
|
me.frm.call("unblock_invoice", null, () => {
|
||||||
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.unblock_invoice",
|
me.frm.reload_doc();
|
||||||
args: { name: me.frm.doc.name },
|
|
||||||
callback: (r) => me.frm.reload_doc(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,15 +292,16 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
|
|
||||||
this.dialog.set_primary_action(__("Save"), function () {
|
this.dialog.set_primary_action(__("Save"), function () {
|
||||||
const dialog_data = me.dialog.get_values();
|
const dialog_data = me.dialog.get_values();
|
||||||
frappe.call({
|
me.frm.call(
|
||||||
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.block_invoice",
|
"block_invoice",
|
||||||
args: {
|
{
|
||||||
name: me.frm.doc.name,
|
|
||||||
hold_comment: dialog_data.hold_comment,
|
hold_comment: dialog_data.hold_comment,
|
||||||
release_date: dialog_data.release_date,
|
release_date: dialog_data.release_date,
|
||||||
},
|
},
|
||||||
callback: (r) => me.frm.reload_doc(),
|
() => {
|
||||||
});
|
me.frm.reload_doc();
|
||||||
|
}
|
||||||
|
);
|
||||||
me.dialog.hide();
|
me.dialog.hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -341,10 +340,9 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
}
|
}
|
||||||
|
|
||||||
set_release_date(data) {
|
set_release_date(data) {
|
||||||
return frappe.call({
|
const me = this;
|
||||||
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.change_release_date",
|
return me.frm.call("change_release_date", { release_date: data.release_date }, () => {
|
||||||
args: data,
|
me.frm.reload_doc();
|
||||||
callback: (r) => this.frm.reload_doc(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -352,6 +352,7 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"collapsible_depends_on": "eval:doc.on_hold",
|
"collapsible_depends_on": "eval:doc.on_hold",
|
||||||
|
"depends_on": "eval:doc.on_hold",
|
||||||
"fieldname": "sb_14",
|
"fieldname": "sb_14",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Hold Invoice"
|
"label": "Hold Invoice"
|
||||||
@@ -1702,7 +1703,7 @@
|
|||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-07-12 23:54:21.263951",
|
"modified": "2026-08-05 15:40:16.519774",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import frappe
|
|||||||
from frappe import _, qb, throw
|
from frappe import _, qb, throw
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
|
from frappe.utils import DateTimeLikeObject, cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||||
@@ -309,6 +309,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
PurchaseTaxWithholding(self).on_validate()
|
PurchaseTaxWithholding(self).on_validate()
|
||||||
self.set_percentage_received()
|
self.set_percentage_received()
|
||||||
|
|
||||||
|
if self.on_hold:
|
||||||
|
self.validate_invoice_hold()
|
||||||
|
|
||||||
def set_percentage_received(self):
|
def set_percentage_received(self):
|
||||||
total_billed_qty = 0.0
|
total_billed_qty = 0.0
|
||||||
total_received_qty = 0.0
|
total_received_qty = 0.0
|
||||||
@@ -320,6 +323,13 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if total_billed_qty and total_received_qty:
|
if total_billed_qty and total_received_qty:
|
||||||
self.per_received = total_received_qty / total_billed_qty * 100
|
self.per_received = total_received_qty / total_billed_qty * 100
|
||||||
|
|
||||||
|
def validate_invoice_hold(self):
|
||||||
|
if self.is_return:
|
||||||
|
frappe.throw(_("Return Purchase Invoice cannot be held."))
|
||||||
|
|
||||||
|
if self.docstatus < 1:
|
||||||
|
frappe.throw(_("Purchase Invoice can be held after submitting."))
|
||||||
|
|
||||||
def validate_release_date(self):
|
def validate_release_date(self):
|
||||||
if self.release_date and getdate(nowdate()) >= getdate(self.release_date):
|
if self.release_date and getdate(nowdate()) >= getdate(self.release_date):
|
||||||
frappe.throw(_("Release date must be in the future"))
|
frappe.throw(_("Release date must be in the future"))
|
||||||
@@ -1901,14 +1911,38 @@ class PurchaseInvoice(BuyingController):
|
|||||||
def on_recurring(self, reference_doc, auto_repeat_doc):
|
def on_recurring(self, reference_doc, auto_repeat_doc):
|
||||||
self.due_date = None
|
self.due_date = None
|
||||||
|
|
||||||
def block_invoice(self, hold_comment=None, release_date=None):
|
@frappe.whitelist(methods=["POST"])
|
||||||
self.db_set("on_hold", 1)
|
def block_invoice(self, hold_comment: str | None = None, release_date: DateTimeLikeObject | None = None):
|
||||||
self.db_set("hold_comment", cstr(hold_comment))
|
self.check_permission("write")
|
||||||
|
self.on_hold = 1
|
||||||
|
self.release_date = release_date
|
||||||
|
self.validate_block_invoice()
|
||||||
|
|
||||||
|
self.db_set({"on_hold": 1, "hold_comment": cstr(hold_comment), "release_date": release_date})
|
||||||
|
|
||||||
|
@frappe.whitelist(methods=["POST"])
|
||||||
|
def unblock_invoice(self):
|
||||||
|
self.check_permission("write")
|
||||||
|
self.db_set({"on_hold": 0, "release_date": None})
|
||||||
|
|
||||||
|
@frappe.whitelist(methods=["POST"])
|
||||||
|
def change_release_date(self, release_date: DateTimeLikeObject | None = None):
|
||||||
|
self.check_permission("write")
|
||||||
|
|
||||||
|
if not self.on_hold:
|
||||||
|
frappe.throw(_("Invoice is not blocked. Block the invoice to change the release date."))
|
||||||
|
|
||||||
|
self.release_date = release_date
|
||||||
|
self.validate_block_invoice()
|
||||||
|
|
||||||
self.db_set("release_date", release_date)
|
self.db_set("release_date", release_date)
|
||||||
|
|
||||||
def unblock_invoice(self):
|
def validate_block_invoice(self):
|
||||||
self.db_set("on_hold", 0)
|
self.validate_invoice_hold()
|
||||||
self.db_set("release_date", None)
|
if self.outstanding_amount <= 0:
|
||||||
|
frappe.throw(_("Purchase Invoice without any outstanding amount cannot be held."))
|
||||||
|
|
||||||
|
self.validate_release_date()
|
||||||
|
|
||||||
def set_status(self, update=False, status=None, update_modified=True):
|
def set_status(self, update=False, status=None, update_modified=True):
|
||||||
if self.is_new():
|
if self.is_new():
|
||||||
@@ -2033,28 +2067,6 @@ def make_stock_entry(source_name, target_doc=None):
|
|||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def change_release_date(name, release_date=None):
|
|
||||||
if frappe.db.exists("Purchase Invoice", name):
|
|
||||||
pi = frappe.get_lazy_doc("Purchase Invoice", name)
|
|
||||||
pi.check_permission()
|
|
||||||
pi.db_set("release_date", release_date)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def unblock_invoice(name):
|
|
||||||
if frappe.db.exists("Purchase Invoice", name):
|
|
||||||
pi = frappe.get_lazy_doc("Purchase Invoice", name)
|
|
||||||
pi.unblock_invoice()
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def block_invoice(name, release_date, hold_comment=None):
|
|
||||||
if frappe.db.exists("Purchase Invoice", name):
|
|
||||||
pi = frappe.get_lazy_doc("Purchase Invoice", name)
|
|
||||||
pi.block_invoice(hold_comment, release_date)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_inter_company_sales_invoice(source_name, target_doc=None):
|
def make_inter_company_sales_invoice(source_name, target_doc=None):
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
|
||||||
|
|||||||
@@ -278,14 +278,166 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
|
|||||||
|
|
||||||
def test_purchase_invoice_explicit_block(self):
|
def test_purchase_invoice_explicit_block(self):
|
||||||
pi = make_purchase_invoice()
|
pi = make_purchase_invoice()
|
||||||
pi.block_invoice()
|
release_date = add_days(nowdate(), 10)
|
||||||
|
|
||||||
|
pi.block_invoice(hold_comment="Waiting for the goods", release_date=release_date)
|
||||||
|
|
||||||
self.assertEqual(pi.on_hold, 1)
|
self.assertEqual(pi.on_hold, 1)
|
||||||
|
|
||||||
|
on_hold, hold_comment, saved_release_date = frappe.db.get_value(
|
||||||
|
"Purchase Invoice", pi.name, ["on_hold", "hold_comment", "release_date"]
|
||||||
|
)
|
||||||
|
self.assertEqual(on_hold, 1)
|
||||||
|
self.assertEqual(hold_comment, "Waiting for the goods")
|
||||||
|
self.assertEqual(getdate(saved_release_date), getdate(release_date))
|
||||||
|
|
||||||
pi.unblock_invoice()
|
pi.unblock_invoice()
|
||||||
|
|
||||||
self.assertEqual(pi.on_hold, 0)
|
self.assertEqual(pi.on_hold, 0)
|
||||||
|
|
||||||
|
on_hold, saved_release_date = frappe.db.get_value(
|
||||||
|
"Purchase Invoice", pi.name, ["on_hold", "release_date"]
|
||||||
|
)
|
||||||
|
self.assertEqual(on_hold, 0)
|
||||||
|
self.assertIsNone(saved_release_date)
|
||||||
|
|
||||||
|
def test_purchase_invoice_cannot_be_held_before_submission(self):
|
||||||
|
pi = make_purchase_invoice(do_not_save=True)
|
||||||
|
pi.on_hold = 1
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, pi.save)
|
||||||
|
|
||||||
|
pi.on_hold = 0
|
||||||
|
pi.save()
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
pi.block_invoice()
|
||||||
|
self.assertEqual(frappe.db.get_value("Purchase Invoice", pi.name, "on_hold"), 1)
|
||||||
|
|
||||||
|
def test_return_purchase_invoice_cannot_be_held(self):
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
|
||||||
|
return_pi = make_return_doc(pi.doctype, pi.name)
|
||||||
|
return_pi.on_hold = 1
|
||||||
|
self.assertRaisesRegex(frappe.ValidationError, "cannot be held", return_pi.save)
|
||||||
|
|
||||||
|
return_pi.on_hold = 0
|
||||||
|
return_pi.save()
|
||||||
|
return_pi.submit()
|
||||||
|
|
||||||
|
self.assertRaisesRegex(frappe.ValidationError, "cannot be held", return_pi.block_invoice)
|
||||||
|
|
||||||
|
def test_return_purchase_invoice_is_not_affected_by_hold_validations(self):
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
|
||||||
|
# a return has a negative outstanding amount, which must not be mistaken
|
||||||
|
# for an invalid hold on a document that was never held
|
||||||
|
return_pi = make_return_doc(pi.doctype, pi.name)
|
||||||
|
return_pi.save()
|
||||||
|
return_pi.submit()
|
||||||
|
|
||||||
|
self.assertEqual(return_pi.docstatus, 1)
|
||||||
|
self.assertEqual(return_pi.on_hold, 0)
|
||||||
|
self.assertLess(return_pi.outstanding_amount, 0)
|
||||||
|
|
||||||
|
def test_settled_purchase_invoice_cannot_be_held(self):
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
|
||||||
|
pe = get_payment_entry("Purchase Invoice", dn=pi.name, bank_account="_Test Bank - _TC")
|
||||||
|
pe.reference_no = "1"
|
||||||
|
pe.reference_date = nowdate()
|
||||||
|
pe.save()
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
|
pi.reload()
|
||||||
|
self.assertEqual(pi.outstanding_amount, 0)
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, pi.block_invoice)
|
||||||
|
self.assertEqual(frappe.db.get_value("Purchase Invoice", pi.name, "on_hold"), 0)
|
||||||
|
|
||||||
|
def test_release_date_of_held_invoice_must_be_in_future(self):
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, pi.block_invoice, "Hold", add_days(nowdate(), -1))
|
||||||
|
self.assertRaises(frappe.ValidationError, pi.block_invoice, "Hold", nowdate())
|
||||||
|
|
||||||
|
def test_rejected_hold_does_not_partially_update_invoice(self):
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, pi.block_invoice, "Hold", add_days(nowdate(), -1))
|
||||||
|
|
||||||
|
pi.reload()
|
||||||
|
self.assertEqual(pi.on_hold, 0)
|
||||||
|
self.assertIsNone(pi.release_date)
|
||||||
|
|
||||||
|
def test_change_release_date_of_held_invoice(self):
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
pi.block_invoice(hold_comment="Hold", release_date=add_days(nowdate(), 10))
|
||||||
|
|
||||||
|
new_release_date = add_days(nowdate(), 20)
|
||||||
|
pi.change_release_date(new_release_date)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
getdate(frappe.db.get_value("Purchase Invoice", pi.name, "release_date")),
|
||||||
|
getdate(new_release_date),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, pi.change_release_date, add_days(nowdate(), -1))
|
||||||
|
|
||||||
|
def test_release_date_cannot_be_changed_on_an_invoice_that_is_not_held(self):
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
|
||||||
|
self.assertRaisesRegex(
|
||||||
|
frappe.ValidationError,
|
||||||
|
"Invoice is not blocked",
|
||||||
|
pi.change_release_date,
|
||||||
|
add_days(nowdate(), 10),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsNone(frappe.db.get_value("Purchase Invoice", pi.name, "release_date"))
|
||||||
|
|
||||||
|
def test_hold_methods_are_whitelisted_document_methods(self):
|
||||||
|
import erpnext.accounts.doctype.purchase_invoice.purchase_invoice as purchase_invoice_module
|
||||||
|
|
||||||
|
pi = frappe.new_doc("Purchase Invoice")
|
||||||
|
|
||||||
|
for method in ("block_invoice", "unblock_invoice", "change_release_date"):
|
||||||
|
# raises if the method is not whitelisted for client side calls
|
||||||
|
pi.is_whitelisted(method)
|
||||||
|
|
||||||
|
self.assertFalse(
|
||||||
|
hasattr(purchase_invoice_module, method),
|
||||||
|
f"{method} should only be exposed as a document method",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_hold_methods_require_write_permission(self):
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
user = "test_pi_hold_permission@example.com"
|
||||||
|
|
||||||
|
if not frappe.db.exists("User", user):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "User",
|
||||||
|
"email": user,
|
||||||
|
"first_name": "Test PI Hold",
|
||||||
|
"roles": [{"role": "Employee"}],
|
||||||
|
}
|
||||||
|
).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
frappe.set_user(user)
|
||||||
|
try:
|
||||||
|
self.assertRaises(frappe.PermissionError, pi.block_invoice)
|
||||||
|
self.assertRaises(frappe.PermissionError, pi.unblock_invoice)
|
||||||
|
self.assertRaises(frappe.PermissionError, pi.change_release_date, add_days(nowdate(), 10))
|
||||||
|
finally:
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
self.assertEqual(frappe.db.get_value("Purchase Invoice", pi.name, "on_hold"), 0)
|
||||||
|
|
||||||
def test_gl_entries_with_perpetual_inventory_against_pr(self):
|
def test_gl_entries_with_perpetual_inventory_against_pr(self):
|
||||||
pr = make_purchase_receipt(
|
pr = make_purchase_receipt(
|
||||||
company="_Test Company with perpetual inventory",
|
company="_Test Company with perpetual inventory",
|
||||||
|
|||||||
Reference in New Issue
Block a user