Merge pull request #5362 from nabinhait/budget

Budgeting
This commit is contained in:
Nabin Hait
2016-05-23 17:56:42 +05:30
39 changed files with 999 additions and 718 deletions

View File

@@ -0,0 +1,32 @@
// 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",
is_group: 0
}
}
})
frm.set_query("monthly_distribution", function() {
return {
filters: {
fiscal_year: frm.doc.fiscal_year
}
}
})
}
});

View File

@@ -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": 1,
"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": 1,
"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 15:00:40.233685",
"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
}

View File

@@ -0,0 +1,132 @@
# -*- 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()
self.validate_accounts()
def validate_duplicate(self):
existing_budget = frappe.db.get_value("Budget", {"cost_center": self.cost_center,
"fiscal_year": self.fiscal_year, "company": self.company,
"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_accounts(self):
account_list = []
for d in self.get('accounts'):
if d.account:
account_details = frappe.db.get_value("Account", d.account,
["is_group", "company", "report_type"], as_dict=1)
if account_details.is_group:
frappe.throw(_("Budget cannot be assigned against Group Account {0}").format(d.account))
elif account_details.company != self.company:
frappe.throw(_("Account {0} does not belongs to company {1}")
.format(d.account, self.company))
elif account_details.report_type != "Profit and Loss":
frappe.throw(_("Budget cannot be assigned against {0}, as it's not an Income or Expense account")
.format(d.account))
if d.account in account_list:
frappe.throw(_("Account {0} has been entered multiple times").format(d.account))
else:
account_list.append(d.account)
def validate_expense_against_budget(args):
args = frappe._dict(args)
if frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"}):
cc_lft, cc_rgt = frappe.db.get_value("Cost Center", args.cost_center, ["lft", "rgt"])
budget_records = frappe.db.sql("""
select ba.budget_amount, b.monthly_distribution, b.cost_center,
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.fiscal_year=%s and ba.account=%s and b.docstatus=1
and exists(select name from `tabCost Center` where lft<=%s and rgt>=%s and name=b.cost_center)
""", (args.fiscal_year, args.account, cc_lft, cc_rgt), as_dict=True)
for budget in budget_records:
if budget.budget_amount:
yearly_action = budget.action_if_annual_budget_exceeded
monthly_action = budget.action_if_accumulated_monthly_budget_exceeded
if monthly_action in ["Stop", "Warn"]:
budget_amount = get_accumulated_monthly_budget(budget.monthly_distribution,
args.posting_date, args.fiscal_year, budget.budget_amount)
args["month_end_date"] = get_last_day(args.posting_date)
compare_expense_with_budget(args, budget.cost_center,
budget_amount, _("Accumulated Monthly"), monthly_action)
elif yearly_action in ["Stop", "Warn"]:
compare_expense_with_budget(args, budget.cost_center,
flt(budget.budget_amount), _("Annual"), yearly_action)
def compare_expense_with_budget(args, cost_center, budget_amount, action_for, action):
actual_expense = get_actual_expense(args, cost_center)
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, 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, cost_center):
lft_rgt = frappe.db.get_value("Cost Center", cost_center, ["lft", "rgt"], as_dict=1)
args.update(lft_rgt)
condition = " and gle.posting_date <= %(month_end_date)s" if args.get("month_end_date") else ""
return flt(frappe.db.sql("""
select sum(gle.debit) - sum(gle.credit)
from `tabGL Entry` gle
where gle.account=%(account)s
and exists(select name from `tabCost Center`
where lft>=%(lft)s and rgt<=%(rgt)s and name=gle.cost_center)
and gle.fiscal_year=%(fiscal_year)s
and gle.company=%(company)s
and gle.docstatus=1
{condition}
""".format(condition=condition), (args))[0][0])

View File

@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
class TestBudget(unittest.TestCase):
def test_monthly_budget_crossed_ignore(self):
set_total_expense_zero("2013-02-28")
budget = make_budget()
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", submit=True)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
budget.cancel()
def test_monthly_budget_crossed_stop(self):
set_total_expense_zero("2013-02-28")
budget = make_budget()
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC")
self.assertRaises(BudgetError, jv.submit)
budget.load_from_db()
budget.cancel()
def test_yearly_budget_crossed_stop(self):
set_total_expense_zero("2013-02-28")
budget = make_budget()
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 150000, "_Test Cost Center - _TC")
self.assertRaises(BudgetError, jv.submit)
budget.cancel()
def test_monthly_budget_on_cancellation(self):
set_total_expense_zero("2013-02-28")
budget = make_budget()
jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", submit=True)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv1.name}))
jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", submit=True)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv2.name}))
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
self.assertRaises(BudgetError, jv1.cancel)
budget.load_from_db()
budget.cancel()
def test_monthly_budget_against_group_cost_center(self):
set_total_expense_zero("2013-02-28")
set_total_expense_zero("2013-02-28", "_Test Cost Center 2 - _TC")
budget = make_budget("_Test Company - _TC")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC")
self.assertRaises(BudgetError, jv.submit)
budget.load_from_db()
budget.cancel()
def set_total_expense_zero(posting_date, cost_center=None):
existing_expense = get_actual_expense({
"account": "_Test Account Cost for Goods Sold - _TC",
"cost_center": cost_center or "_Test Cost Center - _TC",
"monthly_end_date": posting_date,
"company": "_Test Company",
"fiscal_year": "_Test Fiscal Year 2013"
}, cost_center or "_Test Cost Center - _TC")
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True)
def make_budget(cost_center=None):
budget = frappe.new_doc("Budget")
budget.cost_center = cost_center or "_Test Cost Center - _TC"
budget.fiscal_year = "_Test Fiscal Year 2013"
budget.monthly_distribution = "_Test Distribution"
budget.company = "_Test Company"
budget.action_if_annual_budget_exceeded = "Stop"
budget.action_if_accumulated_monthly_budget_exceeded = "Ignore"
budget.append("accounts", {
"account": "_Test Account Cost for Goods Sold - _TC",
"budget_amount": 100000
})
budget.insert()
budget.submit()
return budget

View File

