diff --git a/account_lock_to_date/README.rst b/account_lock_to_date/README.rst new file mode 100644 index 00000000000..98225a17b09 --- /dev/null +++ b/account_lock_to_date/README.rst @@ -0,0 +1,87 @@ +==================== +Account Lock To Date +==================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Faccount--financial--tools-lightgray.png?logo=github + :target: https://github.com/OCA/account-financial-tools/tree/11.0/account_lock_to_date + :alt: OCA/account-financial-tools +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/account-financial-tools-11-0/account-financial-tools-11-0-account_lock_to_date + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/92/11.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to set a Period and Fiscal year Locking end dates. This +will prevent users from posting journal entries on a date after the defined +period or fiscal year end date. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To set a new lock to dates, go to *Invoicing / Adviser / Actions / Update accounting lock to dates*. + +A user without an Adviser group will not be able to post or update posted +journal entries on the date "Lock To Date for Non-Advisers" or after. + +A user that has an Adviser group will not be able to post or update posted +journal entries on the date "Lock To Date" or after. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Eficent + +Contributors +~~~~~~~~~~~~ + +* Eficent + ** Jordi Ballester Alomar + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/account-financial-tools `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_lock_to_date/__init__.py b/account_lock_to_date/__init__.py new file mode 100644 index 00000000000..aee8895e7a3 --- /dev/null +++ b/account_lock_to_date/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/account_lock_to_date/__manifest__.py b/account_lock_to_date/__manifest__.py new file mode 100644 index 00000000000..5da8d5a59ff --- /dev/null +++ b/account_lock_to_date/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2019 Eficent Business and IT Consulting Services, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Account Lock To Date', + 'summary': """ + Allows to set an account lock date in the future.""", + 'version': '11.0.1.0.0', + 'license': 'AGPL-3', + 'author': 'Eficent, Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/account-financial-tools', + 'installable': True, + 'depends': [ + 'account', + ], + 'data': [ + 'wizards/account_update_lock_to_date.xml', + ], +} diff --git a/account_lock_to_date/models/__init__.py b/account_lock_to_date/models/__init__.py new file mode 100644 index 00000000000..25ee4054fd4 --- /dev/null +++ b/account_lock_to_date/models/__init__.py @@ -0,0 +1,2 @@ +from . import res_company +from . import account_move diff --git a/account_lock_to_date/models/account_move.py b/account_lock_to_date/models/account_move.py new file mode 100644 index 00000000000..28d9b146e76 --- /dev/null +++ b/account_lock_to_date/models/account_move.py @@ -0,0 +1,31 @@ +# Copyright 2019 Eficent Business and IT Consulting Services, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, models, _ +from odoo.exceptions import UserError + + +class AccountMove(models.Model): + _inherit = 'account.move' + + @api.multi + def _check_lock_date(self): + res = super()._check_lock_date() + for move in self: + lock_to_date = min( + move.company_id.period_lock_to_date or '0000-00-00', + move.company_id.fiscalyear_lock_to_date or '0000-00-00') + if self.user_has_groups('account.group_account_manager'): + lock_to_date = move.company_id.fiscalyear_lock_to_date + if lock_to_date and move.date >= (lock_to_date or '0000-00-00'): + if self.user_has_groups('account.group_account_manager'): + message = _("You cannot add/modify entries after and " + "inclusive of the lock to date %s") % ( + lock_to_date) + else: + message = _("You cannot add/modify entries after and " + "inclusive of the lock to date %s. " + "Check the company settings or ask someone " + "with the 'Adviser' role") % ( + lock_to_date) + raise UserError(message) + return res diff --git a/account_lock_to_date/models/res_company.py b/account_lock_to_date/models/res_company.py new file mode 100644 index 00000000000..88c0b94d117 --- /dev/null +++ b/account_lock_to_date/models/res_company.py @@ -0,0 +1,120 @@ +# Copyright 2019 Eficent Business and IT Consulting Services, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from datetime import datetime +from dateutil.relativedelta import relativedelta +import calendar +import time +from odoo import api, fields, models, SUPERUSER_ID, _ +from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT +from odoo.exceptions import ValidationError + + +class ResCompany(models.Model): + _inherit = 'res.company' + + period_lock_to_date = fields.Date( + string="Lock To Date for Non-Advisers", + help="Only users with the 'Adviser' role can edit " + "accounts after this date. " + "Use it for period locking inside an open fiscal year, " + "for example.") + fiscalyear_lock_to_date = fields.Date( + string="Lock To Date", + help="No users, including Advisers, can edit accounts after " + "this date. Use it for fiscal year locking for example.") + + @api.multi + def write(self, vals): + # fiscalyear_lock_date can't be set to a prior date + if 'fiscalyear_lock_to_date' in vals or 'period_lock_to_date' in vals: + self._check_lock_to_dates(vals) + return super(ResCompany, self).write(vals) + + @api.multi + def _check_lock_to_dates(self, vals): + '''Check the lock to dates for the current companies. + + :param vals: The values passed to the write method. + ''' + period_lock_to_date = vals.get('period_lock_to_date') and\ + time.strptime(vals['period_lock_to_date'], + DEFAULT_SERVER_DATE_FORMAT) + fiscalyear_lock_to_date = vals.get('fiscalyear_lock_to_date') and\ + time.strptime(vals['fiscalyear_lock_to_date'], + DEFAULT_SERVER_DATE_FORMAT) + + next_month = datetime.strptime( + fields.Date.today(), + DEFAULT_SERVER_DATE_FORMAT) + relativedelta(months=+1) + days_next_month = calendar.monthrange(next_month.year, + next_month.month) + next_month = next_month.replace( + day=days_next_month[1]).timetuple() + for company in self: + old_fiscalyear_lock_to_date = company.fiscalyear_lock_to_date and\ + time.strptime(company.fiscalyear_lock_to_date, + DEFAULT_SERVER_DATE_FORMAT) + + # The user attempts to remove the lock date for advisors + if old_fiscalyear_lock_to_date and \ + not fiscalyear_lock_to_date and \ + 'fiscalyear_lock_to_date' in vals and \ + not self._uid == SUPERUSER_ID: + raise ValidationError(_('The lock date for advisors is ' + 'irreversible and can\'t be removed.')) + + # The user attempts to set a lock date for advisors prior + # to the previous one + if old_fiscalyear_lock_to_date and fiscalyear_lock_to_date and \ + fiscalyear_lock_to_date > old_fiscalyear_lock_to_date: + raise ValidationError( + _('The new lock to date for advisors must be set after ' + 'the previous lock to date.')) + + # In case of no new fiscal year in vals, fallback to the oldest + if not fiscalyear_lock_to_date: + if old_fiscalyear_lock_to_date: + fiscalyear_lock_to_date = old_fiscalyear_lock_to_date + else: + continue + + # The user attempts to set a lock date for advisors after + # the first day of next month + if fiscalyear_lock_to_date < next_month: + raise ValidationError( + _('You cannot lock a period that is not finished yet. ' + 'Please make sure that the lock date for advisors is ' + 'not set after the last day of the previous month.')) + + # In case of no new period lock to date in vals, + # fallback to the one defined in the company + if not period_lock_to_date: + if company.period_lock_date: + period_lock_to_date = time.strptime( + company.period_lock_to_date, + DEFAULT_SERVER_DATE_FORMAT) + else: + continue + + # The user attempts to set a lock to date for advisors + # prior to the lock to date for users + if period_lock_to_date > fiscalyear_lock_to_date: + raise ValidationError( + _('You cannot define stricter conditions on advisors ' + 'than on users. Please make sure that the lock date ' + 'on advisor is set after the lock date for users.')) + + @api.multi + def _validate_fiscalyear_lock(self, values): + res = super()._validate_fiscalyear_lock(values) + if values.get('fiscalyear_lock_to_date'): + nb_draft_entries = self.env['account.move'].search([ + ('company_id', 'in', self.ids), + ('state', '=', 'draft'), + ('date', '>=', values['fiscalyear_lock_to_date'])], limit=1) + if nb_draft_entries: + raise ValidationError( + _('There are still unposted entries in the period to date' + ' you want to lock. ' + 'You should either post or delete them.')) + return res diff --git a/account_lock_to_date/readme/CONTRIBUTORS.rst b/account_lock_to_date/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..ed9784e451e --- /dev/null +++ b/account_lock_to_date/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Eficent + ** Jordi Ballester Alomar diff --git a/account_lock_to_date/readme/DESCRIPTION.rst b/account_lock_to_date/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..e1301431252 --- /dev/null +++ b/account_lock_to_date/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module allows to set a Period and Fiscal year Locking end dates. This +will prevent users from posting journal entries on a date after the defined +period or fiscal year end date. diff --git a/account_lock_to_date/readme/USAGE.rst b/account_lock_to_date/readme/USAGE.rst new file mode 100644 index 00000000000..1209da377cd --- /dev/null +++ b/account_lock_to_date/readme/USAGE.rst @@ -0,0 +1,7 @@ +To set a new lock to dates, go to *Invoicing / Adviser / Actions / Update accounting lock to dates*. + +A user without an Adviser group will not be able to post or update posted +journal entries on the date "Lock To Date for Non-Advisers" or after. + +A user that has an Adviser group will not be able to post or update posted +journal entries on the date "Lock To Date" or after. diff --git a/account_lock_to_date/static/description/icon.png b/account_lock_to_date/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/account_lock_to_date/static/description/icon.png differ diff --git a/account_lock_to_date/static/description/index.html b/account_lock_to_date/static/description/index.html new file mode 100644 index 00000000000..9922051124e --- /dev/null +++ b/account_lock_to_date/static/description/index.html @@ -0,0 +1,413 @@ + + + + + + +Account Lock To Date + + + +
+

