[IMP] account_budget: complete review of the module

* budgetary position nor analytic account aren't required anymore (although one of the 2 have to be given)
* by extension, the computation of practical amount is not either based on the analytic lines or the account.move.line
* usability improved (for eaxmple: there's a button to open the list of items on which the practical amount computation is done)
* addition of a pivot and graph views of the budget lines

Was PR: #22342
Was task: 31995
This commit is contained in:
Vincent Schippefilt
2018-05-15 15:19:28 +02:00
committed by qdp-odoo
parent f53d9c369d
commit 9148960174
8 changed files with 802 additions and 489 deletions
+1 -1
View File
@@ -468,7 +468,7 @@ class AccountMoveLine(models.Model):
analytic_line_ids = fields.One2many('account.analytic.line', 'move_id', string='Analytic lines', oldname="analytic_lines")
tax_ids = fields.Many2many('account.tax', string='Taxes')
tax_line_id = fields.Many2one('account.tax', string='Originator tax', ondelete='restrict')
analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account')
analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', index=True)
analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags')
company_id = fields.Many2one('res.company', related='account_id.company_id', string='Company', store=True, readonly=True)
counterpart = fields.Char("Counterpart", compute='_get_counterpart', help="Compute the counter part accounts of this journal item for this journal entry. This can be needed in reports.")
@@ -384,7 +384,7 @@
<div class="o_setting_right_pane">
<label for="module_account_budget"/>
<div class="text-muted">
Compare actual revenues &amp; costs with budgets
Use budgets to compare actual with expected revenues and costs
</div>
<div id="msg_account_budget" class="content-group" attrs="{'invisible': [('module_account_budget', '=', False)]}">
<div class="text-warning mt16 mb4">
+3 -22
View File
@@ -2,30 +2,11 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Budgets Management',
'name': 'Budget Management',
'category': 'Accounting',
'description': """
This module allows accountants to manage analytic and crossovered budgets.
==========================================================================
Once the Budgets are defined (in Invoicing/Budgets/Budgets), the Project Managers
can set the planned amount on each Analytic Account.
The accountant has the possibility to see the total of amount planned for each
Budget in order to ensure the total planned is not greater/lower than what he
planned for this Budget. Each list of record can also be switched to a graphical
view of it.
Three reports are available:
----------------------------
1. The first is available from a list of Budgets. It gives the spreading, for
these Budgets, of the Analytic Accounts.
2. The second is a summary of the previous one, it only gives the spreading,
for the selected Budgets, of the Analytic Accounts.
3. The last one is available from the Analytic Chart of Accounts. It gives
the spreading, for the selected Analytic Accounts of Budgets.
Use budgets to compare actual with expected revenues and costs
--------------------------------------------------------------
""",
'website': 'https://www.odoo.com/page/accounting',
'depends': ['account'],
@@ -2,21 +2,205 @@
<odoo>
<!-- Budgets -->
<data noupdate="1">
<!-- Optimistic -->
<record id="crossovered_budget_budgetoptimistic0" model="crossovered.budget">
<field eval="'Budget '+str(time.localtime(time.time())[0]+1)+': Optimistic'" name="name"/>
<field eval="str(time.localtime(time.time())[0]+1)+'-01-01'" name="date_from"/>
<field eval="'Budget '+str(datetime.now().year+1)+': Optimistic'" name="name"/>
<field eval="str(datetime.now().year+1)+'-01-01'" name="date_from"/>
<field eval="&quot;&quot;&quot;draft&quot;&quot;&quot;" name="state"/>
<field eval="str(time.localtime(time.time())[0]+1)+'-12-31'" name="date_to"/>
<field name="creating_user_id" ref="base.user_root"/>
<field eval="str(datetime.now().year+1)+'-12-31'" name="date_to"/>
<field name="user_id" ref="base.user_root"/>
</record>
</data>
<data noupdate="1">
<record id="budget_line_analytic_admin" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0" />
<field eval="str(datetime.now().year+1)+'-01-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-12-31'" name="date_to"/>
<field name="planned_amount">-35000</field>
<field name="analytic_account_id" ref="analytic.analytic_administratif"/>
</record>
<record id="budget_line_analytic_agrolait1" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0" />
<field eval="str(datetime.now().year+1)+'-01-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-01-31'" name="date_to"/>
<field name="planned_amount">10000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait2" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0" />
<field eval="str(datetime.now().year+1)+'-02-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-02-28'" name="date_to"/>
<field name="planned_amount">10000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait3" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0" />
<field eval="str(datetime.now().year+1)+'-03-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-03-31'" name="date_to"/>
<field name="planned_amount">12000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait4" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0" />
<field eval="str(datetime.now().year+1)+'-04-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-04-30'" name="date_to"/>
<field name="planned_amount">15000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait5" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0" />
<field eval="str(datetime.now().year+1)+'-05-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-05-31'" name="date_to"/>
<field name="planned_amount">15000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait6" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0" />
<field eval="str(datetime.now().year+1)+'-06-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-06-30'" name="date_to"/>
<field name="planned_amount">15000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait7" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0" />
<field eval="str(datetime.now().year+1)+'-07-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-07-31'" name="date_to"/>
<field name="planned_amount">13000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait8" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0" />
<field eval="str(datetime.now().year+1)+'-08-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-08-31'" name="date_to"/>
<field name="planned_amount">9000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait9" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0" />
<field eval="str(datetime.now().year+1)+'-09-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-09-30'" name="date_to"/>
<field name="planned_amount">8000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait10" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0" />
<field eval="str(datetime.now().year+1)+'-10-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-10-31'" name="date_to"/>
<field name="planned_amount">15000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait11" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0" />
<field eval="str(datetime.now().year+1)+'-11-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-11-30'" name="date_to"/>
<field name="planned_amount">15000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait12" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetoptimistic0" />
<field eval="str(datetime.now().year+1)+'-12-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-12-31'" name="date_to"/>
<field name="planned_amount">18000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<!-- pessimistic-->
<record id="crossovered_budget_budgetpessimistic0" model="crossovered.budget">
<field eval="'Budget '+str(time.localtime(time.time())[0]+1)+': Pessimistic'" name="name"/>
<field eval="str(time.localtime(time.time())[0]+1)+'-01-01'" name="date_from"/>
<field eval="'Budget '+str(datetime.now().year+1)+': Pessimistic'" name="name"/>
<field eval="str(datetime.now().year+1)+'-01-01'" name="date_from"/>
<field eval="&quot;&quot;&quot;draft&quot;&quot;&quot;" name="state"/>
<field eval="str(time.localtime(time.time())[0]+1)+'-12-31'" name="date_to"/>
<field name="creating_user_id" ref="base.user_root"/>
<field eval="str(datetime.now().year+1)+'-12-31'" name="date_to"/>
<field name="user_id" ref="base.user_root"/>
</record>
<record id="budget_line_analytic_admin_pessim" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0" />
<field eval="str(datetime.now().year+1)+'-01-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-12-31'" name="date_to"/>
<field name="planned_amount">-55000</field>
<field name="analytic_account_id" ref="analytic.analytic_administratif"/>
</record>
<record id="budget_line_analytic_agrolait_pessim1" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0" />
<field eval="str(datetime.now().year+1)+'-01-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-01-31'" name="date_to"/>
<field name="planned_amount">9000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim2" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0" />
<field eval="str(datetime.now().year+1)+'-02-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-02-28'" name="date_to"/>
<field name="planned_amount">8000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim3" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0" />
<field eval="str(datetime.now().year+1)+'-03-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-03-31'" name="date_to"/>
<field name="planned_amount">10000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim4" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0" />
<field eval="str(datetime.now().year+1)+'-04-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-04-30'" name="date_to"/>
<field name="planned_amount">14000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim5" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0" />
<field eval="str(datetime.now().year+1)+'-05-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-05-31'" name="date_to"/>
<field name="planned_amount">16000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim6" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0" />
<field eval="str(datetime.now().year+1)+'-06-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-06-30'" name="date_to"/>
<field name="planned_amount">13000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim7" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0" />
<field eval="str(datetime.now().year+1)+'-07-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-07-31'" name="date_to"/>
<field name="planned_amount">10000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim8" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0" />
<field eval="str(datetime.now().year+1)+'-08-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-08-31'" name="date_to"/>
<field name="planned_amount">8000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim9" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0" />
<field eval="str(datetime.now().year+1)+'-09-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-09-30'" name="date_to"/>
<field name="planned_amount">7000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim10" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0" />
<field eval="str(datetime.now().year+1)+'-10-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-10-31'" name="date_to"/>
<field name="planned_amount">12000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim11" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0" />
<field eval="str(datetime.now().year+1)+'-11-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-11-30'" name="date_to"/>
<field name="planned_amount">17000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
<record id="budget_line_analytic_agrolait_pessim12" model="crossovered.budget.lines">
<field name="crossovered_budget_id" ref="crossovered_budget_budgetpessimistic0" />
<field eval="str(datetime.now().year+1)+'-12-01'" name="date_from"/>
<field eval="str(datetime.now().year+1)+'-12-31'" name="date_to"/>
<field name="planned_amount">17000</field>
<field name="analytic_account_id" ref="analytic.analytic_agrolait"/>
</record>
</data>
</odoo>
+166 -61
View File
@@ -2,8 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.tools import ustr
from odoo.exceptions import UserError, ValidationError
from odoo.exceptions import ValidationError
# ---------------------------------------------------------
# Budgets
@@ -16,7 +15,6 @@ class AccountBudgetPost(models.Model):
name = fields.Char('Name', required=True)
account_ids = fields.Many2many('account.account', 'account_budget_rel', 'budget_id', 'account_id', 'Accounts',
domain=[('deprecated', '=', False)])
crossovered_budget_line = fields.One2many('crossovered.budget.lines', 'general_budget_id', 'Budget Lines')
company_id = fields.Many2one('res.company', 'Company', required=True,
default=lambda self: self.env['res.company']._company_default_get('account.budget.post'))
@@ -47,7 +45,7 @@ class CrossoveredBudget(models.Model):
_inherit = ['mail.thread']
name = fields.Char('Budget Name', required=True, states={'done': [('readonly', True)]})
creating_user_id = fields.Many2one('res.users', 'Responsible', default=lambda self: self.env.user)
user_id = fields.Many2one('res.users', 'Responsible', default=lambda self: self.env.user, oldname='creating_user_id')
date_from = fields.Date('Start Date', required=True, states={'done': [('readonly', True)]})
date_to = fields.Date('End Date', required=True, states={'done': [('readonly', True)]})
state = fields.Selection([
@@ -87,87 +85,194 @@ class CrossoveredBudgetLines(models.Model):
_name = "crossovered.budget.lines"
_description = "Budget Line"
name = fields.Char(compute='_compute_line_name')
crossovered_budget_id = fields.Many2one('crossovered.budget', 'Budget', ondelete='cascade', index=True, required=True)
analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account')
general_budget_id = fields.Many2one('account.budget.post', 'Budgetary Position', required=True)
analytic_group_id = fields.Many2one('account.analytic.group', 'Analytic Group', related='analytic_account_id.group_id', readonly=True)
general_budget_id = fields.Many2one('account.budget.post', 'Budgetary Position')
date_from = fields.Date('Start Date', required=True)
date_to = fields.Date('End Date', required=True)
paid_date = fields.Date('Paid Date')
planned_amount = fields.Float('Planned Amount', required=True, digits=0)
practical_amount = fields.Float(compute='_compute_practical_amount', string='Practical Amount', digits=0)
theoritical_amount = fields.Float(compute='_compute_theoritical_amount', string='Theoretical Amount', digits=0)
percentage = fields.Float(compute='_compute_percentage', string='Achievement')
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', readonly=True)
planned_amount = fields.Monetary(
'Planned Amount', required=True,
help="Amount you plan to earn/spend. Record a positive amount if it is a revenue and a negative amount if it is a cost.")
practical_amount = fields.Monetary(
compute='_compute_practical_amount', string='Practical Amount', help="Amount really earned/spent.")
theoritical_amount = fields.Monetary(
compute='_compute_theoritical_amount', string='Theoretical Amount',
help="Amount you are supposed to have earned/spent at this date.")
percentage = fields.Float(
compute='_compute_percentage', string='Achievement',
help="Comparison between practical and theoretical amount. This measure tells you if you are below or over budget.")
company_id = fields.Many2one(related='crossovered_budget_id.company_id', comodel_name='res.company',
string='Company', store=True, readonly=True)
is_above_budget = fields.Boolean(compute='_is_above_budget')
crossovered_budget_state = fields.Selection(related='crossovered_budget_id.state', string='Budget State', store=True, readonly=True)
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
# overrides the default read_group in order to compute the computed fields manually for the group
result = super(CrossoveredBudgetLines, self).read_group(domain, fields, groupby, offset=offset, limit=limit,
orderby=orderby, lazy=lazy)
fields_list = ['practical_amount', 'theoritical_amount', 'percentage']
if any(x in fields for x in fields_list):
for group_line in result:
# initialise fields to compute to 0 if they are requested
if 'practical_amount' in fields:
group_line['practical_amount'] = 0
if 'theoritical_amount' in fields:
group_line['theoritical_amount'] = 0
if 'percentage' in fields:
group_line['percentage'] = 0
group_line['practical_amount'] = 0
group_line['theoritical_amount'] = 0
if group_line.get('__domain'):
all_budget_lines_that_compose_group = self.search(group_line['__domain'])
else:
all_budget_lines_that_compose_group = self.search([])
for budget_line_of_group in all_budget_lines_that_compose_group:
if 'practical_amount' in fields or 'percentage' in fields:
group_line['practical_amount'] += budget_line_of_group.practical_amount
if 'theoritical_amount' in fields or 'percentage' in fields:
group_line['theoritical_amount'] += budget_line_of_group.theoritical_amount
if 'percentage' in fields:
if group_line['theoritical_amount']:
# use a weighted average
group_line['percentage'] = float(
(group_line['practical_amount'] or 0.0) / group_line['theoritical_amount']) * 100
return result
@api.multi
def _is_above_budget(self):
for line in self:
if line.theoritical_amount >= 0:
line.is_above_budget = line.practical_amount > line.theoritical_amount
else:
line.is_above_budget = line.practical_amount < line.theoritical_amount
@api.multi
def _compute_line_name(self):
#just in case someone opens the budget line in form view
computed_name = self.crossovered_budget_id.name
if self.general_budget_id:
computed_name += ' - ' + self.general_budget_id.name
if self.analytic_account_id:
computed_name += ' - ' + self.analytic_account_id.name
self.name = computed_name
@api.multi
def _compute_practical_amount(self):
for line in self:
result = 0.0
acc_ids = line.general_budget_id.account_ids.ids
date_to = self.env.context.get('wizard_date_to') or line.date_to
date_from = self.env.context.get('wizard_date_from') or line.date_from
date_to = line.date_to
date_from = line.date_from
if line.analytic_account_id.id:
self.env.cr.execute("""
SELECT SUM(amount)
FROM account_analytic_line
WHERE account_id=%s
AND (date between to_date(%s,'yyyy-mm-dd') AND to_date(%s,'yyyy-mm-dd'))
AND general_account_id=ANY(%s)""",
(line.analytic_account_id.id, date_from, date_to, acc_ids,))
result = self.env.cr.fetchone()[0] or 0.0
line.practical_amount = result
analytic_line_obj = self.env['account.analytic.line']
domain = [('account_id', '=', line.analytic_account_id.id),
('date', '>=', date_from),
('date', '<=', date_to),
]
if acc_ids:
domain += [('general_account_id', 'in', acc_ids)]
where_query = analytic_line_obj._where_calc(domain)
analytic_line_obj._apply_ir_rules(where_query, 'read')
from_clause, where_clause, where_clause_params = where_query.get_sql()
select = "SELECT SUM(amount) from " + from_clause + " where " + where_clause
else:
aml_obj = self.env['account.move.line']
domain = [('account_id', 'in',
line.general_budget_id.account_ids.ids),
('date', '>=', date_from),
('date', '<=', date_to)
]
where_query = aml_obj._where_calc(domain)
aml_obj._apply_ir_rules(where_query, 'read')
from_clause, where_clause, where_clause_params = where_query.get_sql()
select = "SELECT sum(credit)-sum(debit) from " + from_clause + " where " + where_clause
self.env.cr.execute(select, where_clause_params)
line.practical_amount = self.env.cr.fetchone()[0] or 0.0
@api.multi
def _compute_theoritical_amount(self):
today = fields.Datetime.now()
# beware: 'today' variable is mocked in the python tests and thus, its implementation matter
today = fields.Datetime.from_string(fields.Date.today())
for line in self:
# Used for the report
if self.env.context.get('wizard_date_from') and self.env.context.get('wizard_date_to'):
date_from = fields.Datetime.from_string(self.env.context.get('wizard_date_from'))
date_to = fields.Datetime.from_string(self.env.context.get('wizard_date_to'))
if date_from < fields.Datetime.from_string(line.date_from):
date_from = fields.Datetime.from_string(line.date_from)
elif date_from > fields.Datetime.from_string(line.date_to):
date_from = False
if date_to > fields.Datetime.from_string(line.date_to):
date_to = fields.Datetime.from_string(line.date_to)
elif date_to < fields.Datetime.from_string(line.date_from):
date_to = False
theo_amt = 0.00
if date_from and date_to:
line_timedelta = fields.Datetime.from_string(line.date_to) - fields.Datetime.from_string(line.date_from)
elapsed_timedelta = date_to - date_from
if elapsed_timedelta.days > 0:
theo_amt = (elapsed_timedelta.total_seconds() / line_timedelta.total_seconds()) * line.planned_amount
else:
if line.paid_date:
if fields.Datetime.from_string(line.date_to) <= fields.Datetime.from_string(line.paid_date):
theo_amt = 0.00
else:
theo_amt = line.planned_amount
if line.paid_date:
if fields.Datetime.to_string(today) <= line.paid_date:
theo_amt = 0.00
else:
line_timedelta = fields.Datetime.from_string(line.date_to) - fields.Datetime.from_string(line.date_from)
elapsed_timedelta = fields.Datetime.from_string(today) - (fields.Datetime.from_string(line.date_from))
if elapsed_timedelta.days < 0:
# If the budget line has not started yet, theoretical amount should be zero
theo_amt = 0.00
elif line_timedelta.days > 0 and fields.Datetime.from_string(today) < fields.Datetime.from_string(line.date_to):
# If today is between the budget line date_from and date_to
theo_amt = (elapsed_timedelta.total_seconds() / line_timedelta.total_seconds()) * line.planned_amount
else:
theo_amt = line.planned_amount
theo_amt = line.planned_amount
else:
line_timedelta = fields.Datetime.from_string(line.date_to) - fields.Datetime.from_string(
line.date_from)
elapsed_timedelta = today - fields.Datetime.from_string(line.date_from)
if elapsed_timedelta.days < 0:
# If the budget line has not started yet, theoretical amount should be zero
theo_amt = 0.00
elif line_timedelta.days > 0 and today < fields.Datetime.from_string(line.date_to):
# If today is between the budget line date_from and date_to
theo_amt = (elapsed_timedelta.total_seconds() / line_timedelta.total_seconds()) * line.planned_amount
else:
theo_amt = line.planned_amount
line.theoritical_amount = theo_amt
@api.multi
def _compute_percentage(self):
for line in self:
if line.theoritical_amount != 0.00:
line.percentage = float((line.practical_amount or 0.0) / line.theoritical_amount) * 100
line.percentage = float((line.practical_amount or 0.0) / line.theoritical_amount)
else:
line.percentage = 0.00
@api.constrains('general_budget_id', 'analytic_account_id')
def _must_have_analytical_or_budgetary_or_both(self):
if not self.analytic_account_id and not self.general_budget_id:
raise ValidationError(
_("You have to enter at least a budgetary position or analytic account on a budget line."))
@api.multi
def action_open_budget_entries(self):
if self.analytic_account_id:
# if there is an analytic account, then the analytic items are loaded
action = self.env['ir.actions.act_window'].for_xml_id('analytic', 'account_analytic_line_action_entries')
action['domain'] = [('account_id', '=', self.analytic_account_id.id),
('date', '>=', self.date_from),
('date', '<=', self.date_to)
]
if self.general_budget_id:
action['domain'] += [('general_account_id', 'in', self.general_budget_id.account_ids.ids)]
else:
# otherwise the journal entries booked on the accounts of the budgetary postition are opened
action = self.env['ir.actions.act_window'].for_xml_id('account', 'action_account_moves_all_a')
action['domain'] = [('account_id', 'in',
self.general_budget_id.account_ids.ids),
('date', '>=', self.date_from),
('date', '<=', self.date_to)
]
return action
@api.one
@api.constrains('date_from', 'date_to')
def _line_dates_between_budget_dates(self):
budget_date_from = fields.Datetime.from_string(self.crossovered_budget_id.date_from)
budget_date_to = fields.Datetime.from_string(self.crossovered_budget_id.date_to)
if self.date_from:
date_from = fields.Datetime.from_string(self.date_from)
if date_from < budget_date_from or date_from > budget_date_to:
raise ValidationError(_('"Start Date" of the budget line should be included in the Period of the budget'))
if self.date_to:
date_to = fields.Datetime.from_string(self.date_to)
if date_to < budget_date_from or date_to > budget_date_to:
raise ValidationError(_('"End Date" of the budget line should be included in the Period of the budget'))
+37 -37
View File
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
import time
from datetime import datetime
class TestAccountBudgetCommon(AccountingTestCase):
@@ -30,74 +30,74 @@ class TestAccountBudgetCommon(AccountingTestCase):
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_partners_camp_to_camp'),
'general_budget_id': self.account_budget_post_sales0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-01-01',
'date_to': str(time.localtime(time.time())[0] + 1) + '-01-31',
'date_from': str(datetime.now().year + 1) + '-01-01',
'date_to': str(datetime.now().year + 1) + '-01-31',
'planned_amount': 500.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetpessimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_partners_camp_to_camp'),
'general_budget_id': self.account_budget_post_sales0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-02-07',
'date_to': str(time.localtime(time.time())[0] + 1) + '-02-28',
'date_from': str(datetime.now().year + 1) + '-02-07',
'date_to': str(datetime.now().year + 1) + '-02-28',
'planned_amount': 900.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetoptimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_partners_camp_to_camp'),
'general_budget_id': self.account_budget_post_sales0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-03-01',
'date_to': str(time.localtime(time.time())[0] + 1) + '-03-15',
'date_from': str(datetime.now().year + 1) + '-03-01',
'date_to': str(datetime.now().year + 1) + '-03-15',
'planned_amount': 300.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetoptimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_our_super_product'),
'general_budget_id': self.account_budget_post_sales0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-03-16',
'paid_date': str(time.localtime(time.time())[0] + 1) + '-12-03',
'date_to': str(time.localtime(time.time())[0] + 1) + '-03-31',
'date_from': str(datetime.now().year + 1) + '-03-16',
'paid_date': str(datetime.now().year + 1) + '-12-03',
'date_to': str(datetime.now().year + 1) + '-03-31',
'planned_amount': 375.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetpessimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_our_super_product'),
'general_budget_id': self.account_budget_post_sales0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-05-01',
'paid_date': str(time.localtime(time.time())[0] + 1) + '-12-03',
'date_to': str(time.localtime(time.time())[0] + 1) + '-05-31',
'date_from': str(datetime.now().year + 1) + '-05-01',
'paid_date': str(datetime.now().year + 1) + '-12-03',
'date_to': str(datetime.now().year + 1) + '-05-31',
'planned_amount': 375.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetoptimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_seagate_p2'),
'general_budget_id': self.account_budget_post_sales0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-07-16',
'date_to': str(time.localtime(time.time())[0] + 1) + '-07-31',
'date_from': str(datetime.now().year + 1) + '-07-16',
'date_to': str(datetime.now().year + 1) + '-07-31',
'planned_amount': 20000.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetpessimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_seagate_p2'),
'general_budget_id': self.account_budget_post_sales0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-02-01',
'date_to': str(time.localtime(time.time())[0] + 1) + '-02-28',
'date_from': str(datetime.now().year + 1) + '-02-01',
'date_to': str(datetime.now().year + 1) + '-02-28',
'planned_amount': 20000.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetoptimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_seagate_p2'),
'general_budget_id': self.account_budget_post_sales0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-09-16',
'date_to': str(time.localtime(time.time())[0] + 1) + '-09-30',
'date_from': str(datetime.now().year + 1) + '-09-16',
'date_to': str(datetime.now().year + 1) + '-09-30',
'planned_amount': 10000.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetpessimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_seagate_p2'),
'general_budget_id': self.account_budget_post_sales0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-10-01',
'date_to': str(time.localtime(time.time())[0] + 1) + '-12-31',
'date_from': str(datetime.now().year + 1) + '-10-01',
'date_to': str(datetime.now().year + 1) + '-12-31',
'planned_amount': 10000.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetoptimistic0'),
})
@@ -122,64 +122,64 @@ class TestAccountBudgetCommon(AccountingTestCase):
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_partners_camp_to_camp'),
'general_budget_id': self.account_budget_post_purchase0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-01-01',
'date_to': str(time.localtime(time.time())[0] + 1) + '-01-31',
'date_from': str(datetime.now().year + 1) + '-01-01',
'date_to': str(datetime.now().year + 1) + '-01-31',
'planned_amount': -500.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetpessimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_partners_camp_to_camp'),
'general_budget_id': self.account_budget_post_purchase0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-02-01',
'date_to': str(time.localtime(time.time())[0] + 1) + '-02-28',
'date_from': str(datetime.now().year + 1) + '-02-01',
'date_to': str(datetime.now().year + 1) + '-02-28',
'planned_amount': -250.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetoptimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_our_super_product'),
'general_budget_id': self.account_budget_post_purchase0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-04-01',
'date_to': str(time.localtime(time.time())[0] + 1) + '-04-30',
'date_from': str(datetime.now().year + 1) + '-04-01',
'date_to': str(datetime.now().year + 1) + '-04-30',
'planned_amount': -150.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetpessimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_seagate_p2'),
'general_budget_id': self.account_budget_post_purchase0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-06-01',
'date_to': str(time.localtime(time.time())[0] + 1) + '-06-15',
'date_from': str(datetime.now().year + 1) + '-06-01',
'date_to': str(datetime.now().year + 1) + '-06-15',
'planned_amount': -7500.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetpessimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_seagate_p2'),
'general_budget_id': self.account_budget_post_purchase0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-06-16',
'date_to': str(time.localtime(time.time())[0] + 1) + '-06-30',
'date_from': str(datetime.now().year + 1) + '-06-16',
'date_to': str(datetime.now().year + 1) + '-06-30',
'planned_amount': -5000.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetpessimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_seagate_p2'),
'general_budget_id': self.account_budget_post_purchase0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-07-01',
'date_to': str(time.localtime(time.time())[0] + 1) + '-07-15',
'date_from': str(datetime.now().year + 1) + '-07-01',
'date_to': str(datetime.now().year + 1) + '-07-15',
'planned_amount': -2000.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetoptimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_seagate_p2'),
'general_budget_id': self.account_budget_post_purchase0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-08-16',
'date_to': str(time.localtime(time.time())[0] + 1) + '-08-31',
'date_from': str(datetime.now().year + 1) + '-08-16',
'date_to': str(datetime.now().year + 1) + '-08-31',
'planned_amount': -3000.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetpessimistic0'),
})
self.env['crossovered.budget.lines'].create({
'analytic_account_id': self.ref('analytic.analytic_seagate_p2'),
'general_budget_id': self.account_budget_post_purchase0.id,
'date_from': str(time.localtime(time.time())[0] + 1) + '-09-01',
'date_to': str(time.localtime(time.time())[0] + 1) + '-09-15',
'date_from': str(datetime.now().year + 1) + '-09-01',
'date_to': str(datetime.now().year + 1) + '-09-15',
'planned_amount': -1000.0,
'crossovered_budget_id': self.ref('account_budget.crossovered_budget_budgetoptimistic0'),
})
@@ -7,9 +7,13 @@ try:
except ImportError:
from mock import patch
from odoo.fields import Datetime
from odoo.fields import Datetime, Date
from odoo.tests import tagged
from datetime import datetime, timedelta
import logging
_logger = logging.getLogger(__name__)
# ---------------------------------------------------------
# Tests
# ---------------------------------------------------------
@@ -17,12 +21,7 @@ from odoo.tests import tagged
class TestTheoreticalAmount(TestAccountBudgetCommon):
def setUp(self):
super(TestTheoreticalAmount, self).setUp()
crossovered_budget = self.env['crossovered.budget'].create({
'name': 'test budget name',
'date_from': '2014-01-01',
'date_to': '2014-12-31',
})
crossovered_budget_line_obj = self.env['crossovered.budget.lines']
#create the budgetary position
user_type_id = self.ref('account.data_account_type_revenue')
tag_id = self.ref('account.account_tag_operating')
account_rev = self.env['account.account'].create({
@@ -35,76 +34,66 @@ class TestTheoreticalAmount(TestAccountBudgetCommon):
'name': 'Sales',
'account_ids': [(4, account_rev.id, 0)],
})
#create the budget and budget lines
first_january = datetime.now().replace(day=1, month=1)
self.last_day_of_budget = first_january + timedelta(days=364) # will be 30th of December or 31th in case of leap year
crossovered_budget = self.env['crossovered.budget'].create({
'name': 'test budget name',
'date_from': str(first_january.date()),
'date_to': str(self.last_day_of_budget.date()),
})
crossovered_budget_line_obj = self.env['crossovered.budget.lines']
self.line = crossovered_budget_line_obj.create({
'crossovered_budget_id': crossovered_budget.id,
'general_budget_id': buget_post.id,
'date_from': '2014-01-01',
'date_to': '2014-12-31',
'date_from': str(first_january.date()),
'date_to': str(self.last_day_of_budget.date()),
'planned_amount': -364,
})
self.paid_date_line = crossovered_budget_line_obj.create({
'crossovered_budget_id': crossovered_budget.id,
'general_budget_id': buget_post.id,
'date_from': str(first_january.date()),
'date_to': str(self.last_day_of_budget.date()),
'planned_amount': -364,
'paid_date': str(datetime.now().year) + '-09-09',
})
self.patcher = patch('odoo.addons.account_budget.models.account_budget.fields.Datetime', wraps=Datetime)
self.mock_datetime = self.patcher.start()
self.patcher = patch('odoo.addons.account_budget.models.account_budget.fields.Date', wraps=Date)
self.mock_date = self.patcher.start()
def test_01(self):
"""Start"""
date = Datetime.to_string(Datetime.from_string('2014-01-01 00:00:00'))
self.mock_datetime.now.return_value = date
self.assertAlmostEqual(self.line.theoritical_amount, 0)
def test_theoritical_amount_without_paid_date(self):
test_list = [
(str(datetime.now().year) + '-01-01', 0),
(str(datetime.now().year) + '-01-02', -1),
(str(datetime.now().year) + '-01-03', -2),
(str(datetime.now().year) + '-01-11', -10),
(str(datetime.now().year) + '-02-20', -50),
(str(self.last_day_of_budget.date()), -364),
]
for date, expected_amount in test_list:
_logger.info("Checking theoritical amount for the date: " + date)
self.mock_date.today.return_value = date
self.assertAlmostEqual(self.line.theoritical_amount, expected_amount)
#invalidate the cache of the budget lines to recompute the theoritical amount at next iteration
self.line.invalidate_cache()
def test_02(self):
"""After 24 hours"""
date = Datetime.to_string(Datetime.from_string('2014-01-02 00:00:00'))
self.mock_datetime.now.return_value = date
self.assertAlmostEqual(self.line.theoritical_amount, -1)
def test_03(self):
"""After 36 hours"""
date = Datetime.to_string(Datetime.from_string('2014-01-02 12:00:00'))
self.mock_datetime.now.return_value = date
self.assertAlmostEqual(self.line.theoritical_amount, -1.5)
def test_04(self):
"""After 48 hours"""
date = Datetime.to_string(Datetime.from_string('2014-01-03 00:00:00'))
self.mock_datetime.now.return_value = date
self.assertAlmostEqual(self.line.theoritical_amount, -2)
def test_05(self):
"""After 10 days"""
date = Datetime.to_string(Datetime.from_string('2014-01-11 00:00:00'))
self.mock_datetime.now.return_value = date
self.assertAlmostEqual(self.line.theoritical_amount, -10)
def test_06(self):
"""After 50 days"""
date = Datetime.to_string(Datetime.from_string('2014-02-20 00:00:00'))
self.mock_datetime.now.return_value = date
self.assertAlmostEqual(self.line.theoritical_amount, -50)
def test_07(self):
"""After 182 days, exactly half of the budget line"""
date = Datetime.to_string(Datetime.from_string('2014-07-02 00:00:00'))
self.mock_datetime.now.return_value = date
self.assertAlmostEqual(self.line.theoritical_amount, -182)
def test_08(self):
"""After 308 days at noon"""
date = Datetime.to_string(Datetime.from_string('2014-11-05 12:00:00')) # remember, remember
self.mock_datetime.now.return_value = date
self.assertAlmostEqual(self.line.theoritical_amount, -308.5)
def test_09(self):
"""One day before"""
date = Datetime.to_string(Datetime.from_string('2014-12-30 00:00:00'))
self.mock_datetime.now.return_value = date
self.assertAlmostEqual(self.line.theoritical_amount, -363)
def test_10(self):
"""At last"""
date = Datetime.to_string(Datetime.from_string('2014-12-31 00:00:00'))
self.mock_datetime.now.return_value = date
self.assertAlmostEqual(self.line.theoritical_amount, -364)
def test_theoritical_amount_with_paid_date(self):
test_list = [
(str(datetime.now().year) + '-01-01', 0),
(str(datetime.now().year) + '-01-02', 0),
(str(datetime.now().year) + '-09-08', 0),
(str(datetime.now().year) + '-09-09', -364),
(str(datetime.now().year) + '-09-10', -364),
(str(self.last_day_of_budget.date()), -364),
]
for date, expected_amount in test_list:
_logger.info("Checking theoritical amount for the date: " + date)
self.mock_date.today.return_value = date
self.assertAlmostEqual(self.paid_date_line.theoritical_amount, expected_amount)
#invalidate the cache of the budget lines to recompute the theoritical amount at next iteration
self.paid_date_line.invalidate_cache()
def tearDown(self):
self.patcher.stop()
@@ -1,310 +1,364 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!--
Budgets
-->
<record id="view_budget_post_search" model="ir.ui.view">
<field name="name">account.budget.post.search</field>
<field name="model">account.budget.post</field>
<field name="arch" type="xml">
<search string="Budgetary Position">
<field name="name" filter_domain="[('name','ilike',self)]" string="Budgetary Position"/>
<field name="company_id" groups="base.group_multi_company"/>
</search>
</field>
</record>
<!--
Budgets
-->
<record id="view_budget_post_search" model="ir.ui.view">
<field name="name">account.budget.post.search</field>
<field name="model">account.budget.post</field>
<field name="arch" type="xml">
<search string="Budgetary Position">
<field name="name" filter_domain="[('name','ilike',self)]" string="Budgetary Position"/>
<field name="company_id" groups="base.group_multi_company"/>
</search>
</field>
</record>
<record id="view_budget_post_tree" model="ir.ui.view">
<field name="name">account.budget.post.tree</field>
<field name="model">account.budget.post</field>
<field name="arch" type="xml">
<tree string="Budgetary Position">
<record id="view_budget_post_tree" model="ir.ui.view">
<field name="name">account.budget.post.tree</field>
<field name="model">account.budget.post</field>
<field name="arch" type="xml">
<tree string="Budgetary Position">
<field name="name"/>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
</tree>
</field>
</record>
<record id="open_budget_post_form" model="ir.actions.act_window">
<field name="name">Budgetary Positions</field>
<field name="res_model">account.budget.post</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form</field>
<field name="view_id" ref="view_budget_post_tree"/>
<field name="search_view_id" ref="view_budget_post_search"/>
</record>
<menuitem action="open_budget_post_form" id="menu_budget_post_form"
parent="account.account_management_menu" sequence="5"/>
<record model="ir.ui.view" id="view_budget_post_form">
<field name="name">account.budget.post.form</field>
<field name="model">account.budget.post</field>
<field name="arch" type="xml">
<form string="Budgetary Position">
<group col="4">
<field name="name"/>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
</tree>
</field>
</record>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
</group>
<notebook>
<page string="Accounts">
<field name="account_ids">
<tree>
<field name="code"/>
<field name="name"/>
</tree>
</field>
</page>
</notebook>
</form>
</field>
</record>
<record id="open_budget_post_form" model="ir.actions.act_window">
<field name="name">Budgetary Positions</field>
<field name="res_model">account.budget.post</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form</field>
<field name="view_id" ref="view_budget_post_tree"/>
<field name="search_view_id" ref="view_budget_post_search"/>
</record>
<menuitem action="open_budget_post_form" id="menu_budget_post_form" parent="account.account_management_menu" sequence="5"/>
<record model="ir.ui.view" id="view_budget_post_form">
<field name="name">account.budget.post.form</field>
<field name="model">account.budget.post</field>
<field name="arch" type="xml">
<form string="Budgetary Position">
<group col="4">
<field name="name"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
<record model="ir.ui.view" id="crossovered_budget_view_form">
<field name="name">crossovered.budget.view.form</field>
<field name="model">crossovered.budget</field>
<field name="arch" type="xml">
<form string="Budget">
<header>
<button string="Confirm" name="action_budget_confirm" states="draft" type="object"
class="oe_highlight"/>
<button string="Approve" name="action_budget_validate" states="confirm" type="object"
class="oe_highlight"/>
<button string="Done" name="action_budget_done" states="validate" type="object"
class="oe_highlight"/>
<button string="Reset to Draft" name="action_budget_draft" states="cancel" type="object"/>
<button string="Cancel Budget" name="action_budget_cancel" states="confirm,validate" type="object"/>
<field name="state" widget="statusbar" />
</header>
<sheet string="Budget">
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" attrs="{'readonly':[('state','!=','draft')]}" placeholder="Budget Name"/>
</h1>
</div>
<group>
<group>
<field name="user_id" attrs="{'readonly':[('state','!=','draft')]}"/>
</group>
<group>
<label for="date_from" string="Period"/>
<div>
<field name="date_from" class="oe_inline"
attrs="{'readonly':[('state','!=','draft')]}"/>
-
<field name="date_to" class="oe_inline" attrs="{'readonly':[('state','!=','draft')]}"
nolabel="1"/>
</div>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
</group>
</group>
<notebook>
<page string="Accounts">
<field name="account_ids">
<tree>
<field name="code"/>
<field name="name"/>
<page string="Budget Lines">
<field name="crossovered_budget_line"
context="{'default_date_from': date_from,'default_date_to': date_to}" colspan="4"
nolabel="1" attrs="{'readonly':[('state','!=','draft')]}">
<tree string="Budget Lines" decoration-success="is_above_budget and planned_amount &gt; 0" decoration-danger="is_above_budget and planned_amount &lt; 0" editable="bottom">
<field name="general_budget_id"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="paid_date" groups="base.group_no_one"/>
<field name="currency_id" invisible="1"/>
<field name="planned_amount" sum="Planned Amount"/>
<field name="practical_amount" sum="Practical Amount"/>
<field name="theoritical_amount" sum="Theoretical Amount"/>
<field name="percentage" widget="percentage" />
<button type="object" name="action_open_budget_entries" string="Entries..."
icon="fa-arrow-circle-o-right"/>
<field name="is_above_budget" invisible="1"/>
</tree>
<form string="Budget Lines">
<group>
<group>
<field name="currency_id" invisible="1"/>
<field name="general_budget_id"/>
<field name="planned_amount"/>
<field name="analytic_account_id"
groups="analytic.group_analytic_accounting"/>
</group>
<group>
<label for="date_from" string="Period"/>
<div>
<field name="date_from" class="oe_inline"/>
-
<field name="date_to" class="oe_inline"/>
</div>
<field name="paid_date" groups="base.group_no_one"/>
<field name="company_id" options="{'no_create': True}"
groups="base.group_multi_company"/>
</group>
</group>
</form>
</field>
</page>
</notebook>
</form>
</field>
</record>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record model="ir.ui.view" id="crossovered_budget_view_form">
<field name="name">crossovered.budget.view.form</field>
<field name="model">crossovered.budget</field>
<field name="arch" type="xml">
<form string="Budget">
<header>
<button string="Confirm" name="action_budget_confirm" states="draft" type="object" class="oe_highlight"/>
<button string="Approve" name="action_budget_validate" states="confirm" type="object" class="oe_highlight"/>
<button string="Done" name="action_budget_done" states="validate" type="object" class="oe_highlight"/>
<button string="Reset to Draft" name="action_budget_draft" states="cancel" type="object" />
<button string="Cancel Budget" name="action_budget_cancel" states="confirm,validate" type="object"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirm"/>
</header>
<sheet string="Budget">
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" attrs="{'readonly':[('state','!=','draft')]}" placeholder="Budget Name"/>
</h1>
</div>
<group>
<group>
<field name="creating_user_id" attrs="{'readonly':[('state','!=','draft')]}"/>
</group>
<group>
<label for="date_from" string="Period"/>
<div>
<field name="date_from" class="oe_inline" attrs="{'readonly':[('state','!=','draft')]}"/> -
<field name="date_to" class="oe_inline" attrs="{'readonly':[('state','!=','draft')]}" nolabel="1"/>
<record model="ir.ui.view" id="crossovered_budget_view_tree">
<field name="name">crossovered.budget.view.tree</field>
<field name="model">crossovered.budget</field>
<field name="arch" type="xml">
<tree decoration-info="state == 'draft'" decoration-muted="state in ('done','cancel')" string="Budget">
<field name="name" colspan="1"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
<field name="user_id"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="view_crossovered_budget_kanban" model="ir.ui.view">
<field name="name">crossovered.budget.kanban</field>
<field name="model">crossovered.budget</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile">
<field name="name"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="user_id"/>
<field name="state"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div class="row mb4">
<div class="col-xs-8">
<strong>
<field name="name"/>
</strong>
</div>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
</group>
</group>
<notebook>
<page string="Budget Lines">
<field name="crossovered_budget_line" context="{'default_date_from': date_from,'default_date_to': date_to}" colspan="4" nolabel="1" attrs="{'readonly':[('state','!=','draft')]}">
<tree string="Budget Lines">
<field name="general_budget_id"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting" required="1"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="paid_date" groups="base.group_no_one"/>
<field name="planned_amount" sum="Planned Amount" widget="monetary"/>
<field name="practical_amount" sum="Practical Amount" widget="monetary"/>
<field name="theoritical_amount" sum="Theoretical Amount" widget="monetary"/>
<field name="percentage"/>
</tree>
<form string="Budget Lines">
<group>
<group>
<field name="general_budget_id"/>
<field name="planned_amount" widget="monetary"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting" required="1"/>
</group>
<group>
<label for="date_from" string="Period"/>
<div>
<field name="date_from" class="oe_inline"/> -
<field name="date_to" class="oe_inline"/>
</div>
<field name="paid_date" groups="base.group_no_one"/>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
</group>
</group>
</form>
</field>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record model="ir.ui.view" id="crossovered_budget_view_tree">
<field name="name">crossovered.budget.view.tree</field>
<field name="model">crossovered.budget</field>
<field name="arch" type="xml">
<tree decoration-info="state == 'draft'" decoration-muted="state in ('done','cancel')" string="Budget">
<field name="name" colspan="1"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
<field name="creating_user_id"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="view_crossovered_budget_kanban" model="ir.ui.view">
<field name="name">crossovered.budget.kanban</field>
<field name="model">crossovered.budget</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile">
<field name="name"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="creating_user_id"/>
<field name="state"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click">
<div class="row mb4">
<div class="col-xs-8">
<strong><field name="name"/></strong>
</div>
<div class="col-xs-4">
<span class="pull-right">
<field name="state" widget="kanban_label_selection" options="{'classes': {'draft': 'default', 'done': 'success'}}"/>
</span>
</div>
</div>
<div class="row">
<div class="col-xs-10">
<i class="fa fa-clock-o"/> <t t-esc="record.date_from.value"/>-<t t-esc="record.date_to.value"/>
</div>
<div class="col-xs-2">
<span class="pull-right">
<img t-att-src="kanban_image('res.users', 'image_small', record.creating_user_id.raw_value)" t-att-title="record.creating_user_id.value" width="24" height="24" class="oe_kanban_avatar pull-right"/>
</span>
</div>
<div class="col-xs-4">
<span class="pull-right">
<field name="state" widget="kanban_label_selection"
options="{'classes': {'draft': 'default', 'done': 'success'}}"/>
</span>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<div class="row">
<div class="col-xs-10">
<i class="fa fa-clock-o"/>
<t t-esc="record.date_from.value"/>-
<t t-esc="record.date_to.value"/>
</div>
<div class="col-xs-2">
<span class="pull-right">
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)"
t-att-title="record.user_id.value" width="24" height="24"
class="oe_kanban_avatar pull-right"/>
</span>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="view_crossovered_budget_search" model="ir.ui.view">
<field name="name">crossovered.budget.search</field>
<field name="model">crossovered.budget</field>
<field name="arch" type="xml">
<search string="Budget">
<field name="name" filter_domain="[('name','ilike',self)]" string="Budget"/>
<field name="date_from"/>
<field name="date_to"/>
<filter string="Draft" name="draft" domain="[('state','=','draft')]" help="Draft Budgets"/>
<filter string="To Approve" name="toapprove" domain="[('state','=','confirm')]" help="To Approve Budgets" />
<field name="state"/>
</search>
</field>
</record>
<record id="view_crossovered_budget_search" model="ir.ui.view">
<field name="name">crossovered.budget.search</field>
<field name="model">crossovered.budget</field>
<field name="arch" type="xml">
<search string="Budget">
<field name="name" filter_domain="[('name','ilike',self)]" string="Budget"/>
<field name="date_from"/>
<field name="date_to"/>
<filter string="Draft" name="draft" domain="[('state','=','draft')]" help="Draft Budgets"/>
<filter string="To Approve" name="toapprove" domain="[('state','=','confirm')]"
help="To Approve Budgets"/>
<field name="state"/>
</search>
</field>
</record>
<record model="ir.actions.act_window" id="act_crossovered_budget_view">
<field name="name">Budgets</field>
<field name="res_model">crossovered.budget</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form</field>
<field name="view_id" ref="crossovered_budget_view_tree"/>
<field name="search_view_id" ref="view_crossovered_budget_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new budget
</p><p>
A budget is a forecast of your company's income and/or expenses
expected for a period in the future. A budget is defined on some
financial accounts and/or analytic accounts (that may represent
projects, departments, categories of products, etc.)
</p><p>
By keeping track of where your money goes, you may be less
likely to overspend, and more likely to meet your financial
goals. Forecast a budget by detailing the expected revenue per
analytic account and monitor its evolution based on the actuals
realised during that period.
</p>
</field>
</record>
<menuitem parent="account.menu_finance_entries_management"
id="menu_act_crossovered_budget_view"
name="Budgets"
action="act_crossovered_budget_view" sequence="60"
groups="account.group_account_user"/>
<record id="view_crossovered_budget_line_search" model="ir.ui.view">
<field name="name">account.budget.line.search</field>
<field name="model">crossovered.budget.lines</field>
<field name="arch" type="xml">
<search string="Budget Lines">
<field name="analytic_account_id"/>
</search>
</field>
</record>
<record model="ir.ui.view" id="view_crossovered_budget_line_tree">
<field name="name">crossovered.budget.line.tree</field>
<field name="model">crossovered.budget.lines</field>
<field name="arch" type="xml">
<tree string="Budget Lines">
<field name="crossovered_budget_id" invisible="1"/>
<field name="general_budget_id"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="paid_date" groups="base.group_no_one"/>
<field name="planned_amount" widget="monetary"/>
<field name="practical_amount" widget="monetary"/>
<field name="theoritical_amount" widget="monetary"/>
<field name="percentage"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_crossovered_budget_line_form">
<field name="name">crossovered.budget.line.form</field>
<field name="model">crossovered.budget.lines</field>
<field name="arch" type="xml">
<form string="Budget Lines">
<sheet>
<group col="4">
<field name="crossovered_budget_id"/>
<field name="analytic_account_id"/>
<field name="general_budget_id"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="paid_date"/>
<field name="planned_amount" widget="monetary"/>
<field name="practical_amount" widget="monetary"/>
<field name="theoritical_amount" widget="monetary"/>
<field name="percentage"/>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="act_crossovered_budget_view">
<field name="name">Budgets</field>
<field name="res_model">crossovered.budget</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form</field>
<field name="view_id" ref="crossovered_budget_view_tree"/>
<field name="search_view_id" ref="view_crossovered_budget_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Click to create a new budget.
</p>
<p>
Use budgets to compare actual with expected revenues and costs
</p>
</field>
</record>
<menuitem parent="account.menu_finance_entries_management"
id="menu_act_crossovered_budget_view"
name="Budgets"
action="act_crossovered_budget_view" sequence="60"
groups="account.group_account_user"/>
<record model="ir.actions.act_window" id="act_crossovered_budget_lines_view">
<field name="name">Budgets</field>
<field name="res_model">crossovered.budget.lines</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_crossovered_budget_line_tree"/>
</record>
<record id="view_crossovered_budget_line_search" model="ir.ui.view">
<field name="name">account.budget.line.search</field>
<field name="model">crossovered.budget.lines</field>
<field name="arch" type="xml">
<search string="Budget Lines">
<field name="analytic_account_id"/>
<field name="crossovered_budget_id"/>
<filter name="filter_not_cancelled" string="Not Cancelled" domain="[('crossovered_budget_state','!=','cancel')]"/>
<group expand="0" string="Group By">
<filter name="group_crossevered_budgdet_id" string="Budgets" domain="[]" context="{'group_by':'crossovered_budget_id'}"/>
</group>
</search>
<menuitem parent="account.account_reports_management_menu"
id="menu_act_crossovered_budget_lines_view"
action="act_crossovered_budget_lines_view" sequence="20"/>
</field>
</record>
<!-- Shortcuts -->
<act_window name="Budget Items"
context="{'search_default_analytic_account_id': [active_id], 'default_analytic_account_id': active_id}"
res_model="crossovered.budget.lines"
src_model="account.analytic.account"
id="act_account_analytic_account_cb_lines"/>
<record model="ir.ui.view" id="view_crossovered_budget_line_tree">
<field name="name">crossovered.budget.line.tree</field>
<field name="model">crossovered.budget.lines</field>
<field name="arch" type="xml">
<tree string="Budget Lines" create="0">
<field name="currency_id" invisible="1"/>
<field name="crossovered_budget_id" invisible="1"/>
<field name="general_budget_id" />
<field name="analytic_account_id" groups="analytic.group_analytic_accounting" />
<field name="date_from" />
<field name="date_to" />
<field name="paid_date" groups="base.group_no_one" />
<field name="planned_amount"/>
<field name="practical_amount"/>
<field name="theoritical_amount"/>
<field name="percentage" widget="percentage"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_crossovered_budget_line_form">
<field name="name">crossovered.budget.line.form</field>
<field name="model">crossovered.budget.lines</field>
<field name="arch" type="xml">
<form string="Budget Lines">
<sheet>
<group col="4">
<field name="currency_id" invisible="1"/>
<field name="crossovered_budget_state" invisible="1"/>
<field name="crossovered_budget_id" attrs="{'readonly':[('crossovered_budget_state','!=','draft')]}"/>
<field name="analytic_account_id" attrs="{'readonly':[('crossovered_budget_state','!=','draft')]}"/>
<field name="general_budget_id" attrs="{'readonly':[('crossovered_budget_state','!=','draft')]}"/>
<field name="date_from" attrs="{'readonly':[('crossovered_budget_state','!=','draft')]}"/>
<field name="date_to" attrs="{'readonly':[('crossovered_budget_state','!=','draft')]}"/>
<field name="paid_date" attrs="{'readonly':[('crossovered_budget_state','!=','draft')]}"/>
<field name="planned_amount" attrs="{'readonly':[('crossovered_budget_state','!=','draft')]}"/>
<field name="practical_amount" attrs="{'readonly':[('crossovered_budget_state','!=','draft')]}"/>
<field name="theoritical_amount" attrs="{'readonly':[('crossovered_budget_state','!=','draft')]}"/>
<field name="percentage" widget="percentage" attrs="{'readonly':[('crossovered_budget_state','!=','draft')]}"/>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company" attrs="{'readonly':[('crossovered_budget_state','!=','draft')]}"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_crossovered_budget_line_pivot">
<field name="name">crossovered.budget.line.pivot</field>
<field name="model">crossovered.budget.lines</field>
<field name="arch" type="xml">
<pivot string="Budget Lines">
<field name="crossovered_budget_id" type="row"/>
<field name="planned_amount" type="measure" string="Planned amount"/>
<field name="theoritical_amount" type="measure" string="Theoritical amount"/>
<field name="practical_amount" type="measure" string="Practical amount"/>
<field name="percentage" type="measure" widget="percentage"/>
</pivot>
</field>
</record>
<record model="ir.ui.view" id="view_crossovered_budget_line_graph">
<field name="name">crossovered.budget.line.graph</field>
<field name="model">crossovered.budget.lines</field>
<field name="arch" type="xml">
<graph string="Budget Lines">
<field name="crossovered_budget_id" type="row"/>
<field name="planned_amount" type="measure" string="Planned amount"/>
<field name="theoritical_amount" type="measure" string="Theoritical amount"/>
<field name="practical_amount" type="measure" string="Practical amount"/>
</graph>
</field>
</record>
<record model="ir.actions.act_window" id="act_crossovered_budget_lines_view">
<field name="name">Budgets Analysis</field>
<field name="res_model">crossovered.budget.lines</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,pivot,graph</field>
<field name="view_id" eval="False"/>
<field name="context">{'search_default_group_crossevered_budgdet_id': True, 'search_default_filter_not_cancelled':True}</field>
</record>
<menuitem parent="account.account_reports_management_menu"
id="menu_act_crossovered_budget_lines_view"
action="act_crossovered_budget_lines_view" sequence="20"
groups="account.group_account_user"/>
<!-- Shortcuts -->
<act_window name="Budget Items"
context="{'search_default_analytic_account_id': [active_id], 'default_analytic_account_id': active_id}"
res_model="crossovered.budget.lines"
src_model="account.analytic.account"
id="act_account_analytic_account_cb_lines"/>
</odoo>