From 4d4fb6dcbcfcfb3d72f972658063bb79d29458f5 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 15 May 2018 22:12:44 +0530 Subject: [PATCH] [Enhance] Budget validation on material request, purchase order --- erpnext/accounts/doctype/budget/budget.json | 398 ++++++++++++++---- erpnext/accounts/doctype/budget/budget.py | 124 +++++- .../doctype/purchase_order/purchase_order.py | 2 + erpnext/controllers/buying_controller.py | 11 + erpnext/public/js/controllers/transaction.js | 2 +- .../material_request/material_request.js | 49 ++- .../material_request/material_request.py | 2 + .../material_request_item.json | 163 +++++-- erpnext/stock/get_item_details.py | 7 +- 9 files changed, 633 insertions(+), 125 deletions(-) diff --git a/erpnext/accounts/doctype/budget/budget.json b/erpnext/accounts/doctype/budget/budget.json index 45f549e9f2..7c273ee2ca 100644 --- a/erpnext/accounts/doctype/budget/budget.json +++ b/erpnext/accounts/doctype/budget/budget.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 0, "beta": 0, @@ -11,6 +12,7 @@ "editable_grid": 1, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -23,7 +25,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 1, "label": "Budget Against", "length": 0, @@ -39,9 +41,11 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -53,7 +57,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 1, "label": "Company", "length": 0, @@ -69,9 +73,11 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -100,9 +106,11 @@ "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, @@ -131,9 +139,11 @@ "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, @@ -145,7 +155,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 1, "label": "Fiscal Year", "length": 0, @@ -161,9 +171,11 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -189,77 +201,16 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Stop", - "fieldname": "action_if_annual_budget_exceeded", - "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": "Action if Annual Budget Exceeded", - "length": 0, - "no_copy": 0, - "options": "\nStop\nWarn\nIgnore", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Warn", - "description": "", - "fieldname": "action_if_accumulated_monthly_budget_exceeded", - "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": "Action if Accumulated Monthly Budget Exceeded", - "length": 0, - "no_copy": 0, - "options": "\nStop\nWarn\nIgnore", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval:in_list([\"Stop\", \"Warn\"], doc.action_if_accumulated_monthly_budget_exceeded)", + "depends_on": "eval:in_list([\"Stop\", \"Warn\"], doc.action_if_accumulated_monthly_budget_exceeded_on_po || doc.action_if_accumulated_monthly_budget_exceeded_on_mr || doc.action_if_accumulated_monthly_budget_exceeded_on_actual)", "fieldname": "monthly_distribution", "fieldtype": "Link", "hidden": 0, @@ -283,9 +234,11 @@ "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, @@ -312,9 +265,11 @@ "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, @@ -328,6 +283,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Control Section", "length": 0, "no_copy": 0, "permlevel": 0, @@ -340,13 +296,313 @@ "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": "applicable_on_material_request", + "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": "Applicable on Material Request", + "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": "Stop", + "depends_on": "eval:doc.applicable_on_material_request == 1", + "fieldname": "action_if_annual_budget_exceeded_on_mr", + "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": "Action if Annual Budget Exceeded on MR", + "length": 0, + "no_copy": 0, + "options": "\nStop\nWarn\nIgnore", + "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": "Warn", + "depends_on": "eval:doc.applicable_on_material_request == 1", + "fieldname": "action_if_accumulated_monthly_budget_exceeded_on_mr", + "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": "Action if Accumulated Monthly Budget Exceeded on MR", + "length": 0, + "no_copy": 0, + "options": "\nStop\nWarn\nIgnore", + "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": "applicable_on_purchase_order", + "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": "Applicable on Purchase Order", + "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": "Stop", + "depends_on": "eval:doc.applicable_on_purchase_order == 1", + "fieldname": "action_if_annual_budget_exceeded_on_po", + "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": "Action if Annual Budget Exceeded on PO", + "length": 0, + "no_copy": 0, + "options": "\nStop\nWarn\nIgnore", + "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": "Warn", + "depends_on": "eval:doc.applicable_on_purchase_order == 1", + "fieldname": "action_if_accumulated_monthly_budget_exceeded_on_po", + "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": "Action if Accumulated Monthly Budget Exceeded on PO", + "length": 0, + "no_copy": 0, + "options": "\nStop\nWarn\nIgnore", + "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": "applicable_on_booking_actual_expenses", + "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": "Applicable on booking actual expenses", + "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": "Stop", + "depends_on": "eval:doc.applicable_on_booking_actual_expenses == 1", + "fieldname": "action_if_annual_budget_exceeded", + "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": "Action if Annual Budget Exceeded on Actual", + "length": 0, + "no_copy": 0, + "options": "\nStop\nWarn\nIgnore", + "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": "Warn", + "depends_on": "eval:doc.applicable_on_booking_actual_expenses == 1", + "fieldname": "action_if_accumulated_monthly_budget_exceeded", + "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": "Action if Accumulated Monthly Budget Exceeded on Actual", + "length": 0, + "no_copy": 0, + "options": "\nStop\nWarn\nIgnore", + "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": "", "fieldname": "accounts", "fieldtype": "Table", "hidden": 0, @@ -367,23 +623,24 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 0, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 1, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-02-17 16:25:32.342055", + "modified": "2018-05-15 19:26:13.840265", "modified_by": "Administrator", "module": "Accounts", "name": "Budget", @@ -392,7 +649,6 @@ "permissions": [ { "amend": 1, - "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index f99d0bb5f9..f2aa59b8b7 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -5,8 +5,9 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt, getdate, add_months, get_last_day, fmt_money +from frappe.utils import flt, getdate, add_months, get_last_day, fmt_money, nowdate from frappe.model.naming import make_autoname +from erpnext.accounts.utils import get_fiscal_year from frappe.model.document import Document class BudgetError(frappe.ValidationError): pass @@ -23,6 +24,7 @@ class Budget(Document): self.validate_duplicate() self.validate_accounts() self.set_null_value() + self.validate_applicable_for() def validate_duplicate(self): budget_against_field = frappe.scrub(self.budget_against) @@ -61,13 +63,35 @@ class Budget(Document): else: self.cost_center = None -def validate_expense_against_budget(args): + def validate_applicable_for(self): + if (self.applicable_on_material_request + and not (self.applicable_on_purchase_order and self.applicable_on_booking_actual_expenses)): + frappe.throw(_("Please enable Applicable on Purchase Order and Applicable on Booking Actual Expenses")) + + elif (self.applicable_on_purchase_order + and not (self.applicable_on_booking_actual_expenses)): + frappe.throw(_("Please enable Applicable on Booking Actual Expenses")) + + elif not(self.applicable_on_material_request + or self.applicable_on_purchase_order or self.applicable_on_booking_actual_expenses): + self.applicable_on_booking_actual_expenses = 1 + +def validate_expense_against_budget(args, company=None): args = frappe._dict(args) - if not args.cost_center and not args.project: + + if company: + args.company = company + args.fiscal_year = get_fiscal_year(nowdate(), company=company)[0] + + if not (args.get('account') and args.get('cost_center')) and args.item_code: + args.cost_center, args.account = get_item_details(args) + + if not (args.cost_center or args.project) and not args.account: return + for budget_against in ['project', 'cost_center']: - if args.get(budget_against) \ - and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"}): + if (args.get(budget_against) and args.account + and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})): if args.project and budget_against == 'project': condition = "and b.project='%s'" % frappe.db.escape(args.project) @@ -84,8 +108,12 @@ def validate_expense_against_budget(args): budget_records = frappe.db.sql(""" select b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution, - b.action_if_annual_budget_exceeded, - b.action_if_accumulated_monthly_budget_exceeded + ifnull(b.applicable_on_material_request, 0) as for_material_request, + ifnull(applicable_on_purchase_order,0) as for_purchase_order, + ifnull(applicable_on_booking_actual_expenses,0) as for_actual_expenses, + b.action_if_annual_budget_exceeded, b.action_if_accumulated_monthly_budget_exceeded, + b.action_if_annual_budget_exceeded_on_mr, b.action_if_accumulated_monthly_budget_exceeded_on_mr, + b.action_if_annual_budget_exceeded_on_po, b.action_if_accumulated_monthly_budget_exceeded_on_po from `tabBudget` b, `tabBudget Account` ba where @@ -102,8 +130,8 @@ def validate_expense_against_budget(args): def validate_budget_records(args, budget_records): for budget in budget_records: if flt(budget.budget_amount): - yearly_action = budget.action_if_annual_budget_exceeded - monthly_action = budget.action_if_accumulated_monthly_budget_exceeded + amount = get_amount(args, budget) + yearly_action, monthly_action = get_actions(args, budget) if monthly_action in ["Stop", "Warn"]: budget_amount = get_accumulated_monthly_budget(budget.monthly_distribution, @@ -111,16 +139,15 @@ def validate_budget_records(args, budget_records): args["month_end_date"] = get_last_day(args.posting_date) compare_expense_with_budget(args, budget_amount, - _("Accumulated Monthly"), monthly_action, budget.budget_against) + _("Accumulated Monthly"), monthly_action, budget.budget_against, amount) if yearly_action in ("Stop", "Warn") and monthly_action != "Stop" \ and yearly_action != monthly_action: compare_expense_with_budget(args, flt(budget.budget_amount), - _("Annual"), yearly_action, budget.budget_against) + _("Annual"), yearly_action, budget.budget_against, amount) - -def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against): - actual_expense = get_actual_expense(args) +def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0): + actual_expense = amount or get_actual_expense(args) if actual_expense > budget_amount: diff = actual_expense - budget_amount currency = frappe.db.get_value('Company', args.company, 'default_currency') @@ -136,6 +163,45 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_ else: frappe.msgprint(msg, indicator='orange') +def get_actions(args, budget): + yearly_action = budget.action_if_annual_budget_exceeded + monthly_action = budget.action_if_accumulated_monthly_budget_exceeded + + if args.get('doctype') == 'Material Request' and budget.for_material_request: + yearly_action = budget.action_if_annual_budget_exceeded_on_mr + monthly_action = budget.action_if_accumulated_monthly_budget_exceeded_on_mr + + elif args.get('doctype') == 'Purchase Order' and budget.for_purchase_order: + yearly_action = budget.action_if_annual_budget_exceeded_on_po + monthly_action = budget.action_if_accumulated_monthly_budget_exceeded_on_po + + return yearly_action, monthly_action + +def get_amount(args, budget): + amount = 0 + + if args.get('doctype') == 'Material Request' and budget.for_material_request: + amount = (get_requested_amount(args.item_code) + + get_ordered_amount(args.item_code) + get_actual_expense(args)) + + elif args.get('doctype') == 'Purchase Order' and budget.for_purchase_order: + amount = get_ordered_amount(args.item_code) + get_actual_expense(args) + + return amount + +def get_requested_amount(item_code): + data = frappe.db.sql(""" select ifnull((sum(stock_qty - ordered_qty) * rate), 0) as amount + from `tabMaterial Request Item` where item_code = %s and docstatus = 1 + and stock_qty > ordered_qty """, item_code, as_list=1) + + return data[0][0] if data else 0 + +def get_ordered_amount(item_code): + data = frappe.db.sql(""" select ifnull(sum(amount - billed_amt), 0) as amount + from `tabPurchase Order Item` where item_code = %s and docstatus = 1 + and amount > billed_amt""", item_code, as_list=1) + + return data[0][0] if data else 0 def get_actual_expense(args): condition1 = " and gle.posting_date <= %(month_end_date)s" \ @@ -161,7 +227,6 @@ def get_actual_expense(args): {condition2} """.format(condition1=condition1, condition2=condition2), (args))[0][0]) - def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget): distribution = {} if monthly_distribution: @@ -182,3 +247,32 @@ def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_ye dt = add_months(dt, 1) return annual_budget * accumulated_percentage / 100 + +def get_item_details(args): + cost_center, expense_account = None, None + + if args.item_code: + cost_center, expense_account = frappe.db.get_value('Item', + args.item_code, ['buying_cost_center', 'expense_account']) + + if not (cost_center and expense_account): + for doctype in ['Item Group', 'Company']: + data = get_expense_cost_center(doctype, + args.get(frappe.scrub(doctype))) + + if not cost_center and data: + cost_center = data[0] + + if not expense_account and data: + expense_account = data[1] + + if cost_center and expense_account: + return cost_center, expense_account + + return cost_center, expense_account + +def get_expense_cost_center(doctype, value): + fields = (['default_cost_center', 'default_expense_account'] + if doctype == 'Item Group'else ['cost_center', 'default_expense_account']) + + return frappe.db.get_value(doctype, value, fields) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index cce5d19411..5a7573bc93 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -210,6 +210,8 @@ class PurchaseOrder(BuyingController): self.update_prevdoc_status() self.update_requested_qty() self.update_ordered_qty() + self.validate_budget() + if self.is_subcontracted == "Yes": self.update_reserved_qty_for_subcontract() diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 85fb3f0a66..45b9d785bf 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -13,6 +13,7 @@ from erpnext.stock.stock_ledger import get_valuation_rate from erpnext.stock.doctype.stock_entry.stock_entry import get_used_alternative_items from erpnext.stock.doctype.serial_no.serial_no import get_auto_serial_nos, auto_make_serial_nos, get_serial_nos +from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.controllers.stock_controller import StockController class BuyingController(StockController): @@ -465,6 +466,16 @@ class BuyingController(StockController): self.delete_linked_asset() self.update_fixed_asset(field, delete_asset=True) + def validate_budget(self): + if self.docstatus == 1: + for data in self.get('items'): + validate_expense_against_budget({ + 'item_code': data.item_code, + 'item_group': data.item_group, + 'posting_date': data.schedule_date, + 'doctype': self.doctype + }, self.company) + def process_fixed_asset(self): if self.doctype == 'Purchase Invoice' and not self.update_stock: return diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 53a4cbb57b..27afe0194a 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -733,7 +733,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }, conversion_factor: function(doc, cdt, cdn, dont_fetch_price_list_rate) { - if(frappe.meta.get_docfield(cdt, "stock_qty", cdn)) { + if(doc.doctype != 'Material Request' && frappe.meta.get_docfield(cdt, "stock_qty", cdn)) { var item = frappe.get_doc(cdt, cdn); frappe.model.round_floats_in(item, ["qty", "conversion_factor"]); item.stock_qty = flt(item.qty * item.conversion_factor, precision("stock_qty", item)); diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index caf4e498ab..8e66b55389 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -28,7 +28,39 @@ frappe.ui.form.on('Material Request', { filters: {'company': doc.company} } } - } + }, + get_item_data: function(frm, item) { + frm.call({ + method: "erpnext.stock.get_item_details.get_item_details", + child: item, + args: { + args: { + item_code: item.item_code, + warehouse: item.warehouse, + doctype: frm.doc.doctype, + buying_price_list: frappe.defaults.get_default('buying_price_list'), + currency: frappe.defaults.get_default('Currency'), + name: frm.doc.name, + qty: item.qty || 1, + stock_qty: item.stock_qty, + company: frm.doc.company, + conversion_rate: 1, + plc_conversion_rate: 1, + rate: item.rate, + conversion_factor: item.conversion_factor + } + }, + callback: function(r) { + const d = item; + if(!r.exc) { + $.each(r.message, function(k, v) { + if(!d[k]) d[k] = v; + }); + debugger + } + } + }); + }, }); frappe.ui.form.on("Material Request Item", { @@ -37,10 +69,21 @@ frappe.ui.form.on("Material Request Item", { if (flt(d.qty) < flt(d.min_order_qty)) { frappe.msgprint(__("Warning: Material Requested Qty is less than Minimum Order Qty")); } + + const item = locals[doctype][name]; + frm.events.get_item_data(frm, item); + }, + + rate: function(frm, doctype, name) { + const item = locals[doctype][name]; + frm.events.get_item_data(frm, item); }, item_code: function(frm, doctype, name) { + const item = locals[doctype][name]; + item.rate = 0 set_schedule_date(frm); + frm.events.get_item_data(frm, item); }, schedule_date: function(frm, cdt, cdn) { @@ -186,6 +229,10 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten this.get_terms(); }, + item_code: function() { + // to override item code trigger from transaction.js + }, + validate_company_and_party: function(party_field) { return true; }, diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index d4dc523356..df40654986 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -93,6 +93,8 @@ class MaterialRequest(BuyingController): # frappe.db.set(self, 'status', 'Submitted') self.update_requested_qty() self.update_requested_qty_in_production_plan() + if self.material_request_type == 'Purchase': + self.validate_budget() def before_save(self): self.set_status(update=True) diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json index d31d9e7b71..ac6eefc604 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -44,6 +44,7 @@ "reqd": 1, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "100px" }, @@ -73,6 +74,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -105,6 +107,7 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "100px" }, @@ -136,6 +139,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -168,6 +172,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "250px" }, @@ -198,6 +203,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -228,6 +234,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -257,6 +264,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -289,6 +297,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "80px" }, @@ -298,8 +307,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "stock_uom", - "fieldtype": "Link", + "fieldname": "rate", + "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -307,20 +316,51 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Stock UOM", + "label": "Rate", "length": 0, "no_copy": 0, - "options": "UOM", "permlevel": 0, "precision": "", - "print_hide": 1, + "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": "amount", + "fieldtype": "Currency", + "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": "Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -354,9 +394,39 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "100px" }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "col_break2", + "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, + "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, @@ -388,37 +458,10 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "100px" }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break2", - "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, - "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, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -450,6 +493,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "70px" }, @@ -481,6 +525,39 @@ "reqd": 1, "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": "stock_uom", + "fieldtype": "Link", + "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": "Stock UOM", + "length": 0, + "no_copy": 0, + "options": "UOM", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -511,6 +588,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -540,6 +618,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -573,6 +652,7 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -606,6 +686,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "100px" }, @@ -638,6 +719,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -668,6 +750,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -698,6 +781,7 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -729,6 +813,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -760,6 +845,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -790,6 +876,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -818,6 +905,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -850,6 +938,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "70px" }, @@ -883,6 +972,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "70px" }, @@ -914,6 +1004,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -945,6 +1036,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -976,6 +1068,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -989,7 +1082,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-02-12 05:51:39.954530", + "modified": "2018-05-15 18:19:36.673046", "modified_by": "Administrator", "module": "Stock", "name": "Material Request Item", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index ee89cfa8fe..340f03d386 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -101,6 +101,9 @@ def get_item_details(args): out.bom = args.get('bom') or get_default_bom(args.item_code) get_gross_profit(out) + if args.doctype == 'Material Request': + out.rate = args.rate or out.price_list_rate + out.amount = flt(args.qty * out.rate) return out @@ -320,9 +323,9 @@ def get_default_cost_center(args, item): def get_price_list_rate(args, item_doc, out): meta = frappe.get_meta(args.parenttype or args.doctype) - if meta.get_field("currency"): + if meta.get_field("currency") or args.get('currency'): validate_price_list(args) - if args.price_list: + if meta.get_field("currency") and args.price_list: validate_conversion_rate(args, meta) price_list_rate = get_price_list_rate_for(args.price_list, item_doc.name)