summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorLaurent Smet <las@openerp.com>2018-05-30 15:20:45 +0200
committerqdp-odoo <qdp@odoo.com>2018-05-30 18:28:13 +0200
commitdfde326e7532e8ddbe34a3ddf698f6a556e3cfa5 (patch)
tree990ead942ad033e0d4502476bc364293def44d2a /addons
parent075cafa828f2375846b3e2d03f0eed7c49a034f7 (diff)
[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
Diffstat (limited to 'addons')
-rw-r--r--addons/account/__manifest__.py1
-rw-r--r--addons/account/models/__init__.py1
-rw-r--r--addons/account/models/account_fiscal_year.py56
-rw-r--r--addons/account/models/company.py75
-rw-r--r--addons/account/models/res_config_settings.py1
-rw-r--r--addons/account/security/account_security.xml5
-rw-r--r--addons/account/security/ir.model.access.csv2
-rw-r--r--addons/account/tests/__init__.py1
-rw-r--r--addons/account/tests/test_account_fiscal_year.py124
-rw-r--r--addons/account/views/account_fiscal_year_view.xml56
-rw-r--r--addons/account/views/res_config_settings_views.xml32
11 files changed, 318 insertions, 36 deletions
diff --git a/addons/account/__manifest__.py b/addons/account/__manifest__.py
index 47f91fd2cd4..9b98ed5ff11 100644
--- a/addons/account/__manifest__.py
+++ b/addons/account/__manifest__.py
@@ -67,6 +67,7 @@ Core mechanisms for the accounting modules. To display the menuitems, install th
'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',
diff --git a/addons/account/models/__init__.py b/addons/account/models/__init__.py
index 4eb991c1183..95d9fcbf996 100644
--- a/addons/account/models/__init__.py
+++ b/addons/account/models/__init__.py
@@ -10,6 +10,7 @@ from . import chart_template
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
diff --git a/addons/account/models/account_fiscal_year.py b/addons/account/models/account_fiscal_year.py
new file mode 100644
index 00000000000..ce031fb3f59
--- /dev/null
+++ b/addons/account/models/account_fiscal_year.py
@@ -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.'))
diff --git a/addons/account/models/company.py b/addons/account/models/company.py
index aa1526d7ce8..638b0b4bcd7 100644
--- a/addons/account/models/company.py
+++ b/addons/account/models/company.py
@@ -7,6 +7,7 @@ from odoo import fields, models, api, _
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 @@ Best Regards,'''))
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):
diff --git a/addons/account/models/res_config_settings.py b/addons/account/models/res_config_settings.py
index 4f3116abd55..601bda4541e 100644
--- a/addons/account/models/res_config_settings.py
+++ b/addons/account/models/res_config_settings.py
@@ -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')
diff --git a/addons/account/security/account_security.xml b/addons/account/security/account_security.xml
index 6e6d9ce21ce..b73419fd7fb 100644
--- a/addons/account/security/account_security.xml
+++ b/addons/account/security/account_security.xml
@@ -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">
diff --git a/addons/account/security/ir.model.access.csv b/addons/account/security/ir.model.access.csv
index 8171601d4d9..932c565be0f 100644
--- a/addons/account/security/ir.model.access.csv
+++ b/addons/account/security/ir.model.access.csv
@@ -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
diff --git a/addons/account/tests/__init__.py b/addons/account/tests/__init__.py
index d50e277028c..750cddd82fa 100644
--- a/addons/account/tests/__init__.py
+++ b/addons/account/tests/__init__.py
@@ -16,3 +16,4 @@ from . import test_search
from . import test_setup_bar
from . import test_tax
from . import test_templates_consistency
+from . import test_account_fiscal_year
diff --git a/addons/account/tests/test_account_fiscal_year.py b/addons/account/tests/test_account_fiscal_year.py
new file mode 100644
index 00000000000..e1ce44c38fe
--- /dev/null
+++ b/addons/account/tests/test_account_fiscal_year.py
@@ -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',
+ )
diff --git a/addons/account/views/account_fiscal_year_view.xml b/addons/account/views/account_fiscal_year_view.xml
new file mode 100644
index 00000000000..4714e5a0f8b
--- /dev/null
+++ b/addons/account/views/account_fiscal_year_view.xml
@@ -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>
diff --git a/addons/account/views/res_config_settings_views.xml b/addons/account/views/res_config_settings_views.xml
index c92d484504e..0776e2de628 100644
--- a/addons/account/views/res_config_settings_views.xml
+++ b/addons/account/views/res_config_settings_views.xml
@@ -183,6 +183,23 @@
</div>
</div>
</div>
+ <t groups="account.group_account_user">
+ <h2>Fiscal Periods</h2>
+ <div class="row mt16 o_settings_container" id="accounting_reports">
+ <div class="col-xs-12 col-md-6 o_setting_box" id="fiscalyear" invisible="1" groups="account.group_account_user"/>
+ <div class="col-xs-12 col-md-6 o_setting_box" id="dynamic_report" invisible="1" groups="account.group_account_user">
+ <div class="o_setting_left_pane">
+ <field name="module_account_reports" widget="upgrade_boolean"/>
+ </div>
+ <div class="o_setting_right_pane">
+ <label for="module_account_reports"/>
+ <div class="text-muted" id="account_reports">
+ Navigate easily through reports and see what is behind the numbers
+ </div>
+ </div>
+ </div>
+ </div>
+ </t>
<h2>Customer Payments</h2>
<div class="row mt16 o_settings_container">
<div class="col-xs-12 col-md-6 o_setting_box" id="account_followup" title="This feature is useful if you issue a high amounts of invoices.">
@@ -266,21 +283,6 @@
</div>
<t groups="account.group_account_user">
- <h2>Fiscal Periods</h2>
- <div class="row mt16 o_settings_container" id="accounting_reports">
- <div class="col-xs-12 col-md-6 o_setting_box" id="fiscalyear" invisible="1"/>
- <div class="col-xs-12 col-md-6 o_setting_box" id="dynamic_report" invisible="1" groups="account.group_account_user">
- <div class="o_setting_left_pane">
- <field name="module_account_reports" widget="upgrade_boolean"/>
- </div>
- <div class="o_setting_right_pane">
- <label for="module_account_reports"/>
- <div class="text-muted" id="account_reports">
- Navigate easily through reports and see what is behind the numbers
- </div>
- </div>
- </div>
- </div>
<h2>Bank &amp; Cash</h2>
<div class="row mt16 o_settings_container" id="bank_cash">
<div class="col-xs-12 col-md-6 o_setting_box" id="account_yodlee" title="Get your bank statements automatically imported every 4 hours, or in one-click, using Yodlee and Plaid services. Once installed, set “Bank Feeds” to “Bank Synchronization” in bank account settings. Then, click “Configure” on the online account to enter your bank credentials.">