@@ -2,11 +2,12 @@
"allow_copy": 0, "allow_copy": 0,
"allow_import": 0, "allow_import": 0,
"allow_rename": 0, "allow_rename": 0,
"autoname": "hash", "beta": 0,
"creation": "2013-03-07 11:55:04", "creation": "2016-05-16 11:54:09.286135",
"custom": 0, "custom": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"fields": [ "fields": [
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
@@ -16,41 +17,17 @@
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 1, "ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Account", "label": "Account",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"oldfieldname": "account",
"oldfieldtype": "Link",
"options": "Account", "options": "Account",
"permlevel": 0, "permlevel": 0,
"precision": "",
"print_hide": 0, "print_hide": 0,
"read_only": 0, "print_hide_if_no_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "budget_allocated",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Budget Allocated",
"length": 0,
"no_copy": 0,
"oldfieldname": "budget_allocated",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 0,
"read_only": 0, "read_only": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 1, "reqd": 1,
@@ -62,45 +39,72 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"fieldname": "fiscal_year", "fieldname": "column_break_2",
"fieldtype": "Link", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 1, "ignore_xss_filter": 0,
"in_list_view": 1, "in_filter": 0,
"label": "Fiscal Year", "in_list_view": 0,
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"oldfieldname": "fiscal_year",
"oldfieldtype": "Select",
"options": "Fiscal Year",
"permlevel": 0, "permlevel": 0,
"precision": "",
"print_hide": 0, "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, "read_only": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 1, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
} }
], ],
"hide_heading": 0, "hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"idx": 1, "idx": 0,
"in_create": 0, "in_create": 0,
"in_dialog": 0, "in_dialog": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2015-11-16 06:29:43.050558", "modified": "2016-05-16 11:55:29.586591",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Budget Detail", "name": "Budget Account",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1,
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC" "sort_order": "DESC",
"track_seen": 0
} }

View File

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

View File

@@ -1 +0,0 @@
Budget amounts for year and distribution for parent Cost Center.

View File

@@ -1 +0,0 @@
from __future__ import unicode_literals

View File

@@ -1,9 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class BudgetDetail(Document):
pass

View File

@@ -5,36 +5,19 @@ frappe.provide("erpnext.accounts");
cur_frm.list_route = "Accounts Browser/Cost Center"; cur_frm.list_route = "Accounts Browser/Cost Center";
erpnext.accounts.CostCenterController = frappe.ui.form.Controller.extend({
onload: function() {
this.setup_queries();
},
setup_queries: function() { frappe.ui.form.on('Cost Center', {
var me = this; onload: function(frm) {
if(this.frm.fields_dict["budgets"].grid.get_field("account")) { frm.set_query("parent_cost_center", function() {
this.frm.set_query("account", "budgets", function() {
return {
filters:[
['Account', 'company', '=', me.frm.doc.company],
['Account', 'is_group', '=', '0']
]
}
});
}
this.frm.set_query("parent_cost_center", function() {
return { return {
filters:[ filters: {
['Cost Center', 'is_group', '=', '1'], company: frm.doc.company,
['Cost Center', 'company', '=', me.frm.doc.company], is_group: 1
] }
} }
}); })
} }
}); })
$.extend(cur_frm.cscript, new erpnext.accounts.CostCenterController({frm: cur_frm}));
cur_frm.cscript.refresh = function(doc, cdt, cdn) { cur_frm.cscript.refresh = function(doc, cdt, cdn) {
var intro_txt = ''; var intro_txt = '';

View File

@@ -3,6 +3,7 @@
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "field:cost_center_name", "autoname": "field:cost_center_name",
"beta": 0,
"creation": "2013-01-23 19:57:17", "creation": "2013-01-23 19:57:17",
"custom": 0, "custom": 0,
"description": "Track separate Income and Expense for product verticals or divisions.", "description": "Track separate Income and Expense for product verticals or divisions.",
@@ -164,87 +165,6 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "Define Budget for this Cost Center. To set budget action, see \"Company List\"",
"fieldname": "sb1",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Budget",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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,
"description": "Select Monthly Distribution, if you want to track based on seasonality.",
"fieldname": "distribution_id",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Distribution Id",
"length": 0,
"no_copy": 0,
"oldfieldname": "distribution_id",
"oldfieldtype": "Link",
"options": "Monthly Distribution",
"permlevel": 0,
"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,
"description": "Add rows to set annual budgets on Accounts.",
"fieldname": "budgets",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Budgets",
"length": 0,
"no_copy": 0,
"oldfieldname": "budget_details",
"oldfieldtype": "Table",
"options": "Budget Detail",
"permlevel": 0,
"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, "allow_on_submit": 0,
"bold": 0, "bold": 0,
@@ -336,7 +256,7 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2016-03-14 15:59:51.508268", "modified": "2016-05-16 15:23:14.770933",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Cost Center", "name": "Cost Center",
@@ -443,8 +363,11 @@
"write": 0 "write": 0
} }
], ],
"quick_entry": 1,
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"search_fields": "parent_cost_center, is_group", "search_fields": "parent_cost_center, is_group",
"sort_order": "ASC",
"track_seen": 0,
"version": 0 "version": 0
} }

View File

@@ -16,7 +16,6 @@ class CostCenter(NestedSet):
def validate(self): def validate(self):
self.validate_mandatory() self.validate_mandatory()
self.validate_accounts()
def validate_mandatory(self): def validate_mandatory(self):
if self.cost_center_name != self.company and not self.parent_cost_center: if self.cost_center_name != self.company and not self.parent_cost_center:
@@ -24,29 +23,6 @@ class CostCenter(NestedSet):
elif self.cost_center_name == self.company and self.parent_cost_center: elif self.cost_center_name == self.company and self.parent_cost_center:
frappe.throw(_("Root cannot have a parent cost center")) frappe.throw(_("Root cannot have a parent cost center"))
def validate_accounts(self):
if self.is_group==1 and self.get("budgets"):
frappe.throw(_("Budget cannot be set for Group Cost Center"))
check_acc_list = []
for d in self.get('budgets'):
if d.account:
account_details = frappe.db.get_value("Account", d.account,
["is_group", "company", "report_type"], as_dict=1)
if account_details.is_group:
frappe.throw(_("Budget cannot be assigned against Group Account {0}").format(d.account))
elif account_details.company != self.company:
frappe.throw(_("Account {0} does not belongs to company {1}").format(d.account, self.company))
elif account_details.report_type != "Profit and Loss":
frappe.throw(_("Budget cannot be assigned against {0}, as it's not an Income or Expense account")
.format(d.account))
if [d.account, d.fiscal_year] in check_acc_list:
frappe.throw(_("Account {0} has been entered more than once for fiscal year {1}")
.format(d.account, d.fiscal_year))
else:
check_acc_list.append([d.account, d.fiscal_year])
def convert_group_to_ledger(self): def convert_group_to_ledger(self):
if self.check_if_child_exists(): if self.check_if_child_exists():
frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes")) frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes"))