Account Lock To Date

+ + +

Beta License: AGPL-3 OCA/account-financial-tools Translate me on Weblate Try me on Runbot

+

This module allows to set a Period and Fiscal year Locking end dates. This +will prevent users from posting journal entries on a date after the defined +period or fiscal year end date.

+

Table of contents

+ +
+

Usage

+

To set a new lock to dates, go to Invoicing / Adviser / Actions / Update accounting lock to dates.

+

A user without an Adviser group will not be able to post or update posted +journal entries on the date “Lock To Date for Non-Advisers” or after.

+

A user that has an Adviser group will not be able to post or update posted +journal entries on the date “Lock To Date” or after.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Eficent
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/account-financial-tools project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/account_lock_to_date/tests/__init__.py b/account_lock_to_date/tests/__init__.py new file mode 100644 index 00000000000..663d0707c92 --- /dev/null +++ b/account_lock_to_date/tests/__init__.py @@ -0,0 +1 @@ +from . import test_account_lock_to_date_update diff --git a/account_lock_to_date/tests/test_account_lock_to_date_update.py b/account_lock_to_date/tests/test_account_lock_to_date_update.py new file mode 100644 index 00000000000..e0d7e456fa3 --- /dev/null +++ b/account_lock_to_date/tests/test_account_lock_to_date_update.py @@ -0,0 +1,118 @@ +# Copyright 2019 Eficent Business and IT Consulting Services, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase +from odoo.exceptions import UserError, ValidationError + + +class TestAccountLockToDateUpdate(TransactionCase): + + def setUp(self): + super(TestAccountLockToDateUpdate, self).setUp() + self.company = self.env.ref('base.main_company') + self.demo_user = self.env.ref('base.user_demo') + self.adviser_group = self.env.ref('account.group_account_manager') + self.UpdateLockToDateUpdateObj = self.env[ + 'account.update.lock_to_date' + ].sudo(self.demo_user) + self.AccountObj = self.env['account.account'] + self.AccountJournalObj = self.env['account.journal'] + self.AccountMoveObj = self.env['account.move'] + self.bank_journal = self.AccountJournalObj.create( + {'name': 'Bank Journal - BJ', + 'code': 'BJ', + 'type': 'bank', + 'company_id': self.company.id, + } + ) + self.account_type_recv = self.env.ref( + 'account.data_account_type_receivable') + self.account_type_rev = self.env.ref( + 'account.data_account_type_revenue') + + self.account_recv = self.AccountObj.create({ + 'code': 'RECV_DR', + 'name': "Receivable (test)", + 'reconcile': True, + 'user_type_id': self.account_type_recv.id, + }) + self.account_sale = self.AccountObj.create({ + 'code': 'SALE_DR', + 'name': "Receivable (sale)", + 'reconcile': True, + 'user_type_id': self.account_type_rev.id, + }) + + def create_account_move(self, date_str): + return self.AccountMoveObj.create({ + 'journal_id': self.bank_journal.id, + 'date': date_str, + 'line_ids': [ + (0, 0, { + 'name': "Debit", + 'debit': 1000, + 'account_id': self.account_recv.id, + }), + (0, 0, { + 'name': "Credit", + 'credit': 1000, + 'account_id': self.account_sale.id, + }), + ] + }) + + def create_account_lock_date_update(self): + return self.UpdateLockToDateUpdateObj.create({ + 'company_id': self.company.id, + }) + + def test_01_update_without_access(self): + wizard = self.create_account_lock_date_update() + wizard.write({ + 'period_lock_to_date': '2900-01-01', + 'fiscalyear_lock_to_date': '2900-01-01', + }) + self.demo_user.write({ + 'groups_id': [(3, self.adviser_group.id)], + }) + with self.assertRaises(UserError): + wizard.sudo(self.demo_user.id).execute() + + def test_02_update_with_access(self): + wizard = self.create_account_lock_date_update() + wizard.write({ + 'period_lock_to_date': '2900-01-01', + 'fiscalyear_lock_to_date': '2900-02-01', + }) + self.demo_user.write({ + 'groups_id': [(4, self.adviser_group.id)], + }) + wizard.sudo(self.demo_user.id).execute() + self.assertEqual(self.company.period_lock_to_date, '2900-01-01') + self.assertEqual(self.company.fiscalyear_lock_to_date, '2900-02-01') + + def test_03_create_move_outside_period(self): + """We test that we cannot create journal entries after the + locked date""" + self.company.period_lock_to_date = '2900-01-01' + self.company.fiscalyear_lock_to_date = '2900-02-01' + move = self.create_account_move('2900-01-01') + with self.assertRaises(UserError): + move.sudo(self.demo_user.id).post() + + def test_04_create_move_inside_period(self): + """We test that we can successfully create a journal entry + within period that is not locked""" + self.company.period_lock_to_date = '2900-01-01' + self.company.fiscalyear_lock_to_date = '2900-02-01' + move = self.create_account_move('2800-01-01') + move.sudo(self.demo_user.id).post() + self.assertEqual(move.state, 'posted') + + def test_05_lock_period_with_draft_moves(self): + """We test that we cannot change the fiscal year lock to date + if there are draft journal entries after that date.""" + self.create_account_move('2900-02-01') + with self.assertRaises(ValidationError): + self.company.period_lock_to_date = '2900-01-01' + self.company.fiscalyear_lock_to_date = '2900-02-01' diff --git a/account_lock_to_date/wizards/__init__.py b/account_lock_to_date/wizards/__init__.py new file mode 100644 index 00000000000..86389635fb2 --- /dev/null +++ b/account_lock_to_date/wizards/__init__.py @@ -0,0 +1 @@ +from . import account_update_lock_to_date diff --git a/account_lock_to_date/wizards/account_update_lock_to_date.py b/account_lock_to_date/wizards/account_update_lock_to_date.py new file mode 100644 index 00000000000..610adcaef71 --- /dev/null +++ b/account_lock_to_date/wizards/account_update_lock_to_date.py @@ -0,0 +1,52 @@ +# Copyright 2019 Eficent Business and IT Consulting Services, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models, SUPERUSER_ID, _ +from odoo.exceptions import UserError + + +class AccountUpdateLockToDate(models.TransientModel): + _name = 'account.update.lock_to_date' + _description = 'Account Update Lock_to_date' + + company_id = fields.Many2one( + comodel_name='res.company', string="Company", required=True, + default=lambda self: self.env.user.company_id) + period_lock_to_date = fields.Date( + string="Lock To Date for Non-Advisers", + help="Only users with the 'Adviser' role can edit accounts after " + "and inclusive of this date. Use it for period locking inside an " + "open fiscal year, for example.") + fiscalyear_lock_to_date = fields.Date( + string="Lock To Date", + help="No users, including Advisers, can edit accounts after and " + "inclusive of this date. Use it for fiscal year locking for " + "example.") + + @api.model + def default_get(self, field_list): + res = super(AccountUpdateLockToDate, self).default_get(field_list) + company = self.env.user.company_id + res.update({ + 'company_id': company.id, + 'period_lock_to_date': company.period_lock_to_date, + 'fiscalyear_lock_to_date': company.fiscalyear_lock_to_date, + }) + return res + + @api.multi + def _check_execute_allowed(self): + self.ensure_one() + has_adviser_group = self.env.user.has_group( + 'account.group_account_manager') + if not (has_adviser_group or self.env.uid == SUPERUSER_ID): + raise UserError(_("You are not allowed to execute this action.")) + + @api.multi + def execute(self): + self.ensure_one() + self._check_execute_allowed() + self.company_id.sudo().write({ + 'period_lock_to_date': self.period_lock_to_date, + 'fiscalyear_lock_to_date': self.fiscalyear_lock_to_date, + }) diff --git a/account_lock_to_date/wizards/account_update_lock_to_date.xml b/account_lock_to_date/wizards/account_update_lock_to_date.xml new file mode 100644 index 00000000000..20c7b66f366 --- /dev/null +++ b/account_lock_to_date/wizards/account_update_lock_to_date.xml @@ -0,0 +1,44 @@ + + + + + + account.update.lock_to_date.form + account.update.lock_to_date + +
+
+ + + + + + + +
+
+ + + + + + + Update accounting lock to dates + account.update.lock_to_date + form + new + + + + Update accounting lock to dates + + + + + + +