diff --git a/erpnext/accounts/doctype/budget/__init__.py b/erpnext/accounts/doctype/budget/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/budget/budget.js b/erpnext/accounts/doctype/budget/budget.js new file mode 100644 index 0000000000..e39393ece6 --- /dev/null +++ b/erpnext/accounts/doctype/budget/budget.js @@ -0,0 +1,31 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Budget', { + onload: function(frm) { + frm.set_query("cost_center", function() { + return { + filters: { + company: frm.doc.company + } + } + }) + + frm.set_query("account", "accounts", function() { + return { + filters: { + company: frm.doc.company, + report_type: "Profit and Loss" + } + } + }) + + frm.set_query("monthly_distribution", function() { + return { + filters: { + fiscal_year: frm.doc.fiscal_year + } + } + }) + } +}); diff --git a/erpnext/accounts/doctype/budget/budget.json b/erpnext/accounts/doctype/budget/budget.json new file mode 100644 index 0000000000..4de6fa8f2f --- /dev/null +++ b/erpnext/accounts/doctype/budget/budget.json @@ -0,0 +1,314 @@ +{ + "allow_copy": 0, + "allow_import": 1, + "allow_rename": 0, + "beta": 0, + "creation": "2016-05-16 11:42:29.632528", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "cost_center", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Cost Center", + "length": 0, + "no_copy": 0, + "options": "Cost Center", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "fiscal_year", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Fiscal Year", + "length": 0, + "no_copy": 0, + "options": "Fiscal Year", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "monthly_distribution", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Monthly Distribution", + "length": 0, + "no_copy": 0, + "options": "Monthly Distribution", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 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_list_view": 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, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 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_list_view": 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, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "amended_from", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Amended From", + "length": 0, + "no_copy": 1, + "options": "Budget", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "accounts", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Budget Accounts", + "length": 0, + "no_copy": 0, + "options": "Budget Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2016-05-16 13:12:10.439375", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Budget", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 1, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py new file mode 100644 index 0000000000..cb8eff7b61 --- /dev/null +++ b/erpnext/accounts/doctype/budget/budget.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import flt, getdate, add_months, get_last_day +from frappe.model.naming import make_autoname +from frappe.model.document import Document + +class BudgetError(frappe.ValidationError): pass + +class Budget(Document): + def autoname(self): + self.name = make_autoname(self.cost_center + "/" + self.fiscal_year + "/.###") + + def validate(self): + self.validate_duplicate() + + def validate_duplicate(self): + existing_budget = frappe.db.get_value("Budget", {"cost_center": self.cost_center, + "fiscal_year": self.fiscal_year, "name": ["!=", self.name], "docstatus": ["!=", 2]}) + if existing_budget: + frappe.throw(_("Another Budget record {0} already exists against {1} for fiscal year {2}") + .format(existing_budget, self.cost_center, self.fiscal_year)) + + +def validate_expense_against_budget(args): + args = frappe._dict(args) + if frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"}): + budget = frappe.db.sql(""" + select ba.budget_amount, b.monthly_distribution, + b.action_if_annual_budget_exceeded, b.action_if_accumulated_monthly_budget_exceeded + from `tabBudget` b, `tabBudget Account` ba + where b.name=ba.parent and b.cost_center=%s and b.fiscal_year=%s and ba.account=%s + """, (args.cost_center, args.fiscal_year, args.account), as_dict=True) + + if budget and budget[0].budget_amount: + yearly_action = budget[0].action_if_annual_budget_exceeded + monthly_action = budget[0].action_if_accumulated_monthly_budget_exceeded + + action_for = action = "" + + if monthly_action in ["Stop", "Warn"]: + budget_amount = get_accumulated_monthly_budget(budget[0].monthly_distribution, + args.posting_date, args.fiscal_year, budget[0].budget_amount) + + args["month_end_date"] = get_last_day(args.posting_date) + + action_for, action = _("Accumulated Monthly"), monthly_action + + elif yearly_action in ["Stop", "Warn"]: + budget_amount = flt(budget[0].budget_amount) + action_for, action = _("Annual"), yearly_action + + if action_for: + actual_expense = get_actual_expense(args) + if actual_expense > budget_amount: + diff = actual_expense - budget_amount + + msg = _("{0} Budget for Account {1} against Cost Center {2} is {3}. It will exceed by {4}").format(_(action_for), args.account, args.cost_center, budget_amount, diff) + + if action=="Stop": + frappe.throw(msg, BudgetError) + else: + frappe.msgprint(msg) + +def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget): + distribution = {} + if monthly_distribution: + for d in frappe.db.sql("""select mdp.month, mdp.percentage_allocation + from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md + where mdp.parent=md.name and md.fiscal_year=%s""", fiscal_year, as_dict=1): + distribution.setdefault(d.month, d.percentage_allocation) + + dt = frappe.db.get_value("Fiscal Year", fiscal_year, "year_start_date") + accumulated_percentage = 0.0 + + while(dt <= getdate(posting_date)): + if monthly_distribution: + accumulated_percentage += distribution.get(getdate(dt).strftime("%B"), 0) + else: + accumulated_percentage += 100.0/12 + + dt = add_months(dt, 1) + + return annual_budget * accumulated_percentage / 100 + +def get_actual_expense(args): + args["condition"] = " and posting_date <= '%s'" % \ + args.month_end_date if args.get("month_end_date") else "" + + return flt(frappe.db.sql(""" + select sum(debit) - sum(credit) + from `tabGL Entry` + where account='%(account)s' and cost_center='%(cost_center)s' + and fiscal_year='%(fiscal_year)s' and company='%(company)s' and docstatus=1 %(condition)s + """ % (args))[0][0]) \ No newline at end of file diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py new file mode 100644 index 0000000000..7b929153e9 --- /dev/null +++ b/erpnext/accounts/doctype/budget/test_budget.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +# test_records = frappe.get_test_records('Budget') + +class TestBudget(unittest.TestCase): + pass diff --git a/erpnext/accounts/doctype/budget_account/__init__.py b/erpnext/accounts/doctype/budget_account/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/budget_account/budget_account.json b/erpnext/accounts/doctype/budget_account/budget_account.json new file mode 100644 index 0000000000..43dbbd9d8e --- /dev/null +++ b/erpnext/accounts/doctype/budget_account/budget_account.json @@ -0,0 +1,110 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-05-16 11:54:09.286135", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "budget_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Budget Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2016-05-16 11:55:29.586591", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Budget Account", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/budget_account/budget_account.py b/erpnext/accounts/doctype/budget_account/budget_account.py new file mode 100644 index 0000000000..81b2709ba8 --- /dev/null +++ b/erpnext/accounts/doctype/budget_account/budget_account.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class BudgetAccount(Document): + pass diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index bad7f9816f..f94d4ed343 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import unittest, frappe from frappe.utils import flt -from erpnext.accounts.utils import get_actual_expense, BudgetError, get_fiscal_year +from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError, get_fiscal_year from erpnext.exceptions import InvalidAccountCurrency diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index a8a090f9e8..bdee391344 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -6,7 +6,7 @@ import frappe from frappe.utils import flt, cstr, cint from frappe import _ from frappe.model.meta import get_field_precision -from erpnext.accounts.utils import validate_expense_against_budget +from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget class StockAccountInvalidTransaction(frappe.ValidationError): pass diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 683b84b3d6..3b78683253 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import nowdate, cstr, flt, cint, now, getdate, add_months +from frappe.utils import nowdate, cstr, flt, cint, now, getdate from frappe import throw, _ from frappe.utils import formatdate import frappe.desk.reportview @@ -13,7 +13,6 @@ import frappe.desk.reportview from erpnext.accounts.doctype.account.account import get_account_currency class FiscalYearError(frappe.ValidationError): pass -class BudgetError(frappe.ValidationError): pass @frappe.whitelist() def get_fiscal_year(date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False): @@ -321,72 +320,6 @@ def get_stock_and_account_difference(account_list=None, posting_date=None): return difference -def validate_expense_against_budget(args): - args = frappe._dict(args) - if frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"}): - budget = frappe.db.sql(""" - select bd.budget_allocated, cc.distribution_id - from `tabCost Center` cc, `tabBudget Detail` bd - where cc.name=bd.parent and cc.name=%s and account=%s and bd.fiscal_year=%s - """, (args.cost_center, args.account, args.fiscal_year), as_dict=True) - - if budget and budget[0].budget_allocated: - yearly_action, monthly_action = frappe.db.get_value("Company", args.company, - ["yearly_bgt_flag", "monthly_bgt_flag"]) - action_for = action = "" - - if monthly_action in ["Stop", "Warn"]: - budget_amount = get_allocated_budget(budget[0].distribution_id, - args.posting_date, args.fiscal_year, budget[0].budget_allocated) - - args["month_end_date"] = frappe.db.sql("select LAST_DAY(%s)", - args.posting_date)[0][0] - action_for, action = _("Monthly"), monthly_action - - elif yearly_action in ["Stop", "Warn"]: - budget_amount = budget[0].budget_allocated - action_for, action = _("Annual"), yearly_action - - if action_for: - actual_expense = get_actual_expense(args) - if actual_expense > budget_amount: - frappe.msgprint(_("{0} budget for Account {1} against Cost Center {2} will exceed by {3}").format( - _(action_for), args.account, args.cost_center, cstr(actual_expense - budget_amount))) - if action=="Stop": - raise BudgetError - -def get_allocated_budget(distribution_id, posting_date, fiscal_year, yearly_budget): - if distribution_id: - distribution = {} - for d in frappe.db.sql("""select mdp.month, mdp.percentage_allocation - from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md - where mdp.parent=md.name and md.fiscal_year=%s""", fiscal_year, as_dict=1): - distribution.setdefault(d.month, d.percentage_allocation) - - dt = frappe.db.get_value("Fiscal Year", fiscal_year, "year_start_date") - budget_percentage = 0.0 - - while(dt <= getdate(posting_date)): - if distribution_id: - budget_percentage += distribution.get(getdate(dt).strftime("%B"), 0) - else: - budget_percentage += 100.0/12 - - dt = add_months(dt, 1) - - return yearly_budget * budget_percentage / 100 - -def get_actual_expense(args): - args["condition"] = " and posting_date<='%s'" % args.month_end_date \ - if args.get("month_end_date") else "" - - return flt(frappe.db.sql(""" - select sum(debit) - sum(credit) - from `tabGL Entry` - where account='%(account)s' and cost_center='%(cost_center)s' - and fiscal_year='%(fiscal_year)s' and company='%(company)s' %(condition)s - """ % (args))[0][0]) - def get_currency_precision(currency=None): if not currency: currency = frappe.db.get_value("Company", diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py index d0629e7cb0..1b445fe427 100644 --- a/erpnext/config/accounts.py +++ b/erpnext/config/accounts.py @@ -200,6 +200,11 @@ def get_data(): "description": _("Tree of financial Cost Centers."), "doctype": "Cost Center", }, + { + "type": "doctype", + "name": "Budget", + "description": _("Define budget for a financial year.") + }, { "type": "report", "name": "Budget Variance Report",