View File

@@ -1,17 +1,7 @@
[ [
{ {
"budgets": [
{
"account": "_Test Account Cost for Goods Sold - _TC",
"budget_allocated": 100000,
"doctype": "Budget Detail",
"fiscal_year": "_Test Fiscal Year 2013",
"parentfield": "budgets"
}
],
"company": "_Test Company", "company": "_Test Company",
"cost_center_name": "_Test Cost Center", "cost_center_name": "_Test Cost Center",
"distribution_id": "_Test Distribution",
"doctype": "Cost Center", "doctype": "Cost Center",
"is_group": 0, "is_group": 0,
"parent_cost_center": "_Test Company - _TC" "parent_cost_center": "_Test Company - _TC"

View File

@@ -4,7 +4,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest, frappe import unittest, frappe
from frappe.utils import flt from frappe.utils import flt
from erpnext.accounts.utils import get_actual_expense, BudgetError, get_fiscal_year
from erpnext.exceptions import InvalidAccountCurrency from erpnext.exceptions import InvalidAccountCurrency
@@ -96,78 +95,6 @@ class TestJournalEntry(unittest.TestCase):
set_perpetual_inventory(0) set_perpetual_inventory(0)
def test_monthly_budget_crossed_ignore(self):
frappe.db.set_value("Company", "_Test Company", "monthly_bgt_flag", "Ignore")
self.set_total_expense_zero("2013-02-28")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", submit=True)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
def test_monthly_budget_crossed_stop(self):
frappe.db.set_value("Company", "_Test Company", "monthly_bgt_flag", "Stop")
self.set_total_expense_zero("2013-02-28")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC")
self.assertRaises(BudgetError, jv.submit)
frappe.db.set_value("Company", "_Test Company", "monthly_bgt_flag", "Ignore")
def test_yearly_budget_crossed_stop(self):
self.test_monthly_budget_crossed_ignore()
frappe.db.set_value("Company", "_Test Company", "yearly_bgt_flag", "Stop")
self.set_total_expense_zero("2013-02-28")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 150000, "_Test Cost Center - _TC")
self.assertRaises(BudgetError, jv.submit)
frappe.db.set_value("Company", "_Test Company", "yearly_bgt_flag", "Ignore")
def test_monthly_budget_on_cancellation(self):
self.set_total_expense_zero("2013-02-28")
jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", submit=True)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv1.name}))
jv2 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", submit=True)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv2.name}))
frappe.db.set_value("Company", "_Test Company", "monthly_bgt_flag", "Stop")
self.assertRaises(BudgetError, jv1.cancel)
frappe.db.set_value("Company", "_Test Company", "monthly_bgt_flag", "Ignore")
def get_actual_expense(self, monthly_end_date):
return get_actual_expense({
"account": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
"monthly_end_date": monthly_end_date,
"company": "_Test Company",
"fiscal_year": get_fiscal_year(monthly_end_date)[0]
})
def set_total_expense_zero(self, posting_date):
existing_expense = self.get_actual_expense(posting_date)
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True)
def test_multi_currency(self): def test_multi_currency(self):
jv = make_journal_entry("_Test Bank USD - _TC", jv = make_journal_entry("_Test Bank USD - _TC",
"_Test Bank - _TC", 100, exchange_rate=50, save=False) "_Test Bank - _TC", 100, exchange_rate=50, save=False)
@@ -245,9 +172,9 @@ class TestJournalEntry(unittest.TestCase):
jv.submit() jv.submit()
def make_journal_entry(account1, account2, amount, cost_center=None, exchange_rate=1, save=True, submit=False): def make_journal_entry(account1, account2, amount, cost_center=None, posting_date=None, exchange_rate=1, save=True, submit=False):
jv = frappe.new_doc("Journal Entry") jv = frappe.new_doc("Journal Entry")
jv.posting_date = "2013-02-14" jv.posting_date = posting_date or "2013-02-14"
jv.company = "_Test Company" jv.company = "_Test Company"
jv.user_remark = "test" jv.user_remark = "test"
jv.multi_currency = 1 jv.multi_currency = 1

View File

@@ -3,9 +3,10 @@
"allow_import": 0, "allow_import": 0,
"allow_rename": 0, "allow_rename": 0,
"autoname": "field:distribution_id", "autoname": "field:distribution_id",
"beta": 0,
"creation": "2013-01-10 16:34:05", "creation": "2013-01-10 16:34:05",
"custom": 0, "custom": 0,
"description": "**Monthly Distribution** helps you distribute your budget across months if you have seasonality in your business.\n\nTo distribute a budget using this distribution, set this **Monthly Distribution** in the **Cost Center**", "description": "**Monthly Distribution** helps you distribute the Budget/Target across months if you have seasonality in your business.",
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"fields": [ "fields": [
@@ -101,7 +102,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-03-03 02:46:44.493857", "modified": "2016-05-16 16:35:20.349194",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Monthly Distribution", "name": "Monthly Distribution",
@@ -149,8 +150,10 @@
"write": 0 "write": 0
} }
], ],
"quick_entry": 1,
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC" "sort_order": "DESC",
"track_seen": 0
} }

View File

@@ -55,6 +55,7 @@ class PeriodClosingVoucher(AccountsController):
if flt(acc.balance_in_company_currency): if flt(acc.balance_in_company_currency):
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": acc.account, "account": acc.account,
"cost_center": acc.cost_center,
"account_currency": acc.account_currency, "account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.balance_in_account_currency)) \ "debit_in_account_currency": abs(flt(acc.balance_in_account_currency)) \
if flt(acc.balance_in_account_currency) < 0 else 0, if flt(acc.balance_in_account_currency) < 0 else 0,
@@ -84,12 +85,12 @@ class PeriodClosingVoucher(AccountsController):
"""Get balance for pl accounts""" """Get balance for pl accounts"""
return frappe.db.sql(""" return frappe.db.sql("""
select select
t1.account, t2.account_currency, t1.account, t1.cost_center, t2.account_currency,
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency, sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency,
sum(t1.debit) - sum(t1.credit) as balance_in_company_currency sum(t1.debit) - sum(t1.credit) as balance_in_company_currency
from `tabGL Entry` t1, `tabAccount` t2 from `tabGL Entry` t1, `tabAccount` t2
where t1.account = t2.name and t2.report_type = 'Profit and Loss' where t1.account = t2.name and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2 and t2.company = %s and t2.docstatus < 2 and t2.company = %s
and t1.posting_date between %s and %s and t1.posting_date between %s and %s
group by t1.account group by t1.account, t1.cost_center
""", (self.company, self.get("year_start_date"), self.posting_date), as_dict=1) """, (self.company, self.get("year_start_date"), self.posting_date), as_dict=1)

