Permalink
Browse files

[IMP] account: add account.fiscalyear model

In order to improve the fiscal year management, we allow the user to define custom fiscal years
    using the account.fiscalyear model.

Was task: https://www.odoo.com/web#id=39878&view_type=form&model=project.task&menu_id=
Was PR #20936
  • Loading branch information...
smetl authored and qdp-odoo committed May 30, 2018
1 parent 075cafa commit dfde326e7532e8ddbe34a3ddf698f6a556e3cfa5
@@ -67,6 +67,7 @@
'views/report_tax.xml',
'views/res_users_views.xml',
'data/service_cron_reverse.xml',
'views/account_fiscal_year_view.xml',
],
'demo': [
'demo/account_demo.xml',
@@ -10,6 +10,7 @@
from . import account_analytic_line
from . import account_journal_dashboard
from . import product
from . import account_fiscal_year
from . import company
from . import res_config_settings
from . import account_cash_rounding
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
from odoo.exceptions import ValidationError
from odoo import api, fields, models, _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from datetime import datetime
class AccountFiscalYear(models.Model):
_name = 'account.fiscal.year'
_description = 'Fiscal Year'
name = fields.Char(string='Name', required=True)
date_from = fields.Date(string='Start Date', required=True,
help='Start Date, included in the fiscal year.')
date_to = fields.Date(string='End Date', required=True,
help='Ending Date, included in the fiscal year.')
company_id = fields.Many2one('res.company', string='Company', required=True,
default=lambda self: self.env.user.company_id)
@api.constrains('date_from', 'date_to', 'company_id')
def _check_dates(self):
'''
Check interleaving between fiscal years.
There are 3 cases to consider:
s1 s2 e1 e2
( [----)----]
s2 s1 e2 e1
[----(----] )
s1 s2 e2 e1
( [----] )
'''
for fy in self:
# Starting date must be prior to the ending date
date_from = datetime.strptime(fy.date_from, DEFAULT_SERVER_DATE_FORMAT)
date_to = datetime.strptime(fy.date_to, DEFAULT_SERVER_DATE_FORMAT)
if date_to < date_from:
raise ValidationError(_('The ending date must not be prior to the starting date.'))
domain = [
('id', '!=', fy.id),
('company_id', '=', fy.company_id.id),
'|', '|',
'&', ('date_from', '<=', fy.date_from), ('date_to', '>=', fy.date_from),
'&', ('date_from', '<=', fy.date_to), ('date_to', '>=', fy.date_to),
'&', ('date_from', '<=', fy.date_from), ('date_to', '>=', fy.date_to),
]
if self.search_count(domain) > 0:
raise ValidationError(_('You can not have an overlap between two fiscal years, please correct the start and/or end dates of your fiscal years.'))
@@ -7,6 +7,7 @@
from odoo.exceptions import ValidationError, UserError
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_round, float_is_zero
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, date_utils
class ResCompany(models.Model):
@@ -74,27 +75,59 @@ def _verify_fiscalyear_last_day(self, company_id, last_day, last_month):
return last_day > last_day_of_month and last_day_of_month or last_day
@api.multi
def compute_fiscalyear_dates(self, date):
""" Computes the start and end dates of the fiscalyear where the given 'date' belongs to
@param date: a datetime object
@returns: a dictionary with date_from and date_to
"""
self = self[0]
last_month = self.fiscalyear_last_month
last_day = self.fiscalyear_last_day
if (date.month < last_month or (date.month == last_month and date.day <= last_day)):
date = date.replace(month=last_month, day=last_day)
else:
if last_month == 2 and last_day == 29 and (date.year + 1) % 4 != 0:
date = date.replace(month=last_month, day=28, year=date.year + 1)
else:
date = date.replace(month=last_month, day=last_day, year=date.year + 1)
date_to = date
date_from = date + timedelta(days=1)
if date_from.month == 2 and date_from.day == 29:
date_from = date_from.replace(day=28, year=date_from.year - 1)
else:
date_from = date_from.replace(year=date_from.year - 1)
def compute_fiscalyear_dates(self, current_date):
'''Computes the start and end dates of the fiscal year where the given 'date' belongs to.
:param current_date: A datetime.date/datetime.datetime object.
:return: A dictionary containing:
* date_from
* date_to
* [Optionally] record: The fiscal year record.
'''
self.ensure_one()
date_str = current_date.strftime(DEFAULT_SERVER_DATE_FORMAT)
# Search a fiscal year record containing the date.
# If a record is found, then no need further computation, we get the dates range directly.
fiscalyear = self.env['account.fiscal.year'].search([
('company_id', '=', self.id),
('date_from', '<=', date_str),
('date_to', '>=', date_str),
], limit=1)
if fiscalyear:
return {
'date_from': datetime.strptime(fiscalyear.date_from, DEFAULT_SERVER_DATE_FORMAT).date(),
'date_to': datetime.strptime(fiscalyear.date_to, DEFAULT_SERVER_DATE_FORMAT).date(),
'record': fiscalyear,
}
date_from, date_to = date_utils.get_fiscal_year(
current_date, day=self.fiscalyear_last_day, month=self.fiscalyear_last_month)
date_from_str = date_from.strftime(DEFAULT_SERVER_DATE_FORMAT)
date_to_str = date_to.strftime(DEFAULT_SERVER_DATE_FORMAT)
# Search for fiscal year records reducing the delta between the date_from/date_to.
# This case could happen if there is a gap between two fiscal year records.
# E.g. two fiscal year records: 2017-01-01 -> 2017-02-01 and 2017-03-01 -> 2017-12-31.
# => The period 2017-02-02 - 2017-02-30 is not covered by a fiscal year record.
fiscalyear_from = self.env['account.fiscal.year'].search([
('company_id', '=', self.id),
('date_from', '<=', date_from_str),
('date_to', '>=', date_from_str),
], limit=1)
if fiscalyear_from:
date_from = datetime.strptime(fiscalyear_from.date_to, DEFAULT_SERVER_DATE_FORMAT).date() + timedelta(days=1)
fiscalyear_to = self.env['account.fiscal.year'].search([
('company_id', '=', self.id),
('date_from', '<=', date_to_str),
('date_to', '>=', date_to_str),
], limit=1)
if fiscalyear_to:
date_to = datetime.strptime(fiscalyear_to.date_from, DEFAULT_SERVER_DATE_FORMAT).date() - timedelta(days=1)
return {'date_from': date_from, 'date_to': date_to}
def get_new_account_code(self, current_code, old_prefix, new_prefix):
@@ -31,6 +31,7 @@ class ResConfigSettings(models.TransientModel):
group_analytic_tags = fields.Boolean(string='Analytic Tags', implied_group='analytic.group_analytic_tags')
group_warning_account = fields.Boolean(string="Warnings in Invoices", implied_group='account.group_warning_account')
group_cash_rounding = fields.Boolean(string="Cash Rounding", implied_group='account.group_cash_rounding')
group_fiscal_year = fields.Boolean(string='Fiscal Years', implied_group='account.group_fiscal_year')
module_account_asset = fields.Boolean(string='Assets Management')
module_account_deferred_revenue = fields.Boolean(string="Revenue Recognition")
module_account_budget = fields.Boolean(string='Budget Management')
@@ -34,6 +34,11 @@
<field name="name">Allow the cash rounding management</field>
</record>
<record id="group_fiscal_year" model="res.groups">
<field name="name">Allow to define fiscal years of more or less than a year</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
</data>
<data noupdate="1">
@@ -84,3 +84,5 @@ access_account_cashbox_line,account.bank.statement.cashbox.line,model_account_ca
access_account_tax_group_internal_user,account.tax.group internal user,model_account_tax_group,base.group_user,1,0,0,0
access_account_tax_group,account.tax.group,model_account_tax_group,account.group_account_invoice,1,0,0,0
access_account_tax_group_manager,account.tax.group,model_account_tax_group,account.group_account_manager,1,1,1,1
access_account_fiscal_year_user,account.fiscal.year.user,model_account_fiscal_year,account.group_account_user,1,0,0,0
access_account_fiscal_year_manager,account.fiscal.year.manager,model_account_fiscal_year,account.group_account_manager,1,1,1,1
@@ -16,3 +16,4 @@
from . import test_setup_bar
from . import test_tax
from . import test_templates_consistency
from . import test_account_fiscal_year
@@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
import odoo.tests
from datetime import datetime
@odoo.tests.tagged('post_install', '-at_install')
class TestFiscalPosition(AccountingTestCase):
def check_compute_fiscal_year(self, company, date, expected_date_from, expected_date_to):
'''Compute the fiscal year at a certain date for the company passed as parameter.
Then, check if the result matches the 'expected_date_from'/'expected_date_to' dates.
:param company: The company.
:param date: The date belonging to the fiscal year.
:param expected_date_from: The expected date_from after computation.
:param expected_date_to: The expected date_to after computation.
'''
current_date = datetime.strptime(date, DEFAULT_SERVER_DATE_FORMAT)
res = company.compute_fiscalyear_dates(current_date)
self.assertEqual(res['date_from'].strftime(DEFAULT_SERVER_DATE_FORMAT), expected_date_from)
self.assertEqual(res['date_to'].strftime(DEFAULT_SERVER_DATE_FORMAT), expected_date_to)
def test_default_fiscal_year(self):
'''Basic case with a fiscal year xxxx-01-01 - xxxx-12-31.'''
company = self.env.ref('base.main_company')
company.fiscalyear_last_day = 31
company.fiscalyear_last_month = 12
self.check_compute_fiscal_year(
company,
'2017-12-31',
'2017-01-01',
'2017-12-31',
)
self.check_compute_fiscal_year(
company,
'2017-01-01',
'2017-01-01',
'2017-12-31',
)
def test_leap_fiscal_year_1(self):
'''Case with a leap year ending the 29 February.'''
company = self.env.ref('base.main_company')
company.fiscalyear_last_day = 29
company.fiscalyear_last_month = 2
self.check_compute_fiscal_year(
company,
'2016-02-29',
'2015-03-01',
'2016-02-29',
)
self.check_compute_fiscal_year(
company,
'2015-03-01',
'2015-03-01',
'2016-02-29',
)
def test_leap_fiscal_year_2(self):
'''Case with a leap year ending the 28 February.'''
company = self.env.ref('base.main_company')
company.fiscalyear_last_day = 28
company.fiscalyear_last_month = 2
self.check_compute_fiscal_year(
company,
'2016-03-01',
'2016-02-29',
'2017-02-28',
)
def test_custom_fiscal_year(self):
'''Case with custom fiscal years.'''
company = self.env.ref('base.main_company')
company.fiscalyear_last_day = 31
company.fiscalyear_last_month = 12
# Create custom fiscal year covering the 6 first months of 2017.
self.env['account.fiscal.year'].create({
'name': '6 month 2017',
'date_from': '2017-01-01',
'date_to': '2017-05-31',
'company_id': company.id,
})
# Check before the custom fiscal year).
self.check_compute_fiscal_year(
company,
'2017-02-01',
'2017-01-01',
'2017-05-31',
)
# Check after the custom fiscal year.
self.check_compute_fiscal_year(
company,
'2017-11-01',
'2017-06-01',
'2017-12-31',
)
# Create custom fiscal year covering the 3 last months of 2017.
self.env['account.fiscal.year'].create({
'name': 'last 3 month 2017',
'date_from': '2017-10-01',
'date_to': '2017-12-31',
'company_id': company.id,
})
# Check inside the custom fiscal years.
self.check_compute_fiscal_year(
company,
'2017-07-01',
'2017-06-01',
'2017-09-30',
)
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="actions_account_fiscal_year" model="ir.actions.act_window">
<field name="name">Fiscal Years</field>
<field name="res_model">account.fiscal.year</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to create a new fiscal year.
</p>
</field>
</record>
<record id="action_account_fiscal_year_form" model="ir.ui.view">
<field name="name">account.fiscal.year.form</field>
<field name="model">account.fiscal.year</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name" placeholder="Fiscal Year 2018"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_account_fiscal_year_search" model="ir.ui.view">
<field name="name">account.fiscal.year.search</field>
<field name="model">account.fiscal.year</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
</search>
</field>
</record>
<record id="action_account_fiscal_year_tree" model="ir.ui.view">
<field name="name">account.fiscal.year.tree</field>
<field name="model">account.fiscal.year</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
</record>
</data>
</odoo>
Oops, something went wrong.

0 comments on commit dfde326

Please sign in to comment.