* add new fields to Supplier Master: - on_hold: To signal the Customer is blocked from completing certain transactions - hold_type: 3 options - All, invoices and payments * sanitize `on_hold` field input * show hold status in list view * add `release_date` field to Supplier Master: - specifies the date when transaction restraint will be removed * reset release date if supplier is not on hold * add validation to stop transactions when Supplier is blocked * add test cases * return empty list for outstanding references if supplier is blocked * block make button:payment if supplier is blocked * adjust test cases * PEP 8 clean up * more tests * adds new fields to Purchase Invoice: - release_date: once set, invoice will be on hold until set date - hold_comment: so user can add comment pertaining to why invoice is on hold * implement individual purchase invoice on hold logic * allow user to change release date * update manual * final cleanup including more validation and tests * update supplier manual * make default for release_date argument todays date * remove Auto Repeat added by mistake * add on_hold_field to purchase invoice * add 'On Hold' or 'Temporarily on Hold' status for purchase invoice in list view * implement explicit payment hold in purchase invoice * update manual * add dialog for saving comment * bug fix, refactor, clean up * more test cases
This commit is contained in:
parent
1906cadd94
commit
ad08d4ce96
@ -5,14 +5,14 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, erpnext, json
|
import frappe, erpnext, json
|
||||||
from frappe import _, scrub, ValidationError
|
from frappe import _, scrub, ValidationError
|
||||||
from frappe.utils import flt, comma_or, nowdate
|
from frappe.utils import flt, comma_or, nowdate, getdate
|
||||||
from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on
|
from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on
|
||||||
from erpnext.accounts.party import get_party_account, get_patry_tax_withholding_details
|
from erpnext.accounts.party import get_party_account, get_patry_tax_withholding_details
|
||||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
||||||
from erpnext.setup.utils import get_exchange_rate
|
from erpnext.setup.utils import get_exchange_rate
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
|
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController, get_supplier_block_status
|
||||||
|
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
|
||||||
@ -59,6 +59,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.set_remarks()
|
self.set_remarks()
|
||||||
self.validate_duplicate_entry()
|
self.validate_duplicate_entry()
|
||||||
self.validate_allocated_amount()
|
self.validate_allocated_amount()
|
||||||
|
self.ensure_supplier_is_not_blocked()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.setup_party_account_field()
|
self.setup_party_account_field()
|
||||||
@ -537,6 +538,16 @@ def get_outstanding_reference_documents(args):
|
|||||||
if isinstance(args, string_types):
|
if isinstance(args, string_types):
|
||||||
args = json.loads(args)
|
args = json.loads(args)
|
||||||
|
|
||||||
|
# confirm that Supplier is not blocked
|
||||||
|
if args.get('party_type') == 'Supplier':
|
||||||
|
supplier_status = get_supplier_block_status(args['party'])
|
||||||
|
if supplier_status['on_hold']:
|
||||||
|
if supplier_status['hold_type'] == 'All':
|
||||||
|
return []
|
||||||
|
elif supplier_status['hold_type'] == 'Payments':
|
||||||
|
if not supplier_status['release_date'] or getdate(nowdate()) <= supplier_status['release_date']:
|
||||||
|
return []
|
||||||
|
|
||||||
party_account_currency = get_account_currency(args.get("party_account"))
|
party_account_currency = get_account_currency(args.get("party_account"))
|
||||||
company_currency = frappe.db.get_value("Company", args.get("company"), "default_currency")
|
company_currency = frappe.db.get_value("Company", args.get("company"), "default_currency")
|
||||||
|
|
||||||
@ -621,6 +632,9 @@ def get_orders_to_be_billed(posting_date, party_type, party, party_account_curre
|
|||||||
|
|
||||||
def get_negative_outstanding_invoices(party_type, party, party_account, party_account_currency, company_currency):
|
def get_negative_outstanding_invoices(party_type, party, party_account, party_account_currency, company_currency):
|
||||||
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
|
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
|
||||||
|
supplier_condition = ""
|
||||||
|
if voucher_type == "Purchase Invoice":
|
||||||
|
supplier_condition = "and (release_date is null or release_date <= CURDATE())"
|
||||||
if party_account_currency == company_currency:
|
if party_account_currency == company_currency:
|
||||||
grand_total_field = "base_grand_total"
|
grand_total_field = "base_grand_total"
|
||||||
rounded_total_field = "base_rounded_total"
|
rounded_total_field = "base_rounded_total"
|
||||||
@ -638,9 +652,11 @@ def get_negative_outstanding_invoices(party_type, party, party_account, party_ac
|
|||||||
`tab{voucher_type}`
|
`tab{voucher_type}`
|
||||||
where
|
where
|
||||||
{party_type} = %s and {party_account} = %s and docstatus = 1 and outstanding_amount < 0
|
{party_type} = %s and {party_account} = %s and docstatus = 1 and outstanding_amount < 0
|
||||||
|
{supplier_condition}
|
||||||
order by
|
order by
|
||||||
posting_date, name
|
posting_date, name
|
||||||
""".format(**{
|
""".format(**{
|
||||||
|
"supplier_condition": supplier_condition,
|
||||||
"rounded_total_field": rounded_total_field,
|
"rounded_total_field": rounded_total_field,
|
||||||
"grand_total_field": grand_total_field,
|
"grand_total_field": grand_total_field,
|
||||||
"voucher_type": voucher_type,
|
"voucher_type": voucher_type,
|
||||||
@ -854,6 +870,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
|||||||
pe.mode_of_payment = doc.get("mode_of_payment")
|
pe.mode_of_payment = doc.get("mode_of_payment")
|
||||||
pe.party_type = party_type
|
pe.party_type = party_type
|
||||||
pe.party = doc.get(scrub(party_type))
|
pe.party = doc.get(scrub(party_type))
|
||||||
|
|
||||||
|
pe.ensure_supplier_is_not_blocked()
|
||||||
|
|
||||||
pe.paid_from = party_account if payment_type=="Receive" else bank.account
|
pe.paid_from = party_account if payment_type=="Receive" else bank.account
|
||||||
pe.paid_to = party_account if payment_type=="Pay" else bank.account
|
pe.paid_to = party_account if payment_type=="Pay" else bank.account
|
||||||
pe.paid_from_account_currency = party_account_currency \
|
pe.paid_from_account_currency = party_account_currency \
|
||||||
@ -864,6 +883,10 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
|||||||
pe.allocate_payment_amount = 1
|
pe.allocate_payment_amount = 1
|
||||||
pe.letter_head = doc.get("letter_head")
|
pe.letter_head = doc.get("letter_head")
|
||||||
|
|
||||||
|
# only Purchase Invoice can be blocked individually
|
||||||
|
if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked():
|
||||||
|
frappe.msgprint(_('{0} is on hold till {1}'.format(doc.name, doc.release_date)))
|
||||||
|
else:
|
||||||
pe.append("references", {
|
pe.append("references", {
|
||||||
'reference_doctype': dt,
|
'reference_doctype': dt,
|
||||||
'reference_name': dn,
|
'reference_name': dn,
|
||||||
|
@ -40,6 +40,69 @@ class TestPaymentEntry(unittest.TestCase):
|
|||||||
so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid")
|
so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid")
|
||||||
self.assertEqual(so_advance_paid, 0)
|
self.assertEqual(so_advance_paid, 0)
|
||||||
|
|
||||||
|
def test_payment_entry_for_blocked_supplier_invoice(self):
|
||||||
|
supplier = frappe.get_doc('Supplier', '_Test Supplier')
|
||||||
|
supplier.on_hold = 1
|
||||||
|
supplier.hold_type = 'Invoices'
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, make_purchase_invoice)
|
||||||
|
|
||||||
|
supplier.on_hold = 0
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
def test_payment_entry_for_blocked_supplier_payments(self):
|
||||||
|
supplier = frappe.get_doc('Supplier', '_Test Supplier')
|
||||||
|
supplier.on_hold = 1
|
||||||
|
supplier.hold_type = 'Payments'
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
frappe.ValidationError, get_payment_entry, dt='Purchase Invoice', dn=pi.name,
|
||||||
|
bank_account="_Test Bank - _TC")
|
||||||
|
|
||||||
|
supplier.on_hold = 0
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
def test_payment_entry_for_blocked_supplier_payments_today_date(self):
|
||||||
|
supplier = frappe.get_doc('Supplier', '_Test Supplier')
|
||||||
|
supplier.on_hold = 1
|
||||||
|
supplier.hold_type = 'Payments'
|
||||||
|
supplier.release_date = nowdate()
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
frappe.ValidationError, get_payment_entry, dt='Purchase Invoice', dn=pi.name,
|
||||||
|
bank_account="_Test Bank - _TC")
|
||||||
|
|
||||||
|
supplier.on_hold = 0
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
def test_payment_entry_for_blocked_supplier_payments_past_date(self):
|
||||||
|
# this test is meant to fail only if something fails in the try block
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
try:
|
||||||
|
supplier = frappe.get_doc('Supplier', '_Test Supplier')
|
||||||
|
supplier.on_hold = 1
|
||||||
|
supplier.hold_type = 'Payments'
|
||||||
|
supplier.release_date = '2018-03-01'
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
|
||||||
|
get_payment_entry('Purchase Invoice', pi.name, bank_account="_Test Bank - _TC")
|
||||||
|
|
||||||
|
supplier.on_hold = 0
|
||||||
|
supplier.save()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise Exception
|
||||||
|
|
||||||
def test_payment_entry_against_si_usd_to_usd(self):
|
def test_payment_entry_against_si_usd_to_usd(self):
|
||||||
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
|
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
|
||||||
currency="USD", conversion_rate=50)
|
currency="USD", conversion_rate=50)
|
||||||
|
@ -27,6 +27,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(doc) {
|
refresh: function(doc) {
|
||||||
|
const me = this;
|
||||||
this._super();
|
this._super();
|
||||||
|
|
||||||
hide_fields(this.frm.doc);
|
hide_fields(this.frm.doc);
|
||||||
@ -37,6 +38,27 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
this.show_stock_ledger();
|
this.show_stock_ledger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!doc.is_return && doc.docstatus == 1 && doc.outstanding_amount != 0){
|
||||||
|
if(doc.on_hold) {
|
||||||
|
this.frm.add_custom_button(
|
||||||
|
__('Change Release Date'),
|
||||||
|
function() {me.change_release_date()},
|
||||||
|
__('Hold Invoice')
|
||||||
|
);
|
||||||
|
this.frm.add_custom_button(
|
||||||
|
__('Unblock Invoice'),
|
||||||
|
function() {me.unblock_invoice()},
|
||||||
|
__('Make')
|
||||||
|
);
|
||||||
|
} else if (!doc.on_hold) {
|
||||||
|
this.frm.add_custom_button(
|
||||||
|
__('Block Invoice'),
|
||||||
|
function() {me.block_invoice()},
|
||||||
|
__('Make')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!doc.is_return && doc.docstatus==1) {
|
if(!doc.is_return && doc.docstatus==1) {
|
||||||
if(doc.outstanding_amount != 0) {
|
if(doc.outstanding_amount != 0) {
|
||||||
this.frm.add_custom_button(__('Payment'), this.make_payment_entry, __("Make"));
|
this.frm.add_custom_button(__('Payment'), this.make_payment_entry, __("Make"));
|
||||||
@ -56,7 +78,6 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(doc.docstatus===0) {
|
if(doc.docstatus===0) {
|
||||||
var me = this;
|
|
||||||
this.frm.add_custom_button(__('Purchase Order'), function() {
|
this.frm.add_custom_button(__('Purchase Order'), function() {
|
||||||
erpnext.utils.map_current_doc({
|
erpnext.utils.map_current_doc({
|
||||||
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice",
|
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice",
|
||||||
@ -109,6 +130,104 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
unblock_invoice: function() {
|
||||||
|
const me = this;
|
||||||
|
frappe.call({
|
||||||
|
'method': 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.unblock_invoice',
|
||||||
|
'args': {'name': me.frm.doc.name},
|
||||||
|
'callback': (r) => me.frm.reload_doc()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
block_invoice: function() {
|
||||||
|
this.make_comment_dialog_and_block_invoice();
|
||||||
|
},
|
||||||
|
|
||||||
|
change_release_date: function() {
|
||||||
|
this.make_dialog_and_set_release_date();
|
||||||
|
},
|
||||||
|
|
||||||
|
can_change_release_date: function(date) {
|
||||||
|
const diff = frappe.datetime.get_diff(date, frappe.datetime.nowdate());
|
||||||
|
if (diff < 0) {
|
||||||
|
frappe.throw('New release date should be in the future');
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
make_comment_dialog_and_block_invoice: function(){
|
||||||
|
const me = this;
|
||||||
|
|
||||||
|
const title = __('Add Comment');
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
fieldname: 'hold_comment',
|
||||||
|
read_only: 0,
|
||||||
|
fieldtype:'Small Text',
|
||||||
|
label: __('Reason For Putting On Hold'),
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
this.dialog = new frappe.ui.Dialog({
|
||||||
|
title: title,
|
||||||
|
fields: fields
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dialog.set_primary_action(__('Save'), function() {
|
||||||
|
const dialog_data = me.dialog.get_values();
|
||||||
|
frappe.call({
|
||||||
|
'method': 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.block_invoice',
|
||||||
|
'args': {'name': me.frm.doc.name, 'hold_comment': dialog_data.hold_comment},
|
||||||
|
'callback': (r) => me.frm.reload_doc()
|
||||||
|
});
|
||||||
|
me.dialog.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dialog.show();
|
||||||
|
},
|
||||||
|
|
||||||
|
make_dialog_and_set_release_date: function() {
|
||||||
|
const me = this;
|
||||||
|
|
||||||
|
const title = __('Set New Release Date');
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
fieldname: 'release_date',
|
||||||
|
read_only: 0,
|
||||||
|
fieldtype:'Date',
|
||||||
|
label: __('Release Date'),
|
||||||
|
default: me.frm.doc.release_date
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
this.dialog = new frappe.ui.Dialog({
|
||||||
|
title: title,
|
||||||
|
fields: fields
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dialog.set_primary_action(__('Save'), function() {
|
||||||
|
me.dialog_data = me.dialog.get_values();
|
||||||
|
if(me.can_change_release_date(me.dialog_data.release_date)) {
|
||||||
|
me.dialog_data.name = me.frm.doc.name;
|
||||||
|
me.set_release_date(me.dialog_data);
|
||||||
|
me.dialog.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dialog.show();
|
||||||
|
},
|
||||||
|
|
||||||
|
set_release_date: function(data) {
|
||||||
|
return frappe.call({
|
||||||
|
'method': 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.change_release_date',
|
||||||
|
'args': data,
|
||||||
|
'callback': (r) => this.frm.reload_doc()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
supplier: function() {
|
supplier: function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
if(this.frm.updating_party_details)
|
if(this.frm.updating_party_details)
|
||||||
|
@ -432,6 +432,165 @@
|
|||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 1,
|
||||||
|
"collapsible_depends_on": "eval:doc.on_hold",
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "sb_14",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Hold Invoice",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "on_hold",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Hold Invoice",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "eval:doc.on_hold",
|
||||||
|
"description": "Once set, this invoice will be on hold till the set date",
|
||||||
|
"fieldname": "release_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Release Date",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "cb_17",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "eval:doc.on_hold",
|
||||||
|
"fieldname": "hold_comment",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Reason For Putting On Hold",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, erpnext
|
import frappe, erpnext
|
||||||
from frappe.utils import cint, formatdate, flt, getdate
|
from frappe.utils import cint, cstr, formatdate, flt, getdate, nowdate
|
||||||
from frappe import _, throw
|
from frappe import _, throw
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
|
|
||||||
@ -41,6 +41,13 @@ class PurchaseInvoice(BuyingController):
|
|||||||
'overflow_type': 'billing'
|
'overflow_type': 'billing'
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
def before_save(self):
|
||||||
|
if not self.on_hold:
|
||||||
|
self.release_date = ''
|
||||||
|
|
||||||
|
def invoice_is_blocked(self):
|
||||||
|
return self.on_hold and (not self.release_date or self.release_date > getdate(nowdate()))
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if not self.is_opening:
|
if not self.is_opening:
|
||||||
self.is_opening = 'No'
|
self.is_opening = 'No'
|
||||||
@ -61,6 +68,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if self._action=="submit" and self.update_stock:
|
if self._action=="submit" and self.update_stock:
|
||||||
self.make_batches('warehouse')
|
self.make_batches('warehouse')
|
||||||
|
|
||||||
|
self.validate_release_date()
|
||||||
self.check_conversion_rate()
|
self.check_conversion_rate()
|
||||||
self.validate_credit_to_acc()
|
self.validate_credit_to_acc()
|
||||||
self.clear_unallocated_advances("Purchase Invoice Advance", "advances")
|
self.clear_unallocated_advances("Purchase Invoice Advance", "advances")
|
||||||
@ -78,6 +86,10 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.set_status()
|
self.set_status()
|
||||||
validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_invoice_reference)
|
validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_invoice_reference)
|
||||||
|
|
||||||
|
def validate_release_date(self):
|
||||||
|
if self.release_date and getdate(nowdate()) >= getdate(self.release_date):
|
||||||
|
frappe.msgprint('Release date must be in the future', raise_exception=True)
|
||||||
|
|
||||||
def validate_cash(self):
|
def validate_cash(self):
|
||||||
if not self.cash_bank_account and flt(self.paid_amount):
|
if not self.cash_bank_account and flt(self.paid_amount):
|
||||||
frappe.throw(_("Cash or Bank Account is mandatory for making payment entry"))
|
frappe.throw(_("Cash or Bank Account is mandatory for making payment entry"))
|
||||||
@ -730,6 +742,14 @@ 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):
|
||||||
|
self.db_set('on_hold', 1)
|
||||||
|
self.db_set('hold_comment', cstr(hold_comment))
|
||||||
|
|
||||||
|
def unblock_invoice(self):
|
||||||
|
self.db_set('on_hold', 0)
|
||||||
|
self.db_set('release_date', None)
|
||||||
|
|
||||||
def set_tax_withholding(self):
|
def set_tax_withholding(self):
|
||||||
"""
|
"""
|
||||||
1. Get TDS Configurations against Supplier
|
1. Get TDS Configurations against Supplier
|
||||||
@ -768,7 +788,28 @@ 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_doc('Purchase Invoice', name)
|
||||||
|
pi.db_set('release_date', release_date)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def unblock_invoice(name):
|
||||||
|
if frappe.db.exists('Purchase Invoice', name):
|
||||||
|
pi = frappe.get_doc('Purchase Invoice', name)
|
||||||
|
pi.unblock_invoice()
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def block_invoice(name, hold_comment):
|
||||||
|
if frappe.db.exists('Purchase Invoice', name):
|
||||||
|
pi = frappe.get_doc('Purchase Invoice', name)
|
||||||
|
pi.block_invoice(hold_comment)
|
||||||
|
|
||||||
@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_invoice
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_invoice
|
||||||
return make_inter_company_invoice("Purchase Invoice", source_name, target_doc)
|
return make_inter_company_invoice("Purchase Invoice", source_name, target_doc)
|
||||||
|
|
||||||
|
@ -4,12 +4,16 @@
|
|||||||
// render
|
// render
|
||||||
frappe.listview_settings['Purchase Invoice'] = {
|
frappe.listview_settings['Purchase Invoice'] = {
|
||||||
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
|
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
|
||||||
"currency", "is_return"],
|
"currency", "is_return", "release_date", "on_hold"],
|
||||||
get_indicator: function(doc) {
|
get_indicator: function(doc) {
|
||||||
if(cint(doc.is_return)==1) {
|
if(cint(doc.is_return)==1) {
|
||||||
return [__("Return"), "darkgrey", "is_return,=,Yes"];
|
return [__("Return"), "darkgrey", "is_return,=,Yes"];
|
||||||
} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
|
} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
|
||||||
if(frappe.datetime.get_diff(doc.due_date) < 0) {
|
if(cint(doc.on_hold) && !doc.release_date) {
|
||||||
|
return [__("On Hold"), "darkgrey"];
|
||||||
|
} else if(cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
|
||||||
|
return [__("Temporarily on Hold"), "darkgrey"];
|
||||||
|
} else if(frappe.datetime.get_diff(doc.due_date) < 0) {
|
||||||
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
|
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
|
||||||
} else {
|
} else {
|
||||||
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due,>=,Today"];
|
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due,>=,Today"];
|
||||||
|
@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
|||||||
import unittest
|
import unittest
|
||||||
import frappe, erpnext
|
import frappe, erpnext
|
||||||
import frappe.model
|
import frappe.model
|
||||||
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
from frappe.utils import cint, flt, today, nowdate, add_days
|
from frappe.utils import cint, flt, today, nowdate, add_days
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \
|
||||||
@ -91,6 +92,106 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertRaises(frappe.LinkExistsError, pi_doc.cancel)
|
self.assertRaises(frappe.LinkExistsError, pi_doc.cancel)
|
||||||
|
|
||||||
|
def test_purchase_invoice_for_blocked_supplier(self):
|
||||||
|
supplier = frappe.get_doc('Supplier', '_Test Supplier')
|
||||||
|
supplier.on_hold = 1
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, make_purchase_invoice)
|
||||||
|
|
||||||
|
supplier.on_hold = 0
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
def test_purchase_invoice_for_blocked_supplier_invoice(self):
|
||||||
|
supplier = frappe.get_doc('Supplier', '_Test Supplier')
|
||||||
|
supplier.on_hold = 1
|
||||||
|
supplier.hold_type = 'Invoices'
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, make_purchase_invoice)
|
||||||
|
|
||||||
|
supplier.on_hold = 0
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
def test_purchase_invoice_for_blocked_supplier_payment(self):
|
||||||
|
supplier = frappe.get_doc('Supplier', '_Test Supplier')
|
||||||
|
supplier.on_hold = 1
|
||||||
|
supplier.hold_type = 'Payments'
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
frappe.ValidationError, get_payment_entry, dt='Purchase Invoice', dn=pi.name, bank_account="_Test Bank - _TC")
|
||||||
|
|
||||||
|
supplier.on_hold = 0
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
def test_purchase_invoice_for_blocked_supplier_payment_today_date(self):
|
||||||
|
supplier = frappe.get_doc('Supplier', '_Test Supplier')
|
||||||
|
supplier.on_hold = 1
|
||||||
|
supplier.hold_type = 'Payments'
|
||||||
|
supplier.release_date = nowdate()
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
frappe.ValidationError, get_payment_entry, dt='Purchase Invoice', dn=pi.name,
|
||||||
|
bank_account="_Test Bank - _TC")
|
||||||
|
|
||||||
|
supplier.on_hold = 0
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
def test_purchase_invoice_for_blocked_supplier_payment_past_date(self):
|
||||||
|
# this test is meant to fail only if something fails in the try block
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
try:
|
||||||
|
supplier = frappe.get_doc('Supplier', '_Test Supplier')
|
||||||
|
supplier.on_hold = 1
|
||||||
|
supplier.hold_type = 'Payments'
|
||||||
|
supplier.release_date = '2018-03-01'
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
|
||||||
|
get_payment_entry('Purchase Invoice', dn=pi.name, bank_account="_Test Bank - _TC")
|
||||||
|
|
||||||
|
supplier.on_hold = 0
|
||||||
|
supplier.save()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise Exception
|
||||||
|
|
||||||
|
def test_purchase_invoice_blocked_invoice_must_be_in_future(self):
|
||||||
|
pi = make_purchase_invoice(do_not_save=True)
|
||||||
|
pi.release_date = nowdate()
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, pi.save)
|
||||||
|
pi.release_date = ''
|
||||||
|
pi.save()
|
||||||
|
|
||||||
|
def test_purchase_invoice_temporary_blocked(self):
|
||||||
|
pi = make_purchase_invoice(do_not_save=True)
|
||||||
|
pi.release_date = add_days(nowdate(), 10)
|
||||||
|
pi.save()
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
pe = get_payment_entry('Purchase Invoice', dn=pi.name, bank_account="_Test Bank - _TC")
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, pe.save)
|
||||||
|
|
||||||
|
def test_purchase_invoice_explicit_block(self):
|
||||||
|
pi = make_purchase_invoice()
|
||||||
|
pi.block_invoice()
|
||||||
|
|
||||||
|
self.assertEqual(pi.on_hold, 1)
|
||||||
|
|
||||||
|
pi.unblock_invoice()
|
||||||
|
|
||||||
|
self.assertEqual(pi.on_hold, 0)
|
||||||
|
|
||||||
def test_gl_entries_with_perpetual_inventory_against_pr(self):
|
def test_gl_entries_with_perpetual_inventory_against_pr(self):
|
||||||
pr = frappe.copy_doc(pr_test_records[0])
|
pr = frappe.copy_doc(pr_test_records[0])
|
||||||
set_perpetual_inventory(1, pr.company)
|
set_perpetual_inventory(1, pr.company)
|
||||||
|
@ -572,6 +572,22 @@ def get_stock_rbnb_difference(posting_date, company):
|
|||||||
return flt(stock_rbnb) + flt(sys_bal)
|
return flt(stock_rbnb) + flt(sys_bal)
|
||||||
|
|
||||||
|
|
||||||
|
def get_held_invoices(party_type, party):
|
||||||
|
"""
|
||||||
|
Returns a list of names Purchase Invoices for the given party that are on hold
|
||||||
|
"""
|
||||||
|
held_invoices = None
|
||||||
|
|
||||||
|
if party_type == 'Supplier':
|
||||||
|
held_invoices = frappe.db.sql(
|
||||||
|
'select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()',
|
||||||
|
as_dict=1
|
||||||
|
)
|
||||||
|
held_invoices = [d['name'] for d in held_invoices]
|
||||||
|
|
||||||
|
return held_invoices
|
||||||
|
|
||||||
|
|
||||||
def get_outstanding_invoices(party_type, party, account, condition=None):
|
def get_outstanding_invoices(party_type, party, account, condition=None):
|
||||||
outstanding_invoices = []
|
outstanding_invoices = []
|
||||||
precision = frappe.get_precision("Sales Invoice", "outstanding_amount")
|
precision = frappe.get_precision("Sales Invoice", "outstanding_amount")
|
||||||
@ -584,6 +600,8 @@ def get_outstanding_invoices(party_type, party, account, condition=None):
|
|||||||
payment_dr_or_cr = "payment_gl_entry.debit_in_account_currency - payment_gl_entry.credit_in_account_currency"
|
payment_dr_or_cr = "payment_gl_entry.debit_in_account_currency - payment_gl_entry.credit_in_account_currency"
|
||||||
|
|
||||||
invoice = 'Sales Invoice' if erpnext.get_party_account_type(party_type) == 'Receivable' else 'Purchase Invoice'
|
invoice = 'Sales Invoice' if erpnext.get_party_account_type(party_type) == 'Receivable' else 'Purchase Invoice'
|
||||||
|
held_invoices = get_held_invoices(party_type, party)
|
||||||
|
|
||||||
invoice_list = frappe.db.sql("""
|
invoice_list = frappe.db.sql("""
|
||||||
select
|
select
|
||||||
voucher_no, voucher_type, posting_date, ifnull(sum({dr_or_cr}), 0) as invoice_amount,
|
voucher_no, voucher_type, posting_date, ifnull(sum({dr_or_cr}), 0) as invoice_amount,
|
||||||
@ -622,8 +640,9 @@ def get_outstanding_invoices(party_type, party, account, condition=None):
|
|||||||
}, as_dict=True)
|
}, as_dict=True)
|
||||||
|
|
||||||
for d in invoice_list:
|
for d in invoice_list:
|
||||||
due_date = frappe.db.get_value(d.voucher_type, d.voucher_no,
|
if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices:
|
||||||
"posting_date" if party_type == "Employee" else "due_date")
|
due_date = frappe.db.get_value(
|
||||||
|
d.voucher_type, d.voucher_no, "posting_date" if party_type == "Employee" else "due_date")
|
||||||
|
|
||||||
outstanding_invoices.append(
|
outstanding_invoices.append(
|
||||||
frappe._dict({
|
frappe._dict({
|
||||||
|
@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
|||||||
import unittest
|
import unittest
|
||||||
import frappe
|
import frappe
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
from frappe.utils import flt, add_days, nowdate
|
from frappe.utils import flt, add_days, nowdate
|
||||||
from erpnext.buying.doctype.purchase_order.purchase_order import (make_purchase_receipt, make_purchase_invoice, make_rm_stock_entry as make_subcontract_transfer_entry)
|
from erpnext.buying.doctype.purchase_order.purchase_order import (make_purchase_receipt, make_purchase_invoice, make_rm_stock_entry as make_subcontract_transfer_entry)
|
||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
@ -163,6 +164,77 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue(po.get('payment_schedule'))
|
self.assertTrue(po.get('payment_schedule'))
|
||||||
|
|
||||||
|
def test_po_for_blocked_supplier_all(self):
|
||||||
|
supplier = frappe.get_doc('Supplier', '_Test Supplier')
|
||||||
|
supplier.on_hold = 1
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
self.assertEqual(supplier.hold_type, 'All')
|
||||||
|
self.assertRaises(frappe.ValidationError, create_purchase_order)
|
||||||
|
|
||||||
|
supplier.on_hold = 0
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
def test_po_for_blocked_supplier_invoices(self):
|
||||||
|
supplier = frappe.get_doc('Supplier', '_Test Supplier')
|
||||||
|
supplier.on_hold = 1
|
||||||
|
supplier.hold_type = 'Invoices'
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, create_purchase_order)
|
||||||
|
|
||||||
|
supplier.on_hold = 0
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
def test_po_for_blocked_supplier_payments(self):
|
||||||
|
supplier = frappe.get_doc('Supplier', '_Test Supplier')
|
||||||
|
supplier.on_hold = 1
|
||||||
|
supplier.hold_type = 'Payments'
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
po = create_purchase_order()
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
frappe.ValidationError, get_payment_entry, dt='Purchase Order', dn=po.name, bank_account="_Test Bank - _TC")
|
||||||
|
|
||||||
|
supplier.on_hold = 0
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
def test_po_for_blocked_supplier_payments_with_today_date(self):
|
||||||
|
supplier = frappe.get_doc('Supplier', '_Test Supplier')
|
||||||
|
supplier.on_hold = 1
|
||||||
|
supplier.release_date = nowdate()
|
||||||
|
supplier.hold_type = 'Payments'
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
po = create_purchase_order()
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
frappe.ValidationError, get_payment_entry, dt='Purchase Order', dn=po.name, bank_account="_Test Bank - _TC")
|
||||||
|
|
||||||
|
supplier.on_hold = 0
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
def test_po_for_blocked_supplier_payments_past_date(self):
|
||||||
|
# this test is meant to fail only if something fails in the try block
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
try:
|
||||||
|
supplier = frappe.get_doc('Supplier', '_Test Supplier')
|
||||||
|
supplier.on_hold = 1
|
||||||
|
supplier.hold_type = 'Payments'
|
||||||
|
supplier.release_date = '2018-03-01'
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
po = create_purchase_order()
|
||||||
|
get_payment_entry('Purchase Order', po.name, bank_account='_Test Bank - _TC')
|
||||||
|
|
||||||
|
supplier.on_hold = 0
|
||||||
|
supplier.save()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise Exception
|
||||||
|
|
||||||
def test_terms_does_not_copy(self):
|
def test_terms_does_not_copy(self):
|
||||||
po = create_purchase_order()
|
po = create_purchase_order()
|
||||||
|
|
||||||
|
@ -773,6 +773,134 @@
|
|||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"fieldname": "cb_21",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "on_hold",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Block Supplier",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "eval:doc.on_hold",
|
||||||
|
"fieldname": "hold_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Hold Type",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "\nAll\nInvoices\nPayments",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"depends_on": "eval:doc.on_hold",
|
||||||
|
"description": "Leave blank if the Supplier is blocked indefinitely",
|
||||||
|
"fieldname": "release_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Release Date",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
|
@ -10,6 +10,7 @@ from frappe.contacts.address_and_contact import load_address_and_contact, delete
|
|||||||
from erpnext.utilities.transaction_base import TransactionBase
|
from erpnext.utilities.transaction_base import TransactionBase
|
||||||
from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this
|
from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this
|
||||||
|
|
||||||
|
|
||||||
class Supplier(TransactionBase):
|
class Supplier(TransactionBase):
|
||||||
def get_feed(self):
|
def get_feed(self):
|
||||||
return self.supplier_name
|
return self.supplier_name
|
||||||
@ -19,6 +20,13 @@ class Supplier(TransactionBase):
|
|||||||
load_address_and_contact(self)
|
load_address_and_contact(self)
|
||||||
self.load_dashboard_info()
|
self.load_dashboard_info()
|
||||||
|
|
||||||
|
def before_save(self):
|
||||||
|
if not self.on_hold:
|
||||||
|
self.hold_type = ''
|
||||||
|
self.release_date = ''
|
||||||
|
elif self.on_hold and not self.hold_type:
|
||||||
|
self.hold_type = 'All'
|
||||||
|
|
||||||
def load_dashboard_info(self):
|
def load_dashboard_info(self):
|
||||||
info = get_dashboard_info(self.doctype, self.name)
|
info = get_dashboard_info(self.doctype, self.name)
|
||||||
self.set_onload('dashboard_info', info)
|
self.set_onload('dashboard_info', info)
|
||||||
@ -35,7 +43,7 @@ class Supplier(TransactionBase):
|
|||||||
self.naming_series = ''
|
self.naming_series = ''
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
#validation for Naming Series mandatory field...
|
# validation for Naming Series mandatory field...
|
||||||
if frappe.defaults.get_global_default('supp_master_name') == 'Naming Series':
|
if frappe.defaults.get_global_default('supp_master_name') == 'Naming Series':
|
||||||
if not self.naming_series:
|
if not self.naming_series:
|
||||||
msgprint(_("Series is mandatory"), raise_exception=1)
|
msgprint(_("Series is mandatory"), raise_exception=1)
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
frappe.listview_settings['Supplier'] = {
|
frappe.listview_settings['Supplier'] = {
|
||||||
add_fields: ["supplier_name", "supplier_group", "image"],
|
add_fields: ["supplier_name", "supplier_group", "image", "on_hold"],
|
||||||
|
get_indicator: function(doc) {
|
||||||
|
if(cint(doc.on_hold)) {
|
||||||
|
return [__("On Hold"), "red"];
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, erpnext
|
import frappe, erpnext
|
||||||
from frappe import _, throw
|
from frappe import _, throw
|
||||||
from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate, add_days, add_months, get_last_day
|
from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate, add_days, add_months, get_last_day, nowdate
|
||||||
from erpnext.setup.utils import get_exchange_rate
|
from erpnext.setup.utils import get_exchange_rate
|
||||||
from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency
|
from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency
|
||||||
from erpnext.utilities.transaction_base import TransactionBase
|
from erpnext.utilities.transaction_base import TransactionBase
|
||||||
@ -36,10 +36,29 @@ class AccountsController(TransactionBase):
|
|||||||
if self.doctype in relevant_docs:
|
if self.doctype in relevant_docs:
|
||||||
self.set_payment_schedule()
|
self.set_payment_schedule()
|
||||||
|
|
||||||
|
def ensure_supplier_is_not_blocked(self):
|
||||||
|
is_supplier_payment = self.doctype == 'Payment Entry' and self.party_type == 'Supplier'
|
||||||
|
is_buying_invoice = self.doctype in ['Purchase Invoice', 'Purchase Order']
|
||||||
|
supplier = None
|
||||||
|
supplier_name = None
|
||||||
|
|
||||||
|
if is_buying_invoice or is_supplier_payment:
|
||||||
|
supplier_name = self.supplier if is_buying_invoice else self.party
|
||||||
|
supplier = frappe.get_doc('Supplier', supplier_name)
|
||||||
|
|
||||||
|
if supplier and supplier_name and supplier.on_hold:
|
||||||
|
if (is_buying_invoice and supplier.hold_type in ['All', 'Invoices']) or \
|
||||||
|
(is_supplier_payment and supplier.hold_type in ['All', 'Payments']):
|
||||||
|
if not supplier.release_date or getdate(nowdate()) <= supplier.release_date:
|
||||||
|
frappe.msgprint(
|
||||||
|
_('{0} is blocked so this transaction cannot proceed'.format(supplier_name)), raise_exception=1)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if self.get("_action") and self._action != "update_after_submit":
|
if self.get("_action") and self._action != "update_after_submit":
|
||||||
self.set_missing_values(for_validate=True)
|
self.set_missing_values(for_validate=True)
|
||||||
|
|
||||||
|
self.ensure_supplier_is_not_blocked()
|
||||||
|
|
||||||
self.validate_date_with_fiscal_year()
|
self.validate_date_with_fiscal_year()
|
||||||
|
|
||||||
if self.meta.get_field("currency"):
|
if self.meta.get_field("currency"):
|
||||||
@ -969,3 +988,18 @@ def get_due_date(term, posting_date=None, bill_date=None):
|
|||||||
elif term.due_date_based_on == "Month(s) after the end of the invoice month":
|
elif term.due_date_based_on == "Month(s) after the end of the invoice month":
|
||||||
due_date = add_months(get_last_day(date), term.credit_months)
|
due_date = add_months(get_last_day(date), term.credit_months)
|
||||||
return due_date
|
return due_date
|
||||||
|
|
||||||
|
|
||||||
|
def get_supplier_block_status(party_name):
|
||||||
|
"""
|
||||||
|
Returns a dict containing the values of `on_hold`, `release_date` and `hold_type` of
|
||||||
|
a `Supplier`
|
||||||
|
"""
|
||||||
|
supplier = frappe.get_doc('Supplier', party_name)
|
||||||
|
info = {
|
||||||
|
'on_hold': supplier.on_hold,
|
||||||
|
'release_date': supplier.release_date,
|
||||||
|
'hold_type': supplier.hold_type
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
|
||||||
|
BIN
erpnext/docs/assets/img/accounts/purchase-invoice-hold.png
Normal file
BIN
erpnext/docs/assets/img/accounts/purchase-invoice-hold.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
@ -92,4 +92,42 @@ every transaction.
|
|||||||
|
|
||||||
For more help, please contact your Accountant!
|
For more help, please contact your Accountant!
|
||||||
|
|
||||||
|
#### Hold Payments For A Purchase Invoice
|
||||||
|
There are two ways to put a purchase invoice on hold:
|
||||||
|
- Date Span Hold
|
||||||
|
- Explicit Hold
|
||||||
|
|
||||||
|
##### Explicit Hold
|
||||||
|
Explicit hold holds the purchase invoice indefinitely.
|
||||||
|
To do it, in the "Hold Invoice" section of the purchase invoice form, simply
|
||||||
|
check the "Hold Invoice" checkbox. In the "Reason For Putting On Hold" text
|
||||||
|
field, type a comment explaining why the invoice is to be put on hold.
|
||||||
|
|
||||||
|
If you need to hold a submitted invoice, click the "Make" drop down button
|
||||||
|
and click "Block Invoice". Also add a comment explaining why the invoice is
|
||||||
|
to be put on hold in the dialog that pops up and click "Save".
|
||||||
|
|
||||||
|
##### Date Span Hold
|
||||||
|
Date span hold holds the purchase invoice until a
|
||||||
|
specified date. To do it, in the "Hold Invoice" section of the purchase
|
||||||
|
invoice form, check the "Hold Invoice" checkbox. Next, input the release date
|
||||||
|
in the dialog that pops up and click "Save". The release date is the date
|
||||||
|
that the hold on the document expires.
|
||||||
|
|
||||||
|
After the invoice has been saved, you can change the release date by clicking
|
||||||
|
on the "Hold Invoice" drop down button and then "Change Release Date". This
|
||||||
|
action will cause a dialog to appear.
|
||||||
|
|
||||||
|
<img class="screenshot" alt="Purchase Invoice on hold" src="{{docs_base_url}}/assets/img/accounts/purchase-invoice-hold.png">
|
||||||
|
|
||||||
|
Select the new release date and click "Save". You should also enter a comment
|
||||||
|
in the "Reason For Putting On Hold" field.
|
||||||
|
|
||||||
|
Take note of the following:
|
||||||
|
- All purchases that have been placed on hold will not included in a Payment Entry's references table
|
||||||
|
- The release date cannot be in the past.
|
||||||
|
- You can only block or unblock a purchase invoice if it is unpaid.
|
||||||
|
- You can only change the release date if the invoice is unpaid.
|
||||||
|
|
||||||
|
|
||||||
{next}
|
{next}
|
||||||
|
@ -38,7 +38,20 @@ If you don't want to customize payable account, and proceed with default payable
|
|||||||
You can add multiple companies in your ERPNext instance, and one Supplier can be used across multiple companies. In this case, you should define Companywise Payable Account for the Supplier in the "Default Payable Accounts" table.
|
You can add multiple companies in your ERPNext instance, and one Supplier can be used across multiple companies. In this case, you should define Companywise Payable Account for the Supplier in the "Default Payable Accounts" table.
|
||||||
|
|
||||||
<iframe width="660" height="371" src="https://www.youtube.com/embed/anoGi_RpQ20" frameborder="0" allowfullscreen></iframe>
|
<iframe width="660" height="371" src="https://www.youtube.com/embed/anoGi_RpQ20" frameborder="0" allowfullscreen></iframe>
|
||||||
|
|
||||||
(Check from 2:20)
|
(Check from 2:20)
|
||||||
|
|
||||||
|
### Place Supplier On Hold
|
||||||
|
In the Supplier form, check the "Block Supplier" checkbox. Next, choose the "Hold Type".
|
||||||
|
|
||||||
|
The hold types are as follows:
|
||||||
|
- Invoices: ERPNext will not allow Purchase Invoices or Purchase Orders to be created for the supplier
|
||||||
|
- Payments: ERPNext will not allow Payment Entries to be created for the Supplier
|
||||||
|
- All: ERPNext will apply both hold types above
|
||||||
|
|
||||||
|
After selecting the hold type, you can optionally set a release date in the "Release Date" field.
|
||||||
|
|
||||||
|
Take note of the following:
|
||||||
|
- If you do not select a hold type, ERPNext will set it to "All"
|
||||||
|
- If you do not set a release date, ERPNext will hold the Supplier indefinitely
|
||||||
|
|
||||||
{next}
|
{next}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user