View File

@@ -6,7 +6,7 @@ from __future__ import unicode_literals
import unittest import unittest
import frappe import frappe
from frappe.utils import flt, today from frappe.utils import flt, today
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year, now
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
class TestPeriodClosingVoucher(unittest.TestCase): class TestPeriodClosingVoucher(unittest.TestCase):
@@ -14,10 +14,10 @@ class TestPeriodClosingVoucher(unittest.TestCase):
year_start_date = get_fiscal_year(today())[1] year_start_date = get_fiscal_year(today())[1]
make_journal_entry("_Test Bank - _TC", "Sales - _TC", 400, make_journal_entry("_Test Bank - _TC", "Sales - _TC", 400,
"_Test Cost Center - _TC", submit=True) "_Test Cost Center - _TC", posting_date=now(), submit=True)
make_journal_entry("_Test Account Cost for Goods Sold - _TC", make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 600, "_Test Cost Center - _TC", submit=True) "_Test Bank - _TC", 600, "_Test Cost Center - _TC", posting_date=now(), submit=True)
random_expense_account = frappe.db.sql(""" random_expense_account = frappe.db.sql("""
select t1.account, select t1.account,

View File

@@ -6,7 +6,7 @@ import frappe
from frappe.utils import flt, cstr, cint from frappe.utils import flt, cstr, cint
from frappe import _ from frappe import _
from frappe.model.meta import get_field_precision 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 class StockAccountInvalidTransaction(frappe.ValidationError): pass

View File

@@ -8,7 +8,8 @@ frappe.query_reports["Budget Variance Report"] = {
label: __("Fiscal Year"), label: __("Fiscal Year"),
fieldtype: "Link", fieldtype: "Link",
options: "Fiscal Year", options: "Fiscal Year",
default: sys_defaults.fiscal_year default: sys_defaults.fiscal_year,
reqd: 1
}, },
{ {
fieldname: "period", fieldname: "period",
@@ -20,14 +21,16 @@ frappe.query_reports["Budget Variance Report"] = {
{ "value": "Half-Yearly", "label": __("Half-Yearly") }, { "value": "Half-Yearly", "label": __("Half-Yearly") },
{ "value": "Yearly", "label": __("Yearly") } { "value": "Yearly", "label": __("Yearly") }
], ],
default: "Monthly" default: "Monthly",
reqd: 1
}, },
{ {
fieldname: "company", fieldname: "company",
label: __("Company"), label: __("Company"),
fieldtype: "Link", fieldtype: "Link",
options: "Company", options: "Company",
default: frappe.defaults.get_user_default("Company") default: frappe.defaults.get_user_default("Company"),
}, reqd: 1
}
] ]
} }

View File

