Merge pull request #16941 from Mangesh-Khairnar/po-on-hold
Feature: Purchase order on hold
This commit is contained in:
commit
6472ed2c1e
@ -96,7 +96,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
status: ["!=", "Closed"],
|
||||
status: ["not in", ["Closed", "On Hold"]],
|
||||
per_billed: ["<", 99.99],
|
||||
company: me.frm.doc.company
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_bille
|
||||
from erpnext.stock import get_warehouse_account_map
|
||||
from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entries, delete_gl_entries
|
||||
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
||||
from erpnext.buying.utils import check_for_closed_status
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_disabled
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
@ -89,7 +89,7 @@ class PurchaseInvoice(BuyingController):
|
||||
self.check_conversion_rate()
|
||||
self.validate_credit_to_acc()
|
||||
self.clear_unallocated_advances("Purchase Invoice Advance", "advances")
|
||||
self.check_for_closed_status()
|
||||
self.check_on_hold_or_closed_status()
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_uom_is_integer("uom", "qty")
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
@ -152,13 +152,13 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
self.party_account_currency = account.account_currency
|
||||
|
||||
def check_for_closed_status(self):
|
||||
def check_on_hold_or_closed_status(self):
|
||||
check_list = []
|
||||
|
||||
for d in self.get('items'):
|
||||
if d.purchase_order and not d.purchase_order in check_list and not d.purchase_receipt:
|
||||
check_list.append(d.purchase_order)
|
||||
check_for_closed_status('Purchase Order', d.purchase_order)
|
||||
check_on_hold_or_closed_status('Purchase Order', d.purchase_order)
|
||||
|
||||
def validate_with_previous_doc(self):
|
||||
super(PurchaseInvoice, self).validate_with_previous_doc({
|
||||
@ -760,7 +760,7 @@ class PurchaseInvoice(BuyingController):
|
||||
def on_cancel(self):
|
||||
super(PurchaseInvoice, self).on_cancel()
|
||||
|
||||
self.check_for_closed_status()
|
||||
self.check_on_hold_or_closed_status()
|
||||
|
||||
self.update_status_updater_args()
|
||||
|
||||
|
@ -36,7 +36,8 @@ frappe.ui.form.on("Purchase Order", {
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.docstatus == 1 && frm.doc.status == 'To Receive and Bill') {
|
||||
if(frm.doc.docstatus === 1 && frm.doc.status !== 'Closed'
|
||||
&& flt(frm.doc.per_received) < 100 && flt(frm.doc.per_billed) < 100) {
|
||||
frm.add_custom_button(__('Update Items'), () => {
|
||||
erpnext.utils.update_child_items({
|
||||
frm: frm,
|
||||
@ -93,62 +94,63 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.set_df_property("drop_ship", "hidden", !is_drop_ship);
|
||||
this.frm.set_df_property("drop_ship", "hidden", !is_drop_ship);
|
||||
|
||||
if(doc.docstatus == 1 && !in_list(["Closed", "Delivered"], doc.status)) {
|
||||
if (this.frm.has_perm("submit")) {
|
||||
if(flt(doc.per_billed, 2) < 100 || doc.per_received < 100) {
|
||||
cur_frm.add_custom_button(__('Close'), this.close_purchase_order, __("Status"));
|
||||
if(doc.docstatus == 1) {
|
||||
if(!in_list(["Closed", "Delivered"], doc.status)) {
|
||||
if (this.frm.has_perm("submit")) {
|
||||
if(flt(doc.per_billed, 2) < 100 || doc.per_received < 100) {
|
||||
if (doc.status != "On Hold") {
|
||||
this.frm.add_custom_button(__('Hold'), () => this.hold_purchase_order(), __("Status"));
|
||||
} else{
|
||||
this.frm.add_custom_button(__('Resume'), () => this.unhold_purchase_order(), __("Status"));
|
||||
}
|
||||
this.frm.add_custom_button(__('Close'), () => this.close_purchase_order(), __("Status"));
|
||||
}
|
||||
}
|
||||
|
||||
if(is_drop_ship && doc.status!="Delivered") {
|
||||
this.frm.add_custom_button(__('Delivered'),
|
||||
this.delivered_by_supplier, __("Status"));
|
||||
|
||||
this.frm.page.set_inner_btn_group_as_primary(__("Status"));
|
||||
}
|
||||
} else if(in_list(["Closed", "Delivered"], doc.status)) {
|
||||
if (this.frm.has_perm("submit")) {
|
||||
this.frm.add_custom_button(__('Re-open'), () => this.unclose_purchase_order(), __("Status"));
|
||||
}
|
||||
}
|
||||
if(doc.status != "Closed") {
|
||||
if (doc.status != "On Hold") {
|
||||
if(flt(doc.per_received, 2) < 100 && allow_receipt) {
|
||||
cur_frm.add_custom_button(__('Receipt'), this.make_purchase_receipt, __('Create'));
|
||||
if(doc.is_subcontracted==="Yes") {
|
||||
cur_frm.add_custom_button(__('Material to Supplier'),
|
||||
function() { me.make_stock_entry(); }, __("Transfer"));
|
||||
}
|
||||
}
|
||||
if(flt(doc.per_billed, 2) < 100)
|
||||
cur_frm.add_custom_button(__('Invoice'),
|
||||
this.make_purchase_invoice, __('Create'));
|
||||
|
||||
if(is_drop_ship && doc.status!="Delivered"){
|
||||
cur_frm.add_custom_button(__('Delivered'),
|
||||
this.delivered_by_supplier, __("Status"));
|
||||
|
||||
cur_frm.page.set_inner_btn_group_as_primary(__("Status"));
|
||||
if(!doc.auto_repeat) {
|
||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
||||
erpnext.utils.make_subscription(doc.doctype, doc.name)
|
||||
}, __('Create'))
|
||||
}
|
||||
}
|
||||
if(flt(doc.per_billed)==0) {
|
||||
this.frm.add_custom_button(__('Payment Request'),
|
||||
function() { me.make_payment_request() }, __('Create'));
|
||||
}
|
||||
if(flt(doc.per_billed)==0 && doc.status != "Delivered") {
|
||||
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create'));
|
||||
}
|
||||
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
}
|
||||
} else if(doc.docstatus===0) {
|
||||
cur_frm.cscript.add_from_mappers();
|
||||
}
|
||||
|
||||
if(doc.docstatus == 1 && in_list(["Closed", "Delivered"], doc.status)) {
|
||||
if (this.frm.has_perm("submit")) {
|
||||
cur_frm.add_custom_button(__('Re-open'), this.unclose_purchase_order, __("Status"));
|
||||
}
|
||||
}
|
||||
|
||||
if(doc.docstatus == 1 && doc.status != "Closed") {
|
||||
if(flt(doc.per_received, 2) < 100 && allow_receipt) {
|
||||
cur_frm.add_custom_button(__('Receipt'), this.make_purchase_receipt, __('Create'));
|
||||
|
||||
if(doc.is_subcontracted==="Yes") {
|
||||
cur_frm.add_custom_button(__('Material to Supplier'),
|
||||
function() { me.make_stock_entry(); }, __("Transfer"));
|
||||
}
|
||||
}
|
||||
|
||||
if(flt(doc.per_billed, 2) < 100)
|
||||
cur_frm.add_custom_button(__('Invoice'),
|
||||
this.make_purchase_invoice, __('Create'));
|
||||
|
||||
if(flt(doc.per_billed)==0 && doc.status != "Delivered") {
|
||||
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create'));
|
||||
}
|
||||
|
||||
if(!doc.auto_repeat) {
|
||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
||||
erpnext.utils.make_subscription(doc.doctype, doc.name)
|
||||
}, __('Create'))
|
||||
}
|
||||
|
||||
if(flt(doc.per_billed)==0) {
|
||||
this.frm.add_custom_button(__('Payment Request'),
|
||||
function() { me.make_payment_request() }, __('Create'));
|
||||
}
|
||||
|
||||
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
}
|
||||
},
|
||||
|
||||
get_items_from_open_material_requests: function() {
|
||||
@ -427,6 +429,43 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
}
|
||||
},
|
||||
|
||||
unhold_purchase_order: function(){
|
||||
cur_frm.cscript.update_status("Resume", "Draft")
|
||||
},
|
||||
|
||||
hold_purchase_order: function(){
|
||||
var me = this;
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: __('Reason for Hold'),
|
||||
fields: [
|
||||
{
|
||||
"fieldname": "reason_for_hold",
|
||||
"fieldtype": "Text",
|
||||
"reqd": 1,
|
||||
}
|
||||
],
|
||||
primary_action: function() {
|
||||
var data = d.get_values();
|
||||
frappe.call({
|
||||
method: "frappe.desk.form.utils.add_comment",
|
||||
args: {
|
||||
reference_doctype: me.frm.doctype,
|
||||
reference_name: me.frm.docname,
|
||||
content: __('Reason for hold: ')+data.reason_for_hold,
|
||||
comment_email: frappe.session.user
|
||||
},
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
me.update_status('Hold', 'On Hold')
|
||||
d.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
d.show();
|
||||
},
|
||||
|
||||
unclose_purchase_order: function(){
|
||||
cur_frm.cscript.update_status('Re-open', 'Submitted')
|
||||
},
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,7 @@ from erpnext.controllers.buying_controller import BuyingController
|
||||
from erpnext.stock.doctype.item.item import get_last_purchase_details
|
||||
from erpnext.stock.stock_balance import update_bin_qty, get_ordered_qty
|
||||
from frappe.desk.notifications import clear_doctype_notifications
|
||||
from erpnext.buying.utils import validate_for_items, check_for_closed_status
|
||||
from erpnext.buying.utils import validate_for_items, check_on_hold_or_closed_status
|
||||
from erpnext.stock.utils import get_bin
|
||||
from erpnext.accounts.party import get_party_account_currency
|
||||
from six import string_types
|
||||
@ -45,7 +45,7 @@ class PurchaseOrder(BuyingController):
|
||||
self.validate_supplier()
|
||||
self.validate_schedule_date()
|
||||
validate_for_items(self)
|
||||
self.check_for_closed_status()
|
||||
self.check_on_hold_or_closed_status()
|
||||
|
||||
self.validate_uom_is_integer("uom", "qty")
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
@ -144,12 +144,12 @@ class PurchaseOrder(BuyingController):
|
||||
= d.rate = d.last_purchase_rate = item_last_purchase_rate
|
||||
|
||||
# Check for Closed status
|
||||
def check_for_closed_status(self):
|
||||
def check_on_hold_or_closed_status(self):
|
||||
check_list =[]
|
||||
for d in self.get('items'):
|
||||
if d.meta.get_field('material_request') and d.material_request and d.material_request not in check_list:
|
||||
check_list.append(d.material_request)
|
||||
check_for_closed_status('Material Request', d.material_request)
|
||||
check_on_hold_or_closed_status('Material Request', d.material_request)
|
||||
|
||||
def update_requested_qty(self):
|
||||
material_request_map = {}
|
||||
@ -232,7 +232,7 @@ class PurchaseOrder(BuyingController):
|
||||
if self.is_subcontracted == "Yes":
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
|
||||
self.check_for_closed_status()
|
||||
self.check_on_hold_or_closed_status()
|
||||
|
||||
frappe.db.set(self,'status','Cancelled')
|
||||
|
||||
|
@ -4,6 +4,8 @@ frappe.listview_settings['Purchase Order'] = {
|
||||
get_indicator: function (doc) {
|
||||
if (doc.status === "Closed") {
|
||||
return [__("Closed"), "green", "status,=,Closed"];
|
||||
} else if (doc.status === "On Hold") {
|
||||
return [__("On Hold"), "orange", "status,=,On Hold"];
|
||||
} else if (doc.status === "Delivered") {
|
||||
return [__("Delivered"), "green", "status,=,Closed"];
|
||||
} else if (flt(doc.per_received, 2) < 100 && doc.status !== "Closed") {
|
||||
|
@ -120,6 +120,15 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
self.assertEqual(pi.doctype, "Purchase Invoice")
|
||||
self.assertEqual(len(pi.get("items", [])), 1)
|
||||
|
||||
def test_purchase_order_on_hold(self):
|
||||
po = create_purchase_order(item_code="_Test Product Bundle Item")
|
||||
po.db_set('Status', "On Hold")
|
||||
pi = make_purchase_invoice(po.name)
|
||||
pr = make_purchase_receipt(po.name)
|
||||
self.assertRaises(frappe.ValidationError, pr.submit)
|
||||
self.assertRaises(frappe.ValidationError, pi.submit)
|
||||
|
||||
|
||||
def test_make_purchase_invoice_with_terms(self):
|
||||
po = create_purchase_order(do_not_save=True)
|
||||
|
||||
|
@ -73,10 +73,10 @@ def validate_for_items(doc):
|
||||
not cint(frappe.db.get_single_value("Buying Settings", "allow_multiple_items") or 0):
|
||||
frappe.throw(_("Same item cannot be entered multiple times."))
|
||||
|
||||
def check_for_closed_status(doctype, docname):
|
||||
def check_on_hold_or_closed_status(doctype, docname):
|
||||
status = frappe.db.get_value(doctype, docname, "status")
|
||||
|
||||
if status == "Closed":
|
||||
if status in ("Closed", "On Hold"):
|
||||
frappe.throw(_("{0} {1} status is {2}").format(doctype, docname, status), frappe.InvalidStatusError)
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -70,6 +70,7 @@ status_map = {
|
||||
["Completed", "eval:self.per_received == 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||
["Delivered", "eval:self.status=='Delivered'"],
|
||||
["Cancelled", "eval:self.docstatus==2"],
|
||||
["On Hold", "eval:self.status=='On Hold'"],
|
||||
["Closed", "eval:self.status=='Closed'"],
|
||||
],
|
||||
"Delivery Note": [
|
||||
|
@ -13,7 +13,7 @@ from frappe.model.mapper import get_mapped_doc
|
||||
from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
|
||||
from erpnext.buying.utils import check_for_closed_status, validate_for_items
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
|
||||
from erpnext.stock.doctype.item.item import get_item_defaults
|
||||
|
||||
from six import string_types
|
||||
@ -100,7 +100,7 @@ class MaterialRequest(BuyingController):
|
||||
|
||||
def before_cancel(self):
|
||||
# if MRQ is already closed, no point saving the document
|
||||
check_for_closed_status(self.doctype, self.name)
|
||||
check_on_hold_or_closed_status(self.doctype, self.name)
|
||||
self.set_status(update=True, status='Cancelled')
|
||||
|
||||
def check_modified_date(self):
|
||||
|
@ -88,7 +88,7 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
status: ["!=", "Closed"],
|
||||
status: ["not in", ["Closed", "On Hold"]],
|
||||
per_received: ["<", 99.99],
|
||||
company: me.frm.doc.company
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ from frappe.utils import getdate
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from frappe.desk.notifications import clear_doctype_notifications
|
||||
from erpnext.buying.utils import check_for_closed_status
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_disabled
|
||||
from six import iteritems
|
||||
|
||||
@ -62,7 +62,7 @@ class PurchaseReceipt(BuyingController):
|
||||
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
|
||||
self.check_for_closed_status()
|
||||
self.check_on_hold_or_closed_status()
|
||||
|
||||
if getdate(self.posting_date) > getdate(nowdate()):
|
||||
throw(_("Posting Date cannot be future date"))
|
||||
@ -103,13 +103,13 @@ class PurchaseReceipt(BuyingController):
|
||||
return po_qty, po_warehouse
|
||||
|
||||
# Check for Closed status
|
||||
def check_for_closed_status(self):
|
||||
def check_on_hold_or_closed_status(self):
|
||||
check_list =[]
|
||||
for d in self.get('items'):
|
||||
if (d.meta.get_field('purchase_order') and d.purchase_order
|
||||
and d.purchase_order not in check_list):
|
||||
check_list.append(d.purchase_order)
|
||||
check_for_closed_status('Purchase Order', d.purchase_order)
|
||||
check_on_hold_or_closed_status('Purchase Order', d.purchase_order)
|
||||
|
||||
# on submit
|
||||
def on_submit(self):
|
||||
@ -147,7 +147,7 @@ class PurchaseReceipt(BuyingController):
|
||||
def on_cancel(self):
|
||||
super(PurchaseReceipt, self).on_cancel()
|
||||
|
||||
self.check_for_closed_status()
|
||||
self.check_on_hold_or_closed_status()
|
||||
# Check if Purchase Invoice has been submitted against current Purchase Order
|
||||
submitted = frappe.db.sql("""select t1.name
|
||||
from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2
|
||||
|
Loading…
Reference in New Issue
Block a user