Merge pull request #16941 from Mangesh-Khairnar/po-on-hold

Feature: Purchase order on hold
This commit is contained in:
Faris Ansari 2019-04-11 15:36:54 +05:30 committed by GitHub
commit 6472ed2c1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 4081 additions and 4011 deletions

View File

@ -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
}

View File

@ -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()

View File

@ -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

View File

@ -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')

View File

@ -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") {

View File

@ -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)

View File

@ -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()

View File

@ -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": [

View File

@ -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):

View File

@ -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
}

View File

@ -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