@@ -3,48 +3,43 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _, msgprint from frappe import _
from frappe.utils import flt from frappe.utils import flt
from frappe.utils import formatdate from frappe.utils import formatdate
import time
from erpnext.accounts.utils import get_fiscal_year
from erpnext.controllers.trends import get_period_date_ranges, get_period_month_ranges from erpnext.controllers.trends import get_period_date_ranges, get_period_month_ranges
def execute(filters=None): def execute(filters=None):
if not filters: filters = {} if not filters: filters = {}
columns = get_columns(filters) columns = get_columns(filters)
cost_centers = get_cost_centers(filters.company)
period_month_ranges = get_period_month_ranges(filters["period"], filters["fiscal_year"]) period_month_ranges = get_period_month_ranges(filters["period"], filters["fiscal_year"])
cam_map = get_costcenter_account_month_map(filters) cam_map = get_cost_center_account_month_map(filters)
data = [] data = []
for cost_center, cost_center_items in cam_map.items(): for cost_center in cost_centers:
for account, monthwise_data in cost_center_items.items(): cost_center_items = cam_map.get(cost_center)
row = [cost_center, account] if cost_center_items:
totals = [0, 0, 0] for account, monthwise_data in cost_center_items.items():
for relevant_months in period_month_ranges: row = [cost_center, account]
period_data = [0, 0, 0] totals = [0, 0, 0]
for month in relevant_months: for relevant_months in period_month_ranges:
month_data = monthwise_data.get(month, {}) period_data = [0, 0, 0]
for i, fieldname in enumerate(["target", "actual", "variance"]): for month in relevant_months:
value = flt(month_data.get(fieldname)) month_data = monthwise_data.get(month, {})
period_data[i] += value for i, fieldname in enumerate(["target", "actual", "variance"]):
totals[i] += value value = flt(month_data.get(fieldname))
period_data[2] = period_data[0] - period_data[1] period_data[i] += value
row += period_data totals[i] += value
totals[2] = totals[0] - totals[1] period_data[2] = period_data[0] - period_data[1]
row += totals row += period_data
data.append(row) totals[2] = totals[0] - totals[1]
row += totals
data.append(row)
return columns, sorted(data, key=lambda x: (x[0], x[1])) return columns, data
def get_columns(filters): def get_columns(filters):
for fieldname in ["fiscal_year", "period", "company"]:
if not filters.get(fieldname):
label = (" ".join(fieldname.split("_"))).title()
msgprint(_("Please specify") + ": " + label,
raise_exception=True)
columns = [_("Cost Center") + ":Link/Cost Center:120", _("Account") + ":Link/Account:120"] columns = [_("Cost Center") + ":Link/Cost Center:120", _("Account") + ":Link/Account:120"]
group_months = False if filters["period"] == "Monthly" else True group_months = False if filters["period"] == "Monthly" else True
@@ -52,7 +47,7 @@ def get_columns(filters):
for from_date, to_date in get_period_date_ranges(filters["period"], filters["fiscal_year"]): for from_date, to_date in get_period_date_ranges(filters["period"], filters["fiscal_year"]):
for label in [_("Target") + " (%s)", _("Actual") + " (%s)", _("Variance") + " (%s)"]: for label in [_("Target") + " (%s)", _("Actual") + " (%s)", _("Variance") + " (%s)"]:
if group_months: if group_months:
label = label % (formatdate(from_date, format_string="MMM") + " - " + formatdate(from_date, format_string="MMM")) label = label % (formatdate(from_date, format_string="MMM") + " - " + formatdate(to_date, format_string="MMM"))
else: else:
label = label % formatdate(from_date, format_string="MMM") label = label % formatdate(from_date, format_string="MMM")
@@ -61,19 +56,20 @@ def get_columns(filters):
return columns + [_("Total Target") + ":Float:120", _("Total Actual") + ":Float:120", return columns + [_("Total Target") + ":Float:120", _("Total Actual") + ":Float:120",
_("Total Variance") + ":Float:120"] _("Total Variance") + ":Float:120"]
def get_cost_centers(company):
return frappe.db.sql_list("select name from `tabCost Center` where company=%s order by lft", company)
#Get cost center & target details #Get cost center & target details
def get_costcenter_target_details(filters): def get_cost_center_target_details(filters):
return frappe.db.sql("""select cc.name, cc.distribution_id, return frappe.db.sql("""
cc.parent_cost_center, bd.account, bd.budget_allocated select b.cost_center, b.monthly_distribution, ba.account, ba.budget_amount
from `tabCost Center` cc, `tabBudget Detail` bd from `tabBudget` b, `tabBudget Account` ba
where bd.parent=cc.name and bd.fiscal_year=%s and where b.name=ba.parent and b.fiscal_year=%s and b.company=%s
cc.company=%s order by cc.name""" % ('%s', '%s'), """, (filters.fiscal_year, filters.company), as_dict=True)
(filters.get("fiscal_year"), filters.get("company")), as_dict=1)
#Get target distribution details of accounts of cost center #Get target distribution details of accounts of cost center
def get_target_distribution_details(filters): def get_target_distribution_details(filters):
target_details = {} target_details = {}
for d in frappe.db.sql("""select md.name, mdp.month, mdp.percentage_allocation for d in frappe.db.sql("""select md.name, mdp.month, mdp.percentage_allocation
from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md
where mdp.parent=md.name and md.fiscal_year=%s""", (filters["fiscal_year"]), as_dict=1): where mdp.parent=md.name and md.fiscal_year=%s""", (filters["fiscal_year"]), as_dict=1):
@@ -82,45 +78,51 @@ def get_target_distribution_details(filters):
return target_details return target_details
#Get actual details from gl entry #Get actual details from gl entry
def get_actual_details(filters): def get_actual_details(cost_center, fiscal_year):
cc_lft, cc_rgt = frappe.db.get_value("Cost Center", cost_center, ["lft", "rgt"])
ac_details = frappe.db.sql("""select gl.account, gl.debit, gl.credit, ac_details = frappe.db.sql("""select gl.account, gl.debit, gl.credit,
gl.cost_center, MONTHNAME(gl.posting_date) as month_name MONTHNAME(gl.posting_date) as month_name, b.cost_center
from `tabGL Entry` gl, `tabBudget Detail` bd from `tabGL Entry` gl, `tabBudget Account` ba, `tabBudget` b
where gl.fiscal_year=%s and company=%s where
and bd.account=gl.account and bd.parent=gl.cost_center""" % ('%s', '%s'), b.name = ba.parent
(filters.get("fiscal_year"), filters.get("company")), as_dict=1) and ba.account=gl.account
and gl.fiscal_year=%s
and b.cost_center=%s
and exists(select name from `tabCost Center` where name=gl.cost_center and lft>=%s and rgt<=%s)
""", (fiscal_year, cost_center, cc_lft, cc_rgt), as_dict=1)
cc_actual_details = {} cc_actual_details = {}
for d in ac_details: for d in ac_details:
cc_actual_details.setdefault(d.cost_center, {}).setdefault(d.account, []).append(d) cc_actual_details.setdefault(d.account, []).append(d)
return cc_actual_details return cc_actual_details
def get_costcenter_account_month_map(filters): def get_cost_center_account_month_map(filters):
import datetime import datetime
costcenter_target_details = get_costcenter_target_details(filters) cost_center_target_details = get_cost_center_target_details(filters)
tdd = get_target_distribution_details(filters) tdd = get_target_distribution_details(filters)
actual_details = get_actual_details(filters)
cam_map = {} cam_map = {}
for ccd in costcenter_target_details: for ccd in cost_center_target_details:
actual_details = get_actual_details(ccd.cost_center, filters.fiscal_year)
for month_id in range(1, 13): for month_id in range(1, 13):
month = datetime.date(2013, month_id, 1).strftime('%B') month = datetime.date(2013, month_id, 1).strftime('%B')
cam_map.setdefault(ccd.name, {}).setdefault(ccd.account, {})\ cam_map.setdefault(ccd.cost_center, {}).setdefault(ccd.account, {})\
.setdefault(month, frappe._dict({ .setdefault(month, frappe._dict({
"target": 0.0, "actual": 0.0 "target": 0.0, "actual": 0.0
})) }))
tav_dict = cam_map[ccd.name][ccd.account][month] tav_dict = cam_map[ccd.cost_center][ccd.account][month]
month_percentage = tdd.get(ccd.monthly_distribution, {}).get(month, 0) \
if ccd.monthly_distribution else 100.0/12
month_percentage = tdd.get(ccd.distribution_id, {}).get(month, 0) \ tav_dict.target = flt(ccd.budget_amount) * month_percentage / 100
if ccd.distribution_id else 100.0/12
tav_dict.target = flt(ccd.budget_allocated) * month_percentage / 100 for ad in actual_details.get(ccd.account, []):
for ad in actual_details.get(ccd.name, {}).get(ccd.account, []):
if ad.month_name == month: if ad.month_name == month:
tav_dict.actual += flt(ad.debit) - flt(ad.credit) tav_dict.actual += flt(ad.debit) - flt(ad.credit)

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe 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 import throw, _
from frappe.utils import formatdate from frappe.utils import formatdate
import frappe.desk.reportview import frappe.desk.reportview
@@ -13,7 +13,6 @@ import frappe.desk.reportview
from erpnext.accounts.doctype.account.account import get_account_currency from erpnext.accounts.doctype.account.account import get_account_currency
class FiscalYearError(frappe.ValidationError): pass class FiscalYearError(frappe.ValidationError): pass
class BudgetError(frappe.ValidationError): pass
@frappe.whitelist() @frappe.whitelist()
def get_fiscal_year(date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False): 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 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): def get_currency_precision(currency=None):
if not currency: if not currency:
currency = frappe.db.get_value("Company", currency = frappe.db.get_value("Company",

View File

@@ -200,6 +200,11 @@ def get_data():
"description": _("Tree of financial Cost Centers."), "description": _("Tree of financial Cost Centers."),
"doctype": "Cost Center", "doctype": "Cost Center",
}, },
{
"type": "doctype",
"name": "Budget",
"description": _("Define budget for a financial year.")
},
{ {
"type": "report", "type": "report",
"name": "Budget Variance Report", "name": "Budget Variance Report",

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

View File

@@ -69,11 +69,11 @@ Since Project's Cost Center is updated in both sales and purchase entries, you c
#### 3.2 Projectwise Budgeting #### 3.2 Projectwise Budgeting
If you have also define budgets in the Cost Center of a Project, you will get Budget Variance Report for a Cost Center of a Project. You can define budgets against the Cost Center associated with a Project. At any point of time, you can refer Budget Variance Report to analysis the expense vs budget against a cost center.
To check Budget Variance report, go to: To check Budget Variance report, go to:
`Accounts > Standard Reports > Budget Variance Report` `Accounts > Budget and Cost Center > Budget Variance Report`
[Click here to learn how to do budgeting from Cost Center]({{docs_base_url}}/user/manual/en/accounts/budgeting.html). [Click here to learn how to do budgeting from Cost Center]({{docs_base_url}}/user/manual/en/accounts/budgeting.html).

View File

@@ -1,45 +1,29 @@
ERPNext will help you set and manage budgets on your Cost Centers. This is In ERPNext, you can set and manage budgets against a Cost Center. This is useful when, for example, you are doing online sales. You have a budget for search ads, and you want ERPNext to stop or warn you from over spending, based on that budget.
useful when, for example, you are doing online sales. You have a budget for
search ads, and you want ERPNext to stop or warn you from over spending, based
on that budget.
Budgets are also great for planning purposes. When you are making plans for Budgets are also great for planning purposes. When you are making plans for the next financial year, you would typically target a revenue based on which you would set your expenses. Setting a budget will ensure that your expenses do not get out of hand, at any point, as per your plans.
the next financial year, you would typically target a revenue based on which
you would set your expenses. Setting a budget will ensure that your expenses
do not get out of hand, at any point, as per your plans.
You can define it in the Cost Center. If you have seasonal sales you can also To allocate budget, go to:
define a budget distribution that the budget will follow.
In order to allocate budget, go to: > Accounts > Budget and Cost Center > Budget
> Accounts > Setup > Chart of Cost Centers In the Budget form, you can select a Cost Center and for that cost center you can define budgets against any Expense / Income accounts. Budgets can be defined against any Cost Center whether it is a Group / Leaf node in the Chart of Cost Centers.
and click on Chart of Cost Center. Select a Cost Center and click on Open. <img class="screenshot" alt="Budget" src="{{docs_base_url}}/assets/img/accounts/budget.png">
#### Step 1: Click on Edit. If you have seasonal business, you can also define a Monthly Distribution record, to distribute the budget between months. If you don't set the monthly distribution, ERPNext will calculate the budget on yearly
<img alt="Cost Center" class="screenshot" src="{{docs_base_url}}/assets/img/accounts/cost-center-1.gif">
#### Step 3:Add New Row and select budget account.
<img alt="Cost Center Account" class="screenshot" src="{{docs_base_url}}/assets/img/accounts/cost-center-2.png">
#### Step 3: Enter Monthly Distribution (optional)
<img alt="Cost Center" class="screenshot" src="{{docs_base_url}}/assets/img/accounts/cost-center-distribution.gif">
If you leave the** **distribution ID blank, ERPNext will calculate on a yearly
basis or in equal proportion for every month. basis or in equal proportion for every month.
### To Create New Distribution ID <img class="screenshot" alt="Monthly Distribution" src="{{docs_base_url}}/assets/img/accounts/monthly-distribution.png">
ERPNext allows you to take a few budget actions. It signifies whether to stop, warn or Ignore if you exceed budgets. While setting budget, you can also define the actions when expenses will exceed the allocated budget for a period. You can set separate action for monthly and annual budgets. There are 3 types of actions: Stop, Warn and Ignore. If Stop, system will not allow to book expenses more than allocated budget. In Case of Warn, it will just warn the user that expenses has been exceeded from the allocated budget. And Ignore will do nothing.
These can be defined from the Company record.
<img alt="Cost Center" class="screenshot" src="{{docs_base_url}}/assets/img/accounts/cost-center-company.gif"> At any point of time, user can check Budget Variance Report to analysis the expense vs budget against a cost center.
Even if you choose to “ignore” budget overruns, you will get a wealth of information from the “Budget vs Actual” variance report. This report shows month wise actual expenses as compared to the budgeted expenses. To check Budget Variance report, go to:
Accounts > Budget and Cost Center > Budget Variance Report
<img class="screenshot" alt="Budget Variance Report" src="{{docs_base_url}}/assets/img/accounts/budget-variance-report.png">
{next} {next}

View File

@@ -34,45 +34,4 @@ To setup your Chart of Cost Centers go to:
![Chart of Cost Center]({{docs_base_url}}/assets/old_images/erpnext/chart-of-cost-centers.png) ![Chart of Cost Center]({{docs_base_url}}/assets/old_images/erpnext/chart-of-cost-centers.png)
Cost centers help you in one more activity, budgeting.
### Budgeting
ERPNext will help you set and manage budgets on your Cost Centers. This is
useful when, for example, you are doing online sales. You have a budget for
search ads, and you want ERPNext to stop or warn you from over spending, based
on that budget.
Budgets are also great for planning purposes. When you are making plans for
the next financial year, you would typically target a revenue based on which
you would set your expenses. Setting a budget will ensure that your expenses
do not get out of hand, at any point, as per your plans.
You can define it in the Cost Center. If you have seasonal sales you can also
define a budget distribution that the budget will follow.
> Accounts > Setup > Budget Distribution > New Budget Distribution
![Budget Distribution]({{docs_base_url}}/assets/old_images/erpnext/budgeting.png)
#### Budget Actions
ERPNext allows you to either:
* Stop.
* Warn or,
* Ignore
if you exceed budgets.
These can be defined from the Company record.
Even if you choose to “ignore” budget overruns, you will get a wealth of
information from the “Budget vs Actual” variance report.
> Note: When you set a budget, it has to be set as per Account under the Cost
Center. For example if you have a Cost Center “Online Sales”, you can restrict
“Advertising Budget” by creating a row with that Account and defining the
amount.
{next} {next}

View File

@@ -20,7 +20,7 @@ In this table, you should select Item Group, Fiscal Year, Target Qty and Amount.
####1.3 Target Distribution ####1.3 Target Distribution
If you wish to spread allocated target across months, then you shoult setup Target Distribution master, and select it in the Sales Person master. Considering our example, target for the month of December will be set as 5 qty (10% of total allocation). If you wish to spread allocated target across months, then you should setup Monthly Distribution master, and select it in the Sales Person master. Considering our example, target for the month of December will be set as 5 qty (10% of total allocation).
![Sales Person Target Distribution]({{docs_base_url}}/assets/old_images/erpnext/sales-person-target-distribution.png) ![Sales Person Target Distribution]({{docs_base_url}}/assets/old_images/erpnext/sales-person-target-distribution.png)
@@ -52,11 +52,11 @@ In the Territory master, you will find field to select Territory Manager. This f
####2.2 Allocating Target ####2.2 Allocating Target
Allocation Target in the Territory master is same as in Sales Person master. You can follow same steps as given above to specify target in the Territory master as well. Target Allocation in the Territory master is same as in Sales Person master. You can follow same steps as given above to specify target in the Territory master as well.
####2.3 Target Distribution ####2.3 Target Distribution
Using this master, you can divide target Qty or Amount across various months. Using this Monthly Distribution document, you can divide target Qty or Amount across various months.
####2.4 Report - Territory Target Variance Item Groupwise ####2.4 Report - Territory Target Variance Item Groupwise
@@ -68,15 +68,15 @@ This report will provide you variance between target and actual performance of S
###3. Target Distribution ###3. Target Distribution
Target Distribution master allows you to divide allocated target across multiple months. If your product and services is seasonal, you can distribute the sales target accordingly. For example, if you are into umbrella business, then target allocated in the monsoon seasion will be higher than in other months. Target Distribution document allows you to divide allocated target across multiple months. If your product and services is seasonal, you can distribute the sales target accordingly. For example, if you are into umbrella business, then target allocated in the monsoon seasion will be higher than in other months.
To create new Budget Distriibution master, go to: To create new Monthly Distriibution, go to:
`Accounts > Setup > Budget Distributon` `Accounts > Monthly Distributon`
![Target Distribution]({{docs_base_url}}/assets/old_images/erpnext/target-distribution.png) ![Target Distribution]({{docs_base_url}}/assets/old_images/erpnext/target-distribution.png)
You can link target distribution while allocation targets in Sales Person as well as in Territory master. You can link Monthly Distribution while allocating targets in Sales Person as well as in Territory master.
###See Also ###See Also

View File

@@ -265,3 +265,4 @@ erpnext.patches.v7_0.update_item_projected
erpnext.patches.v7_0.fix_duplicate_icons erpnext.patches.v7_0.fix_duplicate_icons
erpnext.patches.v7_0.remove_features_setup erpnext.patches.v7_0.remove_features_setup
erpnext.patches.v7_0.update_home_page erpnext.patches.v7_0.update_home_page
erpnext.patches.v7_0.create_budget_record

View File

@@ -86,9 +86,6 @@ rename_map = {
"Bank Reconciliation": [ "Bank Reconciliation": [
["entries", "journal_entries"] ["entries", "journal_entries"]
], ],
"Cost Center": [
["budget_details", "budgets"]
],
"C-Form": [ "C-Form": [
["invoice_details", "invoices"] ["invoice_details", "invoices"]
], ],

View File

@@ -0,0 +1,49 @@
import frappe
def execute():
frappe.reload_doc("accounts", "doctype", "budget")
frappe.reload_doc("accounts", "doctype", "budget_account")
existing_budgets = frappe.db.sql("""
select
cc.name, cc.company, cc.distribution_id,
budget.account, budget.budget_allocated, budget.fiscal_year
from
`tabCost Center` cc, `tabBudget Detail` budget
where
cc.name=budget.parent
""", as_dict=1)
actions = {}
for d in frappe.db.sql("select name, yearly_bgt_flag, monthly_bgt_flag from tabCompany", as_dict=1):
actions.setdefault(d.name, d)
budget_records = []
for d in existing_budgets:
budget = frappe.db.get_value("Budget",
{"cost_center": d.name, "fiscal_year": d.fiscal_year, "company": d.company})
if not budget:
budget = frappe.new_doc("Budget")
budget.cost_center = d.name
budget.fiscal_year = d.fiscal_year
budget.monthly_distribution = d.distribution_id
budget.company = d.company
budget.action_if_annual_budget_exceeded = actions[d.company]["yearly_bgt_flag"]
budget.action_if_accumulated_monthly_budget_exceeded = actions[d.company]["monthly_bgt_flag"]
else:
budget = frappe.get_doc("Budget", budget)
budget.append("accounts", {
"account": d.account,
"budget_amount": d.budget_allocated
})
budget.insert()
budget_records.append(budget)
for budget in budget_records:
budget.submit()
if frappe.db.get_value("DocType", "Budget Detail"):
frappe.delete_doc("DocType", "Budget Detail")

View File

@@ -3,6 +3,7 @@
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "field:company_name", "autoname": "field:company_name",
"beta": 0,
"creation": "2013-04-10 08:35:39", "creation": "2013-04-10 08:35:39",
"custom": 0, "custom": 0,
"description": "Legal Entity / Subsidiary with a separate Chart of Accounts belonging to the Organization.", "description": "Legal Entity / Subsidiary with a separate Chart of Accounts belonging to the Organization.",
@@ -661,7 +662,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "Cost Center", "label": "Deafult Cost Center",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Cost Center", "options": "Cost Center",
@@ -675,59 +676,6 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "credit_days_based_on",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Credit Days Based On",
"length": 0,
"no_copy": 0,
"options": "\nFixed Days\nLast Day of the Next Month",
"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,
"depends_on": "eval:(!doc.__islocal && doc.credit_days_based_on=='Fixed Days')",
"fieldname": "credit_days",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Credit Days",
"length": 0,
"no_copy": 0,
"oldfieldname": "credit_days",
"oldfieldtype": "Int",
"permlevel": 0,
"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, "allow_on_submit": 0,
"bold": 0, "bold": 0,
@@ -784,21 +732,19 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"depends_on": "eval:!doc.__islocal", "fieldname": "credit_days_based_on",
"fieldname": "yearly_bgt_flag",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "If Yearly Budget Exceeded (for expense account)", "label": "Credit Days Based On",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"oldfieldname": "yearly_bgt_flag", "options": "\nFixed Days\nLast Day of the Next Month",
"oldfieldtype": "Select",
"options": "\nWarn\nIgnore\nStop",
"permlevel": 0, "permlevel": 0,
"precision": "",
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
@@ -812,20 +758,19 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"depends_on": "eval:!doc.__islocal", "depends_on": "eval:(!doc.__islocal && doc.credit_days_based_on=='Fixed Days')",
"fieldname": "monthly_bgt_flag", "fieldname": "credit_days",
"fieldtype": "Select", "fieldtype": "Int",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "If Monthly Budget Exceeded (for expense account)", "label": "Credit Days",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"oldfieldname": "monthly_bgt_flag", "oldfieldname": "credit_days",
"oldfieldtype": "Select", "oldfieldtype": "Int",
"options": "\nWarn\nIgnore\nStop",
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
@@ -1387,7 +1332,7 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2016-03-10 04:34:43.440914", "modified": "2016-05-16 15:24:47.178826",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Company", "name": "Company",
@@ -1534,7 +1479,9 @@
"write": 0 "write": 0
} }
], ],
"quick_entry": 0,
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"sort_order": "ASC" "sort_order": "ASC",
"track_seen": 0
} }

