diff --git a/modules/account/CHANGELOG b/modules/account/CHANGELOG index 4b4c8d414e..da9cb48169 100644 --- a/modules/account/CHANGELOG +++ b/modules/account/CHANGELOG @@ -1,3 +1,4 @@ +* Support reconcile lines with currency exchange * Add reconcile and unreconcile from general ledger lines * Always raise exception when fiscal year or period are not found * Remove support for Python 3.7 diff --git a/modules/account/__init__.py b/modules/account/__init__.py index 7fe6563e7a..d3f280d944 100644 --- a/modules/account/__init__.py +++ b/modules/account/__init__.py @@ -44,6 +44,7 @@ def register(): configuration.ConfigurationDefaultAccount, configuration.DefaultTaxRule, configuration.Sequence, + configuration.Journal, period.Period, journal.Journal, journal.JournalSequence, diff --git a/modules/account/configuration.py b/modules/account/configuration.py index 52c622c9e5..b01289c207 100644 --- a/modules/account/configuration.py +++ b/modules/account/configuration.py @@ -58,16 +58,42 @@ class Configuration( Id('account', 'sequence_type_account_move_reconciliation')), ])) + currency_exchange_journal = fields.MultiValue(fields.Many2One( + 'account.journal', "Currency Exchange Journal", + domain=[ + ('type', '=', 'write-off'), + ])) + currency_exchange_credit_account = fields.MultiValue(fields.Many2One( + 'account.account', "Currency Exchange Credit Account", + domain=[ + ('closed', '!=', True), + ('type.statement', '=', 'income'), + ('company', '=', Eval('context', {}).get('company', -1)), + ('second_currency', '=', None), + ])) + currency_exchange_debit_account = fields.MultiValue(fields.Many2One( + 'account.account', "Currency Exchange Debit Account", + domain=[ + ('closed', '!=', True), + ('type.statement', '=', 'income'), + ('company', '=', Eval('context', {}).get('company', -1)), + ('second_currency', '=', None), + ])) @classmethod def multivalue_model(cls, field): pool = Pool() - if field in {'default_account_receivable', 'default_account_payable'}: + if field in { + 'default_account_receivable', 'default_account_payable', + 'currency_exchange_credit_account', + 'currency_exchange_debit_account'}: return pool.get('account.configuration.default_account') if field in {'default_customer_tax_rule', 'default_supplier_tax_rule'}: return pool.get('account.configuration.default_tax_rule') if field == 'reconciliation_sequence': return pool.get('account.configuration.sequence') + elif field == 'currency_exchange_journal': + return pool.get('account.configuration.journal') return super(Configuration, cls).multivalue_model(field) @classmethod @@ -79,6 +105,11 @@ class Configuration( return cls.multivalue_model( 'reconciliation_sequence').default_reconciliation_sequence() + @classmethod + def default_currency_exchange_journal(cls, **pattern): + return cls.multivalue_model( + 'currency_exchange_journal').default_currency_exchange_journal() + class ConfigurationDefaultAccount(ModelSQL, CompanyValueMixin): "Account Configuration Default Account" @@ -97,6 +128,22 @@ class ConfigurationDefaultAccount(ModelSQL, CompanyValueMixin): ('party_required', '=', True), ('company', '=', Eval('company', -1)), ]) + currency_exchange_credit_account = fields.Many2One( + 'account.account', "Currency Exchange Credit Account", + domain=[ + ('closed', '!=', True), + ('type.statement', '=', 'income'), + ('company', '=', Eval('company', -1)), + ('second_currency', '=', None), + ]) + currency_exchange_debit_account = fields.Many2One( + 'account.account', "Currency Exchange Debit Account", + domain=[ + ('closed', '!=', True), + ('type.statement', '=', 'income'), + ('company', '=', Eval('company', -1)), + ('second_currency', '=', None), + ]) class DefaultTaxRule(ModelSQL, CompanyValueMixin): @@ -167,3 +214,25 @@ class Sequence(ModelSQL, CompanyValueMixin): 'account', 'sequence_account_move_reconciliation') except KeyError: return None + + +class Journal(ModelSQL, CompanyValueMixin): + "Account Configuration Journal" + __name__ = 'account.configuration.journal' + currency_exchange_journal = fields.Many2One( + 'account.journal', "Currency Exchange Journal", + domain=[ + ('type', '=', 'write-off'), + ], + context={ + 'company': Eval('company', -1), + }) + + @classmethod + def default_currency_exchange_journal(cls): + pool = Pool() + ModelData = pool.get('ir.model.data') + try: + return ModelData.get_id('account', 'journal_currency_exchange') + except KeyError: + return None diff --git a/modules/account/journal.xml b/modules/account/journal.xml index 67936e6a8c..8ee7f272f7 100644 --- a/modules/account/journal.xml +++ b/modules/account/journal.xml @@ -244,5 +244,10 @@ this repository contains the full copyright notices and license terms. --> MISC general + + Currency Exchange + EXC + write-off + diff --git a/modules/account/move.py b/modules/account/move.py index e3f435689a..73300b82fa 100644 --- a/modules/account/move.py +++ b/modules/account/move.py @@ -922,7 +922,7 @@ class Line(MoveLineMixin, ModelSQL, ModelView): help='The second currency.', domain=[ If(~Eval('second_currency_required'), - (), + ('id', '!=', Eval('currency', -1)), ('id', '=', Eval('second_currency_required', -1))), ], states={ @@ -1533,25 +1533,49 @@ class Line(MoveLineMixin, ModelSQL, ModelView): reconcile_account = None reconcile_party = None amount = Decimal('0.0') + amount_second_currency = Decimal('0.0') + second_currencies = set() for line in lines: amount += line.debit - line.credit if not reconcile_account: reconcile_account = line.account if not reconcile_party: reconcile_party = line.party - if amount: + if line.amount_second_currency is not None: + amount_second_currency += line.amount_second_currency + second_currencies.add(line.second_currency) + company = reconcile_account.company + + try: + second_currency, = second_currencies + except ValueError: + amount_second_currency = None + second_currency = None + if second_currency: + writeoff_amount = amount_second_currency + writeoff_currency = second_currency + else: + writeoff_amount = amount + writeoff_currency = company.currency + if writeoff_amount: move = cls._get_writeoff_move( - reconcile_account, reconcile_party, amount, + reconcile_account, reconcile_party, + writeoff_amount, writeoff_currency, date, writeoff, description) move.save() - lines += cls.search([ - ('move', '=', move.id), - ('account', '=', reconcile_account.id), - ('debit', '=', amount < Decimal('0.0') and - amount - or Decimal('0.0')), - ('credit', '=', amount > Decimal('0.0') and amount - or Decimal('0.0')), - ], limit=1) + for line in move.lines: + if line.account == reconcile_account: + lines.append(line) + amount += line.debit - line.credit + if second_currency and amount: + move = cls._get_exchange_move( + reconcile_account, reconcile_party, amount, date) + move.save() + for line in move.lines: + if line.account == reconcile_account: + lines.append(line) + amount += line.debit - line.credit + assert not amount, f"{amount} must be zero" reconciliations.append({ 'company': reconcile_account.company, 'lines': [('add', [x.id for x in lines])], @@ -1561,9 +1585,11 @@ class Line(MoveLineMixin, ModelSQL, ModelView): return Reconciliation.create(reconciliations) @classmethod - def _get_writeoff_move(cls, reconcile_account, reconcile_party, amount, + def _get_writeoff_move( + cls, reconcile_account, reconcile_party, amount, currency, date=None, writeoff=None, description=None): pool = Pool() + Currency = pool.get('currency.currency') Date = pool.get('ir.date') Period = pool.get('account.period') Move = pool.get('account.move') @@ -1574,13 +1600,23 @@ class Line(MoveLineMixin, ModelSQL, ModelView): period = Period.find(reconcile_account.company, date=date) account = None journal = None - if writeoff: + if writeoff and writeoff.company == company: if amount >= 0: account = writeoff.debit_account else: account = writeoff.credit_account journal = writeoff.journal + if currency != company.currency: + amount_second_currency = amount + second_currency = currency + with Transaction().set_context(date=date): + amount = Currency.compute( + currency, amount, company.currency) + else: + amount_second_currency = None + second_currency = None + move = Move() move.company = company move.journal = journal @@ -1596,6 +1632,11 @@ class Line(MoveLineMixin, ModelSQL, ModelView): line.party = reconcile_party line.debit = -amount if amount < 0 else 0 line.credit = amount if amount > 0 else 0 + if second_currency: + line.amount_second_currency = ( + amount_second_currency).copy_sign( + line.debit - line.credit) + line.second_currency = second_currency line = cls() lines.append(line) @@ -1604,6 +1645,58 @@ class Line(MoveLineMixin, ModelSQL, ModelView): reconcile_party if account and account.party_required else None) line.debit = amount if amount > 0 else 0 line.credit = -amount if amount < 0 else 0 + if account.second_currency: + with Transaction().set_context(date=date): + line.amount_second_currency = Currency.compute( + company.currency, amount, + reconcile_account.second_currency).copy_sign( + line.debit - line.credit) + + move.lines = lines + return move + + @classmethod + def _get_exchange_move(cls, account, party, amount, date=None): + pool = Pool() + Configuration = pool.get('account.configuration') + Date = pool.get('ir.date') + Move = pool.get('account.move') + Period = pool.get('account.period') + + configuration = Configuration(1) + company = account.company + if not date: + with Transaction().set_context(company=company.id): + date = Date.today() + + period_id = Period.find(company.id, date=date) + + move = Move() + move.company = company + move.journal = configuration.get_multivalue( + 'currency_exchange_journal', company=company.id) + move.period = period_id + move.date = date + + lines = [] + + line = cls() + lines.append(line) + line.account = account + line.party = party + line.debit = -amount if amount < 0 else 0 + line.credit = amount if amount > 0 else 0 + + line = cls() + lines.append(line) + line.debit = amount if amount > 0 else 0 + line.credit = -amount if amount < 0 else 0 + if line.credit: + line.account = configuration.get_multivalue( + 'currency_exchange_credit_account', company=company.id) + else: + line.account = configuration.get_multivalue( + 'currency_exchange_debit_account', company=company.id) move.lines = lines return move @@ -1817,9 +1910,7 @@ class ReconcileLinesWriteOff(ModelView): date = fields.Date('Date', required=True) amount = Monetary( "Amount", currency='currency', digits='currency', readonly=True) - currency = fields.Function(fields.Many2One( - 'currency.currency', "Currency"), - 'on_change_with_currency') + currency = fields.Many2One('currency.currency', "Currency", readonly=True) description = fields.Char('Description') @staticmethod @@ -1827,10 +1918,6 @@ class ReconcileLinesWriteOff(ModelView): Date = Pool().get('ir.date') return Date.today() - @fields.depends('company') - def on_change_with_currency(self, name=None): - return self.company.currency if self.company else None - class ReconcileLines(Wizard): 'Reconcile Lines' @@ -1862,24 +1949,39 @@ class ReconcileLines(Wizard): "Return writeoff amount and company" company = None amount = Decimal('0.0') + amount_second_currency = Decimal('0.0') + second_currencies = set() for line in self.records: amount += line.debit - line.credit + if line.amount_second_currency is not None: + amount_second_currency += line.amount_second_currency + second_currencies.add(line.second_currency) if not company: company = line.account.company - return amount, company + try: + second_currency, = second_currencies + except ValueError: + second_currency = None + if second_currency: + amount = amount_second_currency + currency = second_currency + else: + currency = company.currency + return amount, currency, company def transition_start(self): - amount, company = self.get_writeoff() + amount, currency, company = self.get_writeoff() if not company: return 'end' - if company.currency.is_zero(amount): + if currency.is_zero(amount): return 'reconcile' return 'writeoff' def default_writeoff(self, fields): - amount, company = self.get_writeoff() + amount, currency, company = self.get_writeoff() return { 'amount': amount, + 'currency': currency.id, 'company': company.id, } @@ -1984,7 +2086,7 @@ class Reconcile(Wizard): ))) return [a for a, in cursor] - def get_parties(self, account, _balanced=False, party=None): + def get_parties(self, account, party=None): 'Return a list party to reconcile for the account' pool = Pool() Line = pool.get('account.move.line') @@ -1995,7 +2097,32 @@ class Reconcile(Wizard): lines = [l for l in self.records if not l.reconciliation] return list({l.party for l in lines if l.account == account}) - balance = line.debit - line.credit + where = ((line.reconciliation == Null) + & (line.account == account.id)) + if party: + where &= (line.party == party.id) + cursor.execute(*line.select(line.party, + where=where, + group_by=line.party)) + return [p for p, in cursor] + + def get_currencies(self, account, party, currency=None, _balanced=False): + "Return a list of currencies to reconcile for the account and party" + pool = Pool() + Line = pool.get('account.move.line') + line = Line.__table__() + cursor = Transaction().connection.cursor() + + if self.model and self.model.__name__ == 'account.move.line': + lines = [l for l in self.records if not l.reconciliation] + return list({ + (l.second_currency or l.currency).id + for l in lines + if l.account == account and l.party == party}) + + balance = Case( + (line.second_currency != Null, line.amount_second_currency), + else_=line.debit - line.credit) if _balanced: having = Sum(balance) == 0 else: @@ -2008,12 +2135,15 @@ class Reconcile(Wizard): else_=False) ) where = ((line.reconciliation == Null) - & (line.account == account.id)) - if party: - where &= (line.party == party.id) - cursor.execute(*line.select(line.party, + & (line.account == account.id) + & (line.party == party.id if party else None)) + currency_expr = Coalesce(line.second_currency, account.currency.id) + if currency: + where &= (currency_expr == currency.id) + cursor.execute(*line.select( + currency_expr, where=where, - group_by=line.party, + group_by=line.second_currency, having=having)) return [p for p, in cursor] @@ -2028,8 +2158,9 @@ class Reconcile(Wizard): return account = accounts.pop() self.show.account = account - self.show.parties = self.get_parties(account) self.show.accounts = accounts + self.show.parties = self.get_parties(account) + next_party() return account def next_party(): @@ -2039,18 +2170,35 @@ class Reconcile(Wizard): party = parties.pop() self.show.party = party self.show.parties = parties + self.show.currencies = self.get_currencies( + self.show.account, party) return party, + def next_currency(): + currencies = list(self.show.currencies) + if not currencies: + return + currency = currencies.pop() + self.show.currency = currency + self.show.currencies = currencies + return currency + if getattr(self.show, 'accounts', None) is None: self.show.accounts = self.get_accounts() if not next_account(): return 'end' if getattr(self.show, 'parties', None) is None: self.show.parties = self.get_parties(self.show.account) - - while not next_party(): - if not next_account(): + if not next_party(): return 'end' + if getattr(self.show, 'currencies', None) is None: + self.show.currencies = self.get_currencies( + self.show.account, self.show.party) + + while not next_currency(): + while not next_party(): + if not next_account(): + return 'end' if self.start.automatic or self.start.only_balanced: lines = self._default_lines() if lines and self.start.automatic: @@ -2074,9 +2222,11 @@ class Reconcile(Wizard): defaults['company'] = self.show.account.company.id defaults['parties'] = [p.id for p in self.show.parties] defaults['party'] = self.show.party.id if self.show.party else None - defaults['currency'] = self.show.account.company.currency.id + defaults['currencies'] = [c.id for c in self.show.currencies] + defaults['currency'] = ( + self.show.currency.id if self.show.currency else None) defaults['lines'] = list(map(int, self._default_lines())) - defaults['write_off_amount'] = Decimal(0) + defaults['write_off_amount'] = None with Transaction().set_context(company=self.show.account.company.id): defaults['date'] = Date.today() return defaults @@ -2090,6 +2240,13 @@ class Reconcile(Wizard): ('party', '=', self.show.party.id if self.show.party else None), ('reconciliation', '=', None), + ['OR', + [ + ('currency', '=', self.show.currency.id), + ('second_currency', '=', None), + ], + ('second_currency', '=', self.show.currency.id), + ], ], order=[]) @@ -2102,14 +2259,22 @@ class Reconcile(Wizard): requested = { l for l in self.records if l.account == self.show.account - and l.party == self.show.party} + and l.party == self.show.party + and (l.second_currency or l.currency) == self.show.currency} else: requested = None + currency = self.show.currency - currency = self.show.account.company.currency + def balance(line): + if line.second_currency == currency: + return line.amount_second_currency + elif line.currency == currency: + return line.debit - line.credit + else: + return 0 all_lines = self._all_lines() - amount = sum((l.debit - l.credit) for l in all_lines) + amount = sum(map(balance, all_lines)) if currency.is_zero(amount): return all_lines @@ -2124,7 +2289,7 @@ class Reconcile(Wizard): for comb_lines in combinations(lines, n): if requested and not requested.intersection(comb_lines): continue - amount = sum((l.debit - l.credit) for l in comb_lines) + amount = sum(balance(l) for l in comb_lines) if currency.is_zero(amount): best = comb_lines break @@ -2147,7 +2312,9 @@ class Reconcile(Wizard): writeoff=self.show.write_off, description=self.show.description) - if self.get_parties(self.show.account, party=self.show.party): + if self.get_currencies( + self.show.account, self.show.party, + currency=self.show.currency): return 'show' return 'next_' @@ -2190,11 +2357,21 @@ class ReconcileShow(ModelView): 'company': Eval('company', -1), }, depends={'company'}) + currencies = fields.Many2Many( + 'currency.currency', None, None, "Currencies", readonly=True) + currency = fields.Many2One('currency.currency', "Currency", readonly=True) lines = fields.Many2Many('account.move.line', None, None, 'Lines', domain=[ ('account', '=', Eval('account')), ('party', '=', Eval('party')), ('reconciliation', '=', None), + ['OR', + [ + ('currency', '=', Eval('currency', -1)), + ('second_currency', '=', None), + ], + ('second_currency', '=', Eval('currency', -1)), + ], ]) _write_off_states = { @@ -2202,13 +2379,9 @@ class ReconcileShow(ModelView): 'invisible': ~Eval('write_off_amount', 0), } - write_off_amount = fields.Function(Monetary( - "Amount", currency='currency', digits='currency', - states=_write_off_states), - 'on_change_with_write_off_amount') - currency = fields.Function(fields.Many2One( - 'currency.currency', "Currency"), - 'on_change_with_currency') + write_off_amount = Monetary( + "Amount", currency='currency', digits='currency', readonly=True, + states=_write_off_states) write_off = fields.Many2One( 'account.move.reconcile.write_off', "Write Off", domain=[ @@ -2221,16 +2394,17 @@ class ReconcileShow(ModelView): 'invisible': _write_off_states['invisible'], }) - @fields.depends('lines', 'currency') - def on_change_with_write_off_amount(self, name=None): - amount = sum(((l.debit - l.credit) for l in self.lines), Decimal(0)) + @fields.depends('lines', 'currency', 'company') + def on_change_lines(self): + amount = Decimal(0) + for line in self.lines: + if line.second_currency == self.currency: + amount += line.amount_second_currency + elif line.currency == self.currency: + amount += line.debit - line.credit if self.currency: amount = self.currency.round(amount) - return amount - - @fields.depends('company') - def on_change_with_currency(self, name=None): - return self.company.currency if self.company else None + self.write_off_amount = amount class CancelMoves(Wizard): diff --git a/modules/account/move.xml b/modules/account/move.xml index 3e71baf327..bb95a9fb4c 100644 --- a/modules/account/move.xml +++ b/modules/account/move.xml @@ -208,6 +208,13 @@ this repository contains the full copyright notices and license terms. --> move_line_list_payable_receivable + + account.move.line + tree + + move_line_list_reconcile + + Account Move Lines account.move.line diff --git a/modules/account/tests/scenario_account_reconciliation_alternate_currency.rst b/modules/account/tests/scenario_account_reconciliation_alternate_currency.rst new file mode 100644 index 0000000000..665fc519a7 --- /dev/null +++ b/modules/account/tests/scenario_account_reconciliation_alternate_currency.rst @@ -0,0 +1,111 @@ +========================================= +Account Reconciliation Alternate Currency +========================================= + +Imports:: + + >>> from decimal import Decimal + >>> from proteus import Model, Wizard + >>> from trytond.tests.tools import activate_modules + >>> from trytond.modules.currency.tests.tools import get_currency + >>> from trytond.modules.company.tests.tools import create_company + >>> from trytond.modules.account.tests.tools import ( + ... create_fiscalyear, create_chart, get_accounts) + +Activate modules:: + + >>> config = activate_modules('account') + + >>> Configuration = Model.get('account.configuration') + >>> Journal = Model.get('account.journal') + >>> Move = Model.get('account.move') + >>> Party = Model.get('party.party') + >>> Reconciliation = Model.get('account.move.reconciliation') + +Create currencies:: + + >>> usd = get_currency('USD') + >>> eur = get_currency('EUR') + +Create company:: + + >>> _ = create_company(currency=usd) + +Create fiscal year:: + + >>> fiscalyear = create_fiscalyear() + >>> fiscalyear.click('create_period') + >>> period = fiscalyear.periods[0] + +Create chart of accounts:: + + >>> _ = create_chart() + >>> accounts = get_accounts() + +Configure currency exchange:: + + >>> currency_exchange_account, = ( + ... accounts['revenue'].duplicate( + ... default={'name': "Currency Exchange"})) + >>> configuration = Configuration(1) + >>> configuration.currency_exchange_credit_account = ( + ... currency_exchange_account) + >>> configuration.save() + +Create party:: + + >>> party = Party(name="Party") + >>> party.save() + +Create moves to reconcile:: + + >>> journal_expense, = Journal.find([('code', '=', 'EXP')]) + >>> journal_cash, = Journal.find([('code', '=', 'CASH')]) + + >>> move = Move() + >>> move.period = period + >>> move.journal = journal_expense + >>> move.date = period.start_date + >>> line = move.lines.new() + >>> line.account = accounts['expense'] + >>> line.debit = Decimal('50.00') + >>> line = move.lines.new() + >>> line.account = accounts['payable'] + >>> line.party = party + >>> line.credit = Decimal('50.00') + >>> line.amount_second_currency = Decimal('-90.00') + >>> line.second_currency = eur + >>> move.save() + >>> reconcile1, = [l for l in move.lines if l.account == accounts['payable']] + + >>> move = Move() + >>> move.period = period + >>> move.journal = journal_cash + >>> move.date = period.end_date + >>> line = move.lines.new() + >>> line.account = accounts['cash'] + >>> line.credit = Decimal('45.00') + >>> line = move.lines.new() + >>> line.account = accounts['payable'] + >>> line.party = party + >>> line.debit = Decimal('45.00') + >>> line.amount_second_currency = Decimal('-90.00') + >>> line.second_currency = eur + >>> move.save() + >>> reconcile2, = [l for l in move.lines if l.account == accounts['payable']] + +Reconcile lines:: + + >>> reconcile_lines = Wizard( + ... 'account.move.reconcile_lines', [reconcile1, reconcile2]) + >>> reconcile_lines.state + 'end' + + >>> reconcile1.reconciliation == reconcile2.reconciliation != None + True + >>> reconciliation, = Reconciliation.find([]) + >>> len(reconciliation.lines) + 3 + >>> currency_exchange_account.reload() + >>> currency_exchange_account.balance + Decimal('-5.00') diff --git a/modules/account/tests/scenario_account_reconciliation_alternate_currency_write_off.rst b/modules/account/tests/scenario_account_reconciliation_alternate_currency_write_off.rst new file mode 100644 index 0000000000..fc2a21ca00 --- /dev/null +++ b/modules/account/tests/scenario_account_reconciliation_alternate_currency_write_off.rst @@ -0,0 +1,129 @@ +======================================================== +Account Reconciliation Alternate Currency with Write-Off +======================================================== + +Imports:: + + >>> from decimal import Decimal + >>> from proteus import Model, Wizard + >>> from trytond.tests.tools import activate_modules + >>> from trytond.modules.currency.tests.tools import get_currency + >>> from trytond.modules.company.tests.tools import create_company + >>> from trytond.modules.account.tests.tools import ( + ... create_fiscalyear, create_chart, get_accounts) + +Activate modules:: + + >>> config = activate_modules('account') + + >>> Configuration = Model.get('account.configuration') + >>> Journal = Model.get('account.journal') + >>> Move = Model.get('account.move') + >>> Party = Model.get('party.party') + >>> Reconciliation = Model.get('account.move.reconciliation') + >>> WriteOff = Model.get('account.move.reconcile.write_off') + +Create currencies:: + + >>> usd = get_currency('USD') + >>> eur = get_currency('EUR') + +Create company:: + + >>> _ = create_company(currency=usd) + +Create fiscal year:: + + >>> fiscalyear = create_fiscalyear() + >>> fiscalyear.click('create_period') + >>> period = fiscalyear.periods[0] + +Create chart of accounts:: + + >>> _ = create_chart() + >>> accounts = get_accounts() + +Configure currency exchange:: + + >>> currency_exchange_account, = ( + ... accounts['revenue'].duplicate( + ... default={'name': "Currency Exchange"})) + >>> configuration = Configuration(1) + >>> configuration.currency_exchange_credit_account = ( + ... currency_exchange_account) + >>> configuration.save() + +Create write-off:: + + >>> journal_writeoff = Journal( + ... name="Write-Off", type='write-off') + >>> journal_writeoff.save() + >>> write_off = WriteOff() + >>> write_off.name = "Write-Off" + >>> write_off.journal = journal_writeoff + >>> write_off.debit_account, = accounts['expense'].duplicate() + >>> write_off.credit_account, = accounts['revenue'].duplicate() + >>> write_off.save() + +Create party:: + + >>> party = Party(name="Party") + >>> party.save() + +Create moves to reconcile:: + + >>> journal_expense, = Journal.find([('code', '=', 'EXP')]) + >>> journal_cash, = Journal.find([('code', '=', 'CASH')]) + + >>> move = Move() + >>> move.period = period + >>> move.journal = journal_expense + >>> move.date = period.start_date + >>> line = move.lines.new() + >>> line.account = accounts['expense'] + >>> line.debit = Decimal('50.00') + >>> line = move.lines.new() + >>> line.account = accounts['payable'] + >>> line.party = party + >>> line.credit = Decimal('50.00') + >>> line.amount_second_currency = Decimal('-90.00') + >>> line.second_currency = eur + >>> move.save() + >>> reconcile1, = [l for l in move.lines if l.account == accounts['payable']] + + >>> move = Move() + >>> move.period = period + >>> move.journal = journal_cash + >>> move.date = period.end_date + >>> line = move.lines.new() + >>> line.account = accounts['cash'] + >>> line.credit = Decimal('45.00') + >>> line = move.lines.new() + >>> line.account = accounts['payable'] + >>> line.party = party + >>> line.debit = Decimal('45.00') + >>> line.amount_second_currency = Decimal('-89.00') + >>> line.second_currency = eur + >>> move.save() + >>> reconcile2, = [l for l in move.lines if l.account == accounts['payable']] + +Reconcile lines:: + + >>> reconcile_lines = Wizard( + ... 'account.move.reconcile_lines', [reconcile1, reconcile2]) + >>> reconcile_lines.form_state + 'writeoff' + >>> reconcile_lines.form.writeoff = write_off + >>> reconcile_lines.execute('reconcile') + + >>> reconcile1.reconciliation == reconcile2.reconciliation != None + True + >>> reconciliation, = Reconciliation.find([]) + >>> len(reconciliation.lines) + 4 + >>> write_off.credit_account.reload() + >>> write_off.credit_account.balance + Decimal('-0.50') + >>> currency_exchange_account.reload() + >>> currency_exchange_account.balance + Decimal('-4.50') diff --git a/modules/account/view/configuration_form.xml b/modules/account/view/configuration_form.xml index 65f641f3c9..37d5260159 100644 --- a/modules/account/view/configuration_form.xml +++ b/modules/account/view/configuration_form.xml @@ -17,4 +17,13 @@ this repository contains the full copyright notices and license terms. -->