diff --git a/tryton/modules/account_payment/account.py b/tryton/modules/account_payment/account.py index 801a0e674e..236d5ef56e 100644 --- a/tryton/modules/account_payment/account.py +++ b/tryton/modules/account_payment/account.py @@ -574,7 +574,9 @@ class StatementLine(metaclass=PoolMeta): ('party', '=', Eval('party')), ()), ('state', 'in', ['processing', 'succeeded', 'failed']), - ('currency', '=', Eval('currency', -1)), + If(Eval('second_currency'), + ('currency', '=', Eval('second_currency', -1)), + ('currency', '=', Eval('currency', -1))), ('kind', '=', If(Eval('amount', 0) > 0, 'receivable', If(Eval('amount', 0) < 0, 'payable', ''))), diff --git a/tryton/modules/account_payment_clearing/statement.py b/tryton/modules/account_payment_clearing/statement.py index 60e8f4955d..d76430fd44 100644 --- a/tryton/modules/account_payment_clearing/statement.py +++ b/tryton/modules/account_payment_clearing/statement.py @@ -110,7 +110,9 @@ class StatementLine(metaclass=PoolMeta): ] cls.related_to.domain['account.payment.group'] = [ ('company', '=', Eval('company', -1)), - ('currency', '=', Eval('currency', -1)), + If(Eval('second_currency'), + ('currency', '=', Eval('second_currency', -1)), + ('currency', '=', Eval('currency', -1))), If(Eval('statement_state') == 'draft', ('clearing_reconciled', '!=', True), ()), diff --git a/tryton/modules/account_statement/message.xml b/tryton/modules/account_statement/message.xml index bab7027298..0d19d9439b 100644 --- a/tryton/modules/account_statement/message.xml +++ b/tryton/modules/account_statement/message.xml @@ -36,6 +36,9 @@ this repository contains the full copyright notices and license terms. --> To post statement "%(statement)s" you must create lines for pending %(amount)s of origin "%(origin)s". + + You must set the same sign for second currency than amount. + To post the move "%(move)s" you must post the statement "%(statement)s". diff --git a/tryton/modules/account_statement/statement.py b/tryton/modules/account_statement/statement.py index d3227e77a5..aa93ceebdb 100644 --- a/tryton/modules/account_statement/statement.py +++ b/tryton/modules/account_statement/statement.py @@ -12,7 +12,7 @@ from sql.operators import Concat from trytond.config import config from trytond.i18n import gettext from trytond.model import ( - DictSchemaMixin, Index, ModelSQL, ModelView, Workflow, fields, + Check, DictSchemaMixin, Index, ModelSQL, ModelView, Workflow, fields, sequence_ordered) from trytond.model.exceptions import AccessError from trytond.modules.company import CompanyReport @@ -672,6 +672,9 @@ def origin_mixin(_states): company = fields.Function( fields.Many2One('company.company', "Company"), 'on_change_with_company', searcher='search_company') + company_currency = fields.Function( + fields.Many2One('currency.currency', "Company Currency"), + 'on_change_with_company_currency') number = fields.Char("Number") date = fields.Date( "Date", required=True, states=_states) @@ -680,6 +683,25 @@ def origin_mixin(_states): states=_states) currency = fields.Function(fields.Many2One( 'currency.currency', "Currency"), 'on_change_with_currency') + amount_second_currency = Monetary( + "Amount Second Currency", + currency='second_currency', digits='second_currency', + states={ + 'required': Bool(Eval('second_currency')), + 'readonly': _states['readonly'], + }) + second_currency = fields.Many2One( + 'currency.currency', "Second Currency", + domain=[ + ('id', '!=', Eval('currency', -1)), + If(Eval('currency', -1) != Eval('company_currency', -1), + ('id', '=', Eval('company_currency', -1)), + ()), + ], + states={ + 'required': Bool(Eval('amount_second_currency')), + 'readonly': _states['readonly'], + }) party = fields.Many2One( 'party.party', "Party", states=_states, context={ @@ -723,6 +745,10 @@ def origin_mixin(_states): def search_company(cls, name, clause): return [('statement.' + clause[0],) + tuple(clause[1:])] + @fields.depends('statement', '_parent_statement.company') + def on_change_with_company_currency(self, name=None): + return self.statement.company.currency if self.statement else None + @fields.depends('statement', '_parent_statement.journal') def on_change_with_currency(self, name=None): if self.statement and self.statement.journal: @@ -750,7 +776,10 @@ class Line(origin_mixin(_states), sequence_ordered(), ModelSQL, ModelView): domain={ 'account.invoice': [ ('company', '=', Eval('company', -1)), - ('currency', '=', Eval('currency', -1)), + If(Eval('second_currency'), + ('currency', '=', Eval('second_currency', -1)), + ('currency', '=', Eval('currency', -1)) + ), If(Bool(Eval('party')), ['OR', ('party', '=', Eval('party', -1)), @@ -782,6 +811,7 @@ class Line(origin_mixin(_states), sequence_ordered(), ModelSQL, ModelView): @classmethod def __setup__(cls): super(Line, cls).__setup__() + table = cls.__table__() cls.date.states = { 'readonly': ( (Eval('statement_state') != 'draft') @@ -792,6 +822,14 @@ class Line(origin_mixin(_states), sequence_ordered(), ModelSQL, ModelView): 'required': (Eval('party_required', False) & (Eval('statement_state') == 'draft')), } + cls._sql_constraints += [ + ('second_currency_sign', + Check( + table, + Coalesce(table.amount_second_currency, 0) + * table.amount >= 0), + 'account_statement.msg_statement_line_second_currency_sign'), + ] @classmethod def __register__(cls, module): @@ -1029,24 +1067,29 @@ class Line(origin_mixin(_states), sequence_ordered(), ModelSQL, ModelView): pool = Pool() MoveLine = pool.get('account.move.line') Currency = Pool().get('currency.currency') - zero = Decimal("0.0") if not self.amount: return - with Transaction().set_context(date=self.date): - amount = Currency.compute(self.statement.journal.currency, - self.amount, self.statement.company.currency) - if self.statement.journal.currency != self.statement.company.currency: - second_currency = self.statement.journal.currency.id + if self.second_currency == self.company_currency: + amount = self.amount_second_currency + else: + with Transaction().set_context(date=self.date): + amount = Currency.compute( + self.currency, self.amount, self.company_currency) + if self.currency != self.company_currency: + second_currency = self.currency amount_second_currency = -self.amount + elif self.second_currency: + second_currency = self.second_currency + amount_second_currency = -self.amount_second_currency else: - amount_second_currency = None second_currency = None + amount_second_currency = None return MoveLine( origin=self, description=self.description, - debit=amount < zero and -amount or zero, - credit=amount >= zero and amount or zero, + debit=abs(amount) if amount < 0 else 0, + credit=abs(amount) if amount > 0 else 0, account=self.account, party=self.party if self.account.party_required else None, second_currency=second_currency, @@ -1070,6 +1113,10 @@ class LineGroup(ModelSQL, ModelView): "Amount", currency='currency', digits='currency') currency = fields.Function(fields.Many2One('currency.currency', 'Currency'), 'get_currency') + amount_second_currency = Monetary( + "Amount Second Currency", + currency='second_currency', digits='second_currency') + second_currency = fields.Many2One('currency.currency', "Second Currency") party = fields.Many2One('party.party', 'Party') move = fields.Many2One('account.move', 'Move') @@ -1086,6 +1133,7 @@ class LineGroup(ModelSQL, ModelView): Max(line.number).as_('number'), Max(line.date).as_('date'), Sum(line.amount).as_('amount'), + Sum(line.amount_second_currency).as_('amount_second_currency'), Max(line.party).as_('party'), ] @@ -1103,6 +1151,7 @@ class LineGroup(ModelSQL, ModelView): move.create_date, move.write_uid, move.write_date, + line.second_currency, ] columns = (std_columns + [move.id.as_('move')] diff --git a/tryton/modules/account_statement/tests/scenario_account_statement_second_currency_invoice.rst b/tryton/modules/account_statement/tests/scenario_account_statement_second_currency_invoice.rst new file mode 100644 index 0000000000..5209accc58 --- /dev/null +++ b/tryton/modules/account_statement/tests/scenario_account_statement_second_currency_invoice.rst @@ -0,0 +1,103 @@ +========================================= +Account Statement Second Currency Invoice +========================================= + +Imports:: + + >>> from decimal import Decimal + >>> import datetime as dt + + >>> from proteus import Model + >>> 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) + >>> from trytond.modules.account_invoice.tests.tools import ( + ... set_fiscalyear_invoice_sequences) + + >>> today = dt.date.today() + +Activate modules:: + + >>> config = activate_modules(['account_statement', 'account_invoice']) + + >>> AccountConfiguration = Model.get('account.configuration') + >>> AccountJournal = Model.get('account.journal') + >>> Invoice = Model.get('account.invoice') + >>> Party = Model.get('party.party') + >>> Statement = Model.get('account.statement') + >>> StatementJournal = Model.get('account.statement.journal') + +Create company:: + + >>> usd = get_currency('USD') + >>> eur = get_currency('EUR') + >>> _ = create_company(currency=usd) + +Create fiscal year:: + + >>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear()) + >>> fiscalyear.click('create_period') + +Create chart of accounts:: + + >>> _ = create_chart() + >>> accounts = get_accounts() + +Configure currency exchange:: + + >>> currency_exchange_account, = ( + ... accounts['revenue'].duplicate( + ... default={'name': "Currency Exchange"})) + >>> account_configuration = AccountConfiguration(1) + >>> account_configuration.currency_exchange_debit_account = ( + ... currency_exchange_account) + >>> account_configuration.save() + +Create party:: + + >>> customer = Party(name="Customer") + >>> customer.save() + +Create customer invoice in alternate currency:: + + >>> invoice = Invoice(type='out') + >>> invoice.party = customer + >>> invoice.currency = eur + >>> line = invoice.lines.new() + >>> line.quantity = 1 + >>> line.unit_price = Decimal('50.0000') + >>> line.account = accounts['revenue'] + >>> invoice.click('post') + >>> invoice.state + 'posted' + +Post statement in company currency with second currency:: + + >>> account_journal, = AccountJournal.find([('code', '=', 'STA')], limit=1) + >>> statement_journal = StatementJournal( + ... name="Statement Journal", journal=account_journal, + ... currency=usd, account=accounts['cash']) + >>> statement_journal.save() + + >>> statement = Statement( + ... name="Test", journal=statement_journal, + ... start_balance=Decimal('0.00'), end_balance=Decimal('20.00')) + >>> line = statement.lines.new() + >>> line.number = "1" + >>> line.date = today + >>> line.party = customer + >>> line.amount = Decimal('20.00') + >>> line.amount_second_currency = Decimal('50.00') + >>> line.second_currency = eur + >>> line.related_to = invoice + >>> statement.click('validate_statement') + >>> statement.state + 'validated' + +Check invoice is paid:: + + >>> invoice.reload() + >>> invoice.state + 'paid' diff --git a/tryton/modules/account_statement/view/line_group_form.xml b/tryton/modules/account_statement/view/line_group_form.xml index 24c045781e..f66bd0fd82 100644 --- a/tryton/modules/account_statement/view/line_group_form.xml +++ b/tryton/modules/account_statement/view/line_group_form.xml @@ -5,14 +5,17 @@ this repository contains the full copyright notices and license terms. -->