View File

@@ -194,17 +194,11 @@ class Company(Document):
rec = frappe.db.sql("SELECT name from `tabGL Entry` where company = %s", self.name) rec = frappe.db.sql("SELECT name from `tabGL Entry` where company = %s", self.name)
if not rec: if not rec:
# delete Account frappe.db.sql("""delete from `tabBudget Account` b
frappe.db.sql("delete from `tabAccount` where company = %s", self.name) where exists(select name from tabBudget where name=b.parent and company = %s)""", self.name)
# delete cost center child table - budget detail for doctype in ["Account", "Cost Center", "Budget", "Party Account"]:
frappe.db.sql("""delete bd.* from `tabBudget Detail` bd, `tabCost Center` cc frappe.db.sql("delete from `tab{0}` where company = %s".format(doctype), self.name)
where bd.parent = cc.name and cc.company = %s""", self.name)
#delete cost center
frappe.db.sql("delete from `tabCost Center` WHERE company = %s", self.name)
# delete account from customer and supplier
frappe.db.sql("delete from `tabParty Account` where company=%s", self.name)
if not frappe.db.get_value("Stock Ledger Entry", {"company": self.name}): if not frappe.db.get_value("Stock Ledger Entry", {"company": self.name}):
frappe.db.sql("""delete from `tabWarehouse` where company=%s""", self.name) frappe.db.sql("""delete from `tabWarehouse` where company=%s""", self.name)

View File

@@ -14,7 +14,8 @@ def delete_company_transactions(company_name):
doc = frappe.get_doc("Company", company_name) doc = frappe.get_doc("Company", company_name)
if frappe.session.user != doc.owner: if frappe.session.user != doc.owner:
frappe.throw(_("Transactions can only be deleted by the creator of the Company"), frappe.PermissionError) frappe.throw(_("Transactions can only be deleted by the creator of the Company"),
frappe.PermissionError)
delete_bins(company_name) delete_bins(company_name)
delete_time_logs(company_name) delete_time_logs(company_name)
@@ -22,7 +23,7 @@ def delete_company_transactions(company_name):
for doctype in frappe.db.sql_list("""select parent from for doctype in frappe.db.sql_list("""select parent from
tabDocField where fieldtype='Link' and options='Company'"""): tabDocField where fieldtype='Link' and options='Company'"""):
if doctype not in ("Account", "Cost Center", "Warehouse", "Budget Detail", if doctype not in ("Account", "Cost Center", "Warehouse", "Budget",
"Party Account", "Employee", "Sales Taxes and Charges Template", "Party Account", "Employee", "Sales Taxes and Charges Template",
"Purchase Taxes and Charges Template", "POS Profile", 'BOM'): "Purchase Taxes and Charges Template", "POS Profile", 'BOM'):
delete_for_doctype(doctype, company_name) delete_for_doctype(doctype, company_name)

View File

@@ -70,7 +70,8 @@ def make_stock_entry(**args):
"basic_rate": args.rate or args.basic_rate, "basic_rate": args.rate or args.basic_rate,
"conversion_factor": 1.0, "conversion_factor": 1.0,
"serial_no": args.serial_no, "serial_no": args.serial_no,
'cost_center': args.cost_center 'cost_center': args.cost_center,
'expense_account': args.expense_account
}) })
if not args.do_not_save: if not args.do_not_save:

View File

@@ -122,7 +122,7 @@ class TestStockEntry(unittest.TestCase):
set_perpetual_inventory() set_perpetual_inventory()
mr = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", mr = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC",
qty=50, basic_rate=100) qty=50, basic_rate=100, expense_account="Stock Adjustment - _TC")
stock_in_hand_account = frappe.db.get_value("Account", {"account_type": "Warehouse", stock_in_hand_account = frappe.db.get_value("Account", {"account_type": "Warehouse",
"warehouse": mr.get("items")[0].t_warehouse}) "warehouse": mr.get("items")[0].t_warehouse})
@@ -149,9 +149,10 @@ class TestStockEntry(unittest.TestCase):
set_perpetual_inventory() set_perpetual_inventory()
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC",
qty=50, basic_rate=100) qty=50, basic_rate=100, expense_account="Stock Adjustment - _TC")
mi = make_stock_entry(item_code="_Test Item", source="_Test Warehouse - _TC", qty=40) mi = make_stock_entry(item_code="_Test Item", source="_Test Warehouse - _TC",
qty=40, expense_account="Stock Adjustment - _TC")
self.check_stock_ledger_entries("Stock Entry", mi.name, self.check_stock_ledger_entries("Stock Entry", mi.name,
[["_Test Item", "_Test Warehouse - _TC", -40.0]]) [["_Test Item", "_Test Warehouse - _TC", -40.0]])