From 0ef36b0ec8370f3999f72df4961e5cec83d3dc77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Fri, 9 May 2014 23:32:17 +0200 Subject: [PATCH] Add cancel to Account Move issue3875 review10261002 --- CHANGELOG | 2 + __init__.py | 1 + move.py | 77 +++++++++++++++- move.xml | 10 +++ tests/scenario_move_cancel.rst | 158 +++++++++++++++++++++++++++++++++ tests/test_account.py | 4 + 6 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 tests/scenario_move_cancel.rst diff --git a/CHANGELOG b/CHANGELOG index 0b5968592e..2a4d65d444 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Add cancel to Account Move + Version 3.2.0 - 2014-04-21 * Bug fixes (see mercurial logs for details) * Add start/end date to tax diff --git a/__init__.py b/__init__.py index 2be0f47951..73ffc5113a 100644 --- a/__init__.py +++ b/__init__.py @@ -89,6 +89,7 @@ def register(): ReconcileLines, UnreconcileLines, OpenReconcileLines, + CancelMoves, PrintGeneralJournal, OpenChartTaxCode, OpenTaxCode, diff --git a/move.py b/move.py index 76b310ecb6..95dc9f6f38 100644 --- a/move.py +++ b/move.py @@ -4,6 +4,8 @@ from decimal import Decimal import datetime from itertools import groupby from operator import itemgetter +from collections import defaultdict + from sql.aggregate import Sum from sql.conditionals import Coalesce @@ -21,7 +23,8 @@ from trytond.tools import reduce_ids __all__ = ['Move', 'Reconciliation', 'Line', 'OpenJournalAsk', 'OpenJournal', 'OpenAccount', 'ReconcileLinesWriteOff', 'ReconcileLines', 'UnreconcileLines', 'OpenReconcileLinesStart', - 'OpenReconcileLines', 'FiscalYearLine', 'FiscalYear2', + 'OpenReconcileLines', 'CancelMoves', + 'FiscalYearLine', 'FiscalYear2', 'PrintGeneralJournalStart', 'PrintGeneralJournal', 'GeneralJournal'] __metaclass__ = PoolMeta @@ -86,6 +89,9 @@ class Move(ModelSQL, ModelView): 'because it\'s date is outside its period.'), 'draft_closed_period': ('You can not set to draft move ' '"%(move)s" because period "%(period)s" is closed.'), + 'period_cancel': ( + 'The period of move "%s" is closed.\n' + 'Use the current period?'), }) cls._buttons.update({ 'post': { @@ -156,7 +162,7 @@ class Move(ModelSQL, ModelView): @classmethod def _get_origin(cls): 'Return list of Model names for origin Reference' - return ['account.fiscalyear'] + return ['account.fiscalyear', 'account.move'] @classmethod def get_origin(cls): @@ -334,6 +340,50 @@ class Move(ModelSQL, ModelView): values=[state], where=red_sql)) + def _cancel_default(self): + 'Return default dictionary to cancel move' + pool = Pool() + Date = pool.get('ir.date') + Period = pool.get('account.period') + + default = { + 'origin': str(self), + } + if self.period.state == 'close': + self.raise_user_warning('%s.cancel' % self, + 'period_cancel', self.rec_name) + date = Date.today() + period_id = Period.find(self.company.id, date=date) + default.update({ + 'date': date, + 'period': period_id, + }) + return default + + def cancel(self): + 'Return a cancel move' + pool = Pool() + Line = pool.get('account.move.line') + TaxLine = pool.get('account.tax.line') + default = self._cancel_default() + cancel_move, = Move.copy([self], default=default) + lines = [] + tax_lines = [] + for line in cancel_move.lines: + line.debit *= -1 + line.credit *= -1 + lines.extend(([line], line._save_values)) + line._values = None + for tax_line in line.tax_lines: + tax_line.amount *= -1 + tax_lines.extend(([tax_line], tax_line._save_values)) + tax_line._values = None + if lines: + Line.write(*lines) + if tax_lines: + TaxLine.write(*tax_lines) + return cancel_move + @classmethod @ModelView.button def post(cls, moves): @@ -1670,6 +1720,29 @@ class OpenReconcileLines(Wizard): return action, {} +class CancelMoves(Wizard): + 'Cancel Moves' + __name__ = 'account.move.cancel' + start_state = 'cancel' + cancel = StateTransition() + + def transition_cancel(self): + pool = Pool() + Move = pool.get('account.move') + Line = pool.get('account.move.line') + + moves = Move.browse(Transaction().context['active_ids']) + for move in moves: + cancel_move = move.cancel() + to_reconcile = defaultdict(list) + for line in move.lines + cancel_move.lines: + if line.account.reconcile: + to_reconcile[line.account].append(line) + for lines in to_reconcile.itervalues(): + Line.reconcile(lines) + return 'end' + + class FiscalYearLine(ModelSQL): 'Fiscal Year - Move Line' __name__ = 'account.fiscalyear-account.move.line' diff --git a/move.xml b/move.xml index 68ea2f9f95..9292efe47f 100644 --- a/move.xml +++ b/move.xml @@ -294,6 +294,16 @@ this repository contains the full copyright notices and license terms. --> action="act_open_reconcile_lines" id="menu_open_reconcile_lines"/> + + Cancel Moves + account.move.cancel + + + form_action + account.move,-1 + + + account.move.print_general_journal.start form diff --git a/tests/scenario_move_cancel.rst b/tests/scenario_move_cancel.rst new file mode 100644 index 0000000000..140d3c0197 --- /dev/null +++ b/tests/scenario_move_cancel.rst @@ -0,0 +1,158 @@ +==================== +Move Cancel Scenario +==================== + +============= +General Setup +============= + +Imports:: + + >>> import datetime + >>> from dateutil.relativedelta import relativedelta + >>> from decimal import Decimal + >>> from proteus import config, Model, Wizard + >>> today = datetime.date.today() + +Create database:: + + >>> config = config.set_trytond() + >>> config.pool.test = True + +Install account:: + + >>> Module = Model.get('ir.module.module') + >>> modules = Module.find([ + ... ('name', '=', 'account'), + ... ]) + >>> Module.install([x.id for x in modules], config.context) + >>> Wizard('ir.module.module.install_upgrade').execute('upgrade') + +Create company:: + + >>> Currency = Model.get('currency.currency') + >>> CurrencyRate = Model.get('currency.currency.rate') + >>> Company = Model.get('company.company') + >>> Party = Model.get('party.party') + >>> company_config = Wizard('company.company.config') + >>> company_config.execute('company') + >>> company = company_config.form + >>> party = Party(name='Dunder Mifflin') + >>> party.save() + >>> company.party = party + >>> currencies = Currency.find([('code', '=', 'USD')]) + >>> if not currencies: + ... currency = Currency(name='U.S. Dollar', symbol='$', code='USD', + ... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]', + ... mon_decimal_point='.', mon_thousands_sep=',') + ... currency.save() + ... CurrencyRate(date=today + relativedelta(month=1, day=1), + ... rate=Decimal('1.0'), currency=currency).save() + ... else: + ... currency, = currencies + >>> company.currency = currency + >>> company_config.execute('add') + >>> company, = Company.find() + +Reload the context:: + + >>> User = Model.get('res.user') + >>> config._context = User.get_preferences(True, config.context) + +Create fiscal year:: + + >>> FiscalYear = Model.get('account.fiscalyear') + >>> Sequence = Model.get('ir.sequence') + >>> SequenceStrict = Model.get('ir.sequence.strict') + >>> fiscalyear = FiscalYear(name='%s' % today.year) + >>> fiscalyear.start_date = today + relativedelta(month=1, day=1) + >>> fiscalyear.end_date = today + relativedelta(month=12, day=31) + >>> fiscalyear.company = company + >>> post_move_sequence = Sequence(name='%s' % today.year, + ... code='account.move', company=company) + >>> post_move_sequence.save() + >>> fiscalyear.post_move_sequence = post_move_sequence + >>> fiscalyear.save() + >>> FiscalYear.create_period([fiscalyear.id], config.context) + >>> period = fiscalyear.periods[0] + +Create chart of accounts:: + + >>> AccountTemplate = Model.get('account.account.template') + >>> Account = Model.get('account.account') + >>> account_template, = AccountTemplate.find([('parent', '=', None)]) + >>> create_chart = Wizard('account.create_chart') + >>> create_chart.execute('account') + >>> create_chart.form.account_template = account_template + >>> create_chart.form.company = company + >>> create_chart.execute('create_account') + >>> receivable, = Account.find([ + ... ('kind', '=', 'receivable'), + ... ('company', '=', company.id), + ... ]) + >>> payable, = Account.find([ + ... ('kind', '=', 'payable'), + ... ('company', '=', company.id), + ... ]) + >>> revenue, = Account.find([ + ... ('kind', '=', 'revenue'), + ... ('company', '=', company.id), + ... ]) + >>> create_chart.form.account_receivable = receivable + >>> create_chart.form.account_payable = payable + >>> create_chart.execute('create_properties') + +Create parties:: + + >>> Party = Model.get('party.party') + >>> customer = Party(name='Customer') + >>> customer.save() + +Create Move to cancel:: + + >>> Journal = Model.get('account.journal') + >>> Move = Model.get('account.move') + >>> journal_revenue, = Journal.find([ + ... ('code', '=', 'REV'), + ... ]) + >>> journal_cash, = Journal.find([ + ... ('code', '=', 'CASH'), + ... ]) + >>> move = Move() + >>> move.period = period + >>> move.journal = journal_revenue + >>> move.date = period.start_date + >>> line = move.lines.new() + >>> line.account = revenue + >>> line.credit = Decimal(42) + >>> line = move.lines.new() + >>> line.account = receivable + >>> line.debit = Decimal(42) + >>> line.party = customer + >>> move.save() + >>> revenue.reload() + >>> revenue.credit + Decimal('42.00') + >>> receivable.reload() + >>> receivable.debit + Decimal('42.00') + +Cancel Move:: + + >>> cancel_move = Wizard('account.move.cancel', [move]) + >>> cancel_move.state + 'end' + >>> move.reload() + >>> line, = [l for l in move.lines if l.account == receivable] + >>> bool(line.reconciliation) + True + >>> cancel_move, = [l.move for l in line.reconciliation.lines + ... if l.move != move] + >>> cancel_move.origin == move + True + >>> revenue.reload() + >>> revenue.credit + Decimal('0.00') + >>> receivable.reload() + >>> receivable.debit + Decimal('0.00') diff --git a/tests/test_account.py b/tests/test_account.py index b7a9b9eae8..1fed67a01a 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -436,4 +436,8 @@ def suite(): 'scenario_account_reconciliation.rst', setUp=doctest_dropdb, tearDown=doctest_dropdb, encoding='utf-8', optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) + suite.addTests(doctest.DocFileSuite( + 'scenario_move_cancel.rst', + setUp=doctest_dropdb, tearDown=doctest_dropdb, encoding='utf-8', + optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) return suite