diff --git a/modules/account/CHANGELOG b/modules/account/CHANGELOG index 6ac00b8b4e..5b8c7a5e44 100644 --- a/modules/account/CHANGELOG +++ b/modules/account/CHANGELOG @@ -1,3 +1,4 @@ +* Allow copying move from closed period * Replace Accounting Party by Accounting group Version 7.2.0 - 2024-04-29 diff --git a/modules/account/exceptions.py b/modules/account/exceptions.py index b87358d1dd..a491d34dd7 100644 --- a/modules/account/exceptions.py +++ b/modules/account/exceptions.py @@ -81,6 +81,10 @@ class MoveTemplateKeywordValidationError(ValidationError): pass +class CopyWarning(UserWarning): + pass + + class CancelWarning(UserWarning): pass diff --git a/modules/account/message.xml b/modules/account/message.xml index 7d7f5c5205..4c1d162443 100644 --- a/modules/account/message.xml +++ b/modules/account/message.xml @@ -134,6 +134,10 @@ this repository contains the full copyright notices and license terms. --> You cannot modify posted move "%(move)s". + + The period of move "%(move)s" is closed. +Use the current period to copy the move? + The period of move "%(move)s" is closed. Use the current period to cancel the move? diff --git a/modules/account/move.py b/modules/account/move.py index 0d7edbb6a5..0453b190c8 100644 --- a/modules/account/move.py +++ b/modules/account/move.py @@ -27,9 +27,10 @@ from trytond.wizard import ( Button, StateAction, StateTransition, StateView, Wizard) from .exceptions import ( - AccountMissing, CancelDelegatedWarning, CancelWarning, DelegateLineError, - GroupLineError, JournalMissing, PeriodNotFoundError, PostError, - ReconciliationDeleteWarning, ReconciliationError, RescheduleLineError) + AccountMissing, CancelDelegatedWarning, CancelWarning, CopyWarning, + DelegateLineError, GroupLineError, JournalMissing, PeriodNotFoundError, + PostError, ReconciliationDeleteWarning, ReconciliationError, + RescheduleLineError) _MOVE_STATES = { 'readonly': Eval('state') == 'posted', @@ -335,14 +336,51 @@ class Move(DescriptionOriginMixin, ModelSQL, ModelView): @classmethod def copy(cls, moves, default=None): + pool = Pool() + Date = pool.get('ir.date') + Period = pool.get('account.period') + Warning = pool.get('res.user.warning') + if default is None: default = {} else: default = default.copy() + + def _check_period(origin): + period = Period(origin['period']) + if period.state == 'closed': + move = cls(origin['id']) + key = Warning.format('copy', [move]) + if Warning.check(key): + raise CopyWarning(key, + gettext('account.msg_move_copy_closed_period', + move=move)) + return False + else: + return True + + def default_period(origin): + if not _check_period(origin): + with Transaction().set_context(company=origin['company']): + today = Date.today() + period = Period.find(origin['company'], date=today) + return period.id + else: + return origin['period'] + + def default_date(origin): + if not _check_period(origin): + with Transaction().set_context(company=origin['company']): + return Date.today() + else: + return origin['date'] + default.setdefault('number', None) default.setdefault('post_number', None) default.setdefault('state', cls.default_state()) default.setdefault('post_date', None) + default.setdefault('period', default_period) + default.setdefault('date', default_date) return super().copy(moves, default=default) @classmethod diff --git a/modules/account/tests/scenario_move_copy.rst b/modules/account/tests/scenario_move_copy.rst new file mode 100644 index 0000000000..2cd82595b0 --- /dev/null +++ b/modules/account/tests/scenario_move_copy.rst @@ -0,0 +1,95 @@ +================== +Move Copy Scenario +================== + +Imports:: + + >>> from decimal import Decimal + + >>> from proteus import Model + >>> from trytond.modules.account.tests.tools import ( + ... create_chart, create_fiscalyear, get_accounts) + >>> from trytond.modules.company.tests.tools import create_company + >>> from trytond.tests.tools import ( + ... activate_modules, assertEqual, assertNotEqual, assertTrue) + +Activate modules:: + + >>> config = activate_modules('account') + + >>> Journal = Model.get('account.journal') + >>> Move = Model.get('account.move') + >>> Party = Model.get('party.party') + +Create a company:: + + >>> _ = create_company() + +Create a fiscal year:: + + >>> fiscalyear = create_fiscalyear() + >>> fiscalyear.click('create_period') + >>> period = fiscalyear.periods[0] + +Create a chart of accounts:: + + >>> _ = create_chart() + >>> accounts = get_accounts() + +Create a party:: + + >>> party = Party(name="Party") + >>> party.save() + +Create a posted move:: + + >>> move = Move() + >>> move.period = period + >>> move.journal, = Journal.find([('code', '=', 'REV')]) + >>> move.date = period.start_date + >>> line = move.lines.new() + >>> line.account = accounts['revenue'] + >>> line.credit = Decimal(42) + >>> line = move.lines.new() + >>> line.account = accounts['receivable'] + >>> line.debit = Decimal(42) + >>> line.party = party + >>> move.save() + >>> move.click('post') + >>> move.state + 'posted' + +Copy move on open period:: + + >>> copy_move, = move.duplicate() + >>> assertTrue(copy_move.number) + >>> copy_move.post_number + >>> copy_move.state + 'draft' + >>> copy_move.post_date + >>> assertEqual(copy_move.period, move.period) + >>> assertEqual(copy_move.date, move.date) + >>> copy_move.delete() + +Copy move on closed period:: + + >>> period.click('close') + >>> period.state + 'closed' + + >>> copy_move, = move.duplicate() + Traceback (most recent call last): + ... + CopyWarning: ... + + >>> config.skip_warning = True + >>> copy_move, = move.duplicate() + >>> assertTrue(copy_move.number) + >>> copy_move.post_number + >>> copy_move.state + 'draft' + >>> copy_move.post_date + >>> assertNotEqual(copy_move.period, move.period) + >>> copy_move.period.state + 'open' + >>> assertNotEqual(copy_move.date, move.date)