diff --git a/addons/account/models/company.py b/addons/account/models/company.py index 16f1d676948..72b2c1f0197 100644 --- a/addons/account/models/company.py +++ b/addons/account/models/company.py @@ -1,8 +1,13 @@ # -*- coding: utf-8 -*- +from datetime import timedelta, datetime +import calendar +import time +from dateutil.relativedelta import relativedelta + from odoo import fields, models, api, _ from odoo.exceptions import ValidationError, UserError -from datetime import timedelta +from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT class ResCompany(models.Model): @@ -45,6 +50,61 @@ If you have any queries regarding your account, Please contact us. Thank you in advance for your cooperation. Best Regards,''') + @api.multi + def _check_lock_dates(self, vals): + '''Check the lock dates for the current companies. This can't be done in a api.constrains because we need + to perform some comparison between new/old values. This method forces the lock dates to be irreversible. + + * You cannot define stricter conditions on advisors than on users. Then, the lock date on advisor must be set + after the lock date for users. + * You cannot lock a period that is not finished yet. Then, the lock date for advisors must be set after the + last day of the previous month. + * The new lock date for advisors must be set after the previous lock date. + + :param vals: The values passed to the write method. + ''' + period_lock_date = vals.get('period_lock_date') and\ + time.strptime(vals['period_lock_date'], DEFAULT_SERVER_DATE_FORMAT) + fiscalyear_lock_date = vals.get('fiscalyear_lock_date') and\ + time.strptime(vals['fiscalyear_lock_date'], DEFAULT_SERVER_DATE_FORMAT) + + previous_month = datetime.strptime(fields.Date.today(), DEFAULT_SERVER_DATE_FORMAT) + relativedelta(months=-1) + days_previous_month = calendar.monthrange(previous_month.year, previous_month.month) + previous_month = previous_month.replace(day=days_previous_month[1]).timetuple() + for company in self: + old_fiscalyear_lock_date = company.fiscalyear_lock_date and\ + time.strptime(company.fiscalyear_lock_date, DEFAULT_SERVER_DATE_FORMAT) + + # The user attempts to remove the lock date for advisors + if old_fiscalyear_lock_date and not fiscalyear_lock_date and 'fiscalyear_lock_date' in vals: + 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_date and fiscalyear_lock_date and fiscalyear_lock_date < old_fiscalyear_lock_date: + raise ValidationError(_('The new lock date for advisors must be set after the previous lock date.')) + + # In case of no new fiscal year in vals, fallback to the oldest + if not fiscalyear_lock_date: + if old_fiscalyear_lock_date: + fiscalyear_lock_date = old_fiscalyear_lock_date + else: + continue + + # The user attempts to set a lock date for advisors prior to the last day of previous month + if fiscalyear_lock_date > previous_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 date in vals, fallback to the one defined in the company + if not period_lock_date: + if company.period_lock_date: + period_lock_date = time.strptime(company.period_lock_date, DEFAULT_SERVER_DATE_FORMAT) + else: + continue + + # The user attempts to set a lock date for advisors prior to the lock date for users + if period_lock_date < fiscalyear_lock_date: + raise ValidationError(_('You cannot define stricter conditions on advisors than on users. Please make sure that the lock date on advisor is set before the lock date for users.')) + @api.multi def compute_fiscalyear_dates(self, date): """ Computes the start and end dates of the fiscalyear where the given 'date' belongs to diff --git a/addons/account/tests/test_account_move_closed_period.py b/addons/account/tests/test_account_move_closed_period.py index f0b700247f8..49cc5d1a54f 100644 --- a/addons/account/tests/test_account_move_closed_period.py +++ b/addons/account/tests/test_account_move_closed_period.py @@ -1,6 +1,8 @@ from odoo.addons.account.tests.account_test_classes import AccountingTestCase from odoo.osv.orm import except_orm -from datetime import datetime, timedelta +from datetime import datetime +from dateutil.relativedelta import relativedelta +from calendar import monthrange from odoo.tools import DEFAULT_SERVER_DATE_FORMAT class TestPeriodState(AccountingTestCase): @@ -11,14 +13,16 @@ class TestPeriodState(AccountingTestCase): def setUp(self): super(TestPeriodState, self).setUp() self.user_id = self.env.user - self.day_before_yesterday = datetime.now() - timedelta(2) - self.yesterday = datetime.now() - timedelta(1) - self.yesterday_str = self.yesterday.strftime(DEFAULT_SERVER_DATE_FORMAT) + + last_day_month = datetime.now() - relativedelta(months=1) + last_day_month = last_day_month.replace(day=monthrange(last_day_month.year, last_day_month.month)[1]) + self.last_day_month_str = last_day_month.strftime(DEFAULT_SERVER_DATE_FORMAT) + #make sure there is no unposted entry - draft_entries = self.env['account.move'].search([('date', '<=', self.yesterday_str), ('state', '=', 'draft')]) + draft_entries = self.env['account.move'].search([('date', '<=', self.last_day_month_str), ('state', '=', 'draft')]) if draft_entries: draft_entries.post() - self.user_id.company_id.write({'fiscalyear_lock_date': self.yesterday_str}) + self.user_id.company_id.fiscalyear_lock_date = self.last_day_month_str self.sale_journal_id = self.env['account.journal'].search([('type', '=', 'sale')])[0] self.account_id = self.env['account.account'].search([('internal_type', '=', 'receivable')])[0] @@ -27,7 +31,7 @@ class TestPeriodState(AccountingTestCase): move = self.env['account.move'].create({ 'name': '/', 'journal_id': self.sale_journal_id.id, - 'date': self.day_before_yesterday.strftime(DEFAULT_SERVER_DATE_FORMAT), + 'date': self.last_day_month_str, 'line_ids': [(0, 0, { 'name': 'foo', 'debit': 10, diff --git a/addons/account_lock/__init__.py b/addons/account_lock/__init__.py new file mode 100644 index 00000000000..cde864bae21 --- /dev/null +++ b/addons/account_lock/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/addons/account_lock/__manifest__.py b/addons/account_lock/__manifest__.py new file mode 100644 index 00000000000..67e76b49098 --- /dev/null +++ b/addons/account_lock/__manifest__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +{ + 'name' : 'Irreversible Lock Date', + 'version' : '1.0', + 'category': 'Accounting', + 'description': """ + Make the lock date irreversible: + + * You cannot define stricter conditions on advisors than on users. Then, the lock date on advisor must be set before the lock date for users. + * You cannot lock a period that is not finished yet. Then, the lock date for advisors must be set before the last day of the previous month. + * The new lock date for advisors must be set after the previous lock date. + """, + 'depends' : ['account'], + 'data': [], +} diff --git a/addons/account_lock/models/__init__.py b/addons/account_lock/models/__init__.py new file mode 100644 index 00000000000..e759f74fc8d --- /dev/null +++ b/addons/account_lock/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import res_company diff --git a/addons/account_lock/models/res_company.py b/addons/account_lock/models/res_company.py new file mode 100644 index 00000000000..5a01046f36b --- /dev/null +++ b/addons/account_lock/models/res_company.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +from odoo import models, api + + +class ResCompany(models.Model): + _inherit = 'res.company' + + @api.multi + def write(self, vals): + # fiscalyear_lock_date can't be set to a prior date + if 'fiscalyear_lock_date' in vals or 'period_lock_date' in vals: + self._check_lock_dates(vals) + return super(ResCompany, self).write(vals) diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py index 09a228d8d2b..283315f3e7d 100644 --- a/addons/hw_escpos/escpos/escpos.py +++ b/addons/hw_escpos/escpos/escpos.py @@ -520,6 +520,10 @@ class Escpos: # Print Code if code: self._raw(code) + # We are using type A commands + # So we need to add the 'NULL' character + # https://github.com/python-escpos/python-escpos/pull/98/files#diff-a0b1df12c7c67e38915adbe469051e2dR444 + self._raw('\x00') else: raise exception.BarcodeCodeError() diff --git a/addons/l10n_fr_certification/models/res_company.py b/addons/l10n_fr_certification/models/res_company.py index 91e00081914..0d2c183b4b5 100644 --- a/addons/l10n_fr_certification/models/res_company.py +++ b/addons/l10n_fr_certification/models/res_company.py @@ -30,6 +30,9 @@ class ResCompany(models.Model): if company._is_accounting_unalterable(): sequence_fields = ['l10n_fr_secure_sequence_id'] company._create_secure_sequence(sequence_fields) + # fiscalyear_lock_date can't be set to a prior date + if 'fiscalyear_lock_date' in vals or 'period_lock_date' in vals: + self._check_lock_dates(vals) return res def _create_secure_sequence(self, sequence_fields):