[IMP] account,*: Improve bank statements/payments workflow
- Create journal entries as soon as bank/cash statement lines are created, temporary booked on a suspense account set on the journal.
- Simplify the management of "blue" lines in the reconciliation widget. A "blue" line is now a journal item using a temporary liquidity account (outstanding payment/receipt accounts, set on the journal).
- Adapt and simplify the bank reconciliation report.
- Remove the bank reconciliation threshold date. The reconciliation report will show the not already reconciled journal entries using a liquidity account and the not already reconciled journal entries using a temporary liquidity account. Without accounting, an account.payment will involve directly the liquidity account and then, will be considered as a statement line directly.
- Remove the post_at bank reconciliation feature. The "paid" state will be set on the invoices only if reconciled with a journal entry involving the journal's liquidity account.
With invoicing, the payment will do that so the "in_payment" state should never be shown up.
With accounting, only the statement lines have the power to move an invoice to the "paid" state.
- Fix various corner cases about the management of multi-currency in bank statement lines.
- Fix the conversion dates in multi-currency: Since the bank/cash is always used on the statement lines, it will use always the real "bank" date instead of the fictive payment one.
- Ensure the 'reconcile' method will raise an error if the involved moves are not posted.
related enterprise PR odoo/enterprise#7019
closes odoo/odoo#41301
--task: 2092096
Related: odoo/upgrade#1018
Signed-off-by: Quentin De Paoli (qdp) <qdp@openerp.com>
This commit is contained in:
@@ -27,6 +27,7 @@ You could use this simplified accounting in case you work with an (external) acc
|
||||
'wizard/account_unreconcile_view.xml',
|
||||
'wizard/account_move_reversal_view.xml',
|
||||
'wizard/account_resequence_views.xml',
|
||||
'wizard/account_payment_register_views.xml',
|
||||
'views/account_move_views.xml',
|
||||
'wizard/setup_wizards_view.xml',
|
||||
'wizard/pos_box.xml',
|
||||
|
||||
@@ -127,58 +127,6 @@
|
||||
<field name="payment_type">outbound</field>
|
||||
</record>
|
||||
|
||||
<!-- Payment sequences -->
|
||||
<record id="sequence_payment_customer_invoice" model="ir.sequence">
|
||||
<field name="name">Payments customer invoices sequence</field>
|
||||
<field name="code">account.payment.customer.invoice</field>
|
||||
<field name="prefix">CUST.IN/%(range_year)s/</field>
|
||||
<field eval="1" name="number_next"/>
|
||||
<field eval="1" name="number_increment"/>
|
||||
<field eval="True" name="use_date_range"/>
|
||||
<field eval="False" name="company_id"/>
|
||||
<field name="padding">4</field>
|
||||
</record>
|
||||
<record id="sequence_payment_customer_refund" model="ir.sequence">
|
||||
<field name="name">Payments customer credit notes sequence</field>
|
||||
<field name="code">account.payment.customer.refund</field>
|
||||
<field name="prefix">CUST.OUT/%(range_year)s/</field>
|
||||
<field eval="1" name="number_next"/>
|
||||
<field eval="1" name="number_increment"/>
|
||||
<field eval="True" name="use_date_range"/>
|
||||
<field eval="False" name="company_id"/>
|
||||
<field name="padding">4</field>
|
||||
</record>
|
||||
<record id="sequence_payment_supplier_invoice" model="ir.sequence">
|
||||
<field name="name">Payments supplier invoices sequence</field>
|
||||
<field name="code">account.payment.supplier.invoice</field>
|
||||
<field name="prefix">SUPP.OUT/%(range_year)s/</field>
|
||||
<field eval="1" name="number_next"/>
|
||||
<field eval="1" name="number_increment"/>
|
||||
<field eval="True" name="use_date_range"/>
|
||||
<field eval="False" name="company_id"/>
|
||||
<field name="padding">4</field>
|
||||
</record>
|
||||
<record id="sequence_payment_supplier_refund" model="ir.sequence">
|
||||
<field name="name">Payments supplier credit notes sequence</field>
|
||||
<field name="code">account.payment.supplier.refund</field>
|
||||
<field name="prefix">SUPP.IN/%(range_year)s/</field>
|
||||
<field eval="1" name="number_next"/>
|
||||
<field eval="1" name="number_increment"/>
|
||||
<field eval="True" name="use_date_range"/>
|
||||
<field eval="False" name="company_id"/>
|
||||
<field name="padding">4</field>
|
||||
</record>
|
||||
<record id="sequence_payment_transfer" model="ir.sequence">
|
||||
<field name="name">Payments transfer sequence</field>
|
||||
<field name="code">account.payment.transfer</field>
|
||||
<field name="prefix">TRANS/%(range_year)s/</field>
|
||||
<field eval="1" name="number_next"/>
|
||||
<field eval="1" name="number_increment"/>
|
||||
<field eval="True" name="use_date_range"/>
|
||||
<field eval="False" name="company_id"/>
|
||||
<field name="padding">4</field>
|
||||
</record>
|
||||
|
||||
<!-- Account Tax Group -->
|
||||
<record id="tax_group_taxes" model="account.tax.group">
|
||||
<field name="name">Taxes</field>
|
||||
|
||||
@@ -4,11 +4,9 @@ from . import sequence_mixin
|
||||
from . import partner
|
||||
from . import account
|
||||
from . import account_reconcile_model
|
||||
from . import account_payment
|
||||
from . import account_payment_term
|
||||
from . import account_bank_statement
|
||||
from . import account_move
|
||||
from . import account_payment_term
|
||||
from . import account_payment
|
||||
from . import account_bank_statement
|
||||
from . import chart_template
|
||||
from . import account_analytic_line
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
import math
|
||||
import re
|
||||
import logging
|
||||
|
||||
from odoo.osv import expression
|
||||
from odoo.tools.float_utils import float_round as round, float_compare
|
||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo import api, fields, models, _, tools
|
||||
from odoo.tests.common import Form
|
||||
|
||||
TYPE_TAX_USE = [
|
||||
('sale', 'Sales'),
|
||||
@@ -17,6 +14,8 @@ TYPE_TAX_USE = [
|
||||
('none', 'None'),
|
||||
]
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountAccountType(models.Model):
|
||||
_name = "account.account.type"
|
||||
@@ -415,6 +414,43 @@ class AccountAccount(models.Model):
|
||||
if ids:
|
||||
raise ValidationError(_('Some journal items already exist with this account but in other journals than the allowed ones.'))
|
||||
|
||||
@api.constrains('currency_id')
|
||||
def _check_journal_consistency(self):
|
||||
''' Ensure the currency set on the journal is the same as the currency set on the
|
||||
linked accounts.
|
||||
'''
|
||||
if not self:
|
||||
return
|
||||
|
||||
self.env['account.account'].flush(['currency_id'])
|
||||
self.env['account.journal'].flush([
|
||||
'currency_id',
|
||||
'default_debit_account_id',
|
||||
'default_credit_account_id',
|
||||
'payment_debit_account_id',
|
||||
'payment_credit_account_id',
|
||||
'suspense_account_id',
|
||||
])
|
||||
self._cr.execute('''
|
||||
SELECT account.id, journal.id
|
||||
FROM account_account account
|
||||
JOIN res_company company ON company.id = account.company_id
|
||||
JOIN account_journal journal ON
|
||||
journal.default_debit_account_id = account.id
|
||||
OR
|
||||
journal.default_credit_account_id = account.id
|
||||
WHERE account.id IN %s
|
||||
AND journal.type IN ('bank', 'cash')
|
||||
AND journal.currency_id IS NOT NULL
|
||||
AND journal.currency_id != company.currency_id
|
||||
AND account.currency_id != journal.currency_id
|
||||
''', [tuple(self.ids)])
|
||||
res = self._cr.fetchone()
|
||||
if res:
|
||||
account = self.env['account.account'].browse(res[0])
|
||||
journal = self.env['account.journal'].browse(res[1])
|
||||
raise ValidationError(_("The foreign currency set on the journal '%s' and the account '%s' must be the same.") % (journal.display_name, account.display_name))
|
||||
|
||||
@api.constrains('company_id')
|
||||
def _check_company_consistency(self):
|
||||
if not self:
|
||||
@@ -949,14 +985,33 @@ class AccountJournal(models.Model):
|
||||
account_control_ids = fields.Many2many('account.account', 'journal_account_control_rel', 'journal_id', 'account_id', string='Accounts Allowed',
|
||||
check_company=True,
|
||||
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]")
|
||||
default_credit_account_id = fields.Many2one('account.account', string='Default Credit Account',
|
||||
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]",
|
||||
help="It acts as a default account for credit amount",
|
||||
default_credit_account_id = fields.Many2one('account.account', string='Default Credit Account', copy=False,
|
||||
domain=[('deprecated', '=', False)], help="It acts as a default account for credit amount",
|
||||
check_company=True,
|
||||
ondelete='restrict')
|
||||
default_debit_account_id = fields.Many2one('account.account', string='Default Debit Account',
|
||||
default_debit_account_id = fields.Many2one('account.account', string='Default Debit Account', copy=False,
|
||||
check_company=True,
|
||||
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]", help="It acts as a default account for debit amount", ondelete='restrict')
|
||||
payment_debit_account_id = fields.Many2one('account.account', string='Outstanding Receipts Account', copy=False,
|
||||
ondelete='restrict',
|
||||
domain=[('deprecated', '=', False)],
|
||||
help="Incoming payments entries triggered by invoices/refunds will be posted on the Outstanding Receipts Account "
|
||||
"and displayed as blue lines in the bank reconciliation widget. During the reconciliation process, concerned "
|
||||
"transactions will be reconciled with entries on the Outstanding Receipts Account instead of the "
|
||||
"receivable account.")
|
||||
payment_credit_account_id = fields.Many2one('account.account', string='Outstanding Payments Account', copy=False,
|
||||
ondelete='restrict',
|
||||
domain=[('deprecated', '=', False)],
|
||||
help="Outgoing payments entries triggered by bills/credit notes will be posted on the Outstanding Payments Account "
|
||||
"and displayed as blue lines in the bank reconciliation widget. During the reconciliation process, concerned "
|
||||
"transactions will be reconciled with entries on the Outstanding Payments Account instead of the "
|
||||
"payable account.")
|
||||
suspense_account_id = fields.Many2one('account.account', string='Bank Suspense Account',
|
||||
ondelete='restrict', readonly=False, store=True, require=True,
|
||||
compute='_compute_suspense_account_id',
|
||||
domain=[('deprecated', '=', False), ('reconcile', '=', True)],
|
||||
help="Bank statements transactions will be posted on the suspense account until the final reconciliation "
|
||||
"allowing finding the right account.")
|
||||
restrict_mode_hash_table = fields.Boolean(string="Lock Posted Entries with Hash",
|
||||
help="If ticked, the accounting entry or invoice receives a hash as soon as it is posted and cannot be modified anymore.")
|
||||
sequence = fields.Integer(help='Used to order Journals in the dashboard view', default=10)
|
||||
@@ -1009,7 +1064,6 @@ class AccountJournal(models.Model):
|
||||
bank_statements_source = fields.Selection(selection=_get_bank_statements_available_sources, string='Bank Feeds', default='undefined', help="Defines how the bank statements will be registered")
|
||||
bank_acc_number = fields.Char(related='bank_account_id.acc_number', readonly=False)
|
||||
bank_id = fields.Many2one('res.bank', related='bank_account_id.bank_id', readonly=False)
|
||||
post_at = fields.Selection([('pay_val', 'Payment Validation'), ('bank_rec', 'Bank Reconciliation')], string="Post At", default='pay_val')
|
||||
|
||||
# Sale journals fields
|
||||
sale_activity_type_id = fields.Many2one('mail.activity.type', string='Schedule Activity', default=False, help="Activity will be automatically scheduled on payment due date, improving collection process.")
|
||||
@@ -1035,6 +1089,18 @@ class AccountJournal(models.Model):
|
||||
('code_company_uniq', 'unique (code, name, company_id)', 'The code and name of the journal must be unique per company !'),
|
||||
]
|
||||
|
||||
@api.depends('company_id', 'type')
|
||||
def _compute_suspense_account_id(self):
|
||||
for journal in self:
|
||||
if journal.type not in ('bank', 'cash'):
|
||||
journal.suspense_account_id = False
|
||||
elif journal.suspense_account_id:
|
||||
journal.suspense_account_id = journal.suspense_account_id
|
||||
elif journal.company_id.account_journal_suspense_account_id:
|
||||
journal.suspense_account_id = journal.company_id.account_journal_suspense_account_id
|
||||
else:
|
||||
journal.suspense_account_id = False
|
||||
|
||||
def _compute_alias_domain(self):
|
||||
alias_domain = self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain")
|
||||
for record in self:
|
||||
@@ -1070,15 +1136,6 @@ class AccountJournal(models.Model):
|
||||
if self._cr.fetchone():
|
||||
raise ValidationError(_('Some journal items already exist in this journal but with other accounts than the allowed ones.'))
|
||||
|
||||
@api.constrains('currency_id', 'default_credit_account_id', 'default_debit_account_id')
|
||||
def _check_currency(self):
|
||||
for journal in self:
|
||||
if journal.currency_id:
|
||||
if journal.default_credit_account_id and not journal.default_credit_account_id.currency_id.id == journal.currency_id.id:
|
||||
raise ValidationError(_('The currency of the journal should be the same than the default credit account.'))
|
||||
if journal.default_debit_account_id and not journal.default_debit_account_id.currency_id.id == journal.currency_id.id:
|
||||
raise ValidationError(_('The currency of the journal should be the same than the default debit account.'))
|
||||
|
||||
@api.constrains('type', 'bank_account_id')
|
||||
def _check_bank_account(self):
|
||||
for journal in self:
|
||||
@@ -1106,6 +1163,43 @@ class AccountJournal(models.Model):
|
||||
if self._cr.fetchone():
|
||||
raise UserError(_("You can't change the company of your journal since there are some journal entries linked to it."))
|
||||
|
||||
@api.constrains('default_debit_account_id', 'default_credit_account_id', 'payment_debit_account_id', 'payment_credit_account_id')
|
||||
def _check_journal_not_shared_accounts(self):
|
||||
accounts = self.default_debit_account_id \
|
||||
+ self.default_credit_account_id \
|
||||
+ self.payment_debit_account_id \
|
||||
+ self.payment_credit_account_id
|
||||
|
||||
if not accounts:
|
||||
return
|
||||
|
||||
self.env['account.journal'].flush([
|
||||
'default_debit_account_id',
|
||||
'default_credit_account_id',
|
||||
'payment_debit_account_id',
|
||||
'payment_credit_account_id',
|
||||
])
|
||||
self._cr.execute('''
|
||||
SELECT
|
||||
account.name,
|
||||
ARRAY_AGG(DISTINCT journal.name) AS journal_names
|
||||
FROM account_account account
|
||||
LEFT JOIN account_journal journal ON
|
||||
journal.default_debit_account_id = account.id
|
||||
OR
|
||||
journal.default_credit_account_id = account.id
|
||||
OR
|
||||
journal.payment_debit_account_id = account.id
|
||||
OR
|
||||
journal.payment_credit_account_id = account.id
|
||||
WHERE account.id IN %s
|
||||
GROUP BY account.name
|
||||
HAVING COUNT(DISTINCT journal.id) > 1
|
||||
''', [tuple(accounts.ids)])
|
||||
res = self._cr.fetchone()
|
||||
if res:
|
||||
raise ValidationError(_("The account %s can't be shared between multiple journals: %s") % (res[0], ', '.join(res[1])))
|
||||
|
||||
@api.constrains('type', 'default_credit_account_id', 'default_debit_account_id')
|
||||
def _check_type_default_credit_account_id_type(self):
|
||||
journals_to_check = self.filtered(lambda journal: journal.type in ('sale', 'purchase'))
|
||||
@@ -1183,10 +1277,6 @@ class AccountJournal(models.Model):
|
||||
'partner_id': company.partner_id.id,
|
||||
})
|
||||
if 'currency_id' in vals:
|
||||
if not 'default_debit_account_id' in vals and journal.default_debit_account_id:
|
||||
journal.default_debit_account_id.currency_id = vals['currency_id']
|
||||
if not 'default_credit_account_id' in vals and journal.default_credit_account_id:
|
||||
journal.default_credit_account_id.currency_id = vals['currency_id']
|
||||
if journal.bank_account_id:
|
||||
journal.bank_account_id.currency_id = vals['currency_id']
|
||||
if 'bank_account_id' in vals:
|
||||
@@ -1205,16 +1295,15 @@ class AccountJournal(models.Model):
|
||||
raise UserError(_("You cannot modify the field %s of a journal that already has accounting entries.") % field_string)
|
||||
result = super(AccountJournal, self).write(vals)
|
||||
|
||||
for journal in self:
|
||||
# Ensure the liquidity accounts are sharing the same foreign currency.
|
||||
accounts = journal.default_debit_account_id + journal.default_credit_account_id
|
||||
accounts.write({'currency_id': journal.currency_id.id})
|
||||
|
||||
# Create the bank_account_id if necessary
|
||||
if 'bank_acc_number' in vals:
|
||||
for journal in self.filtered(lambda r: r.type == 'bank' and not r.bank_account_id):
|
||||
journal.set_bank_account(vals.get('bank_acc_number'), vals.get('bank_id'))
|
||||
# Changing the 'post_at' option will post the draft payment moves and change the related invoices' state.
|
||||
if 'post_at' in vals and vals['post_at'] != 'bank_rec':
|
||||
draft_moves = self.env['account.move'].search([('journal_id', 'in', self.ids), ('state', '=', 'draft')])
|
||||
pending_payments = draft_moves.mapped('line_ids.payment_id')
|
||||
pending_payments.mapped('move_line_ids.move_id').post()
|
||||
pending_payments.mapped('reconciled_invoice_ids').filtered(lambda x: x.state == 'in_payment').write({'state': 'paid'})
|
||||
for record in self:
|
||||
if record.restrict_mode_hash_table and not record.secure_sequence_id:
|
||||
record._create_secure_sequence(['secure_sequence_id'])
|
||||
@@ -1222,41 +1311,9 @@ class AccountJournal(models.Model):
|
||||
return result
|
||||
|
||||
@api.model
|
||||
def _prepare_liquidity_account(self, name, company, currency_id, type):
|
||||
'''
|
||||
This function prepares the value to use for the creation of the default debit and credit accounts of a
|
||||
liquidity journal (created through the wizard of generating COA from templates for example).
|
||||
|
||||
:param name: name of the bank account
|
||||
:param company: company for which the wizard is running
|
||||
:param currency_id: ID of the currency in which is the bank account
|
||||
:param type: either 'cash' or 'bank'
|
||||
:return: mapping of field names and values
|
||||
:rtype: dict
|
||||
'''
|
||||
digits = 6
|
||||
acc = self.env['account.account'].search([('company_id', '=', company.id)], limit=1)
|
||||
if acc:
|
||||
digits = len(acc.code)
|
||||
# Seek the next available number for the account code
|
||||
if type == 'bank':
|
||||
account_code_prefix = company.bank_account_code_prefix or ''
|
||||
else:
|
||||
account_code_prefix = company.cash_account_code_prefix or company.bank_account_code_prefix or ''
|
||||
|
||||
liquidity_type = self.env.ref('account.data_account_type_liquidity')
|
||||
return {
|
||||
'name': name,
|
||||
'currency_id': currency_id or False,
|
||||
'code': self.env['account.account']._search_new_account_code(company, digits, account_code_prefix),
|
||||
'user_type_id': liquidity_type and liquidity_type.id or False,
|
||||
'company_id': company.id,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def get_next_bank_cash_default_code(self, journal_type, company_id):
|
||||
def get_next_bank_cash_default_code(self, journal_type, company):
|
||||
journal_code_base = (journal_type == 'cash' and 'CSH' or 'BNK')
|
||||
journals = self.env['account.journal'].search([('code', 'like', journal_code_base + '%'), ('company_id', '=', company_id)])
|
||||
journals = self.env['account.journal'].search([('code', 'like', journal_code_base + '%'), ('company_id', '=', company.id)])
|
||||
for num in range(1, 100):
|
||||
# journal_code has a maximal size of 5, hence we can enforce the boundary num < 100
|
||||
journal_code = journal_code_base + str(num)
|
||||
@@ -1264,38 +1321,89 @@ class AccountJournal(models.Model):
|
||||
return journal_code
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
company_id = vals.get('company_id', self.env.company.id)
|
||||
if vals.get('type') in ('bank', 'cash'):
|
||||
# For convenience, the name can be inferred from account number
|
||||
if not vals.get('name') and 'bank_acc_number' in vals:
|
||||
vals['name'] = vals['bank_acc_number']
|
||||
def _fill_missing_values(self, vals):
|
||||
journal_type = vals.get('type')
|
||||
|
||||
# If no code provided, loop to find next available journal code
|
||||
if not vals.get('code'):
|
||||
vals['code'] = self.get_next_bank_cash_default_code(vals['type'], company_id)
|
||||
# 'type' field is required.
|
||||
if not journal_type:
|
||||
return
|
||||
|
||||
# === Fill missing company ===
|
||||
company = self.env['res.company'].browse(vals['company_id']) if vals.get('company_id') else self.env.company
|
||||
vals['company_id'] = company.id
|
||||
|
||||
# Don't get the digits on 'chart_template_id' since the chart template could be a custom one.
|
||||
random_account = self.env['account.account'].search([('company_id', '=', company.id)], limit=1)
|
||||
digits = len(random_account.code) if random_account else 6
|
||||
|
||||
liquidity_type = self.env.ref('account.data_account_type_liquidity')
|
||||
current_assets_type = self.env.ref('account.data_account_type_current_assets')
|
||||
|
||||
if journal_type in ('bank', 'cash'):
|
||||
has_liquidity_accounts = vals.get('default_debit_account_id') or vals.get('default_credit_account_id')
|
||||
has_payment_accounts = vals.get('payment_debit_account_id') or vals.get('payment_credit_account_id')
|
||||
has_profit_account = vals.get('profit_account_id')
|
||||
has_loss_account = vals.get('loss_account_id')
|
||||
|
||||
if journal_type == 'bank':
|
||||
liquidity_account_prefix = company.bank_account_code_prefix or ''
|
||||
else:
|
||||
liquidity_account_prefix = company.cash_account_code_prefix or company.bank_account_code_prefix or ''
|
||||
|
||||
# === Fill missing name ===
|
||||
vals['name'] = vals.get('name') or vals.get('bank_acc_number')
|
||||
|
||||
# === Fill missing code ===
|
||||
if 'code' not in vals:
|
||||
vals['code'] = self.get_next_bank_cash_default_code(journal_type, company)
|
||||
if not vals['code']:
|
||||
raise UserError(_("Cannot generate an unused journal code. Please fill the 'Shortcode' field."))
|
||||
|
||||
# === Fill missing accounts ===
|
||||
if not has_liquidity_accounts:
|
||||
liquidity_account = self.env['account.account'].create({
|
||||
'name': vals.get('name'),
|
||||
'code': self.env['account.account']._search_new_account_code(company, digits, liquidity_account_prefix),
|
||||
'user_type_id': liquidity_type.id,
|
||||
'currency_id': vals.get('currency_id'),
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
# Create a default debit/credit account if not given
|
||||
default_account = vals.get('default_debit_account_id') or vals.get('default_credit_account_id')
|
||||
company = self.env['res.company'].browse(company_id)
|
||||
if not default_account:
|
||||
account_vals = self._prepare_liquidity_account(vals.get('name'), company, vals.get('currency_id'), vals.get('type'))
|
||||
default_account = self.env['account.account'].create(account_vals)
|
||||
vals['default_debit_account_id'] = default_account.id
|
||||
vals['default_credit_account_id'] = default_account.id
|
||||
if vals['type'] == 'cash':
|
||||
if not vals.get('profit_account_id'):
|
||||
vals['profit_account_id'] = company.default_cash_difference_income_account_id.id
|
||||
if not vals.get('loss_account_id'):
|
||||
vals['loss_account_id'] = company.default_cash_difference_expense_account_id.id
|
||||
vals.update({
|
||||
'default_debit_account_id': liquidity_account.id,
|
||||
'default_credit_account_id': liquidity_account.id,
|
||||
})
|
||||
if not has_payment_accounts:
|
||||
vals['payment_debit_account_id'] = self.env['account.account'].create({
|
||||
'name': _("Outstanding Receipts"),
|
||||
'code': self.env['account.account']._search_new_account_code(company, digits, liquidity_account_prefix),
|
||||
'reconcile': True,
|
||||
'user_type_id': current_assets_type.id,
|
||||
'company_id': company.id,
|
||||
}).id
|
||||
vals['payment_credit_account_id'] = self.env['account.account'].create({
|
||||
'name': _("Outstanding Payments"),
|
||||
'code': self.env['account.account']._search_new_account_code(company, digits, liquidity_account_prefix),
|
||||
'reconcile': True,
|
||||
'user_type_id': current_assets_type.id,
|
||||
'company_id': company.id,
|
||||
}).id
|
||||
if journal_type == 'cash' and not has_profit_account:
|
||||
vals['profit_account_id'] = company.default_cash_difference_income_account_id.id
|
||||
if journal_type == 'cash' and not has_loss_account:
|
||||
vals['loss_account_id'] = company.default_cash_difference_expense_account_id.id
|
||||
|
||||
# === Fill missing refund_sequence ===
|
||||
if 'refund_sequence' not in vals:
|
||||
vals['refund_sequence'] = vals['type'] in ('sale', 'purchase')
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
# OVERRIDE
|
||||
self._fill_missing_values(vals)
|
||||
|
||||
journal = super(AccountJournal, self.with_context(mail_create_nolog=True)).create(vals)
|
||||
|
||||
if 'alias_name' in vals:
|
||||
journal._update_mail_alias(vals)
|
||||
|
||||
@@ -1401,6 +1509,123 @@ class AccountJournal(models.Model):
|
||||
if vals_write:
|
||||
journal.write(vals_write)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# REPORTING METHODS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def _get_journal_bank_account_balance(self, domain=None):
|
||||
''' Get the bank balance of the current journal by filtering the journal items using the journal's accounts.
|
||||
|
||||
/!\ The current journal is not part of the applied domain. This is the expected behavior since we only want
|
||||
a logic based on accounts.
|
||||
|
||||
:param domain: An additional domain to be applied on the account.move.line model.
|
||||
:return: The balance expressed in the journal's currency.
|
||||
'''
|
||||
self.ensure_one()
|
||||
self.env['account.move.line'].check_access_rights('read')
|
||||
|
||||
accounts = self.default_debit_account_id + self.default_credit_account_id
|
||||
if not accounts:
|
||||
return 0.0
|
||||
|
||||
domain = (domain or []) + [
|
||||
('account_id', 'in', tuple(accounts.ids)),
|
||||
('display_type', 'not in', ('line_section', 'line_note')),
|
||||
('move_id.state', '!=', 'cancel'),
|
||||
]
|
||||
query = self.env['account.move.line']._where_calc(domain)
|
||||
tables, where_clause, where_params = query.get_sql()
|
||||
|
||||
query = '''
|
||||
SELECT
|
||||
COUNT(account_move_line.id) AS nb_lines,
|
||||
COALESCE(SUM(account_move_line.balance), 0.0),
|
||||
COALESCE(SUM(account_move_line.amount_currency), 0.0)
|
||||
FROM ''' + tables + '''
|
||||
WHERE ''' + where_clause + '''
|
||||
'''
|
||||
self._cr.execute(query, where_params)
|
||||
|
||||
company_currency = self.company_id.currency_id
|
||||
journal_currency = self.currency_id if self.currency_id and self.currency_id != company_currency else False
|
||||
|
||||
nb_lines, balance, amount_currency = self._cr.fetchone()
|
||||
return amount_currency if journal_currency else balance, nb_lines
|
||||
|
||||
def _get_journal_outstanding_payments_account_balance(self, domain=None, date=None):
|
||||
''' Get the outstanding payments balance of the current journal by filtering the journal items using the
|
||||
journal's accounts.
|
||||
|
||||
/!\ The current journal is not part of the applied domain. This is the expected behavior since we only want
|
||||
a logic based on accounts.
|
||||
|
||||
:param domain: An additional domain to be applied on the account.move.line model.
|
||||
:param date: The date to be used when performing the currency conversions.
|
||||
:return: The balance expressed in the journal's currency.
|
||||
'''
|
||||
self.ensure_one()
|
||||
self.env['account.move.line'].check_access_rights('read')
|
||||
conversion_date = date or fields.Date.context_today(self)
|
||||
|
||||
accounts = self.payment_debit_account_id + self.payment_credit_account_id
|
||||
if not accounts:
|
||||
return 0.0
|
||||
|
||||
domain = (domain or []) + [
|
||||
('account_id', 'in', tuple(accounts.ids)),
|
||||
('display_type', 'not in', ('line_section', 'line_note')),
|
||||
('move_id.state', '!=', 'cancel'),
|
||||
('reconciled', '=', False),
|
||||
]
|
||||
query = self.env['account.move.line']._where_calc(domain)
|
||||
tables, where_clause, where_params = query.get_sql()
|
||||
|
||||
self._cr.execute('''
|
||||
SELECT
|
||||
COUNT(account_move_line.id) AS nb_lines,
|
||||
account_move_line.currency_id,
|
||||
account.reconcile AS is_account_reconcile,
|
||||
SUM(account_move_line.amount_residual) AS amount_residual,
|
||||
SUM(account_move_line.balance) AS balance,
|
||||
SUM(account_move_line.amount_residual_currency) AS amount_residual_currency,
|
||||
SUM(account_move_line.amount_currency) AS amount_currency
|
||||
FROM ''' + tables + '''
|
||||
JOIN account_account account ON account.id = account_move_line.account_id
|
||||
WHERE ''' + where_clause + '''
|
||||
GROUP BY account_move_line.currency_id, account.reconcile
|
||||
''', where_params)
|
||||
|
||||
company_currency = self.company_id.currency_id
|
||||
journal_currency = self.currency_id if self.currency_id and self.currency_id != company_currency else False
|
||||
balance_currency = journal_currency or company_currency
|
||||
|
||||
total_balance = 0.0
|
||||
nb_lines = 0
|
||||
for res in self._cr.dictfetchall():
|
||||
nb_lines += res['nb_lines']
|
||||
|
||||
amount_currency = res['amount_residual_currency'] if res['is_account_reconcile'] else res['amount_currency']
|
||||
balance = res['amount_residual'] if res['is_account_reconcile'] else res['balance']
|
||||
|
||||
if res['currency_id'] and journal_currency and res['currency_id'] == journal_currency.id:
|
||||
total_balance += amount_currency
|
||||
elif journal_currency:
|
||||
total_balance += company_currency._convert(balance, balance_currency, self.company_id, conversion_date)
|
||||
else:
|
||||
total_balance += balance
|
||||
return total_balance, nb_lines
|
||||
|
||||
def _get_last_bank_statement(self, domain=None):
|
||||
''' Retrieve the last bank statement created using this journal.
|
||||
:param domain: An additional domain to be applied on the account.bank.statement model.
|
||||
:return: An account.bank.statement record or an empty recordset.
|
||||
'''
|
||||
self.ensure_one()
|
||||
last_statement_domain = (domain or []) + [('journal_id', '=', self.id)]
|
||||
last_st_line = self.env['account.bank.statement.line'].search(last_statement_domain, order='date desc, id desc', limit=1)
|
||||
return last_st_line.statement_id
|
||||
|
||||
|
||||
class ResPartnerBank(models.Model):
|
||||
_inherit = "res.partner.bank"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -100,7 +100,7 @@ class account_journal(models.Model):
|
||||
locale = get_lang(self.env).code
|
||||
|
||||
#starting point of the graph is the last statement
|
||||
last_stmt = BankStatement.search([('journal_id', '=', self.id), ('date', '<=', today.strftime(DF))], order='date desc, id desc', limit=1)
|
||||
last_stmt = self._get_last_bank_statement(domain=[('move_id.state', '=', 'posted')])
|
||||
|
||||
last_balance = last_stmt and last_stmt.balance_end_real or 0
|
||||
data.append(build_graph_data(today, last_balance))
|
||||
@@ -109,15 +109,16 @@ class account_journal(models.Model):
|
||||
#(graph is drawn backward)
|
||||
date = today
|
||||
amount = last_balance
|
||||
query = """SELECT l.date, sum(l.amount) as amount
|
||||
FROM account_bank_statement_line l
|
||||
RIGHT JOIN account_bank_statement st ON l.statement_id = st.id
|
||||
WHERE st.journal_id = %s
|
||||
AND l.date > %s
|
||||
AND l.date <= %s
|
||||
GROUP BY l.date
|
||||
ORDER BY l.date desc
|
||||
"""
|
||||
query = '''
|
||||
SELECT move.date, sum(st_line.amount) as amount
|
||||
FROM account_bank_statement_line st_line
|
||||
JOIN account_move move ON move.id = st_line.move_id
|
||||
WHERE move.journal_id = %s
|
||||
AND move.date > %s
|
||||
AND move.date <= %s
|
||||
GROUP BY move.date
|
||||
ORDER BY move.date desc
|
||||
'''
|
||||
self.env.cr.execute(query, (self.id, last_month, today))
|
||||
query_result = self.env.cr.dictfetchall()
|
||||
for val in query_result:
|
||||
@@ -218,37 +219,37 @@ class account_journal(models.Model):
|
||||
|
||||
def get_journal_dashboard_datas(self):
|
||||
currency = self.currency_id or self.company_id.currency_id
|
||||
number_to_reconcile = number_to_check = last_balance = account_sum = 0
|
||||
number_to_reconcile = number_to_check = last_balance = 0
|
||||
has_at_least_one_statement = False
|
||||
bank_account_balance = nb_lines_bank_account_balance = 0
|
||||
outstanding_pay_account_balance = nb_lines_outstanding_pay_account_balance = 0
|
||||
title = ''
|
||||
number_draft = number_waiting = number_late = to_check_balance = 0
|
||||
sum_draft = sum_waiting = sum_late = 0.0
|
||||
if self.type in ['bank', 'cash']:
|
||||
last_bank_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids)], order="date desc, id desc", limit=1)
|
||||
last_balance = last_bank_stmt and last_bank_stmt[0].balance_end or 0
|
||||
#Get the number of items to reconcile for that bank journal
|
||||
self.env.cr.execute("""SELECT COUNT(DISTINCT(line.id))
|
||||
FROM account_bank_statement_line AS line
|
||||
LEFT JOIN account_bank_statement AS st
|
||||
ON line.statement_id = st.id
|
||||
WHERE st.journal_id IN %s AND st.state = 'open' AND line.amount != 0.0 AND line.account_id IS NULL
|
||||
AND not exists (select 1 from account_move_line aml where aml.statement_line_id = line.id)
|
||||
""", (tuple(self.ids),))
|
||||
if self.type in ('bank', 'cash'):
|
||||
last_statement = self._get_last_bank_statement(
|
||||
domain=[('move_id.state', '=', 'posted')])
|
||||
last_balance = last_statement.balance_end
|
||||
has_at_least_one_statement = bool(last_statement)
|
||||
bank_account_balance, nb_lines_bank_account_balance = self._get_journal_bank_account_balance(
|
||||
domain=[('move_id.state', '=', 'posted')])
|
||||
outstanding_pay_account_balance, nb_lines_outstanding_pay_account_balance = self._get_journal_outstanding_payments_account_balance(
|
||||
domain=[('move_id.state', '=', 'posted')])
|
||||
|
||||
self._cr.execute('''
|
||||
SELECT COUNT(st_line.id)
|
||||
FROM account_bank_statement_line st_line
|
||||
JOIN account_move st_line_move ON st_line_move.id = st_line.move_id
|
||||
JOIN account_bank_statement st ON st_line.statement_id = st.id
|
||||
WHERE st_line_move.journal_id IN %s
|
||||
AND st.state = 'posted'
|
||||
AND NOT st_line.is_reconciled
|
||||
''', [tuple(self.ids)])
|
||||
number_to_reconcile = self.env.cr.fetchone()[0]
|
||||
|
||||
to_check_ids = self.to_check_ids()
|
||||
number_to_check = len(to_check_ids)
|
||||
to_check_balance = sum([r.amount for r in to_check_ids])
|
||||
# optimization to read sum of balance from account_move_line
|
||||
account_ids = tuple(ac for ac in [self.default_debit_account_id.id, self.default_credit_account_id.id] if ac)
|
||||
if account_ids:
|
||||
amount_field = 'aml.balance' if (not self.currency_id or self.currency_id == self.company_id.currency_id) else 'aml.amount_currency'
|
||||
query = """SELECT sum(%s) FROM account_move_line aml
|
||||
LEFT JOIN account_move move ON aml.move_id = move.id
|
||||
WHERE aml.account_id in %%s
|
||||
AND move.date <= %%s AND move.state = 'posted';""" % (amount_field,)
|
||||
self.env.cr.execute(query, (account_ids, fields.Date.context_today(self),))
|
||||
query_results = self.env.cr.dictfetchall()
|
||||
if query_results and query_results[0].get('sum') != None:
|
||||
account_sum = query_results[0].get('sum')
|
||||
#TODO need to check if all invoices are in the same currency than the journal!!!!
|
||||
elif self.type in ['sale', 'purchase']:
|
||||
title = _('Bills to pay') if self.type == 'purchase' else _('Invoices owed to you')
|
||||
@@ -293,17 +294,18 @@ class account_journal(models.Model):
|
||||
number_to_check = read[0]['__count']
|
||||
to_check_balance = read[0]['amount_total']
|
||||
|
||||
difference = currency.round(last_balance-account_sum) + 0.0
|
||||
|
||||
is_sample_data = self.kanban_dashboard_graph and any(data.get('is_sample_data', False) for data in json.loads(self.kanban_dashboard_graph))
|
||||
|
||||
return {
|
||||
'number_to_check': number_to_check,
|
||||
'to_check_balance': formatLang(self.env, to_check_balance, currency_obj=currency),
|
||||
'number_to_reconcile': number_to_reconcile,
|
||||
'account_balance': formatLang(self.env, currency.round(account_sum) + 0.0, currency_obj=currency),
|
||||
'account_balance': formatLang(self.env, currency.round(bank_account_balance), currency_obj=currency),
|
||||
'has_at_least_one_statement': has_at_least_one_statement,
|
||||
'nb_lines_bank_account_balance': nb_lines_bank_account_balance,
|
||||
'outstanding_pay_account_balance': formatLang(self.env, currency.round(outstanding_pay_account_balance), currency_obj=currency),
|
||||
'nb_lines_outstanding_pay_account_balance': nb_lines_outstanding_pay_account_balance,
|
||||
'last_balance': formatLang(self.env, currency.round(last_balance) + 0.0, currency_obj=currency),
|
||||
'difference': formatLang(self.env, difference, currency_obj=currency) if difference else False,
|
||||
'number_draft': number_draft,
|
||||
'number_waiting': number_waiting,
|
||||
'number_late': number_late,
|
||||
@@ -494,7 +496,10 @@ class account_journal(models.Model):
|
||||
[action] = self.env.ref(action_ref).read()
|
||||
action['context'] = dict(ast.literal_eval(action.get('context')), default_journal_id=self.id, search_default_journal_id=self.id)
|
||||
if payment_type == 'transfer':
|
||||
action['context']['default_partner_id'] = self.company_id.partner_id.id
|
||||
action['context'].update({
|
||||
'default_partner_id': self.company_id.partner_id.id,
|
||||
'default_is_internal_transfer': True,
|
||||
})
|
||||
if mode == 'form':
|
||||
action['views'] = [[False, 'form']]
|
||||
return action
|
||||
|
||||
@@ -49,6 +49,30 @@ class AccountMove(models.Model):
|
||||
def _sequence_fixed_regex(self):
|
||||
return self.journal_id.sequence_override_regex or super()._sequence_fixed_regex
|
||||
|
||||
@api.model
|
||||
def _search_default_journal(self, journal_types):
|
||||
company_id = self._context.get('default_company_id', self.env.company.id)
|
||||
domain = [('company_id', '=', company_id), ('type', 'in', journal_types)]
|
||||
|
||||
journal = None
|
||||
if self._context.get('default_currency_id'):
|
||||
currency_domain = domain + [('currency_id', '=', self._context['default_currency_id'])]
|
||||
journal = self.env['account.journal'].search(currency_domain, limit=1)
|
||||
|
||||
if not journal:
|
||||
journal = self.env['account.journal'].search(domain, limit=1)
|
||||
|
||||
if not journal:
|
||||
company = self.env['res.company'].browse(company_id)
|
||||
|
||||
error_msg = _("No journal could be found in company %s for any of those types: %s") % (
|
||||
company.display_name,
|
||||
', '.join(journal_types),
|
||||
)
|
||||
raise UserError(error_msg)
|
||||
|
||||
return journal
|
||||
|
||||
@api.model
|
||||
def _get_default_journal(self):
|
||||
''' Get the default journal.
|
||||
@@ -56,36 +80,21 @@ class AccountMove(models.Model):
|
||||
either be determined by the default type.
|
||||
'''
|
||||
move_type = self._context.get('default_move_type', 'entry')
|
||||
journal_type = 'general'
|
||||
if move_type in self.get_sale_types(include_receipts=True):
|
||||
journal_type = 'sale'
|
||||
journal_types = ['sale']
|
||||
elif move_type in self.get_purchase_types(include_receipts=True):
|
||||
journal_type = 'purchase'
|
||||
journal_types = ['purchase']
|
||||
else:
|
||||
journal_types = self._context.get('default_move_journal_types', ['general'])
|
||||
|
||||
if self._context.get('default_journal_id'):
|
||||
journal = self.env['account.journal'].browse(self._context['default_journal_id'])
|
||||
|
||||
if move_type != 'entry' and journal.type != journal_type:
|
||||
if move_type != 'entry' and journal.type not in journal_types:
|
||||
raise UserError(_("Cannot create an invoice of type %s with a journal having %s as type.") % (move_type, journal.type))
|
||||
else:
|
||||
company_id = self._context.get('force_company', self._context.get('default_company_id', self.env.company.id))
|
||||
domain = [('company_id', '=', company_id), ('type', '=', journal_type)]
|
||||
journal = self._search_default_journal(journal_types)
|
||||
|
||||
journal = None
|
||||
if self._context.get('default_currency_id'):
|
||||
currency_domain = domain + [('currency_id', '=', self._context['default_currency_id'])]
|
||||
journal = self.env['account.journal'].search(currency_domain, limit=1)
|
||||
|
||||
if not journal:
|
||||
journal = self.env['account.journal'].search(domain, limit=1)
|
||||
|
||||
if not journal:
|
||||
error_msg = _('Please define an accounting miscellaneous journal in your company')
|
||||
if journal_type == 'sale':
|
||||
error_msg = _('Please define an accounting sale journal in your company')
|
||||
elif journal_type == 'purchase':
|
||||
error_msg = _('Please define an accounting purchase journal in your company')
|
||||
raise UserError(error_msg)
|
||||
return journal
|
||||
|
||||
@api.model
|
||||
@@ -154,6 +163,21 @@ class AccountMove(models.Model):
|
||||
commercial_partner_id = fields.Many2one('res.partner', string='Commercial Entity', store=True, readonly=True,
|
||||
compute='_compute_commercial_partner_id')
|
||||
country_code = fields.Char(related='company_id.country_id.code', readonly=True)
|
||||
user_id = fields.Many2one(string='User', related='invoice_user_id',
|
||||
help='Technical field used to fit the generic behavior in mail templates.')
|
||||
is_move_sent = fields.Boolean(readonly=True, default=False, copy=False,
|
||||
help="It indicates that the invoice/payment has been sent.")
|
||||
partner_bank_id = fields.Many2one('res.partner.bank', string='Bank Account',
|
||||
help='Bank Account Number to which the invoice will be paid. A Company bank account if this is a Customer Invoice or Vendor Credit Note, otherwise a Partner bank account number.',
|
||||
check_company=True)
|
||||
payment_reference = fields.Char(string='Payment Reference', index=True, copy=False,
|
||||
help="The payment reference to set on journal items.")
|
||||
payment_id = fields.Many2one(
|
||||
comodel_name='account.payment',
|
||||
string="Payment", copy=False, check_company=True)
|
||||
statement_line_id = fields.Many2one(
|
||||
comodel_name='account.bank.statement.line',
|
||||
string="Statement Line", copy=False, check_company=True)
|
||||
|
||||
# === Amount fields ===
|
||||
amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, tracking=True,
|
||||
@@ -183,7 +207,7 @@ class AccountMove(models.Model):
|
||||
('partial', 'Partially Paid'),
|
||||
('reversed', 'Reversed'),
|
||||
('invoicing_legacy', 'Invoicing App Legacy')],
|
||||
string='Payment', store=True, readonly=True, copy=False, tracking=True,
|
||||
string="Payment State", store=True, readonly=True, copy=False, tracking=True,
|
||||
compute='_compute_amount')
|
||||
|
||||
# ==== Cash basis feature fields ====
|
||||
@@ -215,17 +239,11 @@ class AccountMove(models.Model):
|
||||
invoice_user_id = fields.Many2one('res.users', copy=False, tracking=True,
|
||||
string='Salesperson',
|
||||
default=lambda self: self.env.user)
|
||||
user_id = fields.Many2one(string='User', related='invoice_user_id',
|
||||
help='Technical field used to fit the generic behavior in mail templates.')
|
||||
invoice_date = fields.Date(string='Invoice/Bill Date', readonly=True, index=True, copy=False,
|
||||
states={'draft': [('readonly', False)]},
|
||||
default=_get_default_invoice_date)
|
||||
invoice_date_due = fields.Date(string='Due Date', readonly=True, index=True, copy=False,
|
||||
states={'draft': [('readonly', False)]})
|
||||
invoice_payment_ref = fields.Char(string='Payment Reference', index=True, copy=False,
|
||||
help="The payment reference to set on journal items.")
|
||||
invoice_sent = fields.Boolean(readonly=True, default=False, copy=False,
|
||||
help="It indicates that the invoice has been sent.")
|
||||
invoice_origin = fields.Char(string='Origin', readonly=True, tracking=True,
|
||||
help="The document(s) that generated the invoice.")
|
||||
invoice_payment_term_id = fields.Many2one('account.payment.term', string='Payment Terms',
|
||||
@@ -236,9 +254,6 @@ class AccountMove(models.Model):
|
||||
copy=False, readonly=True,
|
||||
domain=[('exclude_from_invoice_tab', '=', False)],
|
||||
states={'draft': [('readonly', False)]})
|
||||
invoice_partner_bank_id = fields.Many2one('res.partner.bank', string='Bank Account',
|
||||
help='Bank Account Number to which the invoice will be paid. A Company bank account if this is a Customer Invoice or Vendor Credit Note, otherwise a Partner bank account number.',
|
||||
check_company=True)
|
||||
invoice_incoterm_id = fields.Many2one('account.incoterms', string='Incoterm',
|
||||
default=_get_default_invoice_incoterm,
|
||||
help='International Commercial Terms are a series of predefined commercial terms used in international transactions.')
|
||||
@@ -280,12 +295,38 @@ class AccountMove(models.Model):
|
||||
help="Technical field used to display a message when the invoice's accounting date is prior of the tax lock date.")
|
||||
# Technical field to hide Reconciled Entries stat button
|
||||
has_reconciled_entries = fields.Boolean(compute="_compute_has_reconciled_entries")
|
||||
|
||||
# ==== Hash Fields ====
|
||||
restrict_mode_hash_table = fields.Boolean(related='journal_id.restrict_mode_hash_table')
|
||||
secure_sequence_number = fields.Integer(string="Inalteralbility No Gap Sequence #", readonly=True, copy=False)
|
||||
inalterable_hash = fields.Char(string="Inalterability Hash", readonly=True, copy=False)
|
||||
string_to_hash = fields.Char(compute='_compute_string_to_hash', readonly=True)
|
||||
|
||||
@api.model
|
||||
def _field_will_change(self, record, vals, field_name):
|
||||
if field_name not in vals:
|
||||
return False
|
||||
field = record._fields[field_name]
|
||||
if field.type == 'many2one':
|
||||
return record[field_name].id != vals[field_name]
|
||||
if field.type == 'many2many':
|
||||
current_ids = set(record[field_name].ids)
|
||||
after_write_ids = set(record.new({field_name: vals[field_name]})[field_name].ids)
|
||||
return current_ids != after_write_ids
|
||||
if field.type == 'one2many':
|
||||
return True
|
||||
if field.type == 'monetary' and record[field.currency_field]:
|
||||
return not record[field.currency_field].is_zero(record[field_name] - vals[field_name])
|
||||
return record[field_name] != vals[field_name]
|
||||
|
||||
@api.model
|
||||
def _cleanup_write_orm_values(self, record, vals):
|
||||
cleaned_vals = dict(vals)
|
||||
for field_name, value in vals.items():
|
||||
if not self._field_will_change(record, vals, field_name):
|
||||
del cleaned_vals[field_name]
|
||||
return cleaned_vals
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# ONCHANGE METHODS
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -349,7 +390,7 @@ class AccountMove(models.Model):
|
||||
line.account_id = new_term_account
|
||||
|
||||
self._compute_bank_partner_id()
|
||||
self.invoice_partner_bank_id = self.bank_partner_id.bank_ids and self.bank_partner_id.bank_ids[0]
|
||||
self.partner_bank_id = self.bank_partner_id.bank_ids and self.bank_partner_id.bank_ids[0]
|
||||
|
||||
# Find the new fiscal position.
|
||||
delivery_partner_id = self._get_invoice_delivery_partner_id()
|
||||
@@ -376,10 +417,10 @@ class AccountMove(models.Model):
|
||||
|
||||
self._recompute_dynamic_lines(recompute_tax_base_amount=True)
|
||||
|
||||
@api.onchange('invoice_payment_ref')
|
||||
def _onchange_invoice_payment_ref(self):
|
||||
@api.onchange('payment_reference')
|
||||
def _onchange_payment_reference(self):
|
||||
for line in self.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable')):
|
||||
line.name = self.invoice_payment_ref
|
||||
line.name = self.payment_reference
|
||||
|
||||
@api.onchange('invoice_vendor_bill_id')
|
||||
def _onchange_invoice_vendor_bill(self):
|
||||
@@ -854,7 +895,7 @@ class AccountMove(models.Model):
|
||||
# Create new line.
|
||||
create_method = in_draft_mode and self.env['account.move.line'].new or self.env['account.move.line'].create
|
||||
candidate = create_method({
|
||||
'name': self.invoice_payment_ref or '',
|
||||
'name': self.payment_reference or '',
|
||||
'debit': balance < 0.0 and -balance or 0.0,
|
||||
'credit': balance > 0.0 and balance or 0.0,
|
||||
'quantity': 1.0,
|
||||
@@ -890,7 +931,7 @@ class AccountMove(models.Model):
|
||||
self.line_ids -= existing_terms_lines - new_terms_lines
|
||||
|
||||
if new_terms_lines:
|
||||
self.invoice_payment_ref = new_terms_lines[-1].name or ''
|
||||
self.payment_reference = new_terms_lines[-1].name or ''
|
||||
self.invoice_date_due = new_terms_lines[-1].date_maturity
|
||||
|
||||
def _recompute_dynamic_lines(self, recompute_all_taxes=False, recompute_tax_base_amount=False):
|
||||
@@ -1056,7 +1097,18 @@ class AccountMove(models.Model):
|
||||
else:
|
||||
move.bank_partner_id = move.company_id.partner_id
|
||||
|
||||
@api.model
|
||||
def _get_invoice_in_payment_state(self):
|
||||
''' Hook to give the state when the invoice becomes fully paid. This is necessary because the users working
|
||||
with only invoicing don't want to see the 'in_payment' state. Then, this method will be overridden in the
|
||||
accountant module to enable the 'in_payment' state. '''
|
||||
return 'paid'
|
||||
|
||||
@api.depends(
|
||||
'line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual',
|
||||
'line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual_currency',
|
||||
'line_ids.matched_credit_ids.credit_move_id.move_id.line_ids.amount_residual',
|
||||
'line_ids.matched_credit_ids.credit_move_id.move_id.line_ids.amount_residual_currency',
|
||||
'line_ids.debit',
|
||||
'line_ids.credit',
|
||||
'line_ids.currency_id',
|
||||
@@ -1066,30 +1118,6 @@ class AccountMove(models.Model):
|
||||
'line_ids.payment_id.state',
|
||||
'line_ids.full_reconcile_id')
|
||||
def _compute_amount(self):
|
||||
invoice_ids = [move.id for move in self if move.id and move.is_invoice(include_receipts=True)]
|
||||
self.env['account.payment'].flush(['state'])
|
||||
if invoice_ids:
|
||||
self._cr.execute(
|
||||
'''
|
||||
SELECT move.id
|
||||
FROM account_move move
|
||||
JOIN account_move_line line ON line.move_id = move.id
|
||||
JOIN account_partial_reconcile part ON part.debit_move_id = line.id OR part.credit_move_id = line.id
|
||||
JOIN account_move_line rec_line ON
|
||||
(rec_line.id = part.credit_move_id AND line.id = part.debit_move_id)
|
||||
OR
|
||||
(rec_line.id = part.debit_move_id AND line.id = part.credit_move_id)
|
||||
JOIN account_payment payment ON payment.id = rec_line.payment_id
|
||||
JOIN account_journal journal ON journal.id = rec_line.journal_id
|
||||
WHERE payment.state IN ('posted', 'sent')
|
||||
AND journal.post_at = 'bank_rec'
|
||||
AND move.id IN %s
|
||||
''', [tuple(invoice_ids)]
|
||||
)
|
||||
in_payment_set = set(res[0] for res in self._cr.fetchall())
|
||||
else:
|
||||
in_payment_set = {}
|
||||
|
||||
for move in self:
|
||||
|
||||
if move.payment_state == 'invoicing_legacy':
|
||||
@@ -1162,10 +1190,10 @@ class AccountMove(models.Model):
|
||||
if move.is_invoice(include_receipts=True) and move.state == 'posted':
|
||||
|
||||
if currency.is_zero(move.amount_residual):
|
||||
if move.id in in_payment_set:
|
||||
new_pmt_state = 'in_payment'
|
||||
else:
|
||||
if all(payment.is_matched for payment in move._get_reconciled_payments()):
|
||||
new_pmt_state = 'paid'
|
||||
else:
|
||||
new_pmt_state = move._get_invoice_in_payment_state()
|
||||
elif currency.compare_amounts(total_to_pay, total_residual) != 0:
|
||||
new_pmt_state = 'partial'
|
||||
|
||||
@@ -1252,7 +1280,7 @@ class AccountMove(models.Model):
|
||||
pay_term_line_ids = move.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
|
||||
|
||||
domain = [('account_id', 'in', pay_term_line_ids.mapped('account_id').ids),
|
||||
'|', ('move_id.state', '=', 'posted'), '&', ('move_id.state', '=', 'draft'), ('journal_id.post_at', '=', 'bank_rec'),
|
||||
('move_id.state', '=', 'posted'),
|
||||
('partner_id', '=', move.commercial_partner_id.id),
|
||||
('reconciled', '=', False), '|', ('amount_residual', '!=', 0.0),
|
||||
('amount_residual_currency', '!=', 0.0)]
|
||||
@@ -1320,7 +1348,7 @@ class AccountMove(models.Model):
|
||||
'currency': self.currency_id.symbol,
|
||||
'digits': [69, self.currency_id.decimal_places],
|
||||
'position': self.currency_id.position,
|
||||
'date': counterpart_line.date,
|
||||
'date': counterpart_line.payment_id.date or counterpart_line.date,
|
||||
'payment_id': counterpart_line.id,
|
||||
'account_payment_id': counterpart_line.payment_id.id,
|
||||
'payment_method_name': counterpart_line.payment_id.payment_method_id.name if counterpart_line.journal_id.type == 'bank' else None,
|
||||
@@ -1408,13 +1436,30 @@ class AccountMove(models.Model):
|
||||
move.tax_lock_date_message = False
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# CONSTRAINS METHODS
|
||||
# BUSINESS MODELS SYNCHRONIZATION
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@api.constrains('line_ids', 'journal_id')
|
||||
def _validate_move_modification(self):
|
||||
if 'posted' in self.mapped('line_ids.payment_id.state'):
|
||||
raise ValidationError(_("You cannot modify a journal entry linked to a posted payment."))
|
||||
def _synchronize_business_models(self, changed_fields):
|
||||
''' Ensure the consistency between:
|
||||
account.payment & account.move
|
||||
account.bank.statement.line & account.move
|
||||
|
||||
The idea is to call the method performing the synchronization of the business
|
||||
models regarding their related journal entries. To avoid cycling, the
|
||||
'skip_account_move_synchronization' key is used through the context.
|
||||
|
||||
:param changed_fields: A set containing all modified fields on account.move.
|
||||
'''
|
||||
if self._context.get('skip_account_move_synchronization'):
|
||||
return
|
||||
|
||||
self_sudo = self.sudo()
|
||||
self_sudo.payment_id._synchronize_from_moves(changed_fields)
|
||||
self_sudo.statement_line_id._synchronize_from_moves(changed_fields)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# CONSTRAINT METHODS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@api.constrains('name', 'journal_id', 'state')
|
||||
def _check_unique_sequence_number(self):
|
||||
@@ -1683,7 +1728,7 @@ class AccountMove(models.Model):
|
||||
res = True
|
||||
else:
|
||||
vals.pop('invoice_line_ids', None)
|
||||
res = super(AccountMove, self.with_context(check_move_validity=False)).write(vals)
|
||||
res = super(AccountMove, self.with_context(check_move_validity=False, skip_account_move_synchronization=True)).write(vals)
|
||||
|
||||
# You can't change the date of a not-locked move to a locked period.
|
||||
# You can't post a new journal entry inside a locked period.
|
||||
@@ -1702,13 +1747,15 @@ class AccountMove(models.Model):
|
||||
if 'line_ids' in vals and self._context.get('check_move_validity', True):
|
||||
self._check_balanced()
|
||||
|
||||
self._synchronize_business_models(set(vals.keys()))
|
||||
|
||||
return res
|
||||
|
||||
def unlink(self):
|
||||
for move in self:
|
||||
if move.posted_before and not self._context.get('force_delete'):
|
||||
raise UserError(_("You cannot delete an entry which has been posted once."))
|
||||
move.line_ids.unlink()
|
||||
self.line_ids.unlink()
|
||||
return super(AccountMove, self).unlink()
|
||||
|
||||
@api.depends('name', 'state')
|
||||
@@ -1929,10 +1976,54 @@ class AccountMove(models.Model):
|
||||
|
||||
def _get_reconciled_payments(self):
|
||||
"""Helper used to retrieve the reconciled payments on this journal entry"""
|
||||
pay_term_line_ids = self.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
|
||||
reconciled_amls = pay_term_line_ids.mapped('matched_debit_ids.debit_move_id') + \
|
||||
pay_term_line_ids.mapped('matched_credit_ids.credit_move_id')
|
||||
return reconciled_amls.mapped('payment_id')
|
||||
reconciled_lines = self.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
|
||||
reconciled_amls = reconciled_lines.mapped('matched_debit_ids.debit_move_id') + \
|
||||
reconciled_lines.mapped('matched_credit_ids.credit_move_id')
|
||||
return reconciled_amls.move_id.payment_id
|
||||
|
||||
def _get_reconciled_statement_lines(self):
|
||||
"""Helper used to retrieve the reconciled payments on this journal entry"""
|
||||
reconciled_lines = self.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
|
||||
reconciled_amls = reconciled_lines.mapped('matched_debit_ids.debit_move_id') + \
|
||||
reconciled_lines.mapped('matched_credit_ids.credit_move_id')
|
||||
return reconciled_amls.move_id.statement_line_id
|
||||
|
||||
def _get_reconciled_invoices(self):
|
||||
"""Helper used to retrieve the reconciled payments on this journal entry"""
|
||||
reconciled_lines = self.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
|
||||
reconciled_amls = reconciled_lines.mapped('matched_debit_ids.debit_move_id') + \
|
||||
reconciled_lines.mapped('matched_credit_ids.credit_move_id')
|
||||
return reconciled_amls.move_id.filtered(lambda move: move.is_invoice(include_receipts=True))
|
||||
|
||||
def _get_reconciled_invoices_partials(self):
|
||||
''' Helper to retrieve the details about reconciled invoices.
|
||||
:return A list of tuple (amount, rate, invoice_line).
|
||||
'''
|
||||
self.ensure_one()
|
||||
|
||||
def compute_tuple(partial, payment_line, invoice_line):
|
||||
if payment_line.currency_id == invoice_line.currency_id:
|
||||
# Same currency.
|
||||
exchange_rate = None
|
||||
amount_paid = partial.amount_currency
|
||||
elif invoice_line.currency_id:
|
||||
# Different currency on the payment.
|
||||
exchange_rate = payment_line.amount_currency / payment_line.balance
|
||||
amount_paid = partial.company_currency_id._convert(partial.amount, invoice_line.currency_id, invoice_line.company_id, invoice_line.date)
|
||||
else:
|
||||
# No foreign currency on the payment, don't care about the invoice's currency.
|
||||
exchange_rate = None
|
||||
amount_paid = partial.amount
|
||||
return (amount_paid, exchange_rate, invoice_line)
|
||||
|
||||
receivable_lines = self.line_ids.filtered(lambda line: line.account_internal_type == 'receivable')
|
||||
invoice_partials = []
|
||||
|
||||
for partial in receivable_lines.matched_debit_ids:
|
||||
invoice_partials.append(compute_tuple(partial, partial.credit_move_id, partial.debit_move_id))
|
||||
for partial in receivable_lines.matched_credit_ids:
|
||||
invoice_partials.append(compute_tuple(partial, partial.debit_move_id, partial.credit_move_id))
|
||||
return invoice_partials
|
||||
|
||||
def _reverse_move_vals(self, default_values, cancel=True):
|
||||
''' Reverse values passed as parameter being the copied values of the original journal entry.
|
||||
@@ -2177,6 +2268,7 @@ class AccountMove(models.Model):
|
||||
'state': 'posted',
|
||||
'posted_before': True,
|
||||
})
|
||||
|
||||
for move in self:
|
||||
if move.auto_post and move.date > fields.Date.today():
|
||||
raise UserError(_("This move is configured to be auto-posted on {}".format(move.date.strftime(get_lang(self.env).date_format))))
|
||||
@@ -2186,21 +2278,17 @@ class AccountMove(models.Model):
|
||||
# Compute 'ref' for 'out_invoice'.
|
||||
if move._auto_compute_invoice_reference():
|
||||
to_write = {
|
||||
'invoice_payment_ref': move._get_invoice_computed_reference(),
|
||||
'payment_reference': move._get_invoice_computed_reference(),
|
||||
'line_ids': []
|
||||
}
|
||||
for line in move.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable')):
|
||||
to_write['line_ids'].append((1, line.id, {'name': to_write['invoice_payment_ref']}))
|
||||
to_write['line_ids'].append((1, line.id, {'name': to_write['payment_reference']}))
|
||||
move.write(to_write)
|
||||
|
||||
if move == move.company_id.account_opening_move_id and not move.company_id.account_bank_reconciliation_start:
|
||||
# For opening moves, we set the reconciliation date threshold
|
||||
# to the move's date if it wasn't already set (we don't want
|
||||
# to have to reconcile all the older payments -made before
|
||||
# installing Accounting- with bank statements)
|
||||
move.company_id.account_bank_reconciliation_start = move.date
|
||||
|
||||
if move.is_sale_document() and move.journal_id.sale_activity_type_id and (move.journal_id.sale_activity_user_id or move.invoice_user_id).id not in (self.env.ref('base.user_root').id, False):
|
||||
for move in self:
|
||||
if move.is_sale_document() \
|
||||
and move.journal_id.sale_activity_type_id \
|
||||
and (move.journal_id.sale_activity_user_id or move.invoice_user_id).id not in (self.env.ref('base.user_root').id, False):
|
||||
move.activity_schedule(
|
||||
date_deadline=min((date for date in move.line_ids.mapped('date_maturity') if date), default=move.date),
|
||||
activity_type_id=move.journal_id.sale_activity_type_id.id,
|
||||
@@ -2219,14 +2307,13 @@ class AccountMove(models.Model):
|
||||
lambda m: m.is_invoice(include_receipts=True) and m.currency_id.is_zero(m.amount_total)
|
||||
).action_invoice_paid()
|
||||
|
||||
|
||||
def _auto_compute_invoice_reference(self):
|
||||
''' Hook to be overridden to set custom conditions for auto-computed invoice references.
|
||||
:return True if the move should get a auto-computed reference else False
|
||||
:rtype bool
|
||||
'''
|
||||
self.ensure_one()
|
||||
return self.move_type == 'out_invoice' and not self.invoice_payment_ref
|
||||
return self.move_type == 'out_invoice' and not self.payment_reference
|
||||
|
||||
def action_reverse(self):
|
||||
action = self.env.ref('account.action_view_account_move_reversal').read()[0]
|
||||
@@ -2237,8 +2324,6 @@ class AccountMove(models.Model):
|
||||
return action
|
||||
|
||||
def action_post(self):
|
||||
if self.mapped('line_ids.payment_id') and any(post_at == 'bank_rec' for post_at in self.mapped('journal_id.post_at')):
|
||||
raise UserError(_("A payment journal entry generated in a journal configured to post entries only when payments are reconciled with a bank statement cannot be manually posted. Those will be posted automatically after performing the bank reconciliation."))
|
||||
return self.post()
|
||||
|
||||
def js_assign_outstanding_line(self, line_id):
|
||||
@@ -2265,7 +2350,7 @@ class AccountMove(models.Model):
|
||||
move.mapped('line_ids.analytic_line_ids').unlink()
|
||||
|
||||
self.mapped('line_ids').remove_move_reconcile()
|
||||
self.write({'state': 'draft'})
|
||||
self.write({'state': 'draft', 'is_move_sent': False})
|
||||
|
||||
def button_cancel(self):
|
||||
self.write({'auto_post': False, 'state': 'cancel'})
|
||||
@@ -2357,7 +2442,7 @@ class AccountMove(models.Model):
|
||||
if any(not move.is_invoice(include_receipts=True) for move in self):
|
||||
raise UserError(_("Only invoices could be printed."))
|
||||
|
||||
self.filtered(lambda inv: not inv.invoice_sent).write({'invoice_sent': True})
|
||||
self.filtered(lambda inv: not inv.is_move_sent).write({'is_move_sent': True})
|
||||
if self.user_has_groups('account.group_account_invoice'):
|
||||
return self.env.ref('account.account_invoices').report_action(self)
|
||||
else:
|
||||
@@ -2367,10 +2452,21 @@ class AccountMove(models.Model):
|
||||
''' Hook to be overrided called when the invoice moves to the paid state. '''
|
||||
pass
|
||||
|
||||
def action_invoice_register_payment(self):
|
||||
return self.env['account.payment']\
|
||||
.with_context(active_ids=self.ids, active_model='account.move', active_id=self.id)\
|
||||
.action_register_payment()
|
||||
def action_register_payment(self):
|
||||
''' Open the account.payment.register wizard to pay the selected journal entries.
|
||||
:return: An action opening the account.payment.register wizard.
|
||||
'''
|
||||
return {
|
||||
'name': _('Register Payment'),
|
||||
'res_model': 'account.payment.register',
|
||||
'view_mode': 'form',
|
||||
'context': {
|
||||
'active_model': 'account.move',
|
||||
'active_ids': self.ids,
|
||||
},
|
||||
'target': 'new',
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
|
||||
def action_switch_invoice_into_refund_credit_note(self):
|
||||
if any(move.move_type not in ('in_invoice', 'out_invoice') for move in self):
|
||||
@@ -2394,7 +2490,7 @@ class AccountMove(models.Model):
|
||||
move.write({
|
||||
'move_type': move.move_type.replace('invoice', 'refund'),
|
||||
'invoice_line_ids' : [(5, 0, 0)],
|
||||
'invoice_partner_bank_id': False,
|
||||
'partner_bank_id': False,
|
||||
})
|
||||
move.write({'invoice_line_ids' : new_invoice_line_ids})
|
||||
|
||||
@@ -2498,12 +2594,12 @@ class AccountMove(models.Model):
|
||||
qr_code_method = self.qr_code_method
|
||||
if qr_code_method:
|
||||
# If the user set a qr code generator manually, we check that we can use it
|
||||
if not self.invoice_partner_bank_id._eligible_for_qr_code(self.qr_code_method, self.partner_id, self.currency_id):
|
||||
if not self.partner_bank_id._eligible_for_qr_code(self.qr_code_method, self.partner_id, self.currency_id):
|
||||
raise UserError(_("The chosen QR-code type is not eligible for this invoice."))
|
||||
else:
|
||||
# Else we find one that's eligible and assign it to the invoice
|
||||
for candidate_method, candidate_name in self.env['res.partner.bank'].get_available_qr_methods_in_sequence():
|
||||
if self.invoice_partner_bank_id._eligible_for_qr_code(candidate_method, self.partner_id, self.currency_id):
|
||||
if self.partner_bank_id._eligible_for_qr_code(candidate_method, self.partner_id, self.currency_id):
|
||||
qr_code_method = candidate_method
|
||||
break
|
||||
|
||||
@@ -2512,7 +2608,7 @@ class AccountMove(models.Model):
|
||||
return None
|
||||
|
||||
unstruct_ref = self.ref if self.ref else self.name
|
||||
rslt = self.invoice_partner_bank_id.build_qr_code_url(self.amount_residual, unstruct_ref, self.invoice_payment_ref, self.currency_id, self.partner_id, qr_code_method, silent_errors=False)
|
||||
rslt = self.partner_bank_id.build_qr_code_url(self.amount_residual, unstruct_ref, self.payment_reference, self.currency_id, self.partner_id, qr_code_method, silent_errors=False)
|
||||
|
||||
# We only set qr_code_method after generating the url; otherwise, it
|
||||
# could be set even in case of a failure in the QR code generation
|
||||
@@ -2586,11 +2682,14 @@ class AccountMoveLine(models.Model):
|
||||
|
||||
# ==== Origin fields ====
|
||||
reconcile_model_id = fields.Many2one('account.reconcile.model', string="Reconciliation Model", copy=False, readonly=True, check_company=True)
|
||||
payment_id = fields.Many2one('account.payment', string="Originator Payment", copy=False, check_company=True,
|
||||
help="Payment that created this entry")
|
||||
statement_line_id = fields.Many2one('account.bank.statement.line',
|
||||
string='Bank statement line reconciled with this entry',
|
||||
index=True, copy=False, readonly=True, check_company=True)
|
||||
payment_id = fields.Many2one('account.payment', index=True, store=True,
|
||||
string="Originator Payment",
|
||||
related='move_id.payment_id',
|
||||
help="The payment that created this entry")
|
||||
statement_line_id = fields.Many2one('account.bank.statement.line', index=True, store=True,
|
||||
string="Originator Statement Line",
|
||||
related='move_id.statement_line_id',
|
||||
help="The statement line that created this entry")
|
||||
statement_id = fields.Many2one(related='statement_line_id.statement_id', store=True, index=True, copy=False,
|
||||
help="The bank statement used for bank reconciliation")
|
||||
|
||||
@@ -2823,12 +2922,12 @@ class AccountMoveLine(models.Model):
|
||||
res = {}
|
||||
|
||||
# Compute 'price_subtotal'.
|
||||
price_unit_wo_discount = price_unit * (1 - (discount / 100.0))
|
||||
subtotal = quantity * price_unit_wo_discount
|
||||
line_discount_price_unit = price_unit * (1 - (discount / 100.0))
|
||||
subtotal = quantity * line_discount_price_unit
|
||||
|
||||
# Compute 'price_total'.
|
||||
if taxes:
|
||||
taxes_res = taxes._origin.compute_all(price_unit_wo_discount,
|
||||
taxes_res = taxes._origin.compute_all(line_discount_price_unit,
|
||||
quantity=quantity, currency=currency, product=product, partner=partner, is_refund=move_type in ('out_refund', 'in_refund'))
|
||||
res['price_subtotal'] = taxes_res['total_excluded']
|
||||
res['price_total'] = taxes_res['total_included']
|
||||
@@ -3269,7 +3368,7 @@ class AccountMoveLine(models.Model):
|
||||
def _check_constrains_account_id_journal_id(self):
|
||||
for line in self:
|
||||
account = line.account_id
|
||||
journal = line.journal_id
|
||||
journal = line.move_id.journal_id
|
||||
|
||||
if account.deprecated:
|
||||
raise UserError(_('The account %s (%s) is deprecated.') % (account.name, account.code))
|
||||
@@ -3423,25 +3522,12 @@ class AccountMoveLine(models.Model):
|
||||
moves._check_balanced()
|
||||
moves._check_fiscalyear_lock_date()
|
||||
lines._check_tax_lock_date()
|
||||
moves._synchronize_business_models({'line_ids'})
|
||||
|
||||
return lines
|
||||
|
||||
def write(self, vals):
|
||||
# OVERRIDE
|
||||
def field_will_change(line, field_name):
|
||||
if field_name not in vals:
|
||||
return False
|
||||
field = line._fields[field_name]
|
||||
if field.type == 'many2one':
|
||||
return line[field_name].id != vals[field_name]
|
||||
if field.type in ('one2many', 'many2many'):
|
||||
current_ids = set(line[field_name].ids)
|
||||
after_write_ids = set(line.new({field_name: vals[field_name]})[field_name].ids)
|
||||
return current_ids != after_write_ids
|
||||
if field.type == 'monetary' and line[field.currency_field]:
|
||||
return not line[field.currency_field].is_zero(line[field_name] - vals[field_name])
|
||||
return line[field_name] != vals[field_name]
|
||||
|
||||
ACCOUNTING_FIELDS = ('debit', 'credit', 'amount_currency')
|
||||
BUSINESS_FIELDS = ('price_unit', 'quantity', 'discount', 'tax_ids')
|
||||
PROTECTED_FIELDS_TAX_LOCK_DATE = ['debit', 'credit', 'tax_line_id', 'tax_ids', 'tax_tag_ids']
|
||||
@@ -3460,23 +3546,17 @@ class AccountMoveLine(models.Model):
|
||||
raise UserError(_("You cannot edit the following fields due to restrict mode being activated on the journal: %s.") % ', '.join(INTEGRITY_HASH_LINE_FIELDS))
|
||||
if any(key in vals for key in ('tax_ids', 'tax_line_ids')):
|
||||
raise UserError(_('You cannot modify the taxes related to a posted journal item, you should reset the journal entry to draft to do so.'))
|
||||
# When making a reconciliation on an existing liquidity journal item, mark the payment as reconciled
|
||||
if 'statement_line_id' in vals and line.payment_id:
|
||||
# In case of an internal transfer, there are 2 liquidity move lines to match with a bank statement
|
||||
if all(line.statement_id for line in line.payment_id.move_line_ids.filtered(
|
||||
lambda r: r.id != line.id and r.account_id.internal_type == 'liquidity')):
|
||||
line.payment_id.state = 'reconciled'
|
||||
|
||||
# Check the lock date.
|
||||
if any(field_will_change(line, field_name) for field_name in PROTECTED_FIELDS_LOCK_DATE):
|
||||
if any(self.env['account.move']._field_will_change(line, vals, field_name) for field_name in PROTECTED_FIELDS_LOCK_DATE):
|
||||
line.move_id._check_fiscalyear_lock_date()
|
||||
|
||||
# Check the tax lock date.
|
||||
if any(field_will_change(line, field_name) for field_name in PROTECTED_FIELDS_TAX_LOCK_DATE):
|
||||
if any(self.env['account.move']._field_will_change(line, vals, field_name) for field_name in PROTECTED_FIELDS_TAX_LOCK_DATE):
|
||||
line._check_tax_lock_date()
|
||||
|
||||
# Check the reconciliation.
|
||||
if any(field_will_change(line, field_name) for field_name in PROTECTED_FIELDS_RECONCILIATION):
|
||||
if any(self.env['account.move']._field_will_change(line, vals, field_name) for field_name in PROTECTED_FIELDS_RECONCILIATION):
|
||||
line._check_reconciliation()
|
||||
|
||||
# Check switching receivable / payable accounts.
|
||||
@@ -3576,6 +3656,8 @@ class AccountMoveLine(models.Model):
|
||||
if self._context.get('check_move_validity', True):
|
||||
self.mapped('move_id')._check_balanced()
|
||||
|
||||
self.mapped('move_id')._synchronize_business_models({'line_ids'})
|
||||
|
||||
return result
|
||||
|
||||
def _valid_field_parameter(self, field, name):
|
||||
@@ -3910,6 +3992,8 @@ class AccountMoveLine(models.Model):
|
||||
all_accounts.append(line.account_id)
|
||||
if line.reconciled:
|
||||
raise UserError(_('You are trying to reconcile some entries that are already reconciled.'))
|
||||
if line.move_id.state != 'posted':
|
||||
raise UserError(_('You can only reconcile posted entries.'))
|
||||
if len(company_ids) > 1:
|
||||
raise UserError(_('To reconcile the entries company should be the same for all entries.'))
|
||||
if len(set(all_accounts)) > 1:
|
||||
@@ -3923,11 +4007,6 @@ class AccountMoveLine(models.Model):
|
||||
if not self:
|
||||
return
|
||||
|
||||
# List unpaid invoices
|
||||
not_paid_invoices = self.mapped('move_id').filtered(
|
||||
lambda m: m.is_invoice(include_receipts=True) and m.payment_state not in ('paid', 'in_payment')
|
||||
)
|
||||
|
||||
self._check_reconcile_validity()
|
||||
#reconcile everything that can be
|
||||
remaining_moves = self.auto_reconcile_lines()
|
||||
@@ -3948,11 +4027,6 @@ class AccountMoveLine(models.Model):
|
||||
# Check if reconciliation is total or needs an exchange rate entry to be created
|
||||
(self + writeoff_to_reconcile).check_full_reconcile()
|
||||
|
||||
# Trigger action for paid invoices
|
||||
not_paid_invoices.filtered(
|
||||
lambda m: m.payment_state in ('paid', 'in_payment')
|
||||
).action_invoice_paid()
|
||||
|
||||
return True
|
||||
|
||||
def _create_writeoff(self, writeoff_vals):
|
||||
@@ -4048,7 +4122,7 @@ class AccountMoveLine(models.Model):
|
||||
|
||||
def remove_move_reconcile(self):
|
||||
""" Undo a reconciliation """
|
||||
(self.mapped('matched_debit_ids') + self.mapped('matched_credit_ids')).unlink()
|
||||
(self.matched_debit_ids + self.matched_credit_ids).unlink()
|
||||
|
||||
def _copy_data_extend_business_fields(self, values):
|
||||
''' Hook allowing copying business fields under certain conditions.
|
||||
@@ -4454,6 +4528,7 @@ class AccountPartialReconcile(models.Model):
|
||||
self.ensure_one()
|
||||
move_date = self.debit_move_id.date
|
||||
newly_created_move = self.env['account.move']
|
||||
to_reconcile = []
|
||||
# We use a set here in case the reconciled lines belong to the same move (it happens with POS)
|
||||
for move in {self.debit_move_id.move_id, self.credit_move_id.move_id}:
|
||||
#move_date is the max of the 2 reconciled items
|
||||
@@ -4509,9 +4584,7 @@ class AccountPartialReconcile(models.Model):
|
||||
'tax_tag_ids': [(6, 0, line.tax_tag_ids.ids)],
|
||||
})
|
||||
if line.account_id.reconcile and not line.reconciled:
|
||||
#setting the account to allow reconciliation will help to fix rounding errors
|
||||
to_clear_aml |= line
|
||||
to_clear_aml.reconcile()
|
||||
to_reconcile.append(to_clear_aml + line)
|
||||
|
||||
taxes_payment_exigible = line.tax_ids.flatten_taxes_hierarchy().filtered(lambda tax: tax.tax_exigibility == 'on_payment')
|
||||
if taxes_payment_exigible:
|
||||
@@ -4552,6 +4625,8 @@ class AccountPartialReconcile(models.Model):
|
||||
self._set_tax_cash_basis_entry_date(move_date, newly_created_move)
|
||||
# post move
|
||||
newly_created_move.post()
|
||||
for lines in to_reconcile:
|
||||
lines.reconcile()
|
||||
|
||||
def _create_tax_basis_move(self):
|
||||
# Check if company_journal for cash basis is set if not, raise exception
|
||||
@@ -4578,9 +4653,13 @@ class AccountPartialReconcile(models.Model):
|
||||
move._reverse_moves([{'ref': _('Reversal of %s') % move.name}], cancel=True)
|
||||
else:
|
||||
move._reverse_moves([{'date': fields.Date.today(), 'ref': _('Reversal of %s') % move.name}], cancel=True)
|
||||
|
||||
# Unlink partials then the full in this order to avoid a recursive call to the same partials.
|
||||
res = super(AccountPartialReconcile, self).unlink()
|
||||
|
||||
if full_to_unlink:
|
||||
full_to_unlink.unlink()
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@@ -4600,21 +4679,19 @@ class AccountFullReconcile(models.Model):
|
||||
cancel the currency difference entry on the partner account (otherwise it will still appear on the aged balance
|
||||
for example).
|
||||
"""
|
||||
for rec in self:
|
||||
if rec.exists() and rec.exchange_move_id:
|
||||
# reverse the exchange rate entry after de-referencing it to avoid looping
|
||||
# (reversing will cause a nested attempt to drop the full reconciliation)
|
||||
to_reverse = rec.exchange_move_id
|
||||
rec.exchange_move_id = False
|
||||
if to_reverse.date > (to_reverse.company_id.period_lock_date or date.min):
|
||||
reverse_date = to_reverse.date
|
||||
else:
|
||||
reverse_date = fields.Date.today()
|
||||
to_reverse._reverse_moves([{
|
||||
'date': reverse_date,
|
||||
'ref': _('Reversal of: %s') % to_reverse.name,
|
||||
}], cancel=True)
|
||||
return super(AccountFullReconcile, self).unlink()
|
||||
moves_to_reverse = self.exchange_move_id
|
||||
|
||||
res = super(AccountFullReconcile, self).unlink()
|
||||
|
||||
# Reverse all exchange moves at once.
|
||||
today = fields.Date.today()
|
||||
default_values_list = [{
|
||||
'date': today,
|
||||
'ref': _('Reversal of: %s') % move.name,
|
||||
} for move in moves_to_reverse]
|
||||
moves_to_reverse._reverse_moves(default_values_list, cancel=True)
|
||||
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def _prepare_exchange_diff_move(self, move_date, company):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -218,10 +218,9 @@ class AccountReconcileModel(models.Model):
|
||||
:return: A list of dict representing move.lines to be created corresponding to the tax.
|
||||
'''
|
||||
self.ensure_one()
|
||||
balance = base_line_dict['debit'] - base_line_dict['credit']
|
||||
currency = base_line_dict.get('currency_id') and self.env['res.currency'].browse(base_line_dict['currency_id'])
|
||||
balance = base_line_dict['balance']
|
||||
|
||||
res = tax.compute_all(balance, currency=currency)
|
||||
res = tax.compute_all(balance)
|
||||
|
||||
new_aml_dicts = []
|
||||
for tax_res in res['taxes']:
|
||||
@@ -231,27 +230,26 @@ class AccountReconcileModel(models.Model):
|
||||
'account_id': tax_res['account_id'] or base_line_dict['account_id'],
|
||||
'name': tax_res['name'],
|
||||
'partner_id': base_line_dict.get('partner_id'),
|
||||
'debit': tax_res['amount'] > 0 and tax_res['amount'] or 0,
|
||||
'credit': tax_res['amount'] < 0 and -tax_res['amount'] or 0,
|
||||
'balance': tax_res['amount'],
|
||||
'analytic_account_id': tax.analytic and base_line_dict['analytic_account_id'],
|
||||
'analytic_tag_ids': tax.analytic and base_line_dict['analytic_tag_ids'],
|
||||
'tax_exigible': tax_res['tax_exigibility'],
|
||||
'tax_repartition_line_id': tax_res['tax_repartition_line_id'],
|
||||
'tax_ids': [(6, 0, tax_res['tax_ids'])],
|
||||
'tax_tag_ids': [(6, 0, tax_res['tag_ids'])],
|
||||
'currency_id': False,
|
||||
'reconcile_model_id': self.id,
|
||||
})
|
||||
|
||||
# Handle price included taxes.
|
||||
base_line_dict['debit'] = tax_res['base'] > 0 and tax_res['base'] or base_line_dict['debit']
|
||||
base_line_dict['credit'] = tax_res['base'] < 0 and -tax_res['base'] or base_line_dict['credit']
|
||||
base_line_dict['balance'] = tax_res['base']
|
||||
base_line_dict['tax_tag_ids'] = [(6, 0, res['base_tags'])]
|
||||
return new_aml_dicts
|
||||
|
||||
def _get_write_off_move_lines_dict(self, st_line, move_lines=None, residual_balance=None):
|
||||
def _get_write_off_move_lines_dict(self, st_line, residual_balance):
|
||||
''' Get move.lines dict (to be passed to the create()) corresponding to the reconciliation model's write-off lines.
|
||||
:param st_line: An account.bank.statement.line record.(possibly empty, if performing manual reconciliation)
|
||||
:param move_lines: An account.move.line recordset.
|
||||
:param st_line: An account.bank.statement.line record.(possibly empty, if performing manual reconciliation)
|
||||
:param residual_balance: The residual balance of the statement line.
|
||||
:return: A list of dict representing move.lines to be created corresponding to the write-off lines.
|
||||
'''
|
||||
self.ensure_one()
|
||||
@@ -259,42 +257,39 @@ class AccountReconcileModel(models.Model):
|
||||
if self.rule_type == 'invoice_matching' and (not self.match_total_amount or (self.match_total_amount_param == 100)):
|
||||
return []
|
||||
|
||||
line_residual = st_line.currency_id and st_line.amount_currency or st_line.amount
|
||||
line_currency = st_line.currency_id or st_line.journal_id.currency_id or self.company_id.currency_id
|
||||
lines_vals_list = []
|
||||
|
||||
total_residual = move_lines and sum(aml.currency_id and aml.amount_residual_currency or aml.amount_residual for aml in move_lines) or 0.0
|
||||
|
||||
balance = total_residual - line_residual
|
||||
|
||||
new_aml_dicts = []
|
||||
if residual_balance is None:
|
||||
residual_balance = balance
|
||||
for line in self.line_ids:
|
||||
if not line.account_id or float_is_zero(residual_balance, precision_rounding=line_currency.rounding):
|
||||
continue
|
||||
|
||||
if not line.account_id or st_line.company_currency_id.is_zero(residual_balance):
|
||||
return []
|
||||
|
||||
if line.amount_type == 'percentage':
|
||||
line_balance = residual_balance * (line.amount / 100.0)
|
||||
balance = residual_balance * (line.amount / 100.0)
|
||||
elif line.amount_type == "regex":
|
||||
match = re.search(line.amount_string, st_line.name)
|
||||
match = re.search(line.amount_string, st_line.payment_ref)
|
||||
if match:
|
||||
line_balance = copysign(float(re.sub(r'\D' + self.decimal_separator, '', match.group(1)).replace(self.decimal_separator, '.')) * (1 if balance > 0.0 else -1), balance)
|
||||
sign = 1 if residual_balance > 0.0 else -1
|
||||
extracted_balance = float(re.sub(r'\D' + self.decimal_separator, '', match.group(1)).replace(self.decimal_separator, '.'))
|
||||
balance = copysign(extracted_balance * sign, residual_balance)
|
||||
else:
|
||||
line_balance = 0
|
||||
balance = 0
|
||||
else:
|
||||
line_balance = line.amount * (1 if residual_balance > 0.0 else -1)
|
||||
balance = line.amount * (1 if residual_balance > 0.0 else -1)
|
||||
|
||||
writeoff_line = {
|
||||
'name': line.label or st_line.name,
|
||||
'name': line.label or st_line.payment_ref,
|
||||
'balance': balance,
|
||||
'account_id': line.account_id.id,
|
||||
'currency_id': False,
|
||||
'analytic_account_id': line.analytic_account_id.id,
|
||||
'analytic_tag_ids': [(6, 0, line.analytic_tag_ids.ids)],
|
||||
'debit': line_balance > 0 and line_balance or 0,
|
||||
'credit': line_balance < 0 and -line_balance or 0,
|
||||
'reconcile_model_id': self.id,
|
||||
}
|
||||
new_aml_dicts.append(writeoff_line)
|
||||
lines_vals_list.append(writeoff_line)
|
||||
|
||||
residual_balance -= balance
|
||||
|
||||
residual_balance -= line_balance
|
||||
if line.tax_ids:
|
||||
writeoff_line['tax_ids'] = [(6, None, line.tax_ids.ids)]
|
||||
tax = line.tax_ids
|
||||
@@ -302,63 +297,59 @@ class AccountReconcileModel(models.Model):
|
||||
# only allow to set the force_tax_included field if we have one tax selected
|
||||
if line.force_tax_included:
|
||||
tax = tax[0].with_context(force_price_include=True)
|
||||
new_aml_dicts += self._get_taxes_move_lines_dict(tax, writeoff_line)
|
||||
lines_vals_list += self._get_taxes_move_lines_dict(tax, writeoff_line)
|
||||
|
||||
return new_aml_dicts
|
||||
return lines_vals_list
|
||||
|
||||
def _prepare_reconciliation(self, st_line, move_lines=None, partner=None):
|
||||
''' Reconcile the statement line with some move lines using this reconciliation model.
|
||||
:param st_line: An account.bank.statement.line record.
|
||||
:param move_lines: An account.move.line recordset.
|
||||
:param partner_id: An optional res.partner record. If not set, st_line.partner_id will be used.
|
||||
:return: Counterpart account.moves.
|
||||
def _prepare_reconciliation(self, st_line, aml_ids=[], partner=None):
|
||||
''' Prepare the reconciliation of the statement line with some counterpart line but
|
||||
also with some auto-generated write-off lines.
|
||||
|
||||
The complexity of this method comes from the fact the reconciliation will be soft meaning
|
||||
it will be done only if the reconciliation will not trigger an error.
|
||||
For example, the reconciliation will be skipped if we need to create an open balance but we
|
||||
don't have a partner to get the receivable/payable account.
|
||||
|
||||
This method works in two major steps. First, simulate the reconciliation of the account.move.line.
|
||||
Then, add some write-off lines depending the rule's fields.
|
||||
|
||||
:param st_line: An account.bank.statement.line record.
|
||||
:param aml_ids: The ids of some account.move.line to reconcile.
|
||||
:param partner: An optional res.partner record. If not specified, fallback on the statement line's partner.
|
||||
:return: A list of dictionary to be passed to the account.bank.statement.line's 'reconcile' method.
|
||||
'''
|
||||
self.ensure_one()
|
||||
liquidity_lines, suspense_lines, other_lines = st_line._seek_for_lines()
|
||||
|
||||
# Create counterpart_aml_dicts + payment_aml_rec.
|
||||
counterpart_aml_dicts = []
|
||||
payment_aml_rec = self.env['account.move.line']
|
||||
if move_lines:
|
||||
for aml in move_lines:
|
||||
if aml.account_id.internal_type == 'liquidity':
|
||||
payment_aml_rec |= aml
|
||||
else:
|
||||
amount = aml.currency_id and aml.amount_residual_currency or aml.amount_residual
|
||||
counterpart_aml_dicts.append({
|
||||
'name': aml.name if aml.name != '/' else aml.move_id.name,
|
||||
'debit': amount < 0 and -amount or 0,
|
||||
'credit': amount > 0 and amount or 0,
|
||||
'move_line': aml,
|
||||
})
|
||||
if st_line.to_check:
|
||||
residual_balance = -liquidity_lines.balance
|
||||
elif suspense_lines.account_id.reconcile:
|
||||
residual_balance = sum(suspense_lines.mapped('amount_residual'))
|
||||
else:
|
||||
residual_balance = sum(suspense_lines.mapped('balance'))
|
||||
|
||||
# Create new_aml_dicts.
|
||||
new_aml_dicts = self._get_write_off_move_lines_dict(st_line, move_lines=move_lines)
|
||||
partner = partner or st_line.partner_id
|
||||
lines_vals_list = [{'id': aml_id} for aml_id in aml_ids]
|
||||
reconciliation_overview, open_balance_vals = st_line._prepare_reconciliation(lines_vals_list)
|
||||
|
||||
line_residual = st_line.currency_id and st_line.amount_currency or st_line.amount
|
||||
line_currency = st_line.currency_id or st_line.journal_id.currency_id or st_line.company_id.currency_id
|
||||
total_residual = move_lines and sum(aml.currency_id and aml.amount_residual_currency or aml.amount_residual for aml in move_lines) or 0.0
|
||||
total_residual -= sum(aml['debit'] - aml['credit'] for aml in new_aml_dicts)
|
||||
for reconciliation_vals in reconciliation_overview:
|
||||
residual_balance -= reconciliation_vals['line_vals']['debit'] - reconciliation_vals['line_vals']['credit']
|
||||
|
||||
# Create open_balance_dict
|
||||
open_balance_dict = None
|
||||
if float_compare(line_residual, total_residual, precision_rounding=line_currency.rounding) != 0:
|
||||
if not partner and not st_line.partner_id:
|
||||
open_balance_dict = False
|
||||
writeoff_vals_list = self._get_write_off_move_lines_dict(st_line, residual_balance)
|
||||
|
||||
for line_vals in writeoff_vals_list:
|
||||
residual_balance -= st_line.company_currency_id.round(line_vals['balance'])
|
||||
|
||||
# Check we have enough information to create an open balance.
|
||||
if not st_line.company_currency_id.is_zero(residual_balance):
|
||||
if st_line.amount > 0:
|
||||
open_balance_account = partner.property_account_receivable_id
|
||||
else:
|
||||
balance = total_residual - line_residual
|
||||
partner = (partner or st_line.partner_id).with_company(st_line.company_id)
|
||||
open_balance_dict = {
|
||||
'name': '%s : %s' % (st_line.name, _('Open Balance')),
|
||||
'account_id': balance < 0 and partner.property_account_payable_id.id or partner.property_account_receivable_id.id,
|
||||
'debit': balance > 0 and balance or 0,
|
||||
'credit': balance < 0 and -balance or 0,
|
||||
}
|
||||
return {
|
||||
'counterpart_aml_dicts': counterpart_aml_dicts,
|
||||
'payment_aml_rec': payment_aml_rec,
|
||||
'new_aml_dicts': new_aml_dicts,
|
||||
'open_balance_dict': open_balance_dict
|
||||
}
|
||||
open_balance_account = partner.property_account_payable_id
|
||||
if not open_balance_account:
|
||||
return []
|
||||
|
||||
return lines_vals_list + writeoff_vals_list
|
||||
|
||||
####################################################
|
||||
# RECONCILIATION CRITERIA
|
||||
@@ -369,7 +360,7 @@ class AccountReconcileModel(models.Model):
|
||||
rule = self
|
||||
# Filter on journals.
|
||||
if rule.match_journal_ids:
|
||||
query += ' AND st_line.journal_id IN %s'
|
||||
query += ' AND st_line_move.journal_id IN %s'
|
||||
params += [tuple(rule.match_journal_ids.ids)]
|
||||
|
||||
# Filter on amount nature.
|
||||
@@ -393,15 +384,15 @@ class AccountReconcileModel(models.Model):
|
||||
params += [rule.match_amount_min, rule.match_amount_max]
|
||||
|
||||
# Filter on label, note and transaction_type
|
||||
for field, column in [('label', 'name'), ('note', 'note'), ('transaction_type', 'transaction_type')]:
|
||||
for table, field, column in [('st_line', 'label', 'payment_ref'), ('st_line_move', 'note', 'narration'), ('st_line', 'transaction_type', 'transaction_type')]:
|
||||
if rule['match_' + field] == 'contains':
|
||||
query += ' AND st_line.{} ILIKE %s'.format(column)
|
||||
query += ' AND {}.{} ILIKE %s'.format(table, column)
|
||||
params += ['%%%s%%' % rule['match_' + field + '_param']]
|
||||
elif rule['match_' + field] == 'not_contains':
|
||||
query += ' AND st_line.{} NOT ILIKE %s'.format(column)
|
||||
query += ' AND {}.{} NOT ILIKE %s'.format(table, column)
|
||||
params += ['%%%s%%' % rule['match_' + field + '_param']]
|
||||
elif rule['match_' + field] == 'match_regex':
|
||||
query += ' AND st_line.{} ~* %s'.format(column)
|
||||
query += ' AND {}.{} ~* %s'.format(table, column)
|
||||
params += [rule['match_' + field + '_param']]
|
||||
|
||||
# Filter on partners.
|
||||
@@ -478,11 +469,11 @@ class AccountReconcileModel(models.Model):
|
||||
substring(REGEXP_REPLACE(aml.name, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*') != ''
|
||||
AND
|
||||
regexp_split_to_array(substring(REGEXP_REPLACE(aml.name, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'),'\s+')
|
||||
&& regexp_split_to_array(substring(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'), '\s+')
|
||||
&& regexp_split_to_array(substring(REGEXP_REPLACE(st_line.payment_ref, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'), '\s+')
|
||||
)
|
||||
OR
|
||||
regexp_split_to_array(substring(REGEXP_REPLACE(move.name, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'),'\s+')
|
||||
&& regexp_split_to_array(substring(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'), '\s+')
|
||||
&& regexp_split_to_array(substring(REGEXP_REPLACE(st_line.payment_ref, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'), '\s+')
|
||||
OR
|
||||
(
|
||||
move.ref IS NOT NULL
|
||||
@@ -490,24 +481,25 @@ class AccountReconcileModel(models.Model):
|
||||
substring(REGEXP_REPLACE(move.ref, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*') != ''
|
||||
AND
|
||||
regexp_split_to_array(substring(REGEXP_REPLACE(move.ref, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'),'\s+')
|
||||
&& regexp_split_to_array(substring(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'), '\s+')
|
||||
&& regexp_split_to_array(substring(REGEXP_REPLACE(st_line.payment_ref, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'), '\s+')
|
||||
) AS communication_flag,
|
||||
-- Determine a matching or not with the statement line communication using the move.invoice_payment_ref.
|
||||
-- Determine a matching or not with the statement line communication using the move.payment_reference.
|
||||
(
|
||||
move.invoice_payment_ref IS NOT NULL
|
||||
move.payment_reference IS NOT NULL
|
||||
AND
|
||||
regexp_replace(move.invoice_payment_ref, '\s+', '', 'g') = regexp_replace(st_line.name, '\s+', '', 'g')
|
||||
regexp_replace(move.payment_reference, '\s+', '', 'g') = regexp_replace(st_line.payment_ref, '\s+', '', 'g')
|
||||
) AS payment_reference_flag
|
||||
FROM account_bank_statement_line st_line
|
||||
LEFT JOIN account_journal journal ON journal.id = st_line.journal_id
|
||||
JOIN account_move st_line_move ON st_line_move.id = st_line.move_id
|
||||
JOIN account_journal journal ON journal.id = st_line_move.journal_id
|
||||
LEFT JOIN jnl_precision ON jnl_precision.journal_id = journal.id
|
||||
LEFT JOIN res_company company ON company.id = st_line.company_id
|
||||
JOIN res_company company ON company.id = st_line_move.company_id
|
||||
LEFT JOIN partners_table line_partner ON line_partner.line_id = st_line.id
|
||||
, account_move_line aml
|
||||
LEFT JOIN account_move move ON move.id = aml.move_id AND move.state = 'posted'
|
||||
LEFT JOIN account_account account ON account.id = aml.account_id
|
||||
WHERE st_line.id IN %s
|
||||
AND aml.company_id = st_line.company_id
|
||||
AND aml.company_id = st_line_move.company_id
|
||||
AND move.state = 'posted'
|
||||
AND (
|
||||
-- the field match_partner of the rule might enforce the second part of
|
||||
@@ -534,7 +526,7 @@ class AccountReconcileModel(models.Model):
|
||||
(
|
||||
line_partner.partner_id = 0
|
||||
AND
|
||||
substring(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*') != ''
|
||||
substring(REGEXP_REPLACE(st_line.payment_ref, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*') != ''
|
||||
AND
|
||||
(
|
||||
(
|
||||
@@ -543,11 +535,11 @@ class AccountReconcileModel(models.Model):
|
||||
substring(REGEXP_REPLACE(aml.name, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*') != ''
|
||||
AND
|
||||
regexp_split_to_array(substring(REGEXP_REPLACE(aml.name, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'),'\s+')
|
||||
&& regexp_split_to_array(substring(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'), '\s+')
|
||||
&& regexp_split_to_array(substring(REGEXP_REPLACE(st_line.payment_ref, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'), '\s+')
|
||||
)
|
||||
OR
|
||||
regexp_split_to_array(substring(REGEXP_REPLACE(move.name, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'),'\s+')
|
||||
&& regexp_split_to_array(substring(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'), '\s+')
|
||||
&& regexp_split_to_array(substring(REGEXP_REPLACE(st_line.payment_ref, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'), '\s+')
|
||||
OR
|
||||
(
|
||||
move.ref IS NOT NULL
|
||||
@@ -555,41 +547,24 @@ class AccountReconcileModel(models.Model):
|
||||
substring(REGEXP_REPLACE(move.ref, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*') != ''
|
||||
AND
|
||||
regexp_split_to_array(substring(REGEXP_REPLACE(move.ref, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'),'\s+')
|
||||
&& regexp_split_to_array(substring(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'), '\s+')
|
||||
&& regexp_split_to_array(substring(REGEXP_REPLACE(st_line.payment_ref, '[^0-9|^\s]', '', 'g'), '\S(?:.*\S)*'), '\s+')
|
||||
)
|
||||
OR
|
||||
(
|
||||
move.invoice_payment_ref IS NOT NULL
|
||||
move.payment_reference IS NOT NULL
|
||||
AND
|
||||
regexp_replace(move.invoice_payment_ref, '\s+', '', 'g') = regexp_replace(st_line.name, '\s+', '', 'g')
|
||||
regexp_replace(move.payment_reference, '\s+', '', 'g') = regexp_replace(st_line.payment_ref, '\s+', '', 'g')
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
AND
|
||||
(
|
||||
(
|
||||
-- blue lines appearance conditions
|
||||
aml.account_id IN (journal.default_credit_account_id, journal.default_debit_account_id)
|
||||
AND aml.statement_id IS NULL
|
||||
AND (
|
||||
company.account_bank_reconciliation_start IS NULL
|
||||
OR
|
||||
aml.date > company.account_bank_reconciliation_start
|
||||
)
|
||||
)
|
||||
OR
|
||||
(
|
||||
-- black lines appearance conditions
|
||||
account.reconcile IS TRUE
|
||||
AND aml.reconciled IS FALSE
|
||||
)
|
||||
)
|
||||
AND account.reconcile IS TRUE
|
||||
AND aml.reconciled IS FALSE
|
||||
'''
|
||||
# Filter on the same currency.
|
||||
if rule.match_same_currency:
|
||||
query += '''
|
||||
AND COALESCE(st_line.currency_id, journal.currency_id, company.currency_id) = COALESCE(aml.currency_id, company.currency_id)
|
||||
AND COALESCE(st_line.foreign_currency_id, st_line_move.currency_id) = COALESCE(aml.currency_id, company.currency_id)
|
||||
'''
|
||||
|
||||
params = [rule.sequence, rule.id, tuple(st_lines.ids)]
|
||||
@@ -625,9 +600,10 @@ class AccountReconcileModel(models.Model):
|
||||
%s AS model_id,
|
||||
st_line.id AS id
|
||||
FROM account_bank_statement_line st_line
|
||||
LEFT JOIN account_journal journal ON journal.id = st_line.journal_id
|
||||
JOIN account_move st_line_move ON st_line_move.id = st_line.move_id
|
||||
LEFT JOIN account_journal journal ON journal.id = st_line_move.journal_id
|
||||
LEFT JOIN jnl_precision ON jnl_precision.journal_id = journal.id
|
||||
LEFT JOIN res_company company ON company.id = st_line.company_id
|
||||
LEFT JOIN res_company company ON company.id = st_line_move.company_id
|
||||
LEFT JOIN partners_table line_partner ON line_partner.line_id = st_line.id
|
||||
WHERE st_line.id IN %s
|
||||
'''
|
||||
@@ -803,30 +779,20 @@ class AccountReconcileModel(models.Model):
|
||||
# Create write-off lines.
|
||||
move_lines = self.env['account.move.line'].browse(results[line.id]['aml_ids'])
|
||||
partner = partner_map and partner_map.get(line.id) and self.env['res.partner'].browse(partner_map[line.id])
|
||||
reconciliation_results = model._prepare_reconciliation(line, move_lines, partner=partner)
|
||||
lines_vals_list = model._prepare_reconciliation(line, aml_ids=results[line.id]['aml_ids'], partner=partner)
|
||||
|
||||
# A write-off must be applied.
|
||||
if reconciliation_results['new_aml_dicts']:
|
||||
# A write-off must be applied since there is some 'new' lines to propose.
|
||||
if not lines_vals_list or any(not line_vals.get('id') for line_vals in lines_vals_list):
|
||||
results[line.id]['status'] = 'write_off'
|
||||
|
||||
# Process auto-reconciliation.
|
||||
if (first_batch_candidates or second_batch_candidates) and model.auto_reconcile:
|
||||
# An open balance is needed but no partner has been found.
|
||||
if reconciliation_results['open_balance_dict'] is False:
|
||||
break
|
||||
|
||||
new_aml_dicts = reconciliation_results['new_aml_dicts']
|
||||
if reconciliation_results['open_balance_dict']:
|
||||
new_aml_dicts.append(reconciliation_results['open_balance_dict'])
|
||||
if lines_vals_list and (first_batch_candidates or second_batch_candidates) and model.auto_reconcile:
|
||||
if not line.partner_id and partner:
|
||||
line.partner_id = partner
|
||||
counterpart_moves = line.process_reconciliation(
|
||||
counterpart_aml_dicts=reconciliation_results['counterpart_aml_dicts'],
|
||||
payment_aml_rec=reconciliation_results['payment_aml_rec'],
|
||||
new_aml_dicts=new_aml_dicts,
|
||||
)
|
||||
line.reconcile(lines_vals_list)
|
||||
|
||||
results[line.id]['status'] = 'reconciled'
|
||||
results[line.id]['reconciled_lines'] = counterpart_moves.mapped('line_ids')
|
||||
results[line.id]['reconciled_lines'] = line.line_ids
|
||||
|
||||
# The reconciled move lines are no longer candidates for another rule.
|
||||
reconciled_amls_ids.update(move_lines.ids)
|
||||
@@ -840,26 +806,18 @@ class AccountReconcileModel(models.Model):
|
||||
|
||||
# Create write-off lines.
|
||||
partner = partner_map and partner_map.get(line.id) and self.env['res.partner'].browse(partner_map[line.id])
|
||||
reconciliation_results = model._prepare_reconciliation(line, partner=partner)
|
||||
lines_vals_list = model._prepare_reconciliation(line, partner=partner)
|
||||
|
||||
# An open balance is needed but no partner has been found.
|
||||
if reconciliation_results['open_balance_dict'] is False:
|
||||
if not lines_vals_list:
|
||||
break
|
||||
|
||||
# Process auto-reconciliation.
|
||||
if model.auto_reconcile:
|
||||
new_aml_dicts = reconciliation_results['new_aml_dicts']
|
||||
if reconciliation_results['open_balance_dict']:
|
||||
new_aml_dicts.append(reconciliation_results['open_balance_dict'])
|
||||
if not line.partner_id and partner:
|
||||
line.partner_id = partner
|
||||
counterpart_moves = line.process_reconciliation(
|
||||
counterpart_aml_dicts=reconciliation_results['counterpart_aml_dicts'],
|
||||
payment_aml_rec=reconciliation_results['payment_aml_rec'],
|
||||
new_aml_dicts=new_aml_dicts,
|
||||
)
|
||||
line.reconcile(lines_vals_list)
|
||||
results[line.id]['status'] = 'reconciled'
|
||||
results[line.id]['reconciled_lines'] = counterpart_moves.mapped('line_ids')
|
||||
results[line.id]['reconciled_lines'] = line.line_ids
|
||||
|
||||
# Break models loop.
|
||||
break
|
||||
|
||||
@@ -105,6 +105,7 @@ class AccountChartTemplate(models.Model):
|
||||
string="Gain Exchange Rate Account", domain=[('internal_type', '=', 'other'), ('deprecated', '=', False)])
|
||||
expense_currency_exchange_account_id = fields.Many2one('account.account.template',
|
||||
string="Loss Exchange Rate Account", domain=[('internal_type', '=', 'other'), ('deprecated', '=', False)])
|
||||
account_journal_suspense_account_id = fields.Many2one('account.account.template', string='Journal Suspense Account')
|
||||
default_cash_difference_income_account_id = fields.Many2one('account.account.template', string="Cash Difference Income Account")
|
||||
default_cash_difference_expense_account_id = fields.Many2one('account.account.template', string="Cash Difference Expense Account")
|
||||
default_pos_receivable_account_id = fields.Many2one('account.account.template', string="PoS receivable account")
|
||||
@@ -122,14 +123,14 @@ class AccountChartTemplate(models.Model):
|
||||
property_advance_tax_payment_account_id = fields.Many2one('account.account.template', string="Advance tax payment account")
|
||||
|
||||
@api.model
|
||||
def _prepare_transfer_account_template(self):
|
||||
def _prepare_transfer_account_template(self, prefix=None):
|
||||
''' Prepare values to create the transfer account that is an intermediary account used when moving money
|
||||
from a liquidity account to another.
|
||||
|
||||
:return: A dictionary of values to create a new account.account.
|
||||
'''
|
||||
digits = self.code_digits
|
||||
prefix = self.transfer_account_code_prefix or ''
|
||||
prefix = prefix or self.transfer_account_code_prefix or ''
|
||||
# Flatten the hierarchy of chart templates.
|
||||
chart_template = self
|
||||
chart_templates = self
|
||||
@@ -154,6 +155,15 @@ class AccountChartTemplate(models.Model):
|
||||
'chart_template_id': self.id,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _create_liquidity_journal_suspense_account(self, company, code_digits):
|
||||
return self.env['account.account'].create({
|
||||
'name': _("Bank Suspense Account"),
|
||||
'code': self.env['account.account']._search_new_account_code(company, code_digits, company.transfer_account_code_prefix or ''),
|
||||
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
|
||||
def try_loading(self, company=False):
|
||||
""" Installs this chart of accounts for the current company if not chart
|
||||
of accounts had been created for it yet.
|
||||
@@ -239,8 +249,12 @@ class AccountChartTemplate(models.Model):
|
||||
company.write({
|
||||
'default_cash_difference_income_account_id': acc_template_ref.get(self.default_cash_difference_income_account_id.id, False),
|
||||
'default_cash_difference_expense_account_id': acc_template_ref.get(self.default_cash_difference_expense_account_id.id, False),
|
||||
'account_journal_suspense_account_id': acc_template_ref.get(self.account_journal_suspense_account_id.id),
|
||||
})
|
||||
|
||||
if not company.account_journal_suspense_account_id:
|
||||
company.account_journal_suspense_account_id = self._create_liquidity_journal_suspense_account(company, self.code_digits)
|
||||
|
||||
# Set default PoS receivable account in company
|
||||
default_pos_receivable = self.default_pos_receivable_account_id.id
|
||||
if not default_pos_receivable and self.parent_id:
|
||||
@@ -333,19 +347,11 @@ class AccountChartTemplate(models.Model):
|
||||
'type': acc['account_type'],
|
||||
'company_id': company.id,
|
||||
'currency_id': acc.get('currency_id', self.env['res.currency']).id,
|
||||
'sequence': 10
|
||||
'sequence': 10,
|
||||
})
|
||||
|
||||
return bank_journals
|
||||
|
||||
def get_countries_posting_at_bank_rec(self):
|
||||
""" Returns the list of the country codes of the countries for which, by default,
|
||||
payments made on bank journals should be creating draft account.move objects,
|
||||
which get in turn posted when their payment gets reconciled with a bank statement line.
|
||||
This function is an extension hook for localization modules.
|
||||
"""
|
||||
return []
|
||||
|
||||
@api.model
|
||||
def _get_default_bank_journals_data(self):
|
||||
""" Returns the data needed to create the default bank journals when
|
||||
|
||||
@@ -45,6 +45,7 @@ class ResCompany(models.Model):
|
||||
cash_account_code_prefix = fields.Char(string='Prefix of the cash accounts')
|
||||
default_cash_difference_income_account_id = fields.Many2one('account.account', string="Cash Difference Income Account")
|
||||
default_cash_difference_expense_account_id = fields.Many2one('account.account', string="Cash Difference Expense Account")
|
||||
account_journal_suspense_account_id = fields.Many2one('account.account', string='Journal Suspense Account')
|
||||
transfer_account_code_prefix = fields.Char(string='Prefix of the transfer accounts')
|
||||
account_sale_tax_id = fields.Many2one('account.tax', string="Default Sale Tax")
|
||||
account_purchase_tax_id = fields.Many2one('account.tax', string="Default Purchase Tax")
|
||||
@@ -64,9 +65,6 @@ class ResCompany(models.Model):
|
||||
property_stock_valuation_account_id = fields.Many2one('account.account', string="Account Template for Stock Valuation")
|
||||
bank_journal_ids = fields.One2many('account.journal', 'company_id', domain=[('type', '=', 'bank')], string='Bank Journals')
|
||||
tax_exigibility = fields.Boolean(string='Use Cash Basis')
|
||||
account_bank_reconciliation_start = fields.Date(string="Bank Reconciliation Threshold", help="""The bank reconciliation widget won't ask to reconcile payments older than this date.
|
||||
This is useful if you install accounting after having used invoicing for some time and
|
||||
don't want to reconcile all the past payments with bank statements.""")
|
||||
|
||||
incoterm_id = fields.Many2one('account.incoterms', string='Default incoterm',
|
||||
help='International Commercial Terms are a series of predefined commercial terms used in international transactions.')
|
||||
@@ -456,7 +454,7 @@ class ResCompany(models.Model):
|
||||
raise RedirectWarning(msg, action.id, _("Go to the journal configuration"))
|
||||
|
||||
sample_invoice = self.env['account.move'].with_context(default_move_type='out_invoice', default_journal_id=journal.id).create({
|
||||
'invoice_payment_ref': _('Sample invoice'),
|
||||
'payment_reference': _('Sample invoice'),
|
||||
'partner_id': partner.id,
|
||||
'invoice_line_ids': [
|
||||
(0, 0, {
|
||||
|
||||
@@ -74,11 +74,6 @@ class ResConfigSettings(models.TransientModel):
|
||||
module_snailmail_account = fields.Boolean(string="Snailmail")
|
||||
tax_exigibility = fields.Boolean(string='Cash Basis', related='company_id.tax_exigibility', readonly=False)
|
||||
tax_cash_basis_journal_id = fields.Many2one('account.journal', related='company_id.tax_cash_basis_journal_id', string="Tax Cash Basis Journal", readonly=False)
|
||||
account_bank_reconciliation_start = fields.Date(string="Bank Reconciliation Threshold",
|
||||
related='company_id.account_bank_reconciliation_start', readonly=False,
|
||||
help="""The bank reconciliation widget won't ask to reconcile payments older than this date.
|
||||
This is useful if you install accounting after having used invoicing for some time and
|
||||
don't want to reconcile all the past payments with bank statements.""")
|
||||
|
||||
qr_code = fields.Boolean(string='Display SEPA QR-code', related='company_id.qr_code', readonly=False)
|
||||
invoice_is_print = fields.Boolean(string='Print', related='company_id.invoice_is_print', readonly=False)
|
||||
|
||||
@@ -55,7 +55,7 @@ class AccountInvoiceReport(models.Model):
|
||||
_depends = {
|
||||
'account.move': [
|
||||
'name', 'state', 'move_type', 'partner_id', 'invoice_user_id', 'fiscal_position_id',
|
||||
'invoice_date', 'invoice_date_due', 'invoice_payment_term_id', 'invoice_partner_bank_id',
|
||||
'invoice_date', 'invoice_date_due', 'invoice_payment_term_id', 'partner_bank_id',
|
||||
],
|
||||
'account.move.line': [
|
||||
'quantity', 'price_subtotal', 'amount_residual', 'balance', 'amount_currency',
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<a title="assign to invoice" role="button" class="oe_form_field btn btn-link outstanding_credit_assign" t-att-data-id="line.id" style="margin-right: 10px;" href="#" data-toggle="tooltip">Add</a>
|
||||
</td>
|
||||
<td style="max-width: 30em;">
|
||||
<div class="oe_form_field" style="margin-right: 30px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;" t-att-title="line.payment_date" data-toggle="tooltip"><t t-esc="line.journal_name"></t></div>
|
||||
<div class="oe_form_field" style="margin-right: 30px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;" t-att-title="line.date" data-toggle="tooltip"><t t-esc="line.journal_name"></t></div>
|
||||
</td>
|
||||
</t>
|
||||
<t t-if="!outstanding">
|
||||
|
||||
@@ -10,15 +10,13 @@ from . import test_account_account
|
||||
from . import test_account_tax
|
||||
from . import test_account_analytic
|
||||
from . import test_account_payment
|
||||
from . import test_account_bank_statement
|
||||
from . import test_account_move_partner_count
|
||||
from . import test_account_move_rounding
|
||||
from . import test_account_invoice_report
|
||||
from . import test_account_journal_dashboard
|
||||
from . import test_account_bank_statement
|
||||
from . import test_bank_statement_reconciliation
|
||||
from . import test_control_journal_account
|
||||
from . import test_fiscal_position
|
||||
from . import test_payment
|
||||
from . import test_reconciliation
|
||||
from . import test_search
|
||||
from . import test_settings
|
||||
@@ -31,6 +29,6 @@ from . import test_reconciliation_matching_rules
|
||||
from . import test_account_onboarding
|
||||
from . import test_portal_attachment
|
||||
from . import test_tax_report
|
||||
from . import test_reconciliation_heavy_load
|
||||
from . import test_transfer_wizard
|
||||
from . import test_account_incoming_supplier_invoice
|
||||
from . import test_account_payment_register
|
||||
|
||||
@@ -133,6 +133,12 @@ class AccountTestCommon(SavepointCase):
|
||||
'name': 'Opening Expense - (test)',
|
||||
'user_type_id': cls.env.ref('account.data_account_type_expenses').id,
|
||||
})
|
||||
cls.sus = cls.env['account.account'].create({
|
||||
'name': 'Suspense Account - (test)',
|
||||
'code': 'X1115',
|
||||
'user_type_id': cls.env.ref('account.data_account_type_current_assets').id,
|
||||
})
|
||||
cls.company.account_journal_suspense_account_id = cls.sus
|
||||
|
||||
# Profit and Loss
|
||||
cls.income_fx_income = cls.env['account.account'].create({
|
||||
@@ -238,16 +244,12 @@ class AccountTestCommon(SavepointCase):
|
||||
'name': 'Vendor Bills - Test',
|
||||
'code': 'TEXJ',
|
||||
'type': 'purchase',
|
||||
'default_credit_account_id': cls.a_expense.id,
|
||||
'default_debit_account_id': cls.a_expense.id,
|
||||
'refund_sequence': True,
|
||||
})
|
||||
cls.bank_journal = cls.env['account.journal'].create({
|
||||
'name': 'Bank - Test',
|
||||
'code': 'TBNK',
|
||||
'type': 'bank',
|
||||
'default_credit_account_id': cls.bnk.id,
|
||||
'default_debit_account_id': cls.bnk.id,
|
||||
})
|
||||
cls.cash_journal = cls.env['account.journal'].create({
|
||||
'name': 'Cash - Test',
|
||||
@@ -367,6 +369,12 @@ class AccountTestNoChartCommon(SavepointCaseWithUserDemo):
|
||||
'code': 'SJT0',
|
||||
})
|
||||
|
||||
cls.general_journal0 = cls.env['account.journal'].create({
|
||||
'name': 'General Journal',
|
||||
'type': 'general',
|
||||
'code': 'GJT0',
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def setUpAdditionalAccounts(cls):
|
||||
""" Set up some addionnal accounts: expenses, revenue, ... """
|
||||
@@ -415,6 +423,12 @@ class AccountTestNoChartCommon(SavepointCaseWithUserDemo):
|
||||
'type': 'general',
|
||||
'company_id': cls.env.user.company_id.id,
|
||||
})
|
||||
cls.journal_bank = cls.env['account.journal'].create({
|
||||
'name': 'Bank Journal - Test',
|
||||
'code': 'AJ-BANK',
|
||||
'type': 'bank',
|
||||
'company_id': cls.env.user.company_id.id,
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def setUpUsers(cls):
|
||||
@@ -586,10 +600,24 @@ class AccountTestNoChartCommonMultiCompany(AccountTestNoChartCommon):
|
||||
class AccountTestInvoicingCommon(SavepointCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
def copy_account(cls, account):
|
||||
suffix_nb = 1
|
||||
while True:
|
||||
new_code = '%s (%s)' % (account.code, suffix_nb)
|
||||
if account.search_count([('company_id', '=', account.company_id.id), ('code', '=', new_code)]):
|
||||
suffix_nb += 1
|
||||
else:
|
||||
return account.copy(default={'code': new_code})
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super(AccountTestInvoicingCommon, cls).setUpClass()
|
||||
|
||||
chart_template = cls.env.user.company_id.chart_template_id
|
||||
chart_template = None
|
||||
if chart_template_ref:
|
||||
chart_template = cls.env.ref(chart_template_ref)
|
||||
if not chart_template:
|
||||
chart_template = cls.env.user.company_id.chart_template_id
|
||||
if not chart_template:
|
||||
chart_template = cls.env.ref('l10n_generic_coa.configurable_chart_template', raise_if_not_found=False)
|
||||
if not chart_template:
|
||||
@@ -634,8 +662,8 @@ class AccountTestInvoicingCommon(SavepointCase):
|
||||
'uom_id': cls.env.ref('uom.product_uom_dozen').id,
|
||||
'lst_price': 200.0,
|
||||
'standard_price': 160.0,
|
||||
'property_account_income_id': cls.company_data['default_account_revenue'].copy().id,
|
||||
'property_account_expense_id': cls.company_data['default_account_expense'].copy().id,
|
||||
'property_account_income_id': cls.copy_account(cls.company_data['default_account_revenue']).id,
|
||||
'property_account_expense_id': cls.copy_account(cls.company_data['default_account_expense']).id,
|
||||
'taxes_id': [(6, 0, (cls.tax_sale_a + cls.tax_sale_b).ids)],
|
||||
'supplier_taxes_id': [(6, 0, (cls.tax_purchase_a + cls.tax_purchase_b).ids)],
|
||||
})
|
||||
@@ -793,29 +821,30 @@ class AccountTestInvoicingCommon(SavepointCase):
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setup_multi_currency_data(cls):
|
||||
gold_currency = cls.env['res.currency'].create({
|
||||
def setup_multi_currency_data(cls, default_values={}, rate2016=3.0, rate2017=2.0):
|
||||
foreign_currency = cls.env['res.currency'].create({
|
||||
'name': 'Gold Coin',
|
||||
'symbol': '☺',
|
||||
'rounding': 0.001,
|
||||
'position': 'after',
|
||||
'currency_unit_label': 'Gold',
|
||||
'currency_subunit_label': 'Silver',
|
||||
**default_values,
|
||||
})
|
||||
rate1 = cls.env['res.currency.rate'].create({
|
||||
'name': '2016-01-01',
|
||||
'rate': 3.0,
|
||||
'currency_id': gold_currency.id,
|
||||
'rate': rate2016,
|
||||
'currency_id': foreign_currency.id,
|
||||
'company_id': cls.env.company.id,
|
||||
})
|
||||
rate2 = cls.env['res.currency.rate'].create({
|
||||
'name': '2017-01-01',
|
||||
'rate': 2.0,
|
||||
'currency_id': gold_currency.id,
|
||||
'rate': rate2017,
|
||||
'currency_id': foreign_currency.id,
|
||||
'company_id': cls.env.company.id,
|
||||
})
|
||||
return {
|
||||
'currency': gold_currency,
|
||||
'currency': foreign_currency,
|
||||
'rates': rate1 + rate2,
|
||||
}
|
||||
|
||||
@@ -931,9 +960,11 @@ class TestAccountReconciliationCommon(AccountTestCommon):
|
||||
super(TestAccountReconciliationCommon, cls).setUpClass()
|
||||
cls.company = cls.env['res.company'].create({
|
||||
'name': 'A test company',
|
||||
'currency_id': cls.env.ref('base.EUR').id
|
||||
'currency_id': cls.env.ref('base.EUR').id,
|
||||
})
|
||||
|
||||
cls.env.user.company_id = cls.company
|
||||
cls.env.user.groups_id |= cls.env.ref('account.group_account_user')
|
||||
# Generate minimal data for my new company
|
||||
cls.create_accounting_minimal_data()
|
||||
|
||||
@@ -1116,41 +1147,36 @@ class TestAccountReconciliationCommon(AccountTestCommon):
|
||||
auto_validate=True
|
||||
)
|
||||
|
||||
def make_payment(self, invoice_record, bank_journal, amount=0.0, amount_currency=0.0, currency_id=None):
|
||||
def make_payment(self, invoice_record, bank_journal, amount=0.0, amount_currency=0.0, currency_id=None, reconcile_param=[]):
|
||||
bank_stmt = self.acc_bank_stmt_model.create({
|
||||
'journal_id': bank_journal.id,
|
||||
'date': time.strftime('%Y') + '-07-15',
|
||||
'name': 'payment' + invoice_record.name
|
||||
'name': 'payment' + invoice_record.name,
|
||||
'line_ids': [(0, 0, {
|
||||
'payment_ref': 'payment',
|
||||
'partner_id': self.partner_agrolait_id,
|
||||
'amount': amount,
|
||||
'amount_currency': amount_currency,
|
||||
'foreign_currency_id': currency_id,
|
||||
})],
|
||||
})
|
||||
bank_stmt.button_post()
|
||||
|
||||
bank_stmt_line = self.acc_bank_stmt_line_model.create({'name': 'payment',
|
||||
'statement_id': bank_stmt.id,
|
||||
'partner_id': self.partner_agrolait_id,
|
||||
'amount': amount,
|
||||
'amount_currency': amount_currency,
|
||||
'currency_id': currency_id,
|
||||
'date': time.strftime('%Y') + '-07-15',
|
||||
})
|
||||
line_id = invoice_record.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
|
||||
amount_in_widget = currency_id and amount_currency or amount
|
||||
bank_stmt_line.process_reconciliation(counterpart_aml_dicts=[{
|
||||
'move_line': line_id,
|
||||
'debit': amount_in_widget < 0 and -amount_in_widget or 0.0,
|
||||
'credit': amount_in_widget > 0 and amount_in_widget or 0.0,
|
||||
'name': line_id.name,
|
||||
}])
|
||||
bank_stmt.line_ids[0].reconcile(reconcile_param)
|
||||
return bank_stmt
|
||||
|
||||
def make_customer_and_supplier_flows(self, invoice_currency_id, invoice_amount, bank_journal, amount, amount_currency, transaction_currency_id):
|
||||
#we create an invoice in given invoice_currency
|
||||
invoice_record = self.create_invoice(type='out_invoice', invoice_amount=invoice_amount, currency_id=invoice_currency_id)
|
||||
#we encode a payment on it, on the given bank_journal with amount, amount_currency and transaction_currency given
|
||||
bank_stmt = self.make_payment(invoice_record, bank_journal, amount=amount, amount_currency=amount_currency, currency_id=transaction_currency_id)
|
||||
customer_move_lines = bank_stmt.move_line_ids
|
||||
line = invoice_record.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
|
||||
bank_stmt = self.make_payment(invoice_record, bank_journal, amount=amount, amount_currency=amount_currency, currency_id=transaction_currency_id, reconcile_param=[{'id': line.id}])
|
||||
customer_move_lines = bank_stmt.line_ids.line_ids
|
||||
|
||||
#we create a supplier bill in given invoice_currency
|
||||
invoice_record = self.create_invoice(type='in_invoice', invoice_amount=invoice_amount, currency_id=invoice_currency_id)
|
||||
#we encode a payment on it, on the given bank_journal with amount, amount_currency and transaction_currency given
|
||||
bank_stmt = self.make_payment(invoice_record, bank_journal, amount=-amount, amount_currency=-amount_currency, currency_id=transaction_currency_id)
|
||||
supplier_move_lines = bank_stmt.move_line_ids
|
||||
line = invoice_record.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
|
||||
bank_stmt = self.make_payment(invoice_record, bank_journal, amount=-amount, amount_currency=-amount_currency, currency_id=transaction_currency_id, reconcile_param=[{'id': line.id}])
|
||||
supplier_move_lines = bank_stmt.line_ids.line_ids
|
||||
return customer_move_lines, supplier_move_lines
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,30 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.tests import tagged
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestAccountJournal(AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
def test_constraint_currency_consistency_with_accounts(self):
|
||||
''' The accounts linked to a bank/cash journal must share the same foreign currency
|
||||
if specified.
|
||||
'''
|
||||
journal_bank = self.company_data['default_journal_bank']
|
||||
journal_bank.currency_id = self.currency_data['currency']
|
||||
|
||||
# Create another company.
|
||||
cls.company_data_2 = cls.setup_company_data('company_2_data')
|
||||
# Try to set a different currency on the 'debit' account.
|
||||
with self.assertRaises(ValidationError), self.cr.savepoint():
|
||||
journal_bank.default_debit_account_id.currency_id = self.company_data['currency']
|
||||
|
||||
# By default, tests are run with the current user set on the first company.
|
||||
cls.env.user.company_id = cls.company_data['company']
|
||||
def test_constraint_shared_accounts(self):
|
||||
''' Ensure the bank/outstanding accounts are not shared between multiple journals. '''
|
||||
journal_bank = self.company_data['default_journal_bank']
|
||||
|
||||
def test_changing_journal_company(self):
|
||||
''' Ensure you can't change the company of an account.journal if there are some journal entries '''
|
||||
|
||||
self.env['account.move'].create({
|
||||
'move_type': 'entry',
|
||||
'date': '2019-01-01',
|
||||
'journal_id': self.company_data['default_journal_sale'].id,
|
||||
})
|
||||
|
||||
with self.assertRaises(UserError), self.cr.savepoint():
|
||||
self.company_data['default_journal_sale'].company_id = self.company_data_2['company']
|
||||
account_fields = (
|
||||
'default_debit_account_id',
|
||||
'default_credit_account_id',
|
||||
'payment_debit_account_id',
|
||||
'payment_credit_account_id',
|
||||
)
|
||||
for account_field in account_fields:
|
||||
with self.assertRaises(ValidationError), self.cr.savepoint():
|
||||
journal_bank.copy(default={
|
||||
'name': 'test_constraint_shared_accounts %s' % account_field,
|
||||
account_field: journal_bank[account_field].id,
|
||||
})
|
||||
|
||||
@@ -280,6 +280,7 @@ class TestAccountMove(AccountTestInvoicingCommon):
|
||||
# lines[1] = 'move 1 counterpart line'
|
||||
# lines[2] = 'move 1 receivable line'
|
||||
# lines[3] = 'move 2 counterpart line'
|
||||
draft_moves.post()
|
||||
lines = draft_moves.mapped('line_ids').sorted('balance')
|
||||
|
||||
(lines[0] + lines[2]).reconcile()
|
||||
|
||||
@@ -121,7 +121,7 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
|
||||
'journal_id': cls.company_data['default_journal_purchase'].id,
|
||||
'date': fields.Date.from_string('2019-01-01'),
|
||||
'fiscal_position_id': False,
|
||||
'invoice_payment_ref': '',
|
||||
'payment_reference': '',
|
||||
'invoice_payment_term_id': cls.pay_terms_a.id,
|
||||
'amount_untaxed': 960.0,
|
||||
'amount_tax': 168.0,
|
||||
@@ -601,7 +601,7 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
|
||||
def test_in_invoice_line_onchange_partner_1(self):
|
||||
move_form = Form(self.invoice)
|
||||
move_form.partner_id = self.partner_b
|
||||
move_form.invoice_payment_ref = 'turlututu'
|
||||
move_form.payment_reference = 'turlututu'
|
||||
move_form.save()
|
||||
|
||||
self.assertInvoiceValues(self.invoice, [
|
||||
@@ -645,7 +645,7 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
|
||||
], {
|
||||
**self.move_vals,
|
||||
'partner_id': self.partner_b.id,
|
||||
'invoice_payment_ref': 'turlututu',
|
||||
'payment_reference': 'turlututu',
|
||||
'fiscal_position_id': self.fiscal_pos_a.id,
|
||||
'invoice_payment_term_id': self.pay_terms_b.id,
|
||||
'amount_untaxed': 960.0,
|
||||
@@ -706,7 +706,7 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
|
||||
], {
|
||||
**self.move_vals,
|
||||
'partner_id': self.partner_b.id,
|
||||
'invoice_payment_ref': 'turlututu',
|
||||
'payment_reference': 'turlututu',
|
||||
'fiscal_position_id': self.fiscal_pos_a.id,
|
||||
'invoice_payment_term_id': self.pay_terms_b.id,
|
||||
'amount_untaxed': 960.0,
|
||||
|
||||
@@ -121,7 +121,7 @@ class TestAccountMoveInRefundOnchanges(AccountTestInvoicingCommon):
|
||||
'journal_id': cls.company_data['default_journal_purchase'].id,
|
||||
'date': fields.Date.from_string('2019-01-01'),
|
||||
'fiscal_position_id': False,
|
||||
'invoice_payment_ref': '',
|
||||
'payment_reference': '',
|
||||
'invoice_payment_term_id': cls.pay_terms_a.id,
|
||||
'amount_untaxed': 960.0,
|
||||
'amount_tax': 168.0,
|
||||
@@ -313,7 +313,7 @@ class TestAccountMoveInRefundOnchanges(AccountTestInvoicingCommon):
|
||||
def test_in_refund_line_onchange_partner_1(self):
|
||||
move_form = Form(self.invoice)
|
||||
move_form.partner_id = self.partner_b
|
||||
move_form.invoice_payment_ref = 'turlututu'
|
||||
move_form.payment_reference = 'turlututu'
|
||||
move_form.save()
|
||||
|
||||
self.assertInvoiceValues(self.invoice, [
|
||||
@@ -357,7 +357,7 @@ class TestAccountMoveInRefundOnchanges(AccountTestInvoicingCommon):
|
||||
], {
|
||||
**self.move_vals,
|
||||
'partner_id': self.partner_b.id,
|
||||
'invoice_payment_ref': 'turlututu',
|
||||
'payment_reference': 'turlututu',
|
||||
'fiscal_position_id': self.fiscal_pos_a.id,
|
||||
'invoice_payment_term_id': self.pay_terms_b.id,
|
||||
'amount_untaxed': 960.0,
|
||||
@@ -418,7 +418,7 @@ class TestAccountMoveInRefundOnchanges(AccountTestInvoicingCommon):
|
||||
], {
|
||||
**self.move_vals,
|
||||
'partner_id': self.partner_b.id,
|
||||
'invoice_payment_ref': 'turlututu',
|
||||
'payment_reference': 'turlututu',
|
||||
'fiscal_position_id': self.fiscal_pos_a.id,
|
||||
'invoice_payment_term_id': self.pay_terms_b.id,
|
||||
'amount_untaxed': 960.0,
|
||||
|
||||
@@ -123,7 +123,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
|
||||
'journal_id': cls.company_data['default_journal_sale'].id,
|
||||
'date': fields.Date.from_string('2019-01-01'),
|
||||
'fiscal_position_id': False,
|
||||
'invoice_payment_ref': '',
|
||||
'payment_reference': '',
|
||||
'invoice_payment_term_id': cls.pay_terms_a.id,
|
||||
'amount_untaxed': 1200.0,
|
||||
'amount_tax': 210.0,
|
||||
@@ -603,7 +603,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
|
||||
def test_out_invoice_line_onchange_partner_1(self):
|
||||
move_form = Form(self.invoice)
|
||||
move_form.partner_id = self.partner_b
|
||||
move_form.invoice_payment_ref = 'turlututu'
|
||||
move_form.payment_reference = 'turlututu'
|
||||
move_form.save()
|
||||
|
||||
self.assertInvoiceValues(self.invoice, [
|
||||
@@ -647,7 +647,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
|
||||
], {
|
||||
**self.move_vals,
|
||||
'partner_id': self.partner_b.id,
|
||||
'invoice_payment_ref': 'turlututu',
|
||||
'payment_reference': 'turlututu',
|
||||
'fiscal_position_id': self.fiscal_pos_a.id,
|
||||
'invoice_payment_term_id': self.pay_terms_b.id,
|
||||
'amount_untaxed': 1200.0,
|
||||
@@ -708,7 +708,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
|
||||
], {
|
||||
**self.move_vals,
|
||||
'partner_id': self.partner_b.id,
|
||||
'invoice_payment_ref': 'turlututu',
|
||||
'payment_reference': 'turlututu',
|
||||
'fiscal_position_id': self.fiscal_pos_a.id,
|
||||
'invoice_payment_term_id': self.pay_terms_b.id,
|
||||
'amount_untaxed': 1200.0,
|
||||
@@ -2006,7 +2006,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
|
||||
},
|
||||
], {
|
||||
**self.move_vals,
|
||||
'invoice_payment_ref': move.name,
|
||||
'payment_reference': move.name,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'date': frozen_today,
|
||||
'invoice_date': frozen_today,
|
||||
@@ -2112,7 +2112,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
|
||||
},
|
||||
], {
|
||||
**self.move_vals,
|
||||
'invoice_payment_ref': move.name,
|
||||
'payment_reference': move.name,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'date': fields.Date.from_string('2017-01-01'),
|
||||
'amount_untaxed': 1200.0,
|
||||
@@ -2392,7 +2392,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
|
||||
**self.move_vals,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'date': fields.Date.from_string('2017-01-01'),
|
||||
'invoice_payment_ref': 'INV/2017/01/0001',
|
||||
'payment_reference': 'INV/2017/01/0001',
|
||||
})
|
||||
|
||||
accrual_lines = self.env['account.move'].browse(wizard_res['domain'][0][2]).line_ids.sorted('date')
|
||||
|
||||
@@ -121,7 +121,7 @@ class TestAccountMoveOutRefundOnchanges(AccountTestInvoicingCommon):
|
||||
'journal_id': cls.company_data['default_journal_sale'].id,
|
||||
'date': fields.Date.from_string('2019-01-01'),
|
||||
'fiscal_position_id': False,
|
||||
'invoice_payment_ref': '',
|
||||
'payment_reference': '',
|
||||
'invoice_payment_term_id': cls.pay_terms_a.id,
|
||||
'amount_untaxed': 1200.0,
|
||||
'amount_tax': 210.0,
|
||||
@@ -313,7 +313,7 @@ class TestAccountMoveOutRefundOnchanges(AccountTestInvoicingCommon):
|
||||
def test_out_refund_line_onchange_partner_1(self):
|
||||
move_form = Form(self.invoice)
|
||||
move_form.partner_id = self.partner_b
|
||||
move_form.invoice_payment_ref = 'turlututu'
|
||||
move_form.payment_reference = 'turlututu'
|
||||
move_form.save()
|
||||
|
||||
self.assertInvoiceValues(self.invoice, [
|
||||
@@ -357,7 +357,7 @@ class TestAccountMoveOutRefundOnchanges(AccountTestInvoicingCommon):
|
||||
], {
|
||||
**self.move_vals,
|
||||
'partner_id': self.partner_b.id,
|
||||
'invoice_payment_ref': 'turlututu',
|
||||
'payment_reference': 'turlututu',
|
||||
'fiscal_position_id': self.fiscal_pos_a.id,
|
||||
'invoice_payment_term_id': self.pay_terms_b.id,
|
||||
'amount_untaxed': 1200.0,
|
||||
@@ -418,7 +418,7 @@ class TestAccountMoveOutRefundOnchanges(AccountTestInvoicingCommon):
|
||||
], {
|
||||
**self.move_vals,
|
||||
'partner_id': self.partner_b.id,
|
||||
'invoice_payment_ref': 'turlututu',
|
||||
'payment_reference': 'turlututu',
|
||||
'fiscal_position_id': self.fiscal_pos_a.id,
|
||||
'invoice_payment_term_id': self.pay_terms_b.id,
|
||||
'amount_untaxed': 1200.0,
|
||||
|
||||
@@ -2,221 +2,492 @@
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.tests import tagged, new_test_user
|
||||
from odoo.tests.common import Form
|
||||
from odoo import fields
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestAccountMovePayment(AccountTestInvoicingCommon):
|
||||
class TestAccountPayment(AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestAccountMovePayment, cls).setUpClass()
|
||||
super().setUpClass()
|
||||
|
||||
cls.bank_journal = cls.company_data['default_journal_bank']
|
||||
cls.bank_journal.default_credit_account_id = cls.bank_journal.default_debit_account_id.copy()
|
||||
cls.payment_debit_account_id = cls.copy_account(cls.company_data['default_journal_bank'].payment_debit_account_id)
|
||||
cls.payment_credit_account_id = cls.copy_account(cls.company_data['default_journal_bank'].payment_credit_account_id)
|
||||
|
||||
cls.inbound_payment = cls._init_payment(cls, 'inbound', cls.partner_a, partner_type='customer')
|
||||
cls.outbound_payment = cls._init_payment(cls, 'outbound', cls.partner_a, partner_type='supplier')
|
||||
cls.inbound_internal_transfer = cls._init_payment(cls, 'inbound', cls.company_data['company'].partner_id)
|
||||
cls.outbound_internal_transfer = cls._init_payment(cls, 'outbound', cls.company_data['company'].partner_id)
|
||||
cls.company_data['default_journal_bank'].write({
|
||||
'payment_debit_account_id': cls.payment_debit_account_id.id,
|
||||
'payment_credit_account_id': cls.payment_credit_account_id.id,
|
||||
'inbound_payment_method_ids': [(6, 0, cls.env.ref('account.account_payment_method_manual_in').ids)],
|
||||
'outbound_payment_method_ids': [(6, 0, cls.env.ref('account.account_payment_method_manual_out').ids)],
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def _init_payment(cls, payment_type, partner_id, partner_type=None):
|
||||
payment_form = Form(cls.env['account.payment'])
|
||||
payment_form.journal_id = cls.bank_journal
|
||||
payment_form.payment_date = fields.Date.from_string('2019-01-01')
|
||||
payment_form.amount = 100
|
||||
payment_form.partner_type = partner_type
|
||||
payment_form.partner_id = partner_id
|
||||
payment_form.payment_type = payment_type
|
||||
return payment_form.save()
|
||||
def test_payment_move_sync_create_write(self):
|
||||
copy_receivable = self.copy_account(self.company_data['default_account_receivable'])
|
||||
|
||||
def assertPaymentValues(self, payment, expected_lines_values):
|
||||
self.assertRecordValues(payment.move_line_ids.sorted(lambda line: (line.name, line.balance)), expected_lines_values)
|
||||
payment = self.env['account.payment'].create({
|
||||
'amount': 50.0,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
'destination_account_id': copy_receivable.id,
|
||||
})
|
||||
|
||||
def test_inbound_payment_move_1(self):
|
||||
''' Check created account.move for an account.payment having the 'inbound' type. '''
|
||||
liquidity_account = self.bank_journal.default_credit_account_id
|
||||
|
||||
expected_bank_line = {
|
||||
'account_id': liquidity_account.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'currency_id': False,
|
||||
'amount_currency': 0.0,
|
||||
'debit': 100.0,
|
||||
expected_payment_values = {
|
||||
'amount': 50.0,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
'payment_reference': False,
|
||||
'is_reconciled': False,
|
||||
'currency_id': self.company_data['currency'].id,
|
||||
'partner_id': False,
|
||||
'destination_account_id': copy_receivable.id,
|
||||
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
|
||||
}
|
||||
expected_move_values = {
|
||||
'currency_id': self.company_data['currency'].id,
|
||||
'partner_id': False,
|
||||
}
|
||||
expected_liquidity_line = {
|
||||
'debit': 50.0,
|
||||
'credit': 0.0,
|
||||
}
|
||||
expected_receivable_line = {
|
||||
'account_id': self.inbound_payment.destination_account_id .id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'currency_id': False,
|
||||
'amount_currency': 0.0,
|
||||
'currency_id': False,
|
||||
'account_id': self.payment_debit_account_id.id,
|
||||
}
|
||||
expected_counterpart_line = {
|
||||
'debit': 0.0,
|
||||
'credit': 100.0,
|
||||
'credit': 50.0,
|
||||
'amount_currency': 0.0,
|
||||
'currency_id': False,
|
||||
'account_id': copy_receivable.id,
|
||||
}
|
||||
|
||||
# Single currency.
|
||||
|
||||
self.inbound_payment.post()
|
||||
|
||||
self.assertPaymentValues(self.inbound_payment, [
|
||||
expected_bank_line,
|
||||
expected_receivable_line,
|
||||
self.assertRecordValues(payment, [expected_payment_values])
|
||||
self.assertRecordValues(payment.move_id, [expected_move_values])
|
||||
self.assertRecordValues(payment.line_ids.sorted('balance'), [
|
||||
expected_counterpart_line,
|
||||
expected_liquidity_line,
|
||||
])
|
||||
|
||||
# Multi currencies: Foreign currency on payment.
|
||||
# ==== Check editing the account.payment ====
|
||||
|
||||
self.inbound_payment.action_draft()
|
||||
self.inbound_payment.currency_id = self.currency_data['currency']
|
||||
self.inbound_payment.post()
|
||||
payment.write({
|
||||
'partner_type': 'supplier',
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'partner_id': self.partner_a.id,
|
||||
})
|
||||
|
||||
self.assertPaymentValues(self.inbound_payment, [
|
||||
self.assertRecordValues(payment, [{
|
||||
**expected_payment_values,
|
||||
'partner_type': 'supplier',
|
||||
'destination_account_id': self.partner_a.property_account_payable_id.id,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'partner_id': self.partner_a.id,
|
||||
}])
|
||||
self.assertRecordValues(payment.move_id, [{
|
||||
**expected_move_values,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'partner_id': self.partner_a.id,
|
||||
}])
|
||||
self.assertRecordValues(payment.line_ids.sorted('balance'), [
|
||||
{
|
||||
**expected_bank_line,
|
||||
**expected_counterpart_line,
|
||||
'debit': 0.0,
|
||||
'credit': 25.0,
|
||||
'amount_currency': -50.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 100.0,
|
||||
'debit': 50.0,
|
||||
'account_id': self.partner_a.property_account_payable_id.id,
|
||||
},
|
||||
{
|
||||
**expected_receivable_line,
|
||||
**expected_liquidity_line,
|
||||
'debit': 25.0,
|
||||
'credit': 0.0,
|
||||
'amount_currency': 50.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -100.0,
|
||||
'credit': 50.0,
|
||||
},
|
||||
])
|
||||
|
||||
self.inbound_payment.currency_id = self.company_data['currency']
|
||||
# ==== Check editing the account.move.line ====
|
||||
|
||||
# Multi currencies: Foreign currency on journal.
|
||||
liquidity_lines, counterpart_lines, writeoff_lines = payment._seek_for_lines()
|
||||
payment.move_id.write({
|
||||
'line_ids': [
|
||||
(1, counterpart_lines.id, {
|
||||
'debit': 0.0,
|
||||
'credit': 75.0,
|
||||
'amount_currency': 0.0,
|
||||
'currency_id': False,
|
||||
'account_id': copy_receivable.id,
|
||||
'partner_id': self.partner_b.id,
|
||||
}),
|
||||
(1, liquidity_lines.id, {
|
||||
'debit': 100.0,
|
||||
'credit': 0.0,
|
||||
'amount_currency': 0.0,
|
||||
'currency_id': False,
|
||||
'partner_id': self.partner_b.id,
|
||||
}),
|
||||
|
||||
self.inbound_payment.action_draft()
|
||||
self.inbound_payment.journal_id.currency_id = self.currency_data['currency']
|
||||
self.inbound_payment.post()
|
||||
# Additional write-off:
|
||||
(0, 0, {
|
||||
'debit': 0.0,
|
||||
'credit': 25.0,
|
||||
'amount_currency': 0.0,
|
||||
'currency_id': False,
|
||||
'account_id': self.company_data['default_account_revenue'].id,
|
||||
'partner_id': self.partner_b.id,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
self.assertPaymentValues(self.inbound_payment, [
|
||||
self.assertRecordValues(payment, [{
|
||||
**expected_payment_values,
|
||||
'amount': 100.0,
|
||||
'partner_id': self.partner_b.id,
|
||||
}])
|
||||
self.assertRecordValues(payment.move_id, [{
|
||||
**expected_move_values,
|
||||
'partner_id': self.partner_b.id,
|
||||
}])
|
||||
self.assertRecordValues(payment.line_ids.sorted('balance'), [
|
||||
{
|
||||
**expected_bank_line,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 200.0,
|
||||
**expected_counterpart_line,
|
||||
'debit': 0.0,
|
||||
'credit': 75.0,
|
||||
'partner_id': self.partner_b.id,
|
||||
},
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 25.0,
|
||||
'amount_currency': 0.0,
|
||||
'currency_id': False,
|
||||
'account_id': self.company_data['default_account_revenue'].id,
|
||||
'partner_id': self.partner_b.id,
|
||||
},
|
||||
{
|
||||
**expected_liquidity_line,
|
||||
'debit': 100.0,
|
||||
'credit': 0.0,
|
||||
'account_id': self.payment_debit_account_id.id,
|
||||
'partner_id': self.partner_b.id,
|
||||
},
|
||||
expected_receivable_line,
|
||||
])
|
||||
|
||||
def test_outbound_payment_move_1(self):
|
||||
''' Check created account.move for an account.payment having the 'outbound' type. '''
|
||||
liquidity_account = self.bank_journal.default_debit_account_id
|
||||
def test_payment_move_sync_onchange(self):
|
||||
copy_receivable = self.copy_account(self.company_data['default_account_receivable'])
|
||||
|
||||
expected_bank_line = {
|
||||
'account_id': liquidity_account.id,
|
||||
pay_form = Form(self.env['account.payment'].with_context(default_journal_id=self.company_data['default_journal_bank'].id))
|
||||
pay_form.amount = 50.0
|
||||
pay_form.payment_type = 'inbound'
|
||||
pay_form.partner_type = 'customer'
|
||||
pay_form.destination_account_id = copy_receivable
|
||||
payment = pay_form.save()
|
||||
|
||||
expected_payment_values = {
|
||||
'amount': 50.0,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
'payment_reference': False,
|
||||
'is_reconciled': False,
|
||||
'currency_id': self.company_data['currency'].id,
|
||||
'partner_id': False,
|
||||
'destination_account_id': copy_receivable.id,
|
||||
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
|
||||
}
|
||||
expected_move_values = {
|
||||
'currency_id': self.company_data['currency'].id,
|
||||
'partner_id': False,
|
||||
}
|
||||
expected_liquidity_line = {
|
||||
'debit': 50.0,
|
||||
'credit': 0.0,
|
||||
'amount_currency': 0.0,
|
||||
'currency_id': False,
|
||||
'account_id': self.payment_debit_account_id.id,
|
||||
}
|
||||
expected_counterpart_line = {
|
||||
'debit': 0.0,
|
||||
'credit': 50.0,
|
||||
'amount_currency': 0.0,
|
||||
'currency_id': False,
|
||||
'account_id': copy_receivable.id,
|
||||
}
|
||||
|
||||
self.assertRecordValues(payment, [expected_payment_values])
|
||||
self.assertRecordValues(payment.move_id, [expected_move_values])
|
||||
self.assertRecordValues(payment.line_ids.sorted('balance'), [
|
||||
expected_counterpart_line,
|
||||
expected_liquidity_line,
|
||||
])
|
||||
|
||||
# ==== Check editing the account.payment ====
|
||||
|
||||
pay_form = Form(payment)
|
||||
pay_form.partner_type = 'supplier'
|
||||
pay_form.currency_id = self.currency_data['currency']
|
||||
pay_form.partner_id = self.partner_a
|
||||
payment = pay_form.save()
|
||||
|
||||
self.assertRecordValues(payment, [{
|
||||
**expected_payment_values,
|
||||
'partner_type': 'supplier',
|
||||
'destination_account_id': self.partner_a.property_account_payable_id.id,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'currency_id': False,
|
||||
'amount_currency': 0.0,
|
||||
'debit': 0.0,
|
||||
'credit': 100.0,
|
||||
}
|
||||
expected_payable_line = {
|
||||
'account_id': self.outbound_payment.destination_account_id.id,
|
||||
}])
|
||||
self.assertRecordValues(payment.move_id, [{
|
||||
**expected_move_values,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'currency_id': False,
|
||||
'amount_currency': 0.0,
|
||||
'debit': 100.0,
|
||||
'credit': 0.0,
|
||||
}
|
||||
|
||||
# Single currency.
|
||||
|
||||
self.outbound_payment.post()
|
||||
|
||||
self.assertPaymentValues(self.outbound_payment, [
|
||||
expected_bank_line,
|
||||
expected_payable_line,
|
||||
])
|
||||
|
||||
# Multi currencies: Foreign currency on payment.
|
||||
|
||||
self.outbound_payment.action_draft()
|
||||
self.outbound_payment.currency_id = self.currency_data['currency']
|
||||
self.outbound_payment.post()
|
||||
|
||||
self.assertPaymentValues(self.outbound_payment, [
|
||||
}])
|
||||
self.assertRecordValues(payment.line_ids.sorted('balance'), [
|
||||
{
|
||||
**expected_bank_line,
|
||||
**expected_counterpart_line,
|
||||
'debit': 0.0,
|
||||
'credit': 25.0,
|
||||
'amount_currency': -50.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -100.0,
|
||||
'credit': 50.0,
|
||||
'account_id': self.partner_a.property_account_payable_id.id,
|
||||
},
|
||||
{
|
||||
**expected_payable_line,
|
||||
**expected_liquidity_line,
|
||||
'debit': 25.0,
|
||||
'credit': 0.0,
|
||||
'amount_currency': 50.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 100.0,
|
||||
'debit': 50.0,
|
||||
},
|
||||
])
|
||||
|
||||
self.outbound_payment.currency_id = self.company_data['currency']
|
||||
# ==== Check editing the account.move.line ====
|
||||
|
||||
# Multi currencies: Foreign currency on journal.
|
||||
move_form = Form(payment.move_id)
|
||||
with move_form.line_ids.edit(0) as line_form:
|
||||
line_form.currency_id = self.env['res.currency']
|
||||
line_form.amount_currency = 0.0
|
||||
line_form.debit = 100.0
|
||||
line_form.partner_id = self.partner_b
|
||||
with move_form.line_ids.edit(1) as line_form:
|
||||
line_form.currency_id = self.env['res.currency']
|
||||
line_form.amount_currency = 0.0
|
||||
line_form.credit = 75.0
|
||||
line_form.account_id = copy_receivable
|
||||
line_form.partner_id = self.partner_b
|
||||
with move_form.line_ids.new() as line_form:
|
||||
line_form.currency_id = self.env['res.currency']
|
||||
line_form.amount_currency = 0.0
|
||||
line_form.credit = 25.0
|
||||
line_form.account_id = self.company_data['default_account_revenue']
|
||||
line_form.partner_id = self.partner_b
|
||||
move_form.save()
|
||||
|
||||
self.outbound_payment.action_draft()
|
||||
self.outbound_payment.journal_id.currency_id = self.currency_data['currency']
|
||||
self.outbound_payment.post()
|
||||
|
||||
self.assertPaymentValues(self.outbound_payment, [
|
||||
self.assertRecordValues(payment, [{
|
||||
**expected_payment_values,
|
||||
'amount': 100.0,
|
||||
'partner_id': self.partner_b.id,
|
||||
}])
|
||||
self.assertRecordValues(payment.line_ids.sorted('balance'), [
|
||||
{
|
||||
**expected_bank_line,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -200.0,
|
||||
'credit': 100.0,
|
||||
**expected_counterpart_line,
|
||||
'debit': 0.0,
|
||||
'credit': 75.0,
|
||||
'partner_id': self.partner_b.id,
|
||||
},
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 25.0,
|
||||
'amount_currency': 0.0,
|
||||
'currency_id': False,
|
||||
'account_id': self.company_data['default_account_revenue'].id,
|
||||
'partner_id': self.partner_b.id,
|
||||
},
|
||||
{
|
||||
**expected_liquidity_line,
|
||||
'debit': 100.0,
|
||||
'credit': 0.0,
|
||||
'account_id': self.payment_debit_account_id.id,
|
||||
'partner_id': self.partner_b.id,
|
||||
},
|
||||
expected_payable_line,
|
||||
])
|
||||
|
||||
def test_inbound_transfer_payment_1(self):
|
||||
liquidity_bank_account = self.bank_journal.default_debit_account_id
|
||||
self.inbound_internal_transfer.post()
|
||||
transfer_line = {
|
||||
'account_id': self.company_data['company'].transfer_account_id.id,
|
||||
def test_internal_transfer(self):
|
||||
copy_receivable = self.copy_account(self.company_data['default_account_receivable'])
|
||||
|
||||
payment = self.env['account.payment'].create({
|
||||
'amount': 50.0,
|
||||
'is_internal_transfer': True,
|
||||
})
|
||||
|
||||
expected_payment_values = {
|
||||
'amount': 50.0,
|
||||
'payment_type': 'inbound',
|
||||
'currency_id': self.company_data['currency'].id,
|
||||
'partner_id': self.company_data['company'].partner_id.id,
|
||||
'currency_id': False,
|
||||
'destination_account_id': self.company_data['company'].transfer_account_id.id,
|
||||
}
|
||||
expected_move_values = {
|
||||
'currency_id': self.company_data['currency'].id,
|
||||
'partner_id': self.company_data['company'].partner_id.id,
|
||||
}
|
||||
expected_liquidity_line = {
|
||||
'debit': 50.0,
|
||||
'credit': 0.0,
|
||||
'amount_currency': 0.0,
|
||||
'currency_id': False,
|
||||
'account_id': self.payment_debit_account_id.id,
|
||||
}
|
||||
expected_counterpart_line = {
|
||||
'debit': 0.0,
|
||||
'credit': 100.0,
|
||||
}
|
||||
liquidity_bank_line = {
|
||||
'account_id': self.bank_journal.default_credit_account_id.id,
|
||||
'partner_id': self.company_data['company'].partner_id.id,
|
||||
'currency_id': False,
|
||||
'credit': 50.0,
|
||||
'amount_currency': 0.0,
|
||||
'debit': 100.0,
|
||||
'credit': 0.0,
|
||||
}
|
||||
self.assertPaymentValues(self.inbound_internal_transfer, [
|
||||
transfer_line,
|
||||
liquidity_bank_line,
|
||||
])
|
||||
self.assertTrue(self.inbound_internal_transfer.is_internal_transfer)
|
||||
|
||||
def test_outbound_transfer_payment_1(self):
|
||||
self.outbound_internal_transfer.post()
|
||||
transfer_line = {
|
||||
'currency_id': False,
|
||||
'account_id': self.company_data['company'].transfer_account_id.id,
|
||||
'partner_id': self.company_data['company'].partner_id.id,
|
||||
'currency_id': False,
|
||||
'amount_currency': 0.0,
|
||||
'debit': 100.0,
|
||||
'credit': 0.0,
|
||||
}
|
||||
liquidity_bank_line = {
|
||||
'account_id': self.bank_journal.default_debit_account_id.id,
|
||||
'partner_id': self.company_data['company'].partner_id.id,
|
||||
'currency_id': False,
|
||||
'amount_currency': 0.0,
|
||||
'debit': 0.0,
|
||||
'credit': 100.0,
|
||||
}
|
||||
self.assertPaymentValues(self.outbound_internal_transfer, [
|
||||
transfer_line,
|
||||
liquidity_bank_line,
|
||||
|
||||
self.assertRecordValues(payment, [expected_payment_values])
|
||||
self.assertRecordValues(payment.move_id, [expected_move_values])
|
||||
self.assertRecordValues(payment.line_ids.sorted('balance'), [
|
||||
expected_counterpart_line,
|
||||
expected_liquidity_line,
|
||||
])
|
||||
self.assertTrue(self.outbound_internal_transfer.is_internal_transfer)
|
||||
|
||||
# ==== Check editing the account.payment ====
|
||||
|
||||
payment.write({
|
||||
'partner_type': 'customer',
|
||||
'partner_id': self.partner_a.id,
|
||||
'destination_account_id': copy_receivable.id,
|
||||
})
|
||||
|
||||
self.assertRecordValues(payment, [{
|
||||
**expected_payment_values,
|
||||
'partner_type': 'customer',
|
||||
'destination_account_id': copy_receivable.id,
|
||||
'partner_id': self.partner_a.id,
|
||||
'is_internal_transfer': False,
|
||||
}])
|
||||
self.assertRecordValues(payment.move_id, [{
|
||||
**expected_move_values,
|
||||
'partner_id': self.partner_a.id,
|
||||
}])
|
||||
self.assertRecordValues(payment.line_ids.sorted('balance'), [
|
||||
{
|
||||
**expected_counterpart_line,
|
||||
'account_id': copy_receivable.id,
|
||||
},
|
||||
expected_liquidity_line,
|
||||
])
|
||||
|
||||
# ==== Check editing the account.move.line ====
|
||||
|
||||
liquidity_lines, counterpart_lines, writeoff_lines = payment._seek_for_lines()
|
||||
payment.move_id.write({
|
||||
'line_ids': [
|
||||
(1, counterpart_lines.id, {
|
||||
'account_id': self.company_data['company'].transfer_account_id.id,
|
||||
'partner_id': self.company_data['company'].partner_id.id,
|
||||
}),
|
||||
(1, liquidity_lines.id, {
|
||||
'partner_id': self.company_data['company'].partner_id.id,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
self.assertRecordValues(payment, [expected_payment_values])
|
||||
self.assertRecordValues(payment.move_id, [expected_move_values])
|
||||
self.assertRecordValues(payment.line_ids.sorted('balance'), [
|
||||
expected_counterpart_line,
|
||||
expected_liquidity_line,
|
||||
])
|
||||
|
||||
def test_compute_currency_id(self):
|
||||
''' When creating a new account.payment without specifying a currency, the default currency should be the one
|
||||
set on the journal.
|
||||
'''
|
||||
self.company_data['default_journal_bank'].write({
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
})
|
||||
|
||||
payment = self.env['account.payment'].create({
|
||||
'amount': 50.0,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
'partner_id': self.partner_a.id,
|
||||
'journal_id': self.company_data['default_journal_bank'].id,
|
||||
})
|
||||
|
||||
self.assertRecordValues(payment, [{
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
}])
|
||||
self.assertRecordValues(payment.move_id, [{
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
}])
|
||||
self.assertRecordValues(payment.line_ids.sorted('balance'), [
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 25.0,
|
||||
'amount_currency': -50.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
},
|
||||
{
|
||||
'debit': 25.0,
|
||||
'credit': 0.0,
|
||||
'amount_currency': 50.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
},
|
||||
])
|
||||
|
||||
def test_reconciliation_payment_states(self):
|
||||
payment = self.env['account.payment'].create({
|
||||
'amount': 50.0,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
'destination_account_id': self.company_data['default_account_receivable'].id,
|
||||
})
|
||||
liquidity_lines, counterpart_lines, writeoff_lines = payment._seek_for_lines()
|
||||
|
||||
self.assertRecordValues(payment, [{
|
||||
'is_reconciled': False,
|
||||
'is_matched': False,
|
||||
}])
|
||||
|
||||
invoice = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'invoice_line_ids': [(0, 0, {
|
||||
'name': '50 to pay',
|
||||
'price_unit': 50.0,
|
||||
'quantity': 1,
|
||||
'account_id': self.company_data['default_account_revenue'].id,
|
||||
})],
|
||||
})
|
||||
|
||||
payment.action_post()
|
||||
invoice.post()
|
||||
|
||||
(counterpart_lines + invoice.line_ids.filtered(lambda line: line.account_internal_type == 'receivable'))\
|
||||
.reconcile()
|
||||
|
||||
self.assertRecordValues(payment, [{
|
||||
'is_reconciled': True,
|
||||
'is_matched': False,
|
||||
}])
|
||||
|
||||
statement = self.env['account.bank.statement'].create({
|
||||
'name': 'test_statement',
|
||||
'journal_id': self.company_data['default_journal_bank'].id,
|
||||
'line_ids': [
|
||||
(0, 0, {
|
||||
'payment_ref': '50 to pay',
|
||||
'partner_id': self.partner_a.id,
|
||||
'amount': 50.0,
|
||||
}),
|
||||
],
|
||||
})
|
||||
statement.button_post()
|
||||
statement_line = statement.line_ids
|
||||
|
||||
statement_line.reconcile([{'id': liquidity_lines.id}])
|
||||
|
||||
self.assertRecordValues(payment, [{
|
||||
'is_reconciled': True,
|
||||
'is_matched': True,
|
||||
}])
|
||||
|
||||
@@ -0,0 +1,424 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestAccountPaymentRegister(AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.payment_debit_account_id = cls.company_data['default_journal_bank'].payment_debit_account_id.copy()
|
||||
cls.payment_credit_account_id = cls.company_data['default_journal_bank'].payment_credit_account_id.copy()
|
||||
|
||||
cls.custom_payment_method_in = cls.env['account.payment.method'].create({
|
||||
'name': 'custom_payment_method_in',
|
||||
'code': 'CUSTOMIN',
|
||||
'payment_type': 'inbound',
|
||||
})
|
||||
cls.manual_payment_method_in = cls.env.ref('account.account_payment_method_manual_in')
|
||||
|
||||
cls.custom_payment_method_out = cls.env['account.payment.method'].create({
|
||||
'name': 'custom_payment_method_out',
|
||||
'code': 'CUSTOMOUT',
|
||||
'payment_type': 'outbound',
|
||||
})
|
||||
cls.manual_payment_method_out = cls.env.ref('account.account_payment_method_manual_out')
|
||||
|
||||
cls.company_data['default_journal_bank'].write({
|
||||
'payment_debit_account_id': cls.payment_debit_account_id.id,
|
||||
'payment_credit_account_id': cls.payment_credit_account_id.id,
|
||||
'inbound_payment_method_ids': [(6, 0, (
|
||||
cls.manual_payment_method_in.id,
|
||||
cls.custom_payment_method_in.id,
|
||||
))],
|
||||
'outbound_payment_method_ids': [(6, 0, (
|
||||
cls.env.ref('account.account_payment_method_manual_out').id,
|
||||
cls.custom_payment_method_out.id,
|
||||
cls.manual_payment_method_out.id,
|
||||
))],
|
||||
})
|
||||
|
||||
# Customer invoices sharing the same batch.
|
||||
cls.out_invoice_1 = cls.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'date': '2017-01-01',
|
||||
'invoice_date': '2017-01-01',
|
||||
'partner_id': cls.partner_a.id,
|
||||
'currency_id': cls.currency_data['currency'].id,
|
||||
'invoice_line_ids': [(0, 0, {'product_id': cls.product_a.id, 'price_unit': 1000.0})],
|
||||
})
|
||||
cls.out_invoice_1.post()
|
||||
cls.out_invoice_2 = cls.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'date': '2017-01-01',
|
||||
'invoice_date': '2017-01-01',
|
||||
'partner_id': cls.partner_a.id,
|
||||
'currency_id': cls.currency_data['currency'].id,
|
||||
'invoice_line_ids': [(0, 0, {'product_id': cls.product_a.id, 'price_unit': 2000.0})],
|
||||
})
|
||||
(cls.out_invoice_1 + cls.out_invoice_2).post()
|
||||
|
||||
# Vendor bills, in_invoice_1 + in_invoice_2 are sharing the same batch but not in_invoice_3.
|
||||
cls.in_invoice_1 = cls.env['account.move'].create({
|
||||
'move_type': 'in_invoice',
|
||||
'date': '2017-01-01',
|
||||
'invoice_date': '2017-01-01',
|
||||
'partner_id': cls.partner_a.id,
|
||||
'invoice_line_ids': [(0, 0, {'product_id': cls.product_a.id, 'price_unit': 1000.0})],
|
||||
})
|
||||
cls.in_invoice_2 = cls.env['account.move'].create({
|
||||
'move_type': 'in_invoice',
|
||||
'date': '2017-01-01',
|
||||
'invoice_date': '2017-01-01',
|
||||
'partner_id': cls.partner_a.id,
|
||||
'invoice_line_ids': [(0, 0, {'product_id': cls.product_a.id, 'price_unit': 2000.0})],
|
||||
})
|
||||
cls.in_invoice_3 = cls.env['account.move'].create({
|
||||
'move_type': 'in_invoice',
|
||||
'date': '2017-01-01',
|
||||
'invoice_date': '2017-01-01',
|
||||
'partner_id': cls.partner_b.id,
|
||||
'currency_id': cls.currency_data['currency'].id,
|
||||
'invoice_line_ids': [(0, 0, {'product_id': cls.product_a.id, 'price_unit': 3000.0})],
|
||||
})
|
||||
(cls.in_invoice_1 + cls.in_invoice_2 + cls.in_invoice_3).post()
|
||||
|
||||
def test_register_payment_single_batch_grouped_keep_open_lower_amount(self):
|
||||
''' Pay 800.0 with 'open' as payment difference handling on two customer invoices (1000 + 2000). '''
|
||||
active_ids = (self.out_invoice_1 + self.out_invoice_2).ids
|
||||
payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({
|
||||
'amount': 800.0,
|
||||
'group_payment': True,
|
||||
'payment_difference_handling': 'open',
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'payment_method_id': self.custom_payment_method_in.id,
|
||||
})._create_payments()
|
||||
|
||||
self.assertRecordValues(payments, [{
|
||||
'payment_method_id': self.custom_payment_method_in.id,
|
||||
}])
|
||||
self.assertRecordValues(payments.line_ids.sorted('balance'), [
|
||||
# Receivable line:
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 400.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -800.0,
|
||||
'reconciled': True,
|
||||
},
|
||||
# Liquidity line:
|
||||
{
|
||||
'debit': 400.0,
|
||||
'credit': 0.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 800.0,
|
||||
'reconciled': False,
|
||||
},
|
||||
])
|
||||
|
||||
def test_register_payment_single_batch_grouped_keep_open_higher_amount(self):
|
||||
''' Pay 3100.0 with 'open' as payment difference handling on two customer invoices (1000 + 2000). '''
|
||||
active_ids = (self.out_invoice_1 + self.out_invoice_2).ids
|
||||
payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({
|
||||
'amount': 3100.0,
|
||||
'group_payment': True,
|
||||
'payment_difference_handling': 'open',
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'payment_method_id': self.custom_payment_method_in.id,
|
||||
})._create_payments()
|
||||
|
||||
self.assertRecordValues(payments, [{
|
||||
'payment_method_id': self.custom_payment_method_in.id,
|
||||
}])
|
||||
self.assertRecordValues(payments.line_ids.sorted('balance'), [
|
||||
# Receivable line:
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 1550.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -3100.0,
|
||||
'reconciled': False,
|
||||
},
|
||||
# Liquidity line:
|
||||
{
|
||||
'debit': 1550.0,
|
||||
'credit': 0.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 3100.0,
|
||||
'reconciled': False,
|
||||
},
|
||||
])
|
||||
|
||||
def test_register_payment_single_batch_grouped_writeoff_lower_amount(self):
|
||||
''' Pay 800.0 with 'reconcile' as payment difference handling on two customer invoices (1000 + 2000). '''
|
||||
active_ids = (self.out_invoice_1 + self.out_invoice_2).ids
|
||||
payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({
|
||||
'amount': 800.0,
|
||||
'group_payment': True,
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.company_data['default_account_revenue'].id,
|
||||
'writeoff_label': 'writeoff',
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'payment_method_id': self.custom_payment_method_in.id,
|
||||
})._create_payments()
|
||||
|
||||
self.assertRecordValues(payments, [{
|
||||
'payment_method_id': self.custom_payment_method_in.id,
|
||||
}])
|
||||
self.assertRecordValues(payments.line_ids.sorted('balance'), [
|
||||
# Receivable line:
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 1500.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -3000.0,
|
||||
'reconciled': True,
|
||||
},
|
||||
# Liquidity line:
|
||||
{
|
||||
'debit': 400.0,
|
||||
'credit': 0.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 800.0,
|
||||
'reconciled': False,
|
||||
},
|
||||
# Writeoff line:
|
||||
{
|
||||
'debit': 1100.0,
|
||||
'credit': 0.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 2200.0,
|
||||
'reconciled': False,
|
||||
},
|
||||
])
|
||||
|
||||
def test_register_payment_single_batch_grouped_writeoff_higher_amount(self):
|
||||
''' Pay 3100.0 with 'reconcile' as payment difference handling on two customer invoices (1000 + 2000). '''
|
||||
active_ids = (self.out_invoice_1 + self.out_invoice_2).ids
|
||||
payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({
|
||||
'amount': 3100.0,
|
||||
'group_payment': True,
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.company_data['default_account_revenue'].id,
|
||||
'writeoff_label': 'writeoff',
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'payment_method_id': self.custom_payment_method_in.id,
|
||||
})._create_payments()
|
||||
|
||||
self.assertRecordValues(payments, [{
|
||||
'payment_method_id': self.custom_payment_method_in.id,
|
||||
}])
|
||||
self.assertRecordValues(payments.line_ids.sorted('balance'), [
|
||||
# Receivable line:
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 1500.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -3000.0,
|
||||
'reconciled': True,
|
||||
},
|
||||
# Writeoff line:
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 50.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -100.0,
|
||||
'reconciled': False,
|
||||
},
|
||||
# Liquidity line:
|
||||
{
|
||||
'debit': 1550.0,
|
||||
'credit': 0.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 3100.0,
|
||||
'reconciled': False,
|
||||
},
|
||||
])
|
||||
|
||||
def test_register_payment_single_batch_not_grouped(self):
|
||||
''' Choose to pay two customer invoices with separated payments (1000 + 2000). '''
|
||||
active_ids = (self.out_invoice_1 + self.out_invoice_2).ids
|
||||
payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({
|
||||
'group_payment': False,
|
||||
})._create_payments()
|
||||
|
||||
self.assertRecordValues(payments, [
|
||||
{'payment_method_id': self.manual_payment_method_in.id},
|
||||
{'payment_method_id': self.manual_payment_method_in.id},
|
||||
])
|
||||
self.assertRecordValues(payments[0].line_ids.sorted('balance') + payments[1].line_ids.sorted('balance'), [
|
||||
# == Payment 1: to pay out_invoice_1 ==
|
||||
# Receivable line:
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 500.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -1000.0,
|
||||
'reconciled': True,
|
||||
},
|
||||
# Liquidity line:
|
||||
{
|
||||
'debit': 500.0,
|
||||
'credit': 0.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 1000.0,
|
||||
'reconciled': False,
|
||||
},
|
||||
# == Payment 2: to pay out_invoice_2 ==
|
||||
# Receivable line:
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 1000.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -2000.0,
|
||||
'reconciled': True,
|
||||
},
|
||||
# Liquidity line:
|
||||
{
|
||||
'debit': 1000.0,
|
||||
'credit': 0.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 2000.0,
|
||||
'reconciled': False,
|
||||
},
|
||||
])
|
||||
|
||||
def test_register_payment_multi_batches_grouped(self):
|
||||
''' Choose to pay multiple batches, one with two customer invoices (1000 + 2000)
|
||||
and one with a vendor bill of 600, by grouping payments.
|
||||
'''
|
||||
active_ids = (self.in_invoice_1 + self.in_invoice_2 + self.in_invoice_3).ids
|
||||
payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({
|
||||
'group_payment': True,
|
||||
})._create_payments()
|
||||
|
||||
self.assertRecordValues(payments, [
|
||||
{'payment_method_id': self.manual_payment_method_out.id},
|
||||
{'payment_method_id': self.manual_payment_method_out.id},
|
||||
])
|
||||
self.assertRecordValues(payments[0].line_ids.sorted('balance') + payments[1].line_ids.sorted('balance'), [
|
||||
# == Payment 1: to pay in_invoice_1 & in_invoice_2 ==
|
||||
# Liquidity line:
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 3000.0,
|
||||
'currency_id': False,
|
||||
'amount_currency': 0.0,
|
||||
'reconciled': False,
|
||||
},
|
||||
# Payable line:
|
||||
{
|
||||
'debit': 3000.0,
|
||||
'credit': 0.0,
|
||||
'currency_id': False,
|
||||
'amount_currency': 0.0,
|
||||
'reconciled': True,
|
||||
},
|
||||
# == Payment 2: to pay in_invoice_3 ==
|
||||
# Liquidity line:
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 1500.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -3000.0,
|
||||
'reconciled': False,
|
||||
},
|
||||
# Payable line:
|
||||
{
|
||||
'debit': 1500.0,
|
||||
'credit': 0.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 3000.0,
|
||||
'reconciled': True,
|
||||
},
|
||||
])
|
||||
|
||||
def test_register_payment_multi_batches_not_grouped(self):
|
||||
''' Choose to pay multiple batches, one with two customer invoices (1000 + 2000)
|
||||
and one with a vendor bill of 600, by splitting payments.
|
||||
'''
|
||||
active_ids = (self.in_invoice_1 + self.in_invoice_2 + self.in_invoice_3).ids
|
||||
payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({
|
||||
'group_payment': False,
|
||||
})._create_payments()
|
||||
|
||||
self.assertRecordValues(payments, [
|
||||
{'payment_method_id': self.manual_payment_method_out.id},
|
||||
{'payment_method_id': self.manual_payment_method_out.id},
|
||||
{'payment_method_id': self.manual_payment_method_out.id},
|
||||
])
|
||||
self.assertRecordValues(payments[0].line_ids.sorted('balance') + payments[1].line_ids.sorted('balance') + payments[2].line_ids.sorted('balance'), [
|
||||
# == Payment 1: to pay in_invoice_1 ==
|
||||
# Liquidity line:
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 1000.0,
|
||||
'currency_id': False,
|
||||
'amount_currency': 0.0,
|
||||
'reconciled': False,
|
||||
},
|
||||
# Payable line:
|
||||
{
|
||||
'debit': 1000.0,
|
||||
'credit': 0.0,
|
||||
'currency_id': False,
|
||||
'amount_currency': 0.0,
|
||||
'reconciled': True,
|
||||
},
|
||||
# == Payment 2: to pay in_invoice_2 ==
|
||||
# Liquidity line:
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 2000.0,
|
||||
'currency_id': False,
|
||||
'amount_currency': 0.0,
|
||||
'reconciled': False,
|
||||
},
|
||||
# Payable line:
|
||||
{
|
||||
'debit': 2000.0,
|
||||
'credit': 0.0,
|
||||
'currency_id': False,
|
||||
'amount_currency': 0.0,
|
||||
'reconciled': True,
|
||||
},
|
||||
# == Payment 3: to pay in_invoice_3 ==
|
||||
# Liquidity line:
|
||||
{
|
||||
'debit': 0.0,
|
||||
'credit': 1500.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': -3000.0,
|
||||
'reconciled': False,
|
||||
},
|
||||
# Payable line:
|
||||
{
|
||||
'debit': 1500.0,
|
||||
'credit': 0.0,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'amount_currency': 3000.0,
|
||||
'reconciled': True,
|
||||
},
|
||||
])
|
||||
|
||||
def test_register_payment_constraints(self):
|
||||
# Test to register a payment for a draft journal entry.
|
||||
self.out_invoice_1.button_draft()
|
||||
with self.assertRaises(UserError), self.cr.savepoint():
|
||||
self.env['account.payment.register']\
|
||||
.with_context(active_model='account.move', active_ids=self.out_invoice_1.ids)\
|
||||
.create({})
|
||||
|
||||
# Test to register a payment for an already fully reconciled journal entry.
|
||||
self.env['account.payment.register']\
|
||||
.with_context(active_model='account.move', active_ids=self.out_invoice_2.ids)\
|
||||
.create({})\
|
||||
._create_payments()
|
||||
with self.assertRaises(UserError), self.cr.savepoint():
|
||||
self.env['account.payment.register']\
|
||||
.with_context(active_model='account.move', active_ids=self.out_invoice_2.ids)\
|
||||
.create({})
|
||||
@@ -1,111 +0,0 @@
|
||||
from odoo.addons.account.tests.common import AccountTestCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestBankStatementReconciliation(AccountTestCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestBankStatementReconciliation, cls).setUpClass()
|
||||
cls.bs_model = cls.env['account.bank.statement']
|
||||
cls.bsl_model = cls.env['account.bank.statement.line']
|
||||
cls.partner = cls.env['res.partner'].create({'name': 'test'})
|
||||
cls.currency_usd_id = cls.env.ref("base.USD").id
|
||||
cls.currency_euro_id = cls.env.ref("base.EUR").id
|
||||
|
||||
def test_full_reconcile(self):
|
||||
self._reconcile_invoice_with_statement('pay_val')
|
||||
|
||||
def test_post_at_bank_rec_full_reconcile(self):
|
||||
""" Test the full reconciliation of a bank statement directly with an invoice.
|
||||
"""
|
||||
self._reconcile_invoice_with_statement('bank_rec')
|
||||
|
||||
def _reconcile_invoice_with_statement(self, post_at):
|
||||
""" Tests the reconciliation of an invoice with a bank statement, using
|
||||
the provided 'post at bank reconciliation' value for the bank journal
|
||||
where to generate the statement.
|
||||
"""
|
||||
self.bs_model.with_context(journal_type='bank')._default_journal().post_at_bank_reconciliation = post_at == 'bank_rec'
|
||||
rcv_mv_line = self.create_invoice(100)
|
||||
st_line = self.create_statement_line(100)
|
||||
# reconcile
|
||||
st_line.process_reconciliation(counterpart_aml_dicts=[{
|
||||
'move_line': rcv_mv_line,
|
||||
'credit': 100,
|
||||
'debit': 0,
|
||||
'name': rcv_mv_line.name,
|
||||
}])
|
||||
|
||||
# check everything went as expected
|
||||
self.assertTrue(st_line.journal_entry_ids)
|
||||
counterpart_mv_line = None
|
||||
for l in st_line.journal_entry_ids:
|
||||
if l.account_id.user_type_id.type == 'receivable':
|
||||
counterpart_mv_line = l
|
||||
break
|
||||
self.assertIsNotNone(counterpart_mv_line)
|
||||
self.assertTrue(rcv_mv_line.reconciled)
|
||||
self.assertTrue(counterpart_mv_line.reconciled)
|
||||
self.assertEqual(counterpart_mv_line.matched_credit_ids, rcv_mv_line.matched_debit_ids)
|
||||
self.assertEqual(rcv_mv_line.move_id.payment_state, 'paid', "The related invoice's state should now be 'paid'")
|
||||
|
||||
def test_reconcile_with_write_off(self):
|
||||
pass
|
||||
|
||||
def create_invoice(self, amount):
|
||||
""" Return the move line that gets to be reconciled (the one in the receivable account) """
|
||||
move = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner.id,
|
||||
'invoice_line_ids': [(0, 0, {
|
||||
'quantity': 1,
|
||||
'price_unit': amount,
|
||||
'name': 'test invoice',
|
||||
})],
|
||||
})
|
||||
move.post()
|
||||
return move.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
|
||||
|
||||
def create_statement_line(self, st_line_amount):
|
||||
journal = self.bs_model.with_context(journal_type='bank')._default_journal()
|
||||
#journal = self.env.ref('l10n_be.bank_journal')
|
||||
bank_stmt = self.bs_model.create({'journal_id': journal.id})
|
||||
|
||||
bank_stmt_line = self.bsl_model.create({
|
||||
'name': '_',
|
||||
'statement_id': bank_stmt.id,
|
||||
'partner_id': self.partner.id,
|
||||
'amount': st_line_amount,
|
||||
})
|
||||
|
||||
return bank_stmt_line
|
||||
|
||||
def test_confirm_statement_usd(self):
|
||||
company = self.env.ref('base.main_company')
|
||||
self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", [self.currency_euro_id, company.id])
|
||||
self.env['res.currency.rate'].search([]).unlink()
|
||||
self.env['res.currency.rate'].create({
|
||||
'currency_id': self.currency_usd_id,
|
||||
'rate': 2.0,
|
||||
'name': '2001-01-01',
|
||||
})
|
||||
bank_journal_usd = self.env['account.journal'].create({
|
||||
'name': 'Bank US',
|
||||
'type': 'bank',
|
||||
'code': 'BNK68',
|
||||
'currency_id': self.currency_usd_id,
|
||||
})
|
||||
statement = self.bs_model.create({
|
||||
'journal_id': bank_journal_usd.id,
|
||||
'balance_end_real': 100,
|
||||
'line_ids': [(0, 0, {
|
||||
'name': '_',
|
||||
'partner_id': self.partner.id,
|
||||
'amount': 100,
|
||||
'account_id': bank_journal_usd.default_debit_account_id.id,
|
||||
})],
|
||||
})
|
||||
statement.button_open()
|
||||
statement.button_confirm_bank()
|
||||
@@ -1,546 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestCommon
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import Form
|
||||
import time
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestPayment(AccountTestCommon):
|
||||
|
||||
def setUp(self):
|
||||
self.env.ref('base.main_company').currency_id = self.env.ref('base.USD')
|
||||
super(TestPayment, self).setUp()
|
||||
self.register_payments_model = self.env['account.payment.register'].with_context(active_model='account.move')
|
||||
self.payment_model = self.env['account.payment']
|
||||
self.acc_bank_stmt_model = self.env['account.bank.statement']
|
||||
self.acc_bank_stmt_line_model = self.env['account.bank.statement.line']
|
||||
|
||||
self.partner_agrolait = self.env['res.partner'].create({'name': 'Agrolait', 'is_company': True})
|
||||
self.partner_china_exp = self.env['res.partner'].create({'name': 'China Export', 'is_company': True})
|
||||
self.currency_chf_id = self.env.ref("base.CHF").id
|
||||
self.currency_usd_id = self.env.ref("base.USD").id
|
||||
self.currency_eur_id = self.env.ref("base.EUR").id
|
||||
|
||||
company = self.env.ref('base.main_company')
|
||||
self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", [self.currency_eur_id, company.id])
|
||||
|
||||
self.product = self.env['product.product'].create({
|
||||
'name': 'Product Product 4',
|
||||
'standard_price': 500.0,
|
||||
'list_price': 750.0,
|
||||
'type': 'consu',
|
||||
'categ_id': self.env.ref('product.product_category_all').id,
|
||||
})
|
||||
self.payment_method_manual_in = self.env.ref("account.account_payment_method_manual_in")
|
||||
self.payment_method_manual_out = self.env.ref("account.account_payment_method_manual_out")
|
||||
|
||||
self.account_receivable = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1)
|
||||
self.account_payable = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_payable').id)], limit=1)
|
||||
self.account_revenue = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)], limit=1)
|
||||
|
||||
self.bank_journal_euro = self.env['account.journal'].create({'name': 'Bank', 'type': 'bank', 'code': 'BNK67'})
|
||||
self.account_eur = self.bank_journal_euro.default_debit_account_id
|
||||
|
||||
self.cash_journal_euro = self.env['account.journal'].create({'name': 'Cash', 'type': 'cash', 'code': 'CASH'})
|
||||
|
||||
self.bank_journal_usd = self.env['account.journal'].create({'name': 'Bank US', 'type': 'bank', 'code': 'BNK68', 'currency_id': self.currency_usd_id})
|
||||
self.account_usd = self.bank_journal_usd.default_debit_account_id
|
||||
|
||||
if not self.env.user.company_id.transfer_account_id:
|
||||
self.env.user.company_id.transfer_account_id = self.usd_bnk
|
||||
self.transfer_account = self.env.user.company_id.transfer_account_id
|
||||
self.diff_income_account = self.env.user.company_id.income_currency_exchange_account_id
|
||||
self.diff_expense_account = self.env.user.company_id.expense_currency_exchange_account_id
|
||||
|
||||
self.form_payment = Form(self.env['account.payment'])
|
||||
|
||||
self.env['res.currency.rate'].create([
|
||||
{
|
||||
'currency_id': self.env.ref('base.EUR').id,
|
||||
'name': '2010-01-02',
|
||||
'rate': 1.0,
|
||||
}, {
|
||||
'currency_id': self.env.ref('base.USD').id,
|
||||
'name': '2010-01-02',
|
||||
'rate': 1.2834,
|
||||
}, {
|
||||
'currency_id': self.env.ref('base.USD').id,
|
||||
'name': time.strftime('%Y-06-05'),
|
||||
'rate': 1.5289,
|
||||
}
|
||||
])
|
||||
|
||||
def create_invoice(self, amount=100, type='out_invoice', currency_id=None, partner=None, account_id=None):
|
||||
""" Returns an open invoice """
|
||||
invoice = self.env['account.move'].create({
|
||||
'move_type': type,
|
||||
'partner_id': partner or self.partner_agrolait.id,
|
||||
'currency_id': currency_id or self.currency_eur_id,
|
||||
'invoice_date': time.strftime('%Y') + '-06-26',
|
||||
'date': time.strftime('%Y') + '-06-26',
|
||||
'invoice_line_ids': [
|
||||
(0, 0, {'product_id': self.product.id, 'quantity': 1, 'price_unit': amount})
|
||||
],
|
||||
})
|
||||
invoice.post()
|
||||
return invoice
|
||||
|
||||
def reconcile(self, liquidity_aml, amount=0.0, amount_currency=0.0, currency_id=None):
|
||||
""" Reconcile a journal entry corresponding to a payment with its bank statement line """
|
||||
bank_stmt = self.acc_bank_stmt_model.create({
|
||||
'journal_id': liquidity_aml.journal_id.id,
|
||||
'date': time.strftime('%Y') + '-07-15',
|
||||
})
|
||||
bank_stmt_line = self.acc_bank_stmt_line_model.create({
|
||||
'name': 'payment',
|
||||
'statement_id': bank_stmt.id,
|
||||
'partner_id': self.partner_agrolait.id,
|
||||
'amount': amount,
|
||||
'amount_currency': amount_currency,
|
||||
'currency_id': currency_id,
|
||||
'date': time.strftime('%Y') + '-07-15'
|
||||
})
|
||||
|
||||
bank_stmt_line.process_reconciliation(payment_aml_rec=liquidity_aml)
|
||||
return bank_stmt
|
||||
|
||||
def test_full_payment_process(self):
|
||||
""" Create a payment for one invoice, post it and reconcile it with a bank statement """
|
||||
inv_1 = self.create_invoice(amount=300, currency_id=self.currency_eur_id, partner=self.partner_agrolait.id)
|
||||
|
||||
ids = [inv_1.id]
|
||||
register_payments = self.register_payments_model.with_context(active_ids=ids).create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'payment_method_id': self.payment_method_manual_in.id,
|
||||
})
|
||||
payment = self.payment_model.browse(register_payments.create_payments()['res_id'])
|
||||
|
||||
self.assertAlmostEqual(payment.amount, 300)
|
||||
self.assertEqual(payment.state, 'posted')
|
||||
self.assertEqual(payment.state, 'posted')
|
||||
self.assertEqual(inv_1.payment_state, 'paid')
|
||||
|
||||
rec_line = payment.move_line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
|
||||
self.assertRecordValues(payment.move_line_ids.sorted('credit'), [
|
||||
{'account_id': self.account_eur.id, 'debit': 300.0, 'credit': 0.0, 'amount_currency': 0, 'currency_id': False},
|
||||
{'account_id': rec_line.account_id.id, 'debit': 0.0, 'credit': 300.0, 'amount_currency': 0, 'currency_id': False},
|
||||
])
|
||||
self.assertTrue(rec_line.full_reconcile_id.exists())
|
||||
|
||||
liquidity_aml = payment.move_line_ids - rec_line
|
||||
bank_statement = self.reconcile(liquidity_aml, 300, 0, False)
|
||||
|
||||
self.assertEqual(liquidity_aml.statement_id, bank_statement)
|
||||
self.assertEqual(liquidity_aml.statement_line_id, bank_statement.line_ids[0])
|
||||
|
||||
self.assertEqual(payment.state, 'reconciled')
|
||||
|
||||
def test_internal_transfer_journal_usd_journal_eur(self):
|
||||
""" Create a transfer from a EUR journal to a USD journal """
|
||||
payment = self.payment_model.create({
|
||||
'partner_id': self.company.partner_id.id,
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'payment_type': 'outbound',
|
||||
'partner_type': 'customer',
|
||||
'amount': 50,
|
||||
'currency_id': self.currency_usd_id,
|
||||
'journal_id': self.bank_journal_usd.id,
|
||||
'payment_method_id': self.payment_method_manual_out.id,
|
||||
})
|
||||
payment.post()
|
||||
self.assertRecordValues(payment.move_line_ids, [
|
||||
{'account_id': self.transfer_account.id, 'debit': 32.70, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
|
||||
{'account_id': self.account_usd.id, 'debit': 0.0, 'credit': 32.70, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
|
||||
def test_payment_chf_journal_usd(self):
|
||||
self.env['res.currency.rate'].create({
|
||||
'rate': 1.3086,
|
||||
'currency_id': self.currency_chf_id,
|
||||
'name': '2010-01-02',
|
||||
})
|
||||
|
||||
payment = self.payment_model.create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'payment_type': 'outbound',
|
||||
'amount': 50,
|
||||
'currency_id': self.currency_chf_id,
|
||||
'journal_id': self.bank_journal_usd.id,
|
||||
'partner_type': 'supplier',
|
||||
'partner_id': self.partner_china_exp.id,
|
||||
'payment_method_id': self.payment_method_manual_out.id,
|
||||
})
|
||||
payment.post()
|
||||
|
||||
self.assertRecordValues(payment.move_line_ids, [
|
||||
{'account_id': self.partner_china_exp.property_account_payable_id.id, 'debit': 38.21, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_chf_id},
|
||||
{'account_id': self.account_usd.id, 'debit': 0.0, 'credit': 38.21, 'amount_currency': -58.42, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
|
||||
def test_partial_payment(self):
|
||||
""" Create test to pay invoices (cust. inv + vendor bill) with partial payment """
|
||||
# Test Customer Invoice
|
||||
inv_1 = self.create_invoice(amount=600)
|
||||
payment_register = Form(self.env['account.payment'].with_context(active_model='account.move', active_ids=inv_1.ids))
|
||||
payment_register.payment_date = time.strftime('%Y') + '-07-15'
|
||||
payment_register.journal_id = self.bank_journal_euro
|
||||
payment_register.payment_method_id = self.payment_method_manual_in
|
||||
|
||||
# Perform the partial payment by setting the amount at 550 instead of 600
|
||||
payment_register.amount = 550
|
||||
|
||||
payment = payment_register.save()
|
||||
|
||||
self.assertEqual(len(payment), 1)
|
||||
self.assertEqual(payment.invoice_ids[0].id, inv_1.id)
|
||||
self.assertAlmostEqual(payment.amount, 550)
|
||||
self.assertEqual(payment.payment_type, 'inbound')
|
||||
self.assertEqual(payment.partner_id, self.partner_agrolait)
|
||||
self.assertEqual(payment.partner_type, 'customer')
|
||||
|
||||
# Test Vendor Bill
|
||||
inv_2 = self.create_invoice(amount=500, type='in_invoice', partner=self.partner_china_exp.id)
|
||||
payment_register = Form(self.env['account.payment'].with_context(active_model='account.move', active_ids=inv_2.ids))
|
||||
payment_register.payment_date = time.strftime('%Y') + '-07-15'
|
||||
payment_register.journal_id = self.bank_journal_euro
|
||||
payment_register.payment_method_id = self.payment_method_manual_in
|
||||
|
||||
# Perform the partial payment by setting the amount at 300 instead of 500
|
||||
payment_register.amount = 300
|
||||
|
||||
payment = payment_register.save()
|
||||
|
||||
self.assertEqual(len(payment), 1)
|
||||
self.assertEqual(payment.invoice_ids[0].id, inv_2.id)
|
||||
self.assertAlmostEqual(payment.amount, 300)
|
||||
self.assertEqual(payment.payment_type, 'outbound')
|
||||
self.assertEqual(payment.partner_id, self.partner_china_exp)
|
||||
self.assertEqual(payment.partner_type, 'supplier')
|
||||
|
||||
def test_payment_and_writeoff_in_other_currency_1(self):
|
||||
# Use case:
|
||||
# Company is in EUR, create a customer invoice for 25 EUR and register payment of 25 USD.
|
||||
# Mark invoice as fully paid with a write_off
|
||||
# Check that all the aml are correctly created.
|
||||
invoice = self.create_invoice(amount=25, type='out_invoice', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id)
|
||||
receivable_line = invoice.line_ids.filtered(lambda l: l.account_id.user_type_id.type == 'receivable')
|
||||
# register payment on invoice
|
||||
payment = self.payment_model.create({'payment_type': 'inbound',
|
||||
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
|
||||
'partner_type': 'customer',
|
||||
'partner_id': self.partner_agrolait.id,
|
||||
'amount': 25,
|
||||
'currency_id': self.currency_usd_id,
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.account_payable.id,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'invoice_ids': [(4, invoice.id, None)]
|
||||
})
|
||||
payment.post()
|
||||
self.assertRecordValues(payment.move_line_ids, [
|
||||
{'account_id': receivable_line.account_id.id, 'debit': 0.0, 'credit': 25.0, 'amount_currency': -38.22, 'currency_id': self.currency_usd_id},
|
||||
{'account_id': self.account_eur.id, 'debit': 16.35, 'credit': 0.0, 'amount_currency': 25.0, 'currency_id': self.currency_usd_id},
|
||||
{'account_id': self.account_payable.id, 'debit': 8.65, 'credit': 0.0, 'amount_currency': 13.22, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
self.assertTrue(receivable_line.full_reconcile_id)
|
||||
self.assertEqual(invoice.payment_state, 'paid')
|
||||
|
||||
# Use case:
|
||||
# Company is in EUR, create a vendor bill for 25 EUR and register payment of 25 USD.
|
||||
# Mark invoice as fully paid with a write_off
|
||||
# Check that all the aml are correctly created.
|
||||
invoice = self.create_invoice(amount=25, type='in_invoice', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id)
|
||||
payable_line = invoice.line_ids.filtered(lambda l: l.account_id.user_type_id.type == 'payable')
|
||||
# register payment on invoice
|
||||
payment = self.payment_model.create({'payment_type': 'outbound',
|
||||
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
|
||||
'partner_type': 'supplier',
|
||||
'partner_id': self.partner_agrolait.id,
|
||||
'amount': 25,
|
||||
'currency_id': self.currency_usd_id,
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.account_receivable.id,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'invoice_ids': [(4, invoice.id, None)]
|
||||
})
|
||||
payment.post()
|
||||
self.assertRecordValues(payment.move_line_ids, [
|
||||
{'account_id': payable_line.account_id.id, 'debit': 25.0, 'credit': 0.0, 'amount_currency': 38.22, 'currency_id': self.currency_usd_id},
|
||||
{'account_id': self.account_eur.id, 'debit': 0.0, 'credit': 16.35, 'amount_currency': -25.0, 'currency_id': self.currency_usd_id},
|
||||
{'account_id': self.account_receivable.id, 'debit': 0.0, 'credit': 8.65, 'amount_currency': -13.22, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
self.assertTrue(payable_line.full_reconcile_id)
|
||||
self.assertEqual(invoice.payment_state, 'paid')
|
||||
|
||||
def test_payment_and_writeoff_out_refund(self):
|
||||
# Use case:
|
||||
# Company is in EUR, create a credit note for 100 EUR and register payment of 90.
|
||||
# Mark invoice as fully paid with a write_off
|
||||
# Check that all the aml are correctly created.
|
||||
invoice = self.create_invoice(amount=100, type='out_refund', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id)
|
||||
receivable_line = invoice.line_ids.filtered(lambda l: l.account_id.user_type_id.type == 'receivable')
|
||||
# register payment on invoice
|
||||
payment = self.payment_model.create({'payment_type': 'outbound',
|
||||
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
|
||||
'partner_type': 'customer',
|
||||
'partner_id': self.partner_agrolait.id,
|
||||
'amount': 90,
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.account_payable.id,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'invoice_ids': [(4, invoice.id, None)]
|
||||
})
|
||||
payment.post()
|
||||
self.assertRecordValues(payment.move_line_ids, [
|
||||
{'account_id': receivable_line.account_id.id, 'debit': 100.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
|
||||
{'account_id': self.account_eur.id, 'debit': 0.0, 'credit': 90.0, 'amount_currency': 0.0, 'currency_id': False},
|
||||
{'account_id': self.account_payable.id, 'debit': 0.0, 'credit': 10.0, 'amount_currency': 0.0, 'currency_id': False},
|
||||
])
|
||||
self.assertEqual(invoice.payment_state, 'paid')
|
||||
|
||||
def test_payment_and_writeoff_in_other_currency_2(self):
|
||||
# Use case:
|
||||
# Company is in EUR, create a supplier bill of 5325.6 USD and register payment of 5325 USD, at a different rate
|
||||
# Mark invoice as fully paid with a write_off
|
||||
# Check that all the aml are correctly created.
|
||||
|
||||
# Set exchange rates 0.895@2017-11-01 and 0.88@2017-12-01
|
||||
self.env['res.currency.rate'].create({
|
||||
'currency_id': self.currency_usd_id,
|
||||
'rate': 0.895,
|
||||
'name': time.strftime('%Y') + '-06-26'})
|
||||
self.env['res.currency.rate'].create({
|
||||
'currency_id': self.currency_usd_id,
|
||||
'rate': 0.88,
|
||||
'name': time.strftime('%Y') + '-07-15'})
|
||||
|
||||
invoice = self.create_invoice(amount=5325.6, type='in_invoice', currency_id=self.currency_usd_id, partner=self.partner_agrolait.id)
|
||||
payable_line = invoice.line_ids.filtered(lambda l: l.account_id.user_type_id.type == 'payable')
|
||||
|
||||
# register payment on invoice
|
||||
payment = self.payment_model.create({'payment_type': 'outbound',
|
||||
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
|
||||
'partner_type': 'supplier',
|
||||
'partner_id': self.partner_agrolait.id,
|
||||
'amount': 5325,
|
||||
'currency_id': self.currency_usd_id,
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.account_revenue.id,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'invoice_ids': [(4, invoice.id, None)]
|
||||
})
|
||||
payment.post()
|
||||
self.assertRecordValues(payment.move_line_ids, [
|
||||
{'debit': 6051.82, 'credit': 0.0, 'amount_currency': 5325.6, 'currency_id': self.currency_usd_id},
|
||||
{'debit': 0.0, 'credit': 6051.14, 'amount_currency': -5325.0, 'currency_id': self.currency_usd_id},
|
||||
{'debit': 0.0, 'credit': 0.68, 'amount_currency': -0.6, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
exchange_lines = payable_line.full_reconcile_id.exchange_move_id.line_ids
|
||||
self.assertRecordValues(exchange_lines, [
|
||||
{'debit': 0.0, 'credit': 101.43, 'account_id': payable_line.account_id.id},
|
||||
{'debit': 101.43, 'credit': 0.0, 'account_id': self.diff_expense_account.id},
|
||||
])
|
||||
|
||||
#check the invoice status
|
||||
self.assertEqual(invoice.payment_state, 'paid')
|
||||
|
||||
def test_payment_and_writeoff_in_other_currency_3(self):
|
||||
# Use case related in revision 20935462a0cabeb45480ce70114ff2f4e91eaf79
|
||||
# Invoice made in secondary currency for which the rate to the company currency
|
||||
# is higher than the foreign currency decimal precision.
|
||||
# E.g: Company currency is EUR, create a customer invoice of 247590.40 EUR and
|
||||
# register payment of 267 USD (1 USD = 948 EUR)
|
||||
# Mark invoice as fully paid with a write_off
|
||||
# Check that all the aml are correctly created and that the invoice is paid
|
||||
|
||||
self.env['res.currency.rate'].create({
|
||||
'currency_id': self.currency_usd_id,
|
||||
'rate': 1,
|
||||
'name': time.strftime('%Y') + '-06-26'})
|
||||
self.env['res.currency.rate'].create({
|
||||
'currency_id': self.currency_eur_id,
|
||||
'rate': 948,
|
||||
'name': time.strftime('%Y') + '-06-26'})
|
||||
|
||||
invoice = self.create_invoice(amount=247590.4, type='out_invoice', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id)
|
||||
receivable_line = invoice.line_ids.filtered(lambda l: l.account_id.user_type_id.type == 'receivable')
|
||||
|
||||
# register payment on invoice
|
||||
payment = self.payment_model.create({'payment_type': 'inbound',
|
||||
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
|
||||
'partner_type': 'customer',
|
||||
'partner_id': self.partner_agrolait.id,
|
||||
'amount': 267,
|
||||
'currency_id': self.currency_usd_id,
|
||||
'payment_date': time.strftime('%Y') + '-06-26',
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.account_revenue.id,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'invoice_ids': [(4, invoice.id, None)],
|
||||
'name': 'test_payment_and_writeoff_in_other_currency_3',
|
||||
})
|
||||
payment.post()
|
||||
self.assertRecordValues(payment.move_line_ids, [
|
||||
{'account_id': receivable_line.account_id.id, 'debit': 0.0, 'credit': 247589.16, 'amount_currency': -261.17, 'currency_id': self.currency_usd_id},
|
||||
{'account_id': self.account_eur.id, 'debit': 253116.0, 'credit': 0.0, 'amount_currency': 267.0, 'currency_id': self.currency_usd_id},
|
||||
{'account_id': self.account_revenue.id, 'debit': 0.0, 'credit': 5526.84, 'amount_currency': -5.83, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
|
||||
# Check the invoice status and the full reconciliation: the difference on the receivable account
|
||||
# should have been completed by an exchange rate difference entry
|
||||
self.assertEqual(invoice.payment_state, 'paid')
|
||||
self.assertTrue(receivable_line.full_reconcile_id)
|
||||
|
||||
def test_post_at_bank_reconciliation_payment(self):
|
||||
# Create two new payments in a journal requiring the journal entries to be posted at bank reconciliation
|
||||
post_at_bank_rec_journal = self.env['account.journal'].create({
|
||||
'name': 'Bank',
|
||||
'type': 'bank',
|
||||
'code': 'COUCOU',
|
||||
'post_at': 'bank_rec',
|
||||
})
|
||||
payment_one = self.payment_model.create({'payment_type': 'inbound',
|
||||
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
|
||||
'partner_type': 'customer',
|
||||
'partner_id': self.partner_agrolait.id,
|
||||
'amount': 42,
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.account_receivable.id,
|
||||
'journal_id': post_at_bank_rec_journal.id,
|
||||
})
|
||||
payment_one.post()
|
||||
payment_two = self.payment_model.create({'payment_type': 'inbound',
|
||||
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
|
||||
'partner_type': 'customer',
|
||||
'partner_id': self.partner_agrolait.id,
|
||||
'amount': 11,
|
||||
'payment_date': time.strftime('%Y') + '-12-15',
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.account_receivable.id,
|
||||
'journal_id': post_at_bank_rec_journal.id,
|
||||
})
|
||||
payment_two.post()
|
||||
|
||||
# Check the payments and their move state
|
||||
self.assertEqual(payment_one.state, 'posted', "Payment one shoud be in posted state.")
|
||||
self.assertEqual(payment_one.mapped('move_line_ids.move_id.state'), ['draft'], "A posted payment (payment_one) in a bank journal with the 'post at bank reconciliation' option activated should correspond to a draft account.move")
|
||||
self.assertEqual(payment_two.state, 'posted', "Payment two shoud be in posted state.")
|
||||
self.assertEqual(payment_two.mapped('move_line_ids.move_id.state'), ['draft'], "A posted payment (payment_two) in a bank journal with the 'post at bank reconciliation' option activated should correspond to a draft account.move")
|
||||
|
||||
# Reconcile the two payments with an invoice, whose full amount is equal to their sum
|
||||
invoice = self.create_invoice(amount=53, partner=self.partner_agrolait.id)
|
||||
(payment_one.move_line_ids + payment_two.move_line_ids + invoice.line_ids).filtered(lambda x: x.account_id.user_type_id.type == 'receivable').reconcile()
|
||||
|
||||
self.assertEqual(invoice.payment_state, 'in_payment', "Invoice should be in 'in payment' state")
|
||||
|
||||
# Match the first payment with a bank statement line
|
||||
bank_statement_one = self.reconcile(payment_one.move_line_ids.filtered(lambda x: x.account_id.user_type_id.type == 'liquidity'), 42)
|
||||
stmt_line_date_one = bank_statement_one.mapped('line_ids.date')
|
||||
|
||||
self.assertEqual(payment_one.mapped('move_line_ids.move_id.state'), ['posted'], "After bank reconciliation, payment one's account.move should be posted.")
|
||||
self.assertEqual(payment_one.mapped('move_line_ids.move_id.date'), stmt_line_date_one, "After bank reconciliation, payment one's account.move should share the same date as the bank statement.")
|
||||
self.assertEqual([payment_one.payment_date], stmt_line_date_one, "After bank reconciliation, payment one should share the same date as the bank statement.")
|
||||
self.assertEqual(invoice.payment_state, 'in_payment', "The invoice should still be 'in payment', not all its payments are reconciled with a statement")
|
||||
|
||||
# Match the second payment with a bank statement line
|
||||
bank_statement_two = self.reconcile(payment_two.move_line_ids.filtered(lambda x: x.account_id.user_type_id.type == 'liquidity'), 42)
|
||||
stmt_line_date_two = bank_statement_two.mapped('line_ids.date')
|
||||
|
||||
self.assertEqual(payment_two.mapped('move_line_ids.move_id.state'), ['posted'], "After bank reconciliation, payment two's account.move should be posted.")
|
||||
self.assertEqual(payment_two.mapped('move_line_ids.move_id.date'), stmt_line_date_two, "After bank reconciliation, payment two's account.move should share the same date as the bank statement.")
|
||||
self.assertEqual([payment_two.payment_date], stmt_line_date_two, "After bank reconciliation, payment two should share the same date as the bank statement.")
|
||||
|
||||
# The invoice should now be paid
|
||||
self.assertEqual(invoice.payment_state, 'paid', "Invoice should be in 'paid' state after having reconciled the two payments with a bank statement")
|
||||
|
||||
def test_payment_draft_keep_name(self):
|
||||
payment = self.payment_model.create({
|
||||
'payment_type': 'inbound',
|
||||
'payment_method_id': self.payment_method_manual_in.id,
|
||||
'partner_type': 'customer',
|
||||
'partner_id': self.partner_agrolait.id,
|
||||
'amount': 90,
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
})
|
||||
|
||||
payment.post()
|
||||
self.assertEqual(len(payment.move_line_ids.mapped('move_id')), 1)
|
||||
name = payment.move_line_ids.mapped('move_id').name
|
||||
self.assertTrue(name)
|
||||
|
||||
payment.action_draft()
|
||||
self.assertFalse(payment.move_line_ids.mapped('move_id'))
|
||||
|
||||
payment.post()
|
||||
self.assertEqual(len(payment.move_line_ids.mapped('move_id')), 1)
|
||||
self.assertEqual(name, payment.move_line_ids.mapped('move_id').name)
|
||||
|
||||
def test_partial_payment_inv_foreign_payment_domestic(self):
|
||||
"""
|
||||
Invoice of 1000$ (foreign $) at 01/01 with foreign exchange rate of 0.50000
|
||||
Payment of 500 (domestic €) at 15/01 with foreign exchange rate of 1.00000
|
||||
The residuals should be 500 in foreign and 1500 in domestic.
|
||||
"""
|
||||
company = self.env.ref('base.main_company')
|
||||
self.env['res.currency.rate'].search([]).unlink()
|
||||
self.env['res.currency.rate'].create({
|
||||
'name': time.strftime('%Y') + '-01-01',
|
||||
'rate': 1.0,
|
||||
'currency_id': self.currency_eur_id,
|
||||
'company_id': company.id
|
||||
})
|
||||
self.env['res.currency.rate'].create({
|
||||
'name': time.strftime('%Y') + '-01-01',
|
||||
'rate': 0.5, # Don't change this !
|
||||
'currency_id': self.currency_usd_id,
|
||||
'company_id': self.env.ref('base.main_company').id
|
||||
})
|
||||
self.env['res.currency.rate'].create({
|
||||
'name': time.strftime('%Y') + '-01-15',
|
||||
'rate': 1.0, # Don't change this !
|
||||
'currency_id': self.currency_usd_id,
|
||||
'company_id': self.env.ref('base.main_company').id
|
||||
})
|
||||
inv1 = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_agrolait.id,
|
||||
'currency_id': self.currency_usd_id,
|
||||
'invoice_date': time.strftime('%Y') + '-01-01',
|
||||
'date': time.strftime('%Y') + '-01-01',
|
||||
'invoice_line_ids': [
|
||||
(0, 0, {'product_id': self.product.id, 'quantity': 1, 'price_unit': 1000})
|
||||
],
|
||||
})
|
||||
inv1.post()
|
||||
payment = self.payment_model.create({
|
||||
'payment_date': time.strftime('%Y') + '-01-15',
|
||||
'payment_method_id': self.payment_method_manual_in.id,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
'partner_id': inv1.partner_id.id,
|
||||
'amount': 500,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'company_id': company.id,
|
||||
'currency_id': self.currency_eur_id,
|
||||
})
|
||||
payment.post()
|
||||
inv1_receivable = inv1.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
pay_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
|
||||
self.assertEqual(inv1_receivable.balance, 2000)
|
||||
self.assertEqual(pay_receivable.balance, -500)
|
||||
|
||||
(inv1_receivable + pay_receivable).reconcile()
|
||||
self.assertEqual(inv1.amount_residual, 500)
|
||||
self.assertEqual(inv1.amount_residual_signed, 1500)
|
||||
@@ -7,128 +7,12 @@ from datetime import timedelta
|
||||
|
||||
from odoo import api, fields
|
||||
from odoo.addons.account.tests.common import TestAccountReconciliationCommon
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import Form, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
|
||||
def test_statement_usd_invoice_eur_transaction_eur(self):
|
||||
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_euro_id, 30, self.bank_journal_usd, 42, 30, self.currency_euro_id)
|
||||
self.assertRecordValues(customer_move_lines, [
|
||||
{'debit': 0.0, 'credit': 30.0, 'amount_currency': -42, 'currency_id': self.currency_usd_id},
|
||||
{'debit': 30.0, 'credit': 0.0, 'amount_currency': 42, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
self.assertRecordValues(supplier_move_lines, [
|
||||
{'debit': 30.0, 'credit': 0.0, 'amount_currency': 42, 'currency_id': self.currency_usd_id},
|
||||
{'debit': 0.0, 'credit': 30.0, 'amount_currency': -42, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
|
||||
def test_statement_usd_invoice_usd_transaction_usd(self):
|
||||
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_usd_id, 50, self.bank_journal_usd, 50, 0, False)
|
||||
self.assertRecordValues(customer_move_lines, [
|
||||
{'debit': 0.0, 'credit': 32.70, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
|
||||
{'debit': 32.70, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
self.assertRecordValues(supplier_move_lines, [
|
||||
{'debit': 32.70, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
|
||||
{'debit': 0.0, 'credit': 32.70, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
|
||||
def test_statement_usd_invoice_usd_transaction_eur(self):
|
||||
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_usd_id, 50, self.bank_journal_usd, 50, 40, self.currency_euro_id)
|
||||
self.assertRecordValues(customer_move_lines, [
|
||||
{'debit': 0.0, 'credit': 40.0, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
|
||||
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
exchange_lines = customer_move_lines.mapped('full_reconcile_id.exchange_move_id.line_ids')
|
||||
self.assertRecordValues(exchange_lines, [
|
||||
{'debit': 7.30, 'credit': 0.0, 'account_id': self.account_rcv.id},
|
||||
{'debit': 0.0, 'credit': 7.30, 'account_id': self.diff_income_account.id},
|
||||
])
|
||||
|
||||
self.assertRecordValues(supplier_move_lines, [
|
||||
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
|
||||
{'debit': 0.0, 'credit': 40.0, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
exchange_lines = supplier_move_lines.mapped('full_reconcile_id.exchange_move_id.line_ids')
|
||||
self.assertRecordValues(exchange_lines, [
|
||||
{'debit': 0.0, 'credit': 7.30, 'account_id': self.account_rsa.id},
|
||||
{'debit': 7.30, 'credit': 0.0, 'account_id': self.diff_expense_account.id},
|
||||
])
|
||||
|
||||
def test_statement_usd_invoice_chf_transaction_chf(self):
|
||||
self.env['res.currency.rate'].create({
|
||||
'rate': 1.3086,
|
||||
'currency_id': self.env.ref('base.CHF').id,
|
||||
'name': '2010-01-02',
|
||||
})
|
||||
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_swiss_id, 50, self.bank_journal_usd, 42, 50, self.currency_swiss_id)
|
||||
self.assertRecordValues(customer_move_lines, [
|
||||
{'debit': 0.0, 'credit': 27.47, 'amount_currency': -50, 'currency_id': self.currency_swiss_id},
|
||||
{'debit': 27.47, 'credit': 0.0, 'amount_currency': 42, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
exchange_lines = customer_move_lines.mapped('full_reconcile_id.exchange_move_id.line_ids')
|
||||
self.assertRecordValues(exchange_lines, [
|
||||
{'debit': 0.0, 'credit': 10.74, 'account_id': self.account_rcv.id},
|
||||
{'debit': 10.74, 'credit': 0.0, 'account_id': self.diff_expense_account.id},
|
||||
])
|
||||
|
||||
self.assertRecordValues(supplier_move_lines, [
|
||||
{'debit': 27.47, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_swiss_id},
|
||||
{'debit': 0.0, 'credit': 27.47, 'amount_currency': -42, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
exchange_lines = supplier_move_lines.mapped('full_reconcile_id.exchange_move_id.line_ids')
|
||||
self.assertRecordValues(exchange_lines, [
|
||||
{'debit': 10.74, 'credit': 0.0, 'account_id': self.account_rsa.id},
|
||||
{'debit': 0.0, 'credit': 10.74, 'account_id': self.diff_income_account.id},
|
||||
])
|
||||
|
||||
def test_statement_eur_invoice_usd_transaction_usd(self):
|
||||
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_usd_id, 50, self.bank_journal_euro, 40, 50, self.currency_usd_id)
|
||||
self.assertRecordValues(customer_move_lines, [
|
||||
{'debit': 0.0, 'credit': 40.0, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
|
||||
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
exchange_lines = customer_move_lines.mapped('full_reconcile_id.exchange_move_id.line_ids')
|
||||
self.assertRecordValues(exchange_lines, [
|
||||
{'debit': 7.30, 'credit': 0.0, 'account_id': self.account_rcv.id},
|
||||
{'debit': 0.0, 'credit': 7.30, 'account_id': self.diff_income_account.id},
|
||||
])
|
||||
|
||||
self.assertRecordValues(supplier_move_lines, [
|
||||
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
|
||||
{'debit': 0.0, 'credit': 40.0, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
|
||||
])
|
||||
exchange_lines = supplier_move_lines.mapped('full_reconcile_id.exchange_move_id.line_ids')
|
||||
self.assertRecordValues(exchange_lines, [
|
||||
{'debit': 0.0, 'credit': 7.30, 'account_id': self.account_rsa.id},
|
||||
{'debit': 7.30, 'credit': 0.0, 'account_id': self.diff_expense_account.id},
|
||||
])
|
||||
|
||||
def test_statement_eur_invoice_usd_transaction_eur(self):
|
||||
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_usd_id, 50, self.bank_journal_euro, 40, 0.0, False)
|
||||
self.assertRecordValues(customer_move_lines, [
|
||||
{'debit': 0.0, 'credit': 40.0, 'amount_currency': 0.0, 'currency_id': False},
|
||||
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
|
||||
])
|
||||
self.assertRecordValues(supplier_move_lines, [
|
||||
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
|
||||
{'debit': 0.0, 'credit': 40.0, 'amount_currency': 0.0, 'currency_id': False},
|
||||
])
|
||||
|
||||
def test_statement_euro_invoice_usd_transaction_chf(self):
|
||||
customer_move_lines, supplier_move_lines = self.make_customer_and_supplier_flows(self.currency_usd_id, 50, self.bank_journal_euro, 42, 50, self.currency_swiss_id)
|
||||
self.assertRecordValues(customer_move_lines, [
|
||||
{'debit': 0.0, 'credit': 42.0, 'amount_currency': -50, 'currency_id': self.currency_swiss_id},
|
||||
{'debit': 42.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_swiss_id},
|
||||
])
|
||||
self.assertRecordValues(supplier_move_lines, [
|
||||
{'debit': 42.0, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_swiss_id},
|
||||
{'debit': 0.0, 'credit': 42.0, 'amount_currency': -50, 'currency_id': self.currency_swiss_id},
|
||||
])
|
||||
|
||||
def test_statement_euro_invoice_usd_transaction_euro_full(self):
|
||||
# Create a customer invoice of 50 USD.
|
||||
partner = self.env['res.partner'].create({'name': 'test'})
|
||||
@@ -150,7 +34,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'date': '%s-01-01' % time.strftime('%Y'),
|
||||
'line_ids': [
|
||||
(0, 0, {
|
||||
'name': 'test',
|
||||
'payment_ref': 'test',
|
||||
'partner_id': partner.id,
|
||||
'amount': 40.0,
|
||||
'date': '%s-01-01' % time.strftime('%Y')
|
||||
@@ -160,22 +44,16 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
|
||||
# Reconcile the bank statement with the invoice.
|
||||
receivable_line = move.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
|
||||
bank_stmt.line_ids[0].process_reconciliation(counterpart_aml_dicts=[{
|
||||
'move_line': receivable_line,
|
||||
'debit': 0.0,
|
||||
'credit': 32.7,
|
||||
'name': 'test_statement_euro_invoice_usd_transaction_euro_full',
|
||||
}], new_aml_dicts=[{
|
||||
'debit': 0.0,
|
||||
'credit': 7.3,
|
||||
'name': 'exchange difference',
|
||||
'account_id': self.diff_income_account.id
|
||||
}])
|
||||
bank_stmt.button_post()
|
||||
bank_stmt.line_ids[0].reconcile([
|
||||
{'id': receivable_line.id},
|
||||
{'name': 'exchange difference', 'balance': -7.3, 'account_id': self.diff_income_account.id},
|
||||
])
|
||||
|
||||
self.assertRecordValues(bank_stmt.move_line_ids, [
|
||||
self.assertRecordValues(bank_stmt.line_ids.line_ids, [
|
||||
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
|
||||
{'debit': 0.0, 'credit': 7.3, 'amount_currency': 0.0, 'currency_id': False},
|
||||
{'debit': 0.0, 'credit': 32.7, 'amount_currency': 0.0, 'currency_id': False},
|
||||
{'debit': 40.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False},
|
||||
])
|
||||
|
||||
# The invoice should be paid, as the payments totally cover its total
|
||||
@@ -244,9 +122,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
line_id = l
|
||||
break
|
||||
for statement_line in statement.line_ids:
|
||||
statement_line.process_reconciliation([
|
||||
{'counterpart_move_line_id': line_id.id, 'credit': 1.0, 'debit': 0.0, 'name': line_id.name}
|
||||
])
|
||||
statement_line.reconcile([{'id': line_id.id}])
|
||||
|
||||
# The invoice should be paid, as the payments totally cover its total
|
||||
self.assertEqual(invoice.state, 'paid', 'The invoice should be paid by now')
|
||||
@@ -298,7 +174,9 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'line_ids': [(0,0, debit_line_vals), (0, 0, credit_line_vals)]
|
||||
}
|
||||
return self.env['account.move'].create(vals).id
|
||||
move = self.env['account.move'].create(vals)
|
||||
move.post()
|
||||
return move.id
|
||||
move_list_vals = [
|
||||
('1', -1.83, 0, self.currency_swiss_id),
|
||||
('2', 728.35, 795.05, self.currency_swiss_id),
|
||||
@@ -380,6 +258,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'line_ids': [(0,0, debit_line_vals), (0, 0, credit_line_vals)]
|
||||
}
|
||||
move_ids += self.env['account.move'].create(vals)
|
||||
move_ids.post()
|
||||
|
||||
account_move_line = move_ids.mapped('line_ids').filtered(lambda l: l.account_id == self.account_rcv)
|
||||
writeoff_vals = [{
|
||||
@@ -395,80 +274,6 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
self.assertTrue(all(l.reconciled for l in writeoff_line), 'The balance lines should be totally reconciled')
|
||||
self.assertTrue(all(l.reconciled for l in account_move_line), 'The move lines should be totally reconciled')
|
||||
|
||||
def test_reconcile_bank_statement_with_payment_and_writeoff(self):
|
||||
# Use case:
|
||||
# Company is in EUR, create a bill for 80 USD and register payment of 80 USD.
|
||||
# create a bank statement in USD bank journal with a bank statement line of 85 USD
|
||||
# Reconcile bank statement with payment and put the remaining 5 USD in bank fees or another account.
|
||||
|
||||
invoice = self.create_invoice(type='out_invoice', invoice_amount=80, currency_id=self.currency_usd_id)
|
||||
# register payment on invoice
|
||||
payment = self.env['account.payment'].create({'payment_type': 'inbound',
|
||||
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
|
||||
'partner_type': 'customer',
|
||||
'partner_id': self.partner_agrolait_id,
|
||||
'amount': 80,
|
||||
'currency_id': self.currency_usd_id,
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'journal_id': self.bank_journal_usd.id,
|
||||
'invoice_ids': [(6, 0, invoice.ids)],
|
||||
})
|
||||
payment.post()
|
||||
bank_move_line = payment.move_line_ids.filtered(lambda line: line.account_id.internal_type == 'liquidity')
|
||||
|
||||
# create bank statement
|
||||
bank_stmt = self.acc_bank_stmt_model.create({
|
||||
'journal_id': self.bank_journal_usd.id,
|
||||
'date': time.strftime('%Y') + '-07-15',
|
||||
})
|
||||
|
||||
bank_stmt_line = self.acc_bank_stmt_line_model.create({'name': 'payment',
|
||||
'statement_id': bank_stmt.id,
|
||||
'partner_id': self.partner_agrolait_id,
|
||||
'amount': 85,
|
||||
'date': time.strftime('%Y') + '-07-15',})
|
||||
|
||||
#reconcile the statement with invoice and put remaining in another account
|
||||
bank_stmt_line.process_reconciliation(payment_aml_rec= bank_move_line, new_aml_dicts=[{
|
||||
'account_id': self.diff_income_account.id,
|
||||
'debit': 0,
|
||||
'credit': 5,
|
||||
'name': 'bank fees',
|
||||
}])
|
||||
|
||||
# Check that move lines associated to bank_statement are correct
|
||||
bank_stmt_aml = self.env['account.move.line'].search([('statement_id', '=', bank_stmt.id)])
|
||||
bank_stmt_aml |= bank_stmt_aml.mapped('move_id').mapped('line_ids')
|
||||
self.assertEqual(len(bank_stmt_aml), 4, "The bank statement should have 4 moves lines")
|
||||
lines = {
|
||||
self.account_usd.id: [
|
||||
{'debit': 3.27, 'credit': 0.0, 'amount_currency': 5, 'currency_id': self.currency_usd_id},
|
||||
{'debit': 52.33, 'credit': 0, 'amount_currency': 80, 'currency_id': self.currency_usd_id}
|
||||
],
|
||||
self.diff_income_account.id: {'debit': 0.0, 'credit': 3.27, 'amount_currency': -5, 'currency_id': self.currency_usd_id},
|
||||
self.account_rcv.id: {'debit': 0.0, 'credit': 52.33, 'amount_currency': -80, 'currency_id': self.currency_usd_id},
|
||||
}
|
||||
|
||||
payments = bank_stmt_aml.mapped('payment_id')
|
||||
# creation and reconciliation of the over-amount statement
|
||||
# has created an another payment
|
||||
self.assertEqual(len(payments), 2)
|
||||
# Check amount of second, automatically created payment
|
||||
self.assertEqual((payments - payment).amount, 5)
|
||||
|
||||
for aml in bank_stmt_aml:
|
||||
line = lines[aml.account_id.id]
|
||||
if type(line) == list:
|
||||
# find correct line inside the list
|
||||
if line[0]['debit'] == round(aml.debit, 2):
|
||||
line = line[0]
|
||||
else:
|
||||
line = line[1]
|
||||
self.assertEqual(round(aml.debit, 2), line['debit'])
|
||||
self.assertEqual(round(aml.credit, 2), line['credit'])
|
||||
self.assertEqual(round(aml.amount_currency, 2), line['amount_currency'])
|
||||
self.assertEqual(aml.currency_id.id, line['currency_id'])
|
||||
|
||||
def test_partial_reconcile_currencies_01(self):
|
||||
# client Account (payable, rsa)
|
||||
# Debit Credit
|
||||
@@ -482,9 +287,11 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
# Balance Debit = 18.75
|
||||
# Counterpart Credit goes in Exchange diff
|
||||
|
||||
dest_journal_id = self.env['account.journal'].search([('type', '=', 'purchase'), ('company_id', '=', self.company.id)], limit=1)
|
||||
dest_journal_id.write({'default_debit_account_id': self.bank_journal_euro.default_credit_account_id,
|
||||
'default_credit_account_id': self.bank_journal_euro.default_credit_account_id})
|
||||
dest_journal_id = self.env['account.journal'].create({
|
||||
'name': 'dest_journal_id',
|
||||
'type': 'bank',
|
||||
})
|
||||
|
||||
# Setting up rates for USD (main_company is in EUR)
|
||||
self.env['res.currency.rate'].create({'name': time.strftime('%Y') + '-' + '07' + '-01',
|
||||
'rate': 0.5,
|
||||
@@ -531,7 +338,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'currency_id': self.currency_usd_id,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'company_id': self.company.id,
|
||||
'payment_date': time.strftime('%Y') + '-' + '07' + '-01',
|
||||
'date': time.strftime('%Y') + '-' + '07' + '-01',
|
||||
'partner_id': self.partner_agrolait_id,
|
||||
'payment_method_id': self.env.ref('account.account_payment_method_manual_out').id,
|
||||
'partner_type': 'supplier'})
|
||||
@@ -542,7 +349,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'currency_id': self.currency_usd_id,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'company_id': self.company.id,
|
||||
'payment_date': time.strftime('%Y') + '-' + '08' + '-01',
|
||||
'date': time.strftime('%Y') + '-' + '08' + '-01',
|
||||
'partner_id': self.partner_agrolait_id,
|
||||
'payment_method_id': self.env.ref('account.account_payment_method_manual_out').id,
|
||||
'partner_type': 'supplier'})
|
||||
@@ -553,19 +360,19 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'currency_id': self.currency_usd_id,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'company_id': self.company.id,
|
||||
'payment_date': time.strftime('%Y') + '-' + '09' + '-01',
|
||||
'date': time.strftime('%Y') + '-' + '09' + '-01',
|
||||
'partner_id': self.partner_agrolait_id,
|
||||
'payment_method_id': self.env.ref('account.account_payment_method_manual_out').id,
|
||||
'partner_type': 'supplier'})
|
||||
|
||||
payment_a.post()
|
||||
payment_b.post()
|
||||
payment_c.post()
|
||||
payment_a.action_post()
|
||||
payment_b.action_post()
|
||||
payment_c.action_post()
|
||||
|
||||
# Assigning payments to invoices
|
||||
debit_line_a = payment_a.move_line_ids.filtered(lambda l: l.debit and l.account_id == self.account_rsa)
|
||||
debit_line_b = payment_b.move_line_ids.filtered(lambda l: l.debit and l.account_id == self.account_rsa)
|
||||
debit_line_c = payment_c.move_line_ids.filtered(lambda l: l.debit and l.account_id == self.account_rsa)
|
||||
debit_line_a = payment_a.line_ids.filtered(lambda l: l.debit and l.account_id == self.account_rsa)
|
||||
debit_line_b = payment_b.line_ids.filtered(lambda l: l.debit and l.account_id == self.account_rsa)
|
||||
debit_line_c = payment_c.line_ids.filtered(lambda l: l.debit and l.account_id == self.account_rsa)
|
||||
|
||||
invoice_a.js_assign_outstanding_line(debit_line_a.id)
|
||||
invoice_a.js_assign_outstanding_line(debit_line_b.id)
|
||||
@@ -608,8 +415,8 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'currency_id': self.currency_usd_id,
|
||||
'journal_id': self.bank_journal_usd.id,
|
||||
})
|
||||
payment.post()
|
||||
credit_aml = payment.move_line_ids.filtered('credit')
|
||||
payment.action_post()
|
||||
credit_aml = payment.line_ids.filtered('credit')
|
||||
|
||||
# Check residual before assignation
|
||||
self.assertAlmostEqual(inv1.amount_residual, 10)
|
||||
@@ -657,17 +464,17 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'amount': 111,
|
||||
'currency_id': self.currency_usd_id,
|
||||
'journal_id': self.bank_journal_usd.id,
|
||||
'payment_date': time.strftime('%Y') + '-08-01',
|
||||
'date': time.strftime('%Y') + '-08-01',
|
||||
})
|
||||
payment.post()
|
||||
credit_aml = payment.move_line_ids.filtered('credit')
|
||||
payment.action_post()
|
||||
credit_aml = payment.line_ids.filtered('credit')
|
||||
|
||||
# Check residual before assignation
|
||||
self.assertAlmostEqual(inv.amount_residual, 111)
|
||||
|
||||
# Assign credit, check exchange move and residual
|
||||
inv.js_assign_outstanding_line(credit_aml.id)
|
||||
self.assertEqual(len(payment.move_line_ids.mapped('full_reconcile_id').exchange_move_id), 1)
|
||||
self.assertEqual(len(payment.line_ids.mapped('full_reconcile_id').exchange_move_id), 1)
|
||||
self.assertAlmostEqual(inv.amount_residual, 0)
|
||||
|
||||
# Unreconcile invoice and check residual
|
||||
@@ -681,15 +488,15 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'partner_type': 'customer',
|
||||
'partner_id': self.partner_agrolait_id,
|
||||
'journal_id': self.bank_journal_usd.id,
|
||||
'payment_date': '2018-06-04',
|
||||
'date': '2018-06-04',
|
||||
'amount': 666,
|
||||
})
|
||||
payment.post()
|
||||
payment.action_post()
|
||||
|
||||
self.assertEqual(len(payment.move_line_ids), 2)
|
||||
self.assertEqual(len(payment.line_ids), 2)
|
||||
|
||||
bank_line = payment.move_line_ids.filtered(lambda l: l.account_id.id == self.bank_journal_usd.default_debit_account_id.id)
|
||||
customer_line = payment.move_line_ids - bank_line
|
||||
bank_line = payment.line_ids.filtered(lambda l: l.account_id.id == self.bank_journal_usd.payment_debit_account_id.id)
|
||||
customer_line = payment.line_ids - bank_line
|
||||
|
||||
self.assertEqual(len(bank_line), 1)
|
||||
self.assertEqual(len(customer_line), 1)
|
||||
@@ -705,7 +512,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
self.assertEqual(len(reversed_move.line_ids), 2)
|
||||
|
||||
# Testing the reconciliation matching between the move lines and their reversed counterparts
|
||||
reversed_bank_line = reversed_move.line_ids.filtered(lambda l: l.account_id.id == self.bank_journal_usd.default_debit_account_id.id)
|
||||
reversed_bank_line = reversed_move.line_ids.filtered(lambda l: l.account_id.id == self.bank_journal_usd.payment_debit_account_id.id)
|
||||
reversed_customer_line = reversed_move.line_ids - reversed_bank_line
|
||||
|
||||
self.assertEqual(len(reversed_bank_line), 1)
|
||||
@@ -763,17 +570,17 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'amount': 111,
|
||||
'currency_id': self.currency_usd_id,
|
||||
'journal_id': self.bank_journal_usd.id,
|
||||
'payment_date': time.strftime('%Y') + '-08-01',
|
||||
'date': time.strftime('%Y') + '-08-01',
|
||||
})
|
||||
payment.post()
|
||||
payment.action_post()
|
||||
|
||||
credit_aml = payment.move_line_ids.filtered('credit')
|
||||
credit_aml = payment.line_ids.filtered('credit')
|
||||
inv.js_assign_outstanding_line(credit_aml.id)
|
||||
self.assertTrue(inv.payment_state == 'paid', 'The invoice should be paid')
|
||||
self.assertTrue(inv.payment_state in ('in_payment', 'paid'), "Invoice should be paid")
|
||||
|
||||
exchange_reconcile = payment.move_line_ids.mapped('full_reconcile_id')
|
||||
exchange_reconcile = payment.line_ids.mapped('full_reconcile_id')
|
||||
exchange_move = exchange_reconcile.exchange_move_id
|
||||
payment_move = payment.move_line_ids[0].move_id
|
||||
payment_move = payment.line_ids[0].move_id
|
||||
|
||||
reverted_payment_move = payment_move._reverse_moves([{'date': time.strftime('%Y') + '-08-01'}], cancel=True)
|
||||
|
||||
@@ -792,10 +599,11 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
# * Dr. 100 USD / 50 EUR - Accounts receivable
|
||||
# * Cr. 100 USD / 50 EUR - Revenue
|
||||
####
|
||||
dest_journal_id = self.env['account.journal'].search(
|
||||
[('type', '=', 'purchase'),
|
||||
('company_id', '=', self.company.id)],
|
||||
limit=1)
|
||||
dest_journal_id = self.env['account.journal'].create({
|
||||
'name': 'turlututu',
|
||||
'type': 'bank',
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
# Delete any old rate - to make sure that we use the ones we need.
|
||||
old_rates = self.env['res.currency.rate'].search(
|
||||
@@ -836,20 +644,17 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'name': time.strftime('%Y') + '-01-02',
|
||||
'rate': 1,
|
||||
})
|
||||
# register payment on invoice
|
||||
payment = self.env['account.payment'].create(
|
||||
{'payment_type': 'inbound',
|
||||
'payment_method_id': self.env.ref(
|
||||
'account.account_payment_method_manual_in').id,
|
||||
'partner_type': 'customer',
|
||||
'partner_id': self.partner_agrolait_id,
|
||||
'amount': 50,
|
||||
'currency_id': self.currency_usd_id,
|
||||
'payment_date': time.strftime('%Y') + '-01-02',
|
||||
'journal_id': dest_journal_id.id,
|
||||
'invoice_ids': [(6, 0, invoice_cust_1.ids)],
|
||||
})
|
||||
payment.post()
|
||||
|
||||
payment = self.env['account.payment.register']\
|
||||
.with_context(active_model='account.move', active_ids=invoice_cust_1.ids)\
|
||||
.create({
|
||||
'payment_date': time.strftime('%Y') + '-01-02',
|
||||
'amount': 50,
|
||||
'journal_id': dest_journal_id.id,
|
||||
'currency_id': self.currency_usd_id,
|
||||
})\
|
||||
._create_payments()
|
||||
|
||||
# We expect at this point that the invoice should still be open, in 'partial' state,
|
||||
# because they owe us still 50 CC.
|
||||
self.assertEqual(invoice_cust_1.payment_state, 'partial', 'Invoice is in status %s' % invoice_cust_1.state)
|
||||
@@ -884,7 +689,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
)
|
||||
|
||||
payment = self.env['account.payment'].create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'date': time.strftime('%Y') + '-07-15',
|
||||
'payment_type': 'inbound',
|
||||
'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
|
||||
'partner_type': 'customer',
|
||||
@@ -893,9 +698,9 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'currency_id': self.currency_usd_id,
|
||||
'journal_id': self.bank_journal_usd.id,
|
||||
})
|
||||
payment.post()
|
||||
payment.action_post()
|
||||
|
||||
receivable_line = payment.move_line_ids.filtered('credit')
|
||||
receivable_line = payment.line_ids.filtered('credit')
|
||||
invoice.js_assign_outstanding_line(receivable_line.id)
|
||||
|
||||
self.assertTrue(receivable_line.matched_debit_ids)
|
||||
@@ -946,17 +751,17 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
self.assertTrue(cash_basis_moves.exists())
|
||||
|
||||
# check reconciliation in Payable account
|
||||
purchase_move_line_ids = purchase_move.line_ids.sorted()
|
||||
payment_move_line_ids = payment_move.line_ids.sorted()
|
||||
self.assertTrue(purchase_move_line_ids[0].full_reconcile_id.exists())
|
||||
self.assertEqual(purchase_move_line_ids[0].full_reconcile_id.reconciled_line_ids,
|
||||
purchase_move_line_ids[0] + purchase_move_line_ids[1] + payment_move_line_ids[0])
|
||||
purchase_line_ids = purchase_move.line_ids.sorted()
|
||||
payment_line_ids = payment_move.line_ids.sorted()
|
||||
self.assertTrue(purchase_line_ids[0].full_reconcile_id.exists())
|
||||
self.assertEqual(purchase_line_ids[0].full_reconcile_id.reconciled_line_ids,
|
||||
purchase_line_ids[0] + purchase_line_ids[1] + payment_line_ids[0])
|
||||
|
||||
cash_basis_aml_ids = cash_basis_moves.mapped('line_ids')
|
||||
# check reconciliation in the tax waiting account
|
||||
self.assertTrue(purchase_move_line_ids[4].full_reconcile_id.exists())
|
||||
self.assertEqual(purchase_move_line_ids[4].full_reconcile_id.reconciled_line_ids,
|
||||
cash_basis_aml_ids.filtered(lambda l: l.account_id == self.tax_waiting_account) + purchase_move_line_ids[4])
|
||||
self.assertTrue(purchase_line_ids[4].full_reconcile_id.exists())
|
||||
self.assertEqual(purchase_line_ids[4].full_reconcile_id.reconciled_line_ids,
|
||||
cash_basis_aml_ids.filtered(lambda l: l.account_id == self.tax_waiting_account) + purchase_line_ids[4])
|
||||
|
||||
self.assertEqual(len(cash_basis_aml_ids), 8)
|
||||
|
||||
@@ -1052,6 +857,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
})
|
||||
payment_move1.post()
|
||||
|
||||
(purchase_move + payment_move0 + payment_move1).post()
|
||||
(purchase_move + payment_move0).mapped('line_ids').sorted().filtered(lambda l: l.account_id.internal_type == 'payable').reconcile()
|
||||
(purchase_move + payment_move1).mapped('line_ids').sorted().filtered(lambda l: l.account_id.internal_type == 'payable').reconcile()
|
||||
|
||||
@@ -1061,21 +867,21 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
self.assertTrue(cash_basis_moves.exists())
|
||||
|
||||
# check reconciliation in Payable account
|
||||
purchase_move_line_ids = purchase_move.line_ids.sorted()
|
||||
self.assertTrue(purchase_move_line_ids[0].full_reconcile_id.exists())
|
||||
self.assertEqual(purchase_move_line_ids[0].full_reconcile_id.reconciled_line_ids,
|
||||
purchase_line_ids = purchase_move.line_ids.sorted()
|
||||
self.assertTrue(purchase_line_ids[0].full_reconcile_id.exists())
|
||||
self.assertEqual(purchase_line_ids[0].full_reconcile_id.reconciled_line_ids,
|
||||
(purchase_move + payment_move0 + payment_move1).mapped('line_ids').filtered(lambda l: l.account_id.internal_type == 'payable'))
|
||||
|
||||
cash_basis_aml_ids = cash_basis_moves.mapped('line_ids')
|
||||
|
||||
# check reconciliation in the tax waiting account
|
||||
self.assertTrue(purchase_move_line_ids[3].full_reconcile_id.exists())
|
||||
self.assertEqual(purchase_move_line_ids[3].full_reconcile_id.reconciled_line_ids,
|
||||
cash_basis_aml_ids.filtered(lambda l: l.account_id == tax_waiting_account10) + purchase_move_line_ids[3])
|
||||
self.assertTrue(purchase_line_ids[3].full_reconcile_id.exists())
|
||||
self.assertEqual(purchase_line_ids[3].full_reconcile_id.reconciled_line_ids,
|
||||
cash_basis_aml_ids.filtered(lambda l: l.account_id == tax_waiting_account10) + purchase_line_ids[3])
|
||||
|
||||
self.assertTrue(purchase_move_line_ids[5].full_reconcile_id.exists())
|
||||
self.assertEqual(purchase_move_line_ids[5].full_reconcile_id.reconciled_line_ids,
|
||||
cash_basis_aml_ids.filtered(lambda l: l.account_id == self.tax_waiting_account) + purchase_move_line_ids[5])
|
||||
self.assertTrue(purchase_line_ids[5].full_reconcile_id.exists())
|
||||
self.assertEqual(purchase_line_ids[5].full_reconcile_id.reconciled_line_ids,
|
||||
cash_basis_aml_ids.filtered(lambda l: l.account_id == self.tax_waiting_account) + purchase_line_ids[5])
|
||||
|
||||
self.assertEqual(len(cash_basis_aml_ids), 24)
|
||||
|
||||
@@ -1124,40 +930,6 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
)
|
||||
index += 1
|
||||
|
||||
def test_reconciliation_to_check(self):
|
||||
partner = self.env['res.partner'].create({'name': 'UncertainPartner'})
|
||||
currency = self.env.company.currency_id
|
||||
invoice = self.create_invoice_partner(currency_id=currency.id, partner_id=partner.id)
|
||||
journal = self.env['account.journal'].create({'name': 'Bank', 'type': 'bank', 'code': 'THE', 'restrict_mode_hash_table':False})
|
||||
|
||||
statement = self.make_payment(invoice, journal, 50)
|
||||
st_line = statement.line_ids
|
||||
previous_move_lines = st_line.journal_entry_ids.ids
|
||||
previous_name = st_line.move_name
|
||||
|
||||
with self.assertRaises(UserError): #you need edition mode to be able to change it
|
||||
st_line.with_context(suspense_moves_mode=False).process_reconciliation(
|
||||
counterpart_aml_dicts=[],
|
||||
new_aml_dicts=[{
|
||||
'debit': 0,
|
||||
'credit': 50,
|
||||
'name': 'exchange difference',
|
||||
'account_id': self.diff_income_account.id
|
||||
}],
|
||||
)
|
||||
|
||||
st_line.with_context(suspense_moves_mode=True).process_reconciliation(
|
||||
counterpart_aml_dicts=[],
|
||||
new_aml_dicts=[{
|
||||
'debit': 0,
|
||||
'credit': 50,
|
||||
'name': 'exchange difference',
|
||||
'account_id': self.diff_income_account.id
|
||||
}],
|
||||
)
|
||||
self.assertEqual(previous_name, st_line.move_name) # the name of the move hasnt changed
|
||||
self.assertNotEqual(previous_move_lines, st_line.journal_entry_ids.ids) # the lines are new
|
||||
|
||||
def test_reconciliation_cash_basis_fx_01(self):
|
||||
"""
|
||||
Company's Currency EUR
|
||||
@@ -1275,11 +1047,11 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
# check reconciliation in Payable account
|
||||
purchase_line_ids = purchase_move.line_ids.sorted()
|
||||
fx_move_01_line_ids = fx_move_01.line_ids.sorted()
|
||||
payment_move_line_ids = payment_move.line_ids.sorted()
|
||||
payment_line_ids = payment_move.line_ids.sorted()
|
||||
self.assertTrue(purchase_line_ids[2].full_reconcile_id.exists())
|
||||
self.assertEqual(
|
||||
purchase_line_ids[2].full_reconcile_id.reconciled_line_ids,
|
||||
purchase_line_ids[2] + fx_move_01_line_ids[0] + payment_move_line_ids[0])
|
||||
purchase_line_ids[2] + fx_move_01_line_ids[0] + payment_line_ids[0])
|
||||
|
||||
# check cash basis
|
||||
cash_basis_moves = self.env['account.move'].search(
|
||||
@@ -1455,15 +1227,15 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
to_reconcile.reconcile()
|
||||
|
||||
# check reconciliation in Payable account
|
||||
purchase_move_line_ids = purchase_move.line_ids.sorted()
|
||||
purchase_line_ids = purchase_move.line_ids.sorted()
|
||||
fx_move_01_line_ids = fx_move_01.line_ids.sorted()
|
||||
fx_move_02_line_ids = fx_move_02.line_ids.sorted()
|
||||
payment_move_line_ids = payment_move.line_ids.sorted()
|
||||
self.assertTrue(purchase_move_line_ids[2].full_reconcile_id.exists())
|
||||
payment_line_ids = payment_move.line_ids.sorted()
|
||||
self.assertTrue(purchase_line_ids[2].full_reconcile_id.exists())
|
||||
self.assertEqual(
|
||||
purchase_move_line_ids[2].full_reconcile_id.reconciled_line_ids,
|
||||
purchase_move_line_ids[2] + fx_move_01_line_ids[0] + fx_move_02_line_ids[0] +
|
||||
payment_move_line_ids[0])
|
||||
purchase_line_ids[2].full_reconcile_id.reconciled_line_ids,
|
||||
purchase_line_ids[2] + fx_move_01_line_ids[0] + fx_move_02_line_ids[0] +
|
||||
payment_line_ids[0])
|
||||
|
||||
# check cash basis
|
||||
cash_basis_moves = self.env['account.move'].search(
|
||||
@@ -1665,7 +1437,8 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
self.assertTrue(invoice.currency_id != self.env.user.company_id.currency_id)
|
||||
|
||||
# First Payment
|
||||
payment0 = self.make_payment(invoice, journal, amount=59.99)
|
||||
line = invoice.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))
|
||||
payment0 = self.make_payment(invoice, journal, amount=59.99, reconcile_param=[{'id': line.id, 'balance': -59.99}])
|
||||
self.assertEqual(invoice.amount_residual, 0.01)
|
||||
|
||||
tax_waiting_line = invoice.line_ids.filtered(lambda l: l.account_id == self.tax_waiting_account)
|
||||
@@ -1681,7 +1454,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
self.assertEqual(pay_receivable_line0.matched_debit_ids, move_caba0.tax_cash_basis_rec_id)
|
||||
|
||||
# Second Payment
|
||||
payment1 = self.make_payment(invoice, journal, 0.01)
|
||||
payment1 = self.make_payment(invoice, journal, 0.01, reconcile_param=[{'id': line.id, 'balance': -0.01}])
|
||||
self.assertEqual(invoice.amount_residual, 0)
|
||||
self.assertEqual(invoice.payment_state, 'paid')
|
||||
|
||||
@@ -1778,7 +1551,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
inv2 = self.create_invoice(type="out_refund", invoice_amount=400, currency_id=self.currency_usd_id)
|
||||
|
||||
payment = self.env['account.payment'].create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'date': time.strftime('%Y') + '-07-15',
|
||||
'payment_method_id': self.inbound_payment_method.id,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
@@ -1787,11 +1560,11 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
payment.post()
|
||||
payment.action_post()
|
||||
|
||||
inv1_receivable = inv1.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
inv2_receivable = inv2.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
pay_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
pay_receivable = payment.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
|
||||
move_balance = self.env['account.move'].create({
|
||||
'partner_id': inv1.partner_id.id,
|
||||
@@ -1813,7 +1586,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
self.assertEqual(inv1_receivable.full_reconcile_id, pay_receivable.full_reconcile_id)
|
||||
self.assertEqual(inv1_receivable.full_reconcile_id, move_balance_receiv.full_reconcile_id)
|
||||
|
||||
self.assertEqual(inv1.payment_state, 'paid')
|
||||
self.assertTrue(inv1.payment_state in ('in_payment', 'paid'), "Invoice should be paid")
|
||||
self.assertEqual(inv2.payment_state, 'paid')
|
||||
|
||||
def test_inv_refund_foreign_payment_writeoff_domestic3(self):
|
||||
@@ -1846,21 +1619,20 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
inv2 = self.create_invoice(type="out_refund", invoice_amount=225, currency_id=self.currency_usd_id)
|
||||
|
||||
payment = self.env['account.payment'].create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'payment_method_id': self.inbound_payment_method.id,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
'partner_id': inv1.partner_id.id,
|
||||
'amount': 372.10,
|
||||
'payment_date': time.strftime('%Y') + '-07-01',
|
||||
'date': time.strftime('%Y') + '-07-01',
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
payment.post()
|
||||
payment.action_post()
|
||||
|
||||
inv1_receivable = inv1.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
inv2_receivable = inv2.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
pay_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
pay_receivable = payment.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
|
||||
move_balance = self.env['account.move'].create({
|
||||
'partner_id': inv1.partner_id.id,
|
||||
@@ -1884,7 +1656,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
|
||||
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
|
||||
|
||||
self.assertEqual(inv1.payment_state, 'paid')
|
||||
self.assertTrue(inv1.payment_state in ('in_payment', 'paid'), "Invoice should be paid")
|
||||
self.assertEqual(inv2.payment_state, 'paid')
|
||||
|
||||
def test_inv_refund_foreign_payment_writeoff_domestic4(self):
|
||||
@@ -1923,7 +1695,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
inv2 = self._create_invoice(type="out_refund", invoice_amount=225, currency_id=self.currency_usd_id, date_invoice=time.strftime('%Y') + '-07-15', auto_validate=True)
|
||||
|
||||
payment = self.env['account.payment'].create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'date': time.strftime('%Y') + '-07-15',
|
||||
'payment_method_id': self.inbound_payment_method.id,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
@@ -1933,11 +1705,11 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'company_id': company.id,
|
||||
'currency_id': self.currency_euro_id,
|
||||
})
|
||||
payment.post()
|
||||
payment.action_post()
|
||||
|
||||
inv1_receivable = inv1.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
inv2_receivable = inv2.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
pay_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
pay_receivable = payment.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
|
||||
self.assertEqual(inv1_receivable.balance, 658)
|
||||
self.assertEqual(inv2_receivable.balance, -202.59)
|
||||
@@ -1963,7 +1735,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
self.assertEqual(inv1_receivable.full_reconcile_id, pay_receivable.full_reconcile_id)
|
||||
self.assertEqual(inv1_receivable.full_reconcile_id, move_balance_receiv.full_reconcile_id)
|
||||
|
||||
self.assertEqual(inv1.payment_state, 'paid')
|
||||
self.assertTrue(inv1.payment_state in ('in_payment', 'paid'), "Invoice should be paid")
|
||||
self.assertEqual(inv2.payment_state, 'paid')
|
||||
|
||||
def test_inv_refund_foreign_payment_writeoff_domestic5(self):
|
||||
@@ -2006,23 +1778,19 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
inv1.js_assign_outstanding_line(inv2_receivable.id)
|
||||
self.assertEqual(inv1.amount_residual, 350)
|
||||
|
||||
Payment = self.env['account.payment'].with_context(default_invoice_ids=[(4, inv1.id, False)])
|
||||
payment = Payment.create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'payment_method_id': self.inbound_payment_method.id,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
'partner_id': inv1.partner_id.id,
|
||||
'amount': 314.07,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'company_id': company.id,
|
||||
'currency_id': self.currency_euro_id,
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.diff_income_account.id,
|
||||
})
|
||||
payment.post()
|
||||
payment = self.env['account.payment.register']\
|
||||
.with_context(active_model='account.move', active_ids=inv1.ids)\
|
||||
.create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'amount': 314.07,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'currency_id': self.currency_euro_id,
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.diff_income_account.id,
|
||||
})\
|
||||
._create_payments()
|
||||
|
||||
payment_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
payment_receivable = payment.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
self.assertEqual(payment_receivable.balance, -350)
|
||||
|
||||
self.assertTrue(inv1_receivable.full_reconcile_id.exists())
|
||||
@@ -2031,7 +1799,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
|
||||
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
|
||||
|
||||
self.assertEqual(inv1.payment_state, 'paid')
|
||||
self.assertTrue(inv1.payment_state in ('in_payment', 'paid'), "Invoice should be paid")
|
||||
self.assertEqual(inv2.payment_state, 'paid')
|
||||
|
||||
def test_inv_refund_foreign_payment_writeoff_domestic6(self):
|
||||
@@ -2070,24 +1838,19 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
self.assertAlmostEqual(inv1.amount_residual, 350)
|
||||
self.assertAlmostEqual(inv1_receivable.amount_residual, 315.15)
|
||||
|
||||
Payment = self.env['account.payment']
|
||||
payment = Payment.create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'payment_method_id': self.inbound_payment_method.id,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
'partner_id': inv1.partner_id.id,
|
||||
'amount': 314.07,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'company_id': company.id,
|
||||
'currency_id': self.currency_euro_id,
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.diff_income_account.id,
|
||||
'invoice_ids': [(4, inv1.id, False)],
|
||||
})
|
||||
payment.post()
|
||||
payment = self.env['account.payment.register']\
|
||||
.with_context(active_model='account.move', active_ids=inv1.ids)\
|
||||
.create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'amount': 314.07,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'currency_id': self.currency_euro_id,
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.diff_income_account.id,
|
||||
})\
|
||||
._create_payments()
|
||||
|
||||
payment_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
payment_receivable = payment.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
|
||||
self.assertTrue(inv1_receivable.full_reconcile_id.exists())
|
||||
self.assertEqual(inv1_receivable.full_reconcile_id, inv2_receivable.full_reconcile_id)
|
||||
@@ -2096,7 +1859,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
exchange_rcv = inv1_receivable.full_reconcile_id.exchange_move_id.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
self.assertEqual(exchange_rcv.amount_currency, 0.01)
|
||||
|
||||
self.assertEqual(inv1.payment_state, 'paid')
|
||||
self.assertTrue(inv1.payment_state in ('in_payment', 'paid'), "Invoice should be paid")
|
||||
self.assertEqual(inv2.payment_state, 'paid')
|
||||
|
||||
def test_inv_refund_foreign_payment_writeoff_domestic6bis(self):
|
||||
@@ -2146,24 +1909,19 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
self.assertAlmostEqual(inv1.amount_residual, 350)
|
||||
self.assertAlmostEqual(inv1_receivable.amount_residual, 315.15)
|
||||
|
||||
Payment = self.env['account.payment'].with_context(default_invoice_ids=[(4, inv1.id, False)])
|
||||
payment = Payment.create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'payment_method_id': self.inbound_payment_method.id,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
'partner_id': inv1.partner_id.id,
|
||||
'amount': 314.07,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'company_id': company.id,
|
||||
'currency_id': foreign_0.id,
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.diff_income_account.id,
|
||||
'invoice_ids': [(4, inv1.id, False)],
|
||||
})
|
||||
payment.post()
|
||||
payment = self.env['account.payment.register']\
|
||||
.with_context(active_model='account.move', active_ids=inv1.ids)\
|
||||
.create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'amount': 314.07,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'currency_id': foreign_0.id,
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.diff_income_account.id,
|
||||
})\
|
||||
._create_payments()
|
||||
|
||||
payment_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
payment_receivable = payment.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
|
||||
self.assertTrue(inv1_receivable.full_reconcile_id.exists())
|
||||
self.assertEqual(inv1_receivable.full_reconcile_id, inv2_receivable.full_reconcile_id)
|
||||
@@ -2171,7 +1929,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
|
||||
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
|
||||
|
||||
self.assertEqual(inv1.payment_state, 'paid')
|
||||
self.assertTrue(inv1.payment_state in ('in_payment', 'paid'), "Invoice should be paid")
|
||||
self.assertEqual(inv2.payment_state, 'paid')
|
||||
|
||||
def test_inv_refund_foreign_payment_writeoff_domestic7(self):
|
||||
@@ -2206,31 +1964,26 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
|
||||
self.assertAlmostEqual(inv1_receivable.balance, 5384.48)
|
||||
|
||||
Payment = self.env['account.payment']
|
||||
payment = Payment.create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'payment_method_id': self.inbound_payment_method.id,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
'partner_id': inv1.partner_id.id,
|
||||
'amount': 5384.43,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'company_id': company.id,
|
||||
'currency_id': self.currency_euro_id,
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.diff_income_account.id,
|
||||
'invoice_ids': [(4, inv1.id, False)],
|
||||
})
|
||||
payment.post()
|
||||
payment = self.env['account.payment.register']\
|
||||
.with_context(active_model='account.move', active_ids=inv1.ids)\
|
||||
.create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'amount': 5384.43,
|
||||
'journal_id': self.bank_journal_euro.id,
|
||||
'currency_id': self.currency_euro_id,
|
||||
'payment_difference_handling': 'reconcile',
|
||||
'writeoff_account_id': self.diff_income_account.id,
|
||||
})\
|
||||
._create_payments()
|
||||
|
||||
payment_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
payment_receivable = payment.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
|
||||
self.assertTrue(inv1_receivable.full_reconcile_id.exists())
|
||||
self.assertEqual(inv1_receivable.full_reconcile_id, payment_receivable.full_reconcile_id)
|
||||
|
||||
self.assertFalse(inv1_receivable.full_reconcile_id.exchange_move_id)
|
||||
|
||||
self.assertEqual(inv1.payment_state, 'paid')
|
||||
self.assertTrue(inv1.payment_state in ('in_payment', 'paid'), "Invoice should be paid")
|
||||
|
||||
def test_inv_refund_foreign_payment_writeoff_domestic8(self):
|
||||
"""
|
||||
@@ -2260,7 +2013,7 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
|
||||
Payment = self.env['account.payment']
|
||||
payment = Payment.create({
|
||||
'payment_date': time.strftime('%Y') + '-07-15',
|
||||
'date': time.strftime('%Y') + '-07-15',
|
||||
'payment_method_id': self.inbound_payment_method.id,
|
||||
'payment_type': 'inbound',
|
||||
'partner_type': 'customer',
|
||||
@@ -2270,8 +2023,8 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
'company_id': company.id,
|
||||
'currency_id': self.currency_euro_id,
|
||||
})
|
||||
payment.post()
|
||||
payment_receivable = payment.move_line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
payment.action_post()
|
||||
payment_receivable = payment.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
|
||||
move_balance = self.env['account.move'].create({
|
||||
'partner_id': inv1.partner_id.id,
|
||||
@@ -2294,4 +2047,4 @@ class TestReconciliationExec(TestAccountReconciliationCommon):
|
||||
exchange_rcv = inv1_receivable.full_reconcile_id.exchange_move_id.line_ids.filtered(lambda l: l.account_id.internal_type == 'receivable')
|
||||
self.assertEqual(exchange_rcv.amount_currency, 0.01)
|
||||
|
||||
self.assertEqual(inv1.payment_state, 'paid')
|
||||
self.assertTrue(inv1.payment_state in ('in_payment', 'paid'), "Invoice should be paid")
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
from odoo import api, fields
|
||||
from odoo.tests import tagged
|
||||
from odoo.addons.account.tests.common import TestAccountReconciliationCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestReconciliationHeavyLoad(TestAccountReconciliationCommon):
|
||||
"""Check that reconciliation can be done for a move with many lines
|
||||
"""
|
||||
|
||||
def _create_move(self, journal):
|
||||
values = {
|
||||
'ref': "Test reconcile - Auto-generated by script",
|
||||
'journal_id': journal.id,
|
||||
'state': 'draft',
|
||||
'company_id': journal.env.user.company_id.id,
|
||||
}
|
||||
return journal.env['account.move'].create(values)
|
||||
|
||||
def _get_values_account_move_line(
|
||||
self, account, journal, name, move,
|
||||
credit=0, debit=0, date=fields.Date.today()):
|
||||
return {
|
||||
'journal_id': journal.id,
|
||||
'name': name,
|
||||
'account_id': account.id,
|
||||
'move_id': move.id,
|
||||
'quantity': 1,
|
||||
'credit': credit,
|
||||
'debit': debit,
|
||||
'date': date,
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(TestReconciliationHeavyLoad, self).setUp()
|
||||
|
||||
self.account_type = self.env.ref('account.data_account_type_receivable')
|
||||
|
||||
self.journal = self.env['account.journal'].search([
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', self.env.user.company_id.id),
|
||||
], limit=1)
|
||||
|
||||
self.account = self.env['account.account'].search([
|
||||
('user_type_id', '=', self.account_type.id),
|
||||
('company_id', '=', self.env.user.company_id.id),
|
||||
], limit=1)
|
||||
|
||||
def test_heavy_load_reconciliation(self):
|
||||
"""Does reconciliation on a move with nb_lines lines.
|
||||
To avoid burdening uselessly the runbot, we only set nb_lines to 10,
|
||||
but it should be of order 10^3 to be meaningful.
|
||||
The day we manage to use system build settings to execute tests
|
||||
this could be done automatically for "heavy load builds",
|
||||
but for now this should be changed manually.
|
||||
"""
|
||||
|
||||
total = 0
|
||||
line_ids = []
|
||||
amount_per_line = 1
|
||||
nb_lines = 10 # change this to 1000 or more
|
||||
move = self._create_move(self.journal)
|
||||
|
||||
for i in range(nb_lines):
|
||||
name = "Move line credit #%s" % i
|
||||
total += amount_per_line
|
||||
values = self._get_values_account_move_line(
|
||||
self.account, self.journal, name, move, credit=amount_per_line)
|
||||
line_ids.append((0, False, values))
|
||||
|
||||
values = self._get_values_account_move_line(
|
||||
self.account, self.journal, "Move line Debit", move, debit=total)
|
||||
line_ids.append((0, False, values))
|
||||
|
||||
move.write({'line_ids': line_ids})
|
||||
|
||||
move.line_ids.reconcile()
|
||||
|
||||
self.assertTrue(all(move.line_ids.mapped('reconciled')))
|
||||
@@ -13,7 +13,6 @@ class TestReconciliationMatchingRules(AccountTestCommon):
|
||||
super(TestReconciliationMatchingRules, cls).setUpClass()
|
||||
cls.company = cls.env.user.company_id
|
||||
cls.account_pay = cls.a_pay
|
||||
cls.account_liq = cls.bnk
|
||||
cls.account_rcv = cls.a_recv
|
||||
|
||||
cls.partner_1 = cls.env['res.partner'].create({'name': 'partner_1', 'company_id': cls.company.id})
|
||||
@@ -65,16 +64,16 @@ class TestReconciliationMatchingRules(AccountTestCommon):
|
||||
})
|
||||
cls.bank_line_1 = cls.env['account.bank.statement.line'].create({
|
||||
'statement_id': cls.bank_st.id,
|
||||
'name': 'invoice %s-%s-%s' % (invoice_number.split('/')[1], invoice_number.split('/')[2], invoice_number.split('/')[3]),
|
||||
'payment_ref': 'invoice %s-%s-%s' % (invoice_number.split('/')[1], invoice_number.split('/')[2], invoice_number.split('/')[3]),
|
||||
'partner_id': cls.partner_1.id,
|
||||
'amount': '100',
|
||||
'amount': 100,
|
||||
'sequence': 1,
|
||||
})
|
||||
cls.bank_line_2 = cls.env['account.bank.statement.line'].create({
|
||||
'statement_id': cls.bank_st.id,
|
||||
'name': 'xxxxx',
|
||||
'payment_ref': 'xxxxx',
|
||||
'partner_id': cls.partner_1.id,
|
||||
'amount': '600',
|
||||
'amount': 600,
|
||||
'sequence': 2,
|
||||
})
|
||||
|
||||
@@ -84,16 +83,16 @@ class TestReconciliationMatchingRules(AccountTestCommon):
|
||||
})
|
||||
cls.cash_line_1 = cls.env['account.bank.statement.line'].create({
|
||||
'statement_id': cls.cash_st.id,
|
||||
'name': 'yyyyy',
|
||||
'payment_ref': 'yyyyy',
|
||||
'partner_id': cls.partner_2.id,
|
||||
'amount': '-1000',
|
||||
'amount': -1000,
|
||||
'sequence': 1,
|
||||
})
|
||||
|
||||
cls.tax21 = cls.env['account.tax'].create({
|
||||
'name': '21%',
|
||||
'type_tax_use': 'purchase',
|
||||
'amount': '21',
|
||||
'amount': 21,
|
||||
})
|
||||
|
||||
@classmethod
|
||||
@@ -111,6 +110,11 @@ class TestReconciliationMatchingRules(AccountTestCommon):
|
||||
lines = invoice.line_ids
|
||||
return lines.filtered(lambda l: l.account_id.user_type_id.type in ('receivable', 'payable'))
|
||||
|
||||
def _post_statements(self):
|
||||
self.bank_st.balance_end_real = self.bank_st.balance_end
|
||||
self.cash_st.balance_end_real = self.cash_st.balance_end
|
||||
(self.bank_st + self.cash_st).button_post()
|
||||
|
||||
def _check_statement_matching(self, rules, expected_values, statements=None):
|
||||
if statements is None:
|
||||
statements = self.bank_st + self.cash_st
|
||||
@@ -120,9 +124,9 @@ class TestReconciliationMatchingRules(AccountTestCommon):
|
||||
values.pop('reconciled_lines', None)
|
||||
self.assertDictEqual(values, expected_values[st_line_id])
|
||||
|
||||
|
||||
def test_matching_fields(self):
|
||||
''' Test all fields used to restrict the rules's applicability.'''
|
||||
self._post_statements()
|
||||
|
||||
# Check without restriction.
|
||||
self._check_statement_matching(self.rule_1, {
|
||||
@@ -267,6 +271,7 @@ class TestReconciliationMatchingRules(AccountTestCommon):
|
||||
|
||||
def test_mixin_rules(self):
|
||||
''' Test usage of rules together.'''
|
||||
self._post_statements()
|
||||
|
||||
# rule_1 is used before rule_2.
|
||||
self.rule_1.sequence = 1
|
||||
@@ -303,6 +308,8 @@ class TestReconciliationMatchingRules(AccountTestCommon):
|
||||
|
||||
def test_auto_reconcile(self):
|
||||
''' Test auto reconciliation.'''
|
||||
self.bank_line_1.amount += 5
|
||||
self._post_statements()
|
||||
|
||||
self.rule_1.sequence = 2
|
||||
self.rule_1.auto_reconcile = True
|
||||
@@ -311,8 +318,6 @@ class TestReconciliationMatchingRules(AccountTestCommon):
|
||||
self.rule_2.match_partner_ids |= self.partner_2
|
||||
self.rule_2.auto_reconcile = True
|
||||
|
||||
self.bank_line_1.amount += 5
|
||||
|
||||
self._check_statement_matching(self.rule_1 + self.rule_2, {
|
||||
self.bank_line_1.id: {'aml_ids': [self.invoice_line_1.id], 'model': self.rule_1, 'status': 'reconciled'},
|
||||
self.bank_line_2.id: {'aml_ids': []},
|
||||
@@ -320,21 +325,20 @@ class TestReconciliationMatchingRules(AccountTestCommon):
|
||||
})
|
||||
|
||||
# Check first line has been well reconciled.
|
||||
self.assertRecordValues(self.bank_line_1.journal_entry_ids, [
|
||||
self.assertRecordValues(self.bank_line_1.line_ids, [
|
||||
{'partner_id': self.partner_1.id, 'debit': 105.0, 'credit': 0.0},
|
||||
{'partner_id': self.partner_1.id, 'debit': 0.0, 'credit': 5.0},
|
||||
{'partner_id': self.partner_1.id, 'debit': 0.0, 'credit': 100.0},
|
||||
{'partner_id': self.partner_1.id, 'debit': 105.0, 'credit': 0.0},
|
||||
])
|
||||
|
||||
# Check second line has been well reconciled.
|
||||
self.assertRecordValues(self.cash_line_1.journal_entry_ids, [
|
||||
{'partner_id': self.partner_2.id, 'debit': 1000.0, 'credit': 0.0},
|
||||
self.assertRecordValues(self.cash_line_1.line_ids, [
|
||||
{'partner_id': self.partner_2.id, 'debit': 0.0, 'credit': 1000.0},
|
||||
{'partner_id': self.partner_2.id, 'debit': 1000.0, 'credit': 0.0},
|
||||
])
|
||||
|
||||
def test_auto_reconcile_with_tax(self):
|
||||
''' Test auto reconciliation with a tax amount included in the bank statement line'''
|
||||
|
||||
self.rule_1.write({
|
||||
'auto_reconcile': True,
|
||||
'rule_type': 'writeoff_suggestion',
|
||||
@@ -346,6 +350,7 @@ class TestReconciliationMatchingRules(AccountTestCommon):
|
||||
|
||||
self.bank_line_2.unlink()
|
||||
self.bank_line_1.amount = -121
|
||||
self._post_statements()
|
||||
|
||||
self._check_statement_matching(
|
||||
self.rule_1,
|
||||
@@ -356,20 +361,20 @@ class TestReconciliationMatchingRules(AccountTestCommon):
|
||||
)
|
||||
|
||||
# Check first line has been well reconciled.
|
||||
self.assertRecordValues(self.bank_line_1.journal_entry_ids, [
|
||||
self.assertRecordValues(self.bank_line_1.line_ids, [
|
||||
{'partner_id': self.partner_1.id, 'debit': 0.0, 'credit': 121.0, 'tax_ids': [], 'tax_line_id': False},
|
||||
{'partner_id': self.partner_1.id, 'debit': 100.0, 'credit': 0.0, 'tax_ids': [self.tax21.id], 'tax_line_id': False},
|
||||
{'partner_id': self.partner_1.id, 'debit': 21.0, 'credit': 0.0, 'tax_ids': [], 'tax_line_id': self.tax21.id},
|
||||
{'partner_id': self.partner_1.id, 'debit': 0.0, 'credit': 121.0, 'tax_ids': [], 'tax_line_id': False},
|
||||
])
|
||||
|
||||
def test_reverted_move_matching(self):
|
||||
partner = self.env['res.partner'].create({'name': 'Eugene'})
|
||||
AccountMove = self.env['account.move']
|
||||
move = AccountMove.create({
|
||||
'name': 'To Revert',
|
||||
'journal_id': self.bank_journal.id,
|
||||
})
|
||||
|
||||
partner = self.env['res.partner'].create({'name': 'Eugene'})
|
||||
AccountMoveLine = self.env['account.move.line'].with_context(check_move_validity=False)
|
||||
payment_payable_line = AccountMoveLine.create({
|
||||
'account_id': self.account_pay.id,
|
||||
@@ -379,7 +384,7 @@ class TestReconciliationMatchingRules(AccountTestCommon):
|
||||
'debit': 10,
|
||||
})
|
||||
payment_bnk_line = AccountMoveLine.create({
|
||||
'account_id': self.account_liq.id,
|
||||
'account_id': self.bank_journal.payment_credit_account_id.id,
|
||||
'move_id': move.id,
|
||||
'partner_id': partner.id,
|
||||
'name': 'I\'m gonna cut you into little pieces',
|
||||
@@ -398,7 +403,7 @@ class TestReconciliationMatchingRules(AccountTestCommon):
|
||||
bank_st.journal_id.default_debit_account_id = payment_bnk_line.account_id
|
||||
bank_line_1 = self.env['account.bank.statement.line'].create({
|
||||
'statement_id': bank_st.id,
|
||||
'name': '8',
|
||||
'payment_ref': '8',
|
||||
'partner_id': partner.id,
|
||||
'amount': -10,
|
||||
'sequence': 1,
|
||||
|
||||
@@ -233,8 +233,8 @@
|
||||
<!-- On the left, display :
|
||||
- A button corresponding to the bank_statements_source, if it wasn't configured, a button for each of them
|
||||
- If there are statements to reconcile, a link to reconcile them -->
|
||||
<div class="col-12 col-sm-5 mb-3 mb-sm-0 o_kanban_primary_left">
|
||||
<t t-if="journal_type == 'bank'" id="journal_body_bank_cash_left">
|
||||
<div id="dashboard_bank_cash_left" class="col-12 col-sm-5 mb-3 mb-sm-0 o_kanban_primary_left">
|
||||
<t t-if="journal_type == 'bank'">
|
||||
<button t-if="dashboard.bank_statements_source == 'undefined'" name="action_configure_bank_journal" type="object" class="btn btn-primary" groups="account.group_account_invoice">Configure</button>
|
||||
<div name="bank_journal_cta" class="mt-3 mt-sm-0">
|
||||
<div name="bank_statement_create_button" groups="account.group_account_invoice">
|
||||
@@ -250,16 +250,24 @@
|
||||
</t>
|
||||
</div>
|
||||
<!-- On the right, show other common informations/actions -->
|
||||
<div class="col-12 col-sm-7 o_kanban_primary_right">
|
||||
<t t-if="dashboard.last_balance != dashboard.account_balance">
|
||||
<div class="row">
|
||||
<div class="col overflow-hidden text-left">
|
||||
<span title="Balance in General Ledger">Balance in GL</span>
|
||||
</div>
|
||||
<div class="col-auto text-right">
|
||||
<span><t t-esc="dashboard.account_balance"/></span>
|
||||
</div>
|
||||
<div id="dashboard_bank_cash_right" class="col-12 col-sm-7 o_kanban_primary_right">
|
||||
<div class="row" t-if="dashboard.nb_lines_bank_account_balance > 0">
|
||||
<div id="dashboard_bank_cash_balance" class="col overflow-hidden text-left">
|
||||
<span title="Balance in General Ledger">Balance in GL</span>
|
||||
</div>
|
||||
<div class="col-auto text-right">
|
||||
<span><t t-esc="dashboard.account_balance"/></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" t-if="dashboard.nb_lines_outstanding_pay_account_balance > 0">
|
||||
<div id="dashboard_bank_cash_outstanding_balance" class="col overflow-hidden text-left">
|
||||
<span title="Outstanding Payments/Receipts">Outstanding Payments/Receipts</span>
|
||||
</div>
|
||||
<div class="col-auto text-right">
|
||||
<span><t t-esc="dashboard.outstanding_pay_account_balance"/></span>
|
||||
</div>
|
||||
</div>
|
||||
<t t-if="dashboard.has_at_least_one_statement and dashboard.account_balance != dashboard.last_balance">
|
||||
<div class="row" name="latest_statement">
|
||||
<div class="col overflow-hidden text-left">
|
||||
<span title="Latest Statement">Latest Statement</span>
|
||||
@@ -269,16 +277,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="" id="journal_body_bank_cash_right">
|
||||
<div class="row">
|
||||
<div class="col overflow-hidden text-left">
|
||||
<span title="Balance in General Ledger">Balance in GL</span>
|
||||
</div>
|
||||
<div class="col-auto text-right">
|
||||
<span><t t-esc="dashboard.account_balance"/></span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="dashboard.is_sample_data">
|
||||
<span class="badge badge-warning text-uppercase o_sample_data_label">Sample data</span>
|
||||
|
||||
@@ -409,7 +409,7 @@
|
||||
<field name="invoice_date" optional="show" invisible="context.get('default_move_type') not in ('in_invoice', 'in_refund','in_receipt')" string="Bill Date"/>
|
||||
<field name="invoice_date" optional="show" invisible="context.get('default_move_type') not in ('out_invoice', 'out_refund','out_receipt')" string="Invoice Date"/>
|
||||
<field name="invoice_origin" optional="hide" string="Source Document"/>
|
||||
<field name="invoice_payment_ref" optional="hide" invisible="context.get('default_move_type') in ('out_invoice', 'out_refund','out_receipt')"/>
|
||||
<field name="payment_reference" optional="hide" invisible="context.get('default_move_type') in ('out_invoice', 'out_refund','out_receipt')"/>
|
||||
<field name="ref" optional="hide"/>
|
||||
<field name="invoice_user_id" optional="hide" invisible="context.get('default_move_type') not in ('out_invoice', 'out_refund','out_receipt')" string="Sales Person"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}" optional="show"/>
|
||||
@@ -484,18 +484,19 @@
|
||||
<button name="action_invoice_sent"
|
||||
type="object"
|
||||
string="Send & Print"
|
||||
attrs="{'invisible':['|', '|', ('state', '!=', 'posted'), ('invoice_sent', '=', True), ('move_type', 'not in', ('out_invoice', 'out_refund'))]}"
|
||||
attrs="{'invisible':['|', '|', ('state', '!=', 'posted'), ('is_move_sent', '=', True), ('move_type', 'not in', ('out_invoice', 'out_refund'))]}"
|
||||
class="oe_highlight"
|
||||
groups="account.group_account_invoice"/>
|
||||
<button name="action_invoice_sent"
|
||||
type="object"
|
||||
string="Send & Print"
|
||||
attrs="{'invisible':['|', '|', ('state', '!=', 'posted'), ('invoice_sent', '=', False), ('move_type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund'))]}"
|
||||
attrs="{'invisible':['|', '|', ('state', '!=', 'posted'), ('is_move_sent', '=', False), ('move_type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund'))]}"
|
||||
groups="account.group_account_invoice"/>
|
||||
<!-- Register Payment (only invoices / receipts) -->
|
||||
<button name="action_invoice_register_payment" id="account_invoice_payment_btn"
|
||||
<button name="action_register_payment" id="account_invoice_payment_btn"
|
||||
type="object" class="oe_highlight"
|
||||
attrs="{'invisible': ['|', '|', ('state', '!=', 'posted'), ('payment_state', 'not in', ('not_paid', 'partial')), ('move_type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]}"
|
||||
context="{'dont_redirect_to_payments': True}"
|
||||
string="Register Payment"
|
||||
groups="account.group_account_invoice"/>
|
||||
<!-- Preview (only customer invoices) -->
|
||||
@@ -574,7 +575,7 @@
|
||||
<field name="display_qr_code" invisible="1"/>
|
||||
|
||||
<field name="invoice_has_outstanding" invisible="1"/>
|
||||
<field name="invoice_sent" invisible="1"/>
|
||||
<field name="is_move_sent" invisible="1"/>
|
||||
<field name="invoice_has_matching_suspense_amount" invisible="1"/>
|
||||
<field name="has_reconciled_entries" invisible="1"/>
|
||||
<field name="restrict_mode_hash_table" invisible="1"/>
|
||||
@@ -611,9 +612,9 @@
|
||||
<label for="ref" string="Bill Reference"
|
||||
attrs="{'invisible':[('move_type', 'not in', ('in_invoice', 'in_receipt', 'in_refund'))]}" />
|
||||
<field name="ref" nolabel="1" attrs="{'invisible':[('move_type', 'not in', ('in_invoice', 'in_receipt', 'in_refund'))]}" />
|
||||
<field name="invoice_payment_ref"
|
||||
<field name="payment_reference"
|
||||
attrs="{'invisible': ['|',('move_type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt')), ('state', '=', 'draft')], 'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="invoice_partner_bank_id"
|
||||
<field name="partner_bank_id"
|
||||
context="{'default_partner_id': commercial_partner_id}"
|
||||
domain="[('partner_id', '=', bank_partner_id)]"
|
||||
attrs="{'invisible': [('move_type', 'not in', ('in_invoice', 'in_refund', 'in_receipt'))], 'readonly': [('state', '!=', 'draft')]}"/>
|
||||
@@ -637,7 +638,10 @@
|
||||
attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]}"/>
|
||||
|
||||
<field name="date" string="Accounting Date"
|
||||
attrs="{'invisible': [('move_type', 'in', ('out_invoice', 'out_refund', 'out_receipt'))]}"/>
|
||||
attrs="{
|
||||
'invisible': [('move_type', 'in', ('out_invoice', 'out_refund', 'out_receipt'))],
|
||||
'readonly': [('state', '!=', 'draft')],
|
||||
}"/>
|
||||
|
||||
<!-- Invoice payment terms (only invoices) + due date (only invoices / receipts) -->
|
||||
<label for="invoice_payment_term_id" string="Due Date"
|
||||
@@ -993,11 +997,12 @@
|
||||
<field name="invoice_user_id" domain="[('share', '=', False)]"/>
|
||||
<field name="invoice_origin" string="Source Document" force_save="1"
|
||||
attrs="{'invisible': [('invoice_origin', '=', False)]}"/>
|
||||
<field name="invoice_partner_bank_id"
|
||||
<field name="partner_bank_id"
|
||||
context="{'default_partner_id': commercial_partner_id}"
|
||||
domain="[('partner_id', '=', bank_partner_id)]"
|
||||
attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="qr_code_method" attrs="{'invisible': [('display_qr_code', '=', False)]}"/>
|
||||
<field name="qr_code_method"
|
||||
attrs="{'invisible': [('display_qr_code', '=', False)]}"/>
|
||||
</group>
|
||||
<group string="Accounting"
|
||||
name="accounting_info_group"
|
||||
@@ -1094,7 +1099,7 @@
|
||||
filter_domain="[
|
||||
'|', '|' , '|', '|',
|
||||
('name', 'ilike', self), ('invoice_origin', 'ilike', self),
|
||||
('ref', 'ilike', self), ('invoice_payment_ref', 'ilike', self),
|
||||
('ref', 'ilike', self), ('payment_reference', 'ilike', self),
|
||||
('partner_id', 'child_of', self)]"/>
|
||||
<field name="journal_id"/>
|
||||
<field name="partner_id" operator="child_of"/>
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- VIEWS -->
|
||||
|
||||
<record id="view_account_payment_tree" model="ir.ui.view">
|
||||
<field name="name">account.payment.tree</field>
|
||||
<field name="model">account.payment</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree decoration-info="state == 'draft'" decoration-muted="state in ['reconciled', 'cancelled']" edit="false">
|
||||
<field name="payment_date"/>
|
||||
<tree decoration-info="state == 'draft'" decoration-muted="state == 'cancelled'" edit="false">
|
||||
<field name="date"/>
|
||||
<field name="name"/>
|
||||
<field name="journal_id"/>
|
||||
<field name="payment_method_id"/>
|
||||
@@ -15,8 +17,7 @@
|
||||
<field name="amount" sum="Amount"/>
|
||||
<field name="state"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="partner_type" invisible="1"/>
|
||||
<field name="currency_id" groups="base.group_multi_currency"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -40,7 +41,7 @@
|
||||
<kanban class="o_kanban_mobile">
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="payment_date"/>
|
||||
<field name="date"/>
|
||||
<field name="state"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
@@ -50,7 +51,7 @@
|
||||
<strong><span><field name="name"/></span></strong>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<strong><i class="fa fa-clock-o" role="img" aria-label="Date" title="Date"/> <t t-esc="record.payment_date.value"/></strong>
|
||||
<strong><i class="fa fa-clock-o" role="img" aria-label="Date" title="Date"/> <t t-esc="record.date.value"/></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -82,31 +83,36 @@
|
||||
<field name="arch" type="xml">
|
||||
<search string="Payments">
|
||||
<field name="name" string="Payment"
|
||||
filter_domain="['|', '|', ('name', 'ilike', self), ('partner_id', 'ilike', self), ('communication', 'ilike', self)]"/>
|
||||
filter_domain="['|', '|', ('name', 'ilike', self), ('partner_id', 'ilike', self), ('ref', 'ilike', self)]"/>
|
||||
<field name="partner_id" string="Customer/Vendor"/>
|
||||
<field name="journal_id"/>
|
||||
<field name="is_internal_transfer"/>
|
||||
<separator/>
|
||||
<filter string="Customer Payments" name="inbound_filter" domain="[('partner_type', '=', 'customer')]"/>
|
||||
<filter string="Vendor Payments" name="outbound_filter" domain="[('partner_type', '=', 'supplier')]"/>
|
||||
<filter string="Customer Payments"
|
||||
name="inbound_filter"
|
||||
domain="[('partner_type', '=', 'customer'), ('is_internal_transfer', '=', False)]"/>
|
||||
<filter string="Vendor Payments"
|
||||
name="outbound_filter"
|
||||
domain="[('partner_type', '=', 'supplier'), ('is_internal_transfer', '=', False)]"/>
|
||||
<filter string="Transfers" name="transfers_filter" domain="[('is_internal_transfer', '=', True)]"/>
|
||||
<separator/>
|
||||
<filter string="Draft" name="state_draft" domain="[('state', '=', 'draft')]"/>
|
||||
<filter string="Posted" name="state_posted" domain="[('state', '=', 'posted')]"/>
|
||||
<filter string="Sent" name="state_sent" domain="[('state', '=', 'sent')]"/>
|
||||
<filter string="Reconciled" name="reconciled" domain="[('state', '=', 'reconciled')]"/>
|
||||
<separator/>
|
||||
<filter string="Payment Date" name="payment_date" date="payment_date"/>
|
||||
<filter string="Sent" name="state_sent" domain="[('is_move_sent', '=', True)]"/>
|
||||
<filter string="Bank Matched" name="matched" domain="[('is_matched', '=', True)]"/>
|
||||
<filter string="Reconciled" name="reconciled" domain="[('is_reconciled', '=', True)]"/>
|
||||
<separator/>
|
||||
<filter string="Payment Date" name="date" date="date"/>
|
||||
<separator/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<filter string="Customer/Vendor" name="partner" domain="[]" context="{'group_by': 'partner_id'}"/>
|
||||
<filter string="Journal" name="journal" domain="[]" context="{'group_by': 'journal_id'}"/>
|
||||
<filter string="Payment Method" name="paymentmethod" domain="[]" context="{'group_by': 'payment_method_id'}"/>
|
||||
<filter string="Status" name="state" domain="[]" context="{'group_by': 'state'}"/>
|
||||
<filter string="Payment Date" name="groupby_payment_date" domain="[]" context="{'group_by': 'payment_date'}"/>
|
||||
<filter string="Payment Date" name="groupby_date" domain="[]" context="{'group_by': 'date'}"/>
|
||||
<filter string="Currency" name="currency" domain="[]" context="{'group_by': 'currency_id'}" groups="base.group_multi_currency"/>
|
||||
<!-- company_id is not stored (cdca5d4751a979f6b694f02d88160843f81a77f5). But we can't break structure-->
|
||||
<filter string="Company" name="company" domain="[]" context="{'group_by': 'company_id'}" groups="base.group_multi_company" invisible="1"/>
|
||||
<filter string="Company" name="company" domain="[]" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
|
||||
<separator/>
|
||||
<filter invisible="1" string="Late Activities" name="activities_overdue"
|
||||
domain="[('activity_ids.date_deadline', '<', context_today().strftime('%Y-%m-%d'))]"
|
||||
@@ -125,28 +131,50 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Register Payment">
|
||||
<header>
|
||||
<button name="post" class="oe_highlight" states="draft" string="Confirm" type="object"/>
|
||||
<button name="action_draft" class="btn btn-secondary" attrs="{'invisible': [('state', '=', 'draft')]}" string="Reset To Draft" type="object" groups="account.group_account_invoice"/>
|
||||
<button name="cancel" attrs="{'invisible': [('state', 'in', ('cancelled', 'posted', 'invoicing_legacy'))]}" string="Cancel" type="object"/>
|
||||
<button name="mark_as_sent" string="Mark as Sent" type="object" attrs="{'invisible': ['|', ('state', '!=', 'posted'), ('payment_method_code', '!=', 'manual')]}"/>
|
||||
<button name="unmark_as_sent" string="Unmark as Sent" type="object" attrs="{'invisible': ['|', ('state', '!=', 'sent'), ('payment_method_code', '!=', 'manual')]}"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,posted,reconciled,cancelled"/>
|
||||
<button name="action_post" string="Confirm" type="object" class="oe_highlight"
|
||||
attrs="{'invisible': [('state', '!=', 'draft')]}"/>
|
||||
<button name="action_draft" string="Reset To Draft" type="object" class="btn btn-secondary"
|
||||
attrs="{'invisible': [('state', 'not in', ('posted', 'cancel'))]}"
|
||||
groups="account.group_account_invoice"/>
|
||||
<button name="action_cancel" string="Cancel" type="object"
|
||||
attrs="{'invisible': [('state', '!=', 'draft')]}"/>
|
||||
<button name="mark_as_sent" string="Mark as Sent" type="object"
|
||||
attrs="{'invisible': ['|', '|', ('state', '!=', 'posted'), ('is_move_sent', '=', True), ('payment_method_code', '!=', 'manual')]}"/>
|
||||
<button name="unmark_as_sent" string="Unmark as Sent" type="object"
|
||||
attrs="{'invisible': ['|', '|', ('state', '!=', 'posted'), ('is_move_sent', '=', False), ('payment_method_code', '!=', 'manual')]}"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,posted"/>
|
||||
</header>
|
||||
|
||||
<sheet>
|
||||
<!-- Invisible fields -->
|
||||
<field name="id" invisible="1"/>
|
||||
<field name="is_move_sent" invisible="1"/>
|
||||
<field name="is_reconciled" invisible="1"/>
|
||||
<field name="payment_method_code" invisible="1"/>
|
||||
<field name="show_partner_bank_account" invisible="1"/>
|
||||
<field name="require_partner_bank_account" invisible="1"/>
|
||||
<field name="hide_payment_method" invisible="1"/>
|
||||
<field name="available_payment_method_ids" invisible="1"/>
|
||||
<field name="suitable_journal_ids" invisible="1"/>
|
||||
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" name="button_journal_entries"
|
||||
string="Journal Items" type="object"
|
||||
groups="account.group_account_readonly"
|
||||
attrs="{'invisible':['|', ('move_line_ids','=',[]), ('state', '=', 'invoicing_legacy')]}" icon="fa-bars"/>
|
||||
<field name="move_line_ids" invisible="1"/>
|
||||
<button class="oe_stat_button" name="button_invoices" type="object" icon="fa-bars" attrs="{'invisible':[('reconciled_invoices_count','=', 0)]}" >
|
||||
<!-- Invoice stat button -->
|
||||
<button name="button_open_invoices" type="object"
|
||||
class="oe_stat_button" icon="fa-bars"
|
||||
attrs="{'invisible': [('reconciled_invoices_count','=', 0)]}">
|
||||
<field name="reconciled_invoices_count"/>
|
||||
<span attrs="{'invisible':[('reconciled_invoices_count','>',1)]}">Invoice</span>
|
||||
<span attrs="{'invisible':[('reconciled_invoices_count','<=',1)]}">Invoices</span>
|
||||
<span attrs="{'invisible': [('reconciled_invoices_count','>', 1)]}">Invoice</span>
|
||||
<span attrs="{'invisible': [('reconciled_invoices_count','<=', 1)]}">Invoices</span>
|
||||
</button>
|
||||
|
||||
<!-- Statement stat button -->
|
||||
<button name="button_open_statements" type="object"
|
||||
class="oe_stat_button" icon="fa-bars"
|
||||
attrs="{'invisible': [('reconciled_statements_count','=', 0)]}">
|
||||
<field name="reconciled_statements_count"/>
|
||||
<span attrs="{'invisible': [('reconciled_statements_count','>', 1)]}">Statement</span>
|
||||
<span attrs="{'invisible': [('reconciled_statements_count','<=', 1)]}">Statements</span>
|
||||
</button>
|
||||
<field name="has_invoices" invisible="1"/>
|
||||
<field name="move_reconciled" invisible="1"/>
|
||||
</div>
|
||||
|
||||
<widget name="web_ribbon" text="Invoicing App Legacy"
|
||||
@@ -155,54 +183,58 @@
|
||||
tooltip="This payment has been generated through the Invoicing app, before installing Accounting. It has been disabled by the 'Invoicing Switch Threshold Date' setting so that it does not impact your accounting."
|
||||
/>
|
||||
|
||||
<field name="id" invisible="1"/>
|
||||
<div class="oe_title" attrs="{'invisible': [('state', '=', 'draft')]}">
|
||||
<h1><field name="name"/></h1>
|
||||
<h1><field name="name" attrs="{'readonly': [('state', '!=', 'draft')]}"/></h1>
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<field name="invoice_ids" invisible="1"/>
|
||||
<field name="is_internal_transfer" invisible="1"/>
|
||||
<group name="partner_group" invisible="context.get('active_model') == 'account.move'">
|
||||
<field name="payment_type" widget="radio"/>
|
||||
<field name="partner_type" widget="selection" attrs="{'required': [('state', '=', 'draft')], 'invisible': [('is_internal_transfer', '=', True)]}"/>
|
||||
<field name="partner_id" attrs="{'required': [('state', '=', 'draft')]}" context="{'default_is_company': True}"/>
|
||||
<field name="company_id" groups="base.group_multi_company" required="1"/>
|
||||
<group name="group1">
|
||||
<field name="payment_type" widget="radio"
|
||||
attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="partner_type" widget="selection"
|
||||
attrs="{'readonly': [('state', '!=', 'draft')], 'invisible': [('is_internal_transfer', '=', True)]}"/>
|
||||
<field name="partner_id" context="{'default_is_company': True}"
|
||||
attrs="{'readonly': ['|', ('state', '!=', 'draft'), ('is_internal_transfer', '=', True)]}"/>
|
||||
<field name="destination_account_id"
|
||||
attrs="{'readonly': ['|', ('state', '!=', 'draft'), ('is_internal_transfer', '=', True)]}"/>
|
||||
<field name="is_internal_transfer"
|
||||
attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="amount" invisible="1"/>
|
||||
<field name="_suitable_journal_ids" invisible="1"/>
|
||||
<field name="journal_id" widget="selection" attrs="{'invisible': [('amount', '=', 0), ('state', '!=', 'draft')]}"/>
|
||||
<field name="hide_payment_method" invisible="1"/>
|
||||
<field name="_payment_methods" invisible="1"/>
|
||||
<field name="payment_method_id" widget="radio" attrs="{'invisible': [('hide_payment_method', '=', True)]}"/>
|
||||
<field name="possible_bank_partner_ids" invisible="1"/>
|
||||
<field name="partner_bank_account_id" attrs="{'invisible': [('show_partner_bank_account', '!=', True)], 'required': [('require_partner_bank_account', '=', True)]}" context="{'default_partner_id': partner_id}"/>
|
||||
<field name="show_partner_bank_account" invisible="1"/>
|
||||
<field name="require_partner_bank_account" invisible="1"/>
|
||||
<field name="payment_method_code" invisible="1"/>
|
||||
<group name="group2">
|
||||
<field name="journal_id"
|
||||
widget="selection"
|
||||
domain="[('type', 'in', ('bank', 'cash'))]"
|
||||
attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="payment_method_id" widget="radio"
|
||||
required="1"
|
||||
attrs="{'readonly': [('state', '!=', 'draft')], 'invisible': [('hide_payment_method', '=', True)]}"/>
|
||||
<field name="partner_bank_id" context="{'default_partner_id': partner_id}"
|
||||
attrs="{'invisible': [('show_partner_bank_account', '=', False)], 'required': [('require_partner_bank_account', '=', True)]}"/>
|
||||
|
||||
<!-- /!\ Required due to the _inherits but must not be required when saving a new
|
||||
record. Using 'required': [('id', '!=', False)] is not working due to the groups set on
|
||||
the field.
|
||||
-->
|
||||
<field name="move_id"
|
||||
required="0"
|
||||
attrs="{'invisible': [('id', '=', False)]}"
|
||||
groups="account.group_account_readonly"/>
|
||||
</group>
|
||||
<group name="right_column">
|
||||
<group name="group3">
|
||||
<label for="amount"/>
|
||||
<div name="amount_div" class="o_row">
|
||||
<field name="amount"/>
|
||||
<field name="currency_id" options="{'no_create': True, 'no_open': True}" groups="base.group_multi_currency"/>
|
||||
</div>
|
||||
<field name="payment_date"/>
|
||||
<field name="communication"/>
|
||||
</group>
|
||||
<group name="payment_difference" attrs="{'invisible': [('payment_difference', '=', 0.0)]}" groups="account.group_account_readonly">
|
||||
<label for="payment_difference"/>
|
||||
<div>
|
||||
<field name="payment_difference"/>
|
||||
<field name="payment_difference_handling" widget="radio" nolabel="1"/>
|
||||
<div attrs="{'invisible': [('payment_difference_handling','=','open')]}">
|
||||
<label for="writeoff_account_id" class="oe_edit_only" string="Post Difference In"/>
|
||||
<field name="writeoff_account_id" options="{'no_create': True}" string="Post Difference In" attrs="{'required': [('payment_difference_handling', '=', 'reconcile'), ('payment_difference', '!=', 0.0)]}"/>
|
||||
<label for="writeoff_label" class="oe_edit_only" string="Label"/>
|
||||
<field name="writeoff_label" attrs="{'required': [('payment_difference_handling', '=', 'reconcile'), ('payment_difference', '!=', 0.0)]}"/>
|
||||
</div>
|
||||
<field name="amount"
|
||||
attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="currency_id"
|
||||
options="{'no_create': True, 'no_open': True}"
|
||||
required="1"
|
||||
attrs="{'readonly': [('state', '!=', 'draft')]}"
|
||||
groups="base.group_multi_currency"/>
|
||||
</div>
|
||||
<field name="date"
|
||||
attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="ref" string="Memo"/>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
@@ -223,31 +255,6 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_payment_form_multi" model="ir.ui.view">
|
||||
<field name="name">account.payment.form.multi</field>
|
||||
<field name="model">account.payment.register</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Register Payment">
|
||||
<field name="invoice_company_id" invisible="1"/>
|
||||
<field name="available_payment_methods" invisible="1"/>
|
||||
<group>
|
||||
<group>
|
||||
<field name="payment_method_id" widget="radio"/>
|
||||
<field name="group_payment"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="journal_id" widget="selection"/>
|
||||
<field name="payment_date"/>
|
||||
</group>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Create Payment" name="create_payments" type="object" class="oe_highlight"/>
|
||||
<button string="Cancel" class="btn btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_payment_graph" model="ir.ui.view">
|
||||
<field name="name">account.payment.graph</field>
|
||||
<field name="model">account.payment</field>
|
||||
@@ -260,6 +267,8 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ACTIONS -->
|
||||
|
||||
<record id="action_account_payments" model="ir.actions.act_window">
|
||||
<field name="name">Payments</field>
|
||||
<field name="res_model">account.payment</field>
|
||||
@@ -268,7 +277,7 @@
|
||||
'default_payment_type': 'inbound',
|
||||
'default_partner_type': 'customer',
|
||||
'search_default_inbound_filter': 1,
|
||||
'res_partner_search_mode': 'customer',
|
||||
'default_move_journal_types': ('bank', 'cash'),
|
||||
}</field>
|
||||
<field name="view_id" ref="view_account_payment_tree"/>
|
||||
<field name="help" type="html">
|
||||
@@ -288,7 +297,7 @@
|
||||
'default_payment_type': 'outbound',
|
||||
'default_partner_type': 'supplier',
|
||||
'search_default_outbound_filter': 1,
|
||||
'res_partner_search_mode': 'supplier',
|
||||
'default_move_journal_types': ('bank', 'cash'),
|
||||
}</field>
|
||||
<field name="view_id" ref="view_account_supplier_payment_tree"/>
|
||||
<field name="help" type="html">
|
||||
@@ -316,33 +325,16 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_payment_invoice_form" model="ir.ui.view">
|
||||
<field name="name">account.payment.invoice.form</field>
|
||||
<field name="model">account.payment</field>
|
||||
<field name="inherit_id" ref="view_account_payment_form"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//header" position='replace'></xpath>
|
||||
<xpath expr="//div[@name='button_box']" position='replace'></xpath>
|
||||
<xpath expr="//sheet" position='after'>
|
||||
<footer>
|
||||
<field name="state" invisible="1"/>
|
||||
<button string='Validate' name="post" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_invoice_from_list" model="ir.actions.server">
|
||||
<field name="name">Register Payment</field>
|
||||
<field name="groups_id" eval="[(4, ref('account.group_account_invoice'))]"/>
|
||||
<field name="model_id" ref="account.model_account_payment"/>
|
||||
<field name="model_id" ref="account.model_account_move"/>
|
||||
<field name="binding_model_id" ref="account.model_account_move"/>
|
||||
<field name="binding_view_types">list</field>
|
||||
<field name="state">code</field>
|
||||
<field name="code">
|
||||
action = model.action_register_payment()
|
||||
if records:
|
||||
action = records.action_register_payment()
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -356,7 +348,7 @@
|
||||
<field name="binding_model_id" ref="account.model_account_payment"/>
|
||||
<field name="binding_view_types">list</field>
|
||||
<field name="code">
|
||||
records.post()
|
||||
records.action_post()
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -289,8 +289,22 @@
|
||||
<field name="refund_sequence" attrs="{'invisible': [('type', 'not in', ['sale', 'purchase'])]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="default_debit_account_id" options="{'no_create': True}" groups="account.group_account_readonly"/>
|
||||
<field name="default_credit_account_id" options="{'no_create': True}" groups="account.group_account_readonly"/>
|
||||
<field name="id" invisible="1"/>
|
||||
<field name="default_debit_account_id" options="{'no_create': True}"
|
||||
attrs="{'required': [('id', '!=', False), ('type', 'in', ('bank', 'cash'))]}"
|
||||
groups="account.group_account_readonly"/>
|
||||
<field name="default_credit_account_id" options="{'no_create': True}"
|
||||
attrs="{'required': [('id', '!=', False), ('type', 'in', ('bank', 'cash'))]}"
|
||||
groups="account.group_account_readonly"/>
|
||||
<field name="payment_debit_account_id" options="{'no_create': True}"
|
||||
attrs="{'required': [('id', '!=', False), ('type', 'in', ('bank', 'cash'))], 'invisible': [('type', 'not in', ('bank', 'cash'))]}"
|
||||
groups="account.group_account_readonly"/>
|
||||
<field name="payment_credit_account_id" options="{'no_create': True}"
|
||||
attrs="{'required': [('id', '!=', False), ('type', 'in', ('bank', 'cash'))], 'invisible': [('type', 'not in', ('bank', 'cash'))]}"
|
||||
groups="account.group_account_readonly"/>
|
||||
<field name="suspense_account_id" options="{'no_create': True}"
|
||||
attrs="{'required': [('id', '!=', False), ('type', 'in', ('bank', 'cash'))], 'invisible': [('type', 'not in', ('bank', 'cash'))]}"
|
||||
groups="account.group_account_readonly"/>
|
||||
<field name="currency_id" options="{'no_create': True}" groups="base.group_multi_currency"/>
|
||||
</group>
|
||||
</group>
|
||||
@@ -313,16 +327,17 @@
|
||||
<a type='action' name='%(action_open_settings)d' class="btn btn-link" role="button"><i class="fa fa-fw o_button_icon fa-arrow-right"/> Configure Email Servers</a>
|
||||
</div>
|
||||
</group>
|
||||
<group string="Posting" name="column_posting" groups="account.group_account_readonly" attrs="{'invisible': [('type', 'not in', ['bank', 'cash'])]}">
|
||||
<field name="profit_account_id" options="{'no_create': True}" attrs="{'invisible': [('type', '!=', 'cash')]}"/>
|
||||
<field name="loss_account_id" options="{'no_create': True}" attrs="{'invisible': [('type', '!=', 'cash')]}"/>
|
||||
<field name="post_at" widget="radio" attrs="{'invisible': [('type', 'not in', ['bank', 'cash'])]}"/>
|
||||
<group string="Posting" name="column_posting" groups="account.group_account_readonly" attrs="{'invisible': [('type', '!=', 'cash')]}">
|
||||
<field name="profit_account_id" options="{'no_create': True}"/>
|
||||
<field name="loss_account_id" options="{'no_create': True}"/>
|
||||
</group>
|
||||
<group string="Control-Access" groups="account.group_account_manager">
|
||||
<div class="text-muted" colspan="2">Keep empty for no control</div>
|
||||
<field name="type_control_ids" widget="many2many_tags"/>
|
||||
<field name="account_control_ids" widget="many2many_tags"/>
|
||||
<field name="restrict_mode_hash_table" groups="account.group_account_readonly"/>
|
||||
<field name="restrict_mode_hash_table"
|
||||
attrs="{'invisible': [('type', 'in', ('bank', 'cash'))]}"
|
||||
groups="account.group_account_readonly"/>
|
||||
</group>
|
||||
<group string="Payment Method Types" attrs="{'invisible': [('type', 'not in', ['bank', 'cash'])]}">
|
||||
<field name="inbound_payment_method_ids" widget="many2many_checkboxes"/>
|
||||
@@ -572,23 +587,31 @@ action = model.setting_init_bank_account_action()
|
||||
<field name="arch" type="xml">
|
||||
<form string="Bank Statement">
|
||||
<header>
|
||||
<field name="all_lines_reconciled" invisible="1" />
|
||||
<button name="check_confirm_bank" string="Validate" type="object" class="oe_highlight" attrs="{'invisible':['|','|',('all_lines_reconciled','=',False),('line_ids','=',[]),('state', '!=', 'open')]}"/>
|
||||
<button name="button_reopen" states="confirm" string="Reset to New" type="object"/>
|
||||
<button string="Post" states="open" class="oe_highlight"
|
||||
name="button_post" type="object"/>
|
||||
<button string="Validate" class="oe_highlight"
|
||||
name="button_validate_or_action" type="object"
|
||||
attrs="{'invisible':['|', '|', ('all_lines_reconciled', '=', False), ('line_ids','=',[]), ('state', '!=', 'posted')]}"/>
|
||||
<button string="Reset to New"
|
||||
name="button_reopen" type="object"
|
||||
attrs="{'invisible': [('state', '=', 'open')]}"/>
|
||||
<button name="%(action_cash_box_out)d" string="Take Money In/Out" type="action" class="oe_highlight" attrs="{'invisible': [('journal_type','!=','cash')]}"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="open,confirm"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="open,posted,confirm"/>
|
||||
</header>
|
||||
<div class="alert alert-warning" role="alert" attrs="{'invisible': ['|', ('previous_statement_id', '=', False), ('is_valid_balance_start', '=', True)]}">
|
||||
The starting balance <b>doesn't match</b> with the ending balance of the previous bank statement.
|
||||
</div>
|
||||
<sheet>
|
||||
<!-- Invisible fields -->
|
||||
<field name="all_lines_reconciled" invisible="1" />
|
||||
<field name="move_line_count" invisible="1"/>
|
||||
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" name="button_journal_entries"
|
||||
string="Journal Entries" type="object"
|
||||
attrs="{'invisible':[('move_line_count','=', 0)]}" icon="fa-bars" />
|
||||
<field name="move_line_count" invisible="1"/>
|
||||
|
||||
attrs="{'invisible':[('move_line_count','=', 0)]}" icon="fa-bars"/>
|
||||
</div>
|
||||
|
||||
<div class="oe_title oe_inline">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
@@ -601,19 +624,20 @@ action = model.setting_init_bank_account_action()
|
||||
<field name="cashbox_start_id" invisible="1"/>
|
||||
<field name="cashbox_end_id" invisible="1"/>
|
||||
<field name="journal_id" domain="[('type', '=', journal_type)]" attrs="{'readonly': [('move_line_count','!=', 0)]}" widget="selection"/>
|
||||
<field name="date" options="{'datepicker': {'warn_future': true}}" />
|
||||
<field name="accounting_date" groups="base.group_no_one"/>
|
||||
<field name="date"
|
||||
attrs="{'readonly': [('state', '!=', 'open')]}"
|
||||
options="{'datepicker': {'warn_future': true}}"/>
|
||||
<field name='company_id' options="{'no_create': True}" groups="base.group_multi_company" />
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</group><group>
|
||||
<label for="balance_start"/>
|
||||
<div>
|
||||
<field class="oe_inline" name="balance_start"/>
|
||||
<field name="balance_start" class="oe_inline" attrs="{'readonly': [('state', '!=', 'open')]}"/>
|
||||
<button name="open_cashbox_id" attrs="{'invisible': ['|',('state','!=','open'),('journal_type','!=','cash')]}" string="→ Count" type="object" class="oe_edit_only oe_link oe_inline" context="{'balance':'start'}"/>
|
||||
</div>
|
||||
<label for="balance_end_real"/>
|
||||
<div>
|
||||
<field class="oe_inline" name="balance_end_real"/>
|
||||
<field name="balance_end_real" class="oe_inline" attrs="{'readonly': [('state', '!=', 'open')]}"/>
|
||||
<button name="open_cashbox_id" attrs="{'invisible': ['|',('state','!=','open'),('journal_type','!=','cash')]}" string="→ Count" type="object" class="oe_edit_only oe_link oe_inline" context="{'balance':'close'}"/>
|
||||
</div>
|
||||
</group>
|
||||
@@ -621,26 +645,44 @@ action = model.setting_init_bank_account_action()
|
||||
|
||||
<notebook>
|
||||
<page string="Transactions" name="statement_line_ids">
|
||||
<field name="line_ids" context="{'date':date}">
|
||||
<tree editable="bottom" string="Statement lines" decoration-muted="journal_entry_ids" limit="500">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="journal_entry_ids" invisible="1"/>
|
||||
<field name="date" attrs="{'readonly' : [('journal_entry_ids', '!=', [])] }"/>
|
||||
<field name="name" attrs="{'readonly' : [('journal_entry_ids', '!=', [])] }"/>
|
||||
<field name="partner_id" domain="['|',('parent_id','=',False),('is_company','=',True)]"
|
||||
attrs="{'readonly' : [('journal_entry_ids', '!=', [])] }"/>
|
||||
<field name="ref" attrs="{'readonly' : [('journal_entry_ids', '!=', [])] }" groups="base.group_no_one"/>
|
||||
<field name="transaction_type" attrs="{'readonly' : [('journal_entry_ids', '!=', [])] }" optional="hidden"/>
|
||||
<field name="note" optional="hidden"/>
|
||||
<field name="amount" attrs="{'readonly' : [('journal_entry_ids', '!=', [])] }"/>
|
||||
<field name="journal_currency_id" invisible="1"/>
|
||||
<field name="bank_account_id" invisible="1" />
|
||||
<field name="account_number" optional="hidden"/>
|
||||
<field name="amount_currency" optional="hidden"/>
|
||||
<field name="currency_id" optional="hidden"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="line_ids"
|
||||
attrs="{'readonly': [('state', '!=', 'open')]}"
|
||||
context="{'default_date': date, 'default_journal_id': journal_id}">
|
||||
<tree string="Statement lines"
|
||||
editable="bottom"
|
||||
decoration-muted="is_reconciled"
|
||||
limit="500">
|
||||
|
||||
<!-- Invisible fields -->
|
||||
<field name="company_id" invisible="1"/>
|
||||
<button name="button_cancel_reconciliation" attrs="{'invisible': ['|',('journal_entry_ids', '=', []), ('state', '=', 'confirm')]}" string="Revert reconciliation" type="object" icon="fa-undo"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="is_reconciled" invisible="1"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="partner_bank_id" invisible="1"/>
|
||||
|
||||
<!-- Visible fields -->
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="date"
|
||||
attrs="{'readonly': [('parent.state', '!=', 'open')]}"/>
|
||||
<field name="payment_ref"/>
|
||||
<field name="partner_id"
|
||||
attrs="{'readonly': [('parent.state', '!=', 'open')]}"
|
||||
domain="['|', ('parent_id','=', False), ('is_company','=',True)]"/>
|
||||
<field name="ref" optional="hidden"/>
|
||||
<field name="transaction_type" optional="hidden"/>
|
||||
<field name="narration" string="Notes" optional="hidden"/>
|
||||
<field name="amount"
|
||||
attrs="{'readonly': [('parent.state', '!=', 'open')]}"/>
|
||||
<field name="amount_currency" optional="hidden" groups="base.group_multi_currency"
|
||||
attrs="{'readonly': [('parent.state', '!=', 'open')]}"/>
|
||||
<field name="account_number" optional="hidden"/>
|
||||
<field name="foreign_currency_id" optional="hidden" groups="base.group_multi_currency"
|
||||
attrs="{'readonly': [('parent.state', '!=', 'open')]}"/>
|
||||
|
||||
<!-- Buttons -->
|
||||
<button name="button_undo_reconciliation" type="object"
|
||||
attrs="{'invisible': [('is_reconciled', '=', False)]}"
|
||||
string="Revert reconciliation" icon="fa-undo"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
@@ -700,12 +742,12 @@ action = model.setting_init_bank_account_action()
|
||||
<field name="model">account.bank.statement.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Bank Statements Line">
|
||||
<field name="name"/>
|
||||
<field name="payment_ref"/>
|
||||
<field name="statement_id"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="date"/>
|
||||
<field name="journal_id" domain="[('type', 'in', ('bank', 'cash'))]" />
|
||||
<field name="note"/>
|
||||
<field name="narration" string="Notes"/>
|
||||
<field name="transaction_type"/>
|
||||
</search>
|
||||
</field>
|
||||
@@ -716,21 +758,27 @@ action = model.setting_init_bank_account_action()
|
||||
<field name="model">account.bank.statement.line</field>
|
||||
<field name="priority">8</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Statement Line" create="false">
|
||||
<group col="4">
|
||||
<field name="statement_id"/>
|
||||
<field name="date"/>
|
||||
<field name="name"/>
|
||||
<field name="ref"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="amount"/>
|
||||
<field name="journal_currency_id" invisible="1"/>
|
||||
<field name="sequence"/>
|
||||
<field name="note"/>
|
||||
<field name="transaction_type"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
</group>
|
||||
</form>
|
||||
<form string="Statement Line" create="false">
|
||||
<sheet>
|
||||
<group col="4">
|
||||
<!-- Invisible fields -->
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
|
||||
<!-- Displayed fields -->
|
||||
<field name="statement_id"/>
|
||||
<field name="date"/>
|
||||
<field name="payment_ref"/>
|
||||
<field name="ref"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="amount"/>
|
||||
<field name="sequence"/>
|
||||
<field name="narration" string="Notes"/>
|
||||
<field name="transaction_type"/>
|
||||
<field name="move_id"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_bank_statement_line_tree" model="ir.ui.view">
|
||||
@@ -739,17 +787,21 @@ action = model.setting_init_bank_account_action()
|
||||
<field name="priority">8</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Statement lines" create="false">
|
||||
<!-- Invisible fields -->
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="sequence" readonly="1" invisible="1"/>
|
||||
<field name="statement_id" />
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="journal_id" invisible="1" />
|
||||
|
||||
<!-- Displayed fields -->
|
||||
<field name="statement_id" />
|
||||
<field name="date"/>
|
||||
<field name="name"/>
|
||||
<field name="payment_ref"/>
|
||||
<field name="ref" groups="base.group_no_one"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="amount"/>
|
||||
<field name="transaction_type" optional="hidden"/>
|
||||
<field name="note" optional="hidden"/>
|
||||
<field name="journal_currency_id" invisible="1"/>
|
||||
<field name="narration" optional="hidden"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -766,7 +818,7 @@ action = model.setting_init_bank_account_action()
|
||||
A bank statement line is a financial transaction on a bank account
|
||||
</p><p>
|
||||
Odoo allows you to reconcile a statement line directly with
|
||||
the related sale or puchase invoice(s).
|
||||
the related sale or purchase invoice(s).
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -161,8 +161,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p t-if="o.move_type in ('out_invoice', 'in_refund') and o.invoice_payment_ref">
|
||||
Please use the following communication for your payment : <b><span t-field="o.invoice_payment_ref"/></b>
|
||||
<p t-if="o.move_type in ('out_invoice', 'in_refund') and o.payment_reference">
|
||||
Please use the following communication for your payment : <b><span t-field="o.payment_reference"/></b>
|
||||
</p>
|
||||
<p t-if="o.invoice_payment_term_id" name="payment_term">
|
||||
<span t-field="o.invoice_payment_term_id.note"/>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<div class="page">
|
||||
<h3><strong>Payment Receipt: <span t-field="o.name"/></strong></h3>
|
||||
<div class="row mt64">
|
||||
<div class="col-6" t-if="o.payment_date">
|
||||
<strong>Payment Date: </strong> <span t-field="o.payment_date"/>
|
||||
<div class="col-6" t-if="o.date">
|
||||
<strong>Payment Date: </strong> <span t-field="o.date"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -27,8 +27,8 @@
|
||||
<div class="col-6" t-if="o.amount">
|
||||
<strong>Payment Amount: </strong><span t-field="o.amount" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</div>
|
||||
<div class="col-6" t-if="o.communication">
|
||||
<strong>Memo: </strong><span t-field="o.communication"/>
|
||||
<div class="col-6" t-if="o.ref">
|
||||
<strong>Memo: </strong><span t-field="o.ref"/>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-sm">
|
||||
@@ -43,12 +43,14 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="o.reconciled_invoice_ids" t-as="inv">
|
||||
<tr t-foreach="o.move_id._get_reconciled_invoices_partials()" t-as="rec">
|
||||
<t t-set="amount" t-value="rec[0]"/>
|
||||
<t t-set="inv" t-value="rec[2].move_id"/>
|
||||
<td><span t-field="inv.invoice_date"/></td>
|
||||
<td><span t-field="inv.name"/></td>
|
||||
<td><span t-field="inv.ref"/></td>
|
||||
<td class="text-right"><span t-field="inv.amount_total"/></td>
|
||||
<td class="text-right"><span t-esc="o._get_invoice_payment_amount(inv)" t-options="{'widget': 'monetary', 'display_currency': inv.currency_id}"/></td>
|
||||
<td class="text-right"><span t-esc="amount" t-options="{'widget': 'monetary', 'display_currency': inv.currency_id}"/></td>
|
||||
<td class="text-right"><span t-field="inv.amount_residual"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<h5>
|
||||
<strong>
|
||||
<span t-field="o.journal_id"/>
|
||||
<t t-if="o.journal_id.bank_account_id"> -
|
||||
<t t-if="o.journal_id.bank_account_id"> -
|
||||
<span t-field="o.journal_id.bank_account_id"/>
|
||||
</t>
|
||||
<t t-if="o.journal_id.code"> -
|
||||
@@ -101,9 +101,9 @@
|
||||
</td>
|
||||
<td class="p-0">
|
||||
<span class="d-block font-weight-bold" t-if="line.partner_id" t-field="line.partner_id"/>
|
||||
<span class="d-block" t-if="line.bank_account_id" t-field="line.bank_account_id"/>
|
||||
<span class="d-block" t-if="line.name" t-field="line.name"/>
|
||||
<span class="d-block" t-if="line.note" t-field="line.note"/>
|
||||
<span class="d-block" t-if="line.partner_bank_id" t-field="line.partner_bank_id"/>
|
||||
<span class="d-block" t-if="line.payment_ref" t-field="line.payment_ref"/>
|
||||
<span class="d-block" t-if="line.narration" t-field="line.narration"/>
|
||||
</td>
|
||||
<td class="text-right p-0">
|
||||
<span class="d-block font-weight-bold" t-field="line.amount"/>
|
||||
|
||||
@@ -432,16 +432,6 @@
|
||||
<t groups="account.group_account_user">
|
||||
<h2>Bank & Cash</h2>
|
||||
<div class="row mt16 o_settings_container" id="bank_cash">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane"/>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="account_bank_reconciliation_start"/>
|
||||
<div class="text-muted">
|
||||
The payments which have not been matched with a bank statement will not be shown in bank reconciliation data if they were made before this date
|
||||
</div>
|
||||
<field name="account_bank_reconciliation_start"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 o_setting_box"
|
||||
id="account_yodlee"
|
||||
title="Get your bank statements automatically imported every 4 hours, or in one-click, using Yodlee and Plaid services. Once installed, set “Bank Feeds” to “Bank Synchronization” in bank account settings. Then, click “Configure” on the online account to enter your bank credentials.">
|
||||
|
||||
@@ -16,3 +16,4 @@ from . import wizard_tax_adjustments
|
||||
from . import account_invoice_send
|
||||
from . import base_document_layout
|
||||
from . import account_transfer_wizard
|
||||
from . import account_payment_register
|
||||
|
||||
@@ -88,7 +88,7 @@ class AccountInvoiceSend(models.TransientModel):
|
||||
if self.env.context.get('mark_invoice_as_sent'):
|
||||
#Salesman send posted invoice, without the right to write
|
||||
#but they should have the right to change this flag
|
||||
self.mapped('invoice_ids').sudo().write({'invoice_sent': True})
|
||||
self.mapped('invoice_ids').sudo().write({'is_move_sent': True})
|
||||
|
||||
def _print_document(self):
|
||||
""" to override for each type of models that will use this composer."""
|
||||
|
||||
@@ -0,0 +1,504 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AccountPaymentRegister(models.TransientModel):
|
||||
_name = 'account.payment.register'
|
||||
_description = 'Register Payment'
|
||||
|
||||
# == Business fields ==
|
||||
payment_date = fields.Date(String="Payment Date", required=True,
|
||||
default=fields.Date.context_today)
|
||||
amount = fields.Monetary(currency_field='currency_id', store=True, readonly=False,
|
||||
compute='_compute_amount')
|
||||
communication = fields.Char(string="Memo", store=True, readonly=False,
|
||||
compute='_compute_communication')
|
||||
group_payment = fields.Boolean(string="Group Payments", store=True, readonly=False,
|
||||
compute='_compute_group_payment',
|
||||
help="Only one payment will be created by partner (bank)/ currency.")
|
||||
currency_id = fields.Many2one('res.currency', string='Currency', store=True, readonly=False,
|
||||
compute='_compute_currency_id',
|
||||
help="The payment's currency.")
|
||||
journal_id = fields.Many2one('account.journal', store=True, readonly=False,
|
||||
compute='_compute_journal_id',
|
||||
domain="[('company_id', '=', company_id), ('type', 'in', ('bank', 'cash'))]")
|
||||
partner_bank_id = fields.Many2one('res.partner.bank', string="Recipient Bank Account",
|
||||
readonly=False, store=True,
|
||||
compute='_compute_partner_bank_id',
|
||||
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id), ('partner_id', '=', partner_id)]")
|
||||
company_currency_id = fields.Many2one('res.currency', string="Company Currency",
|
||||
related='company_id.currency_id')
|
||||
|
||||
# == Fields given through the context ==
|
||||
line_ids = fields.Many2many('account.move.line', 'account_payment_register_move_line_rel', 'wizard_id', 'line_id',
|
||||
string="Journal items", readonly=True, copy=False,)
|
||||
payment_type = fields.Selection([
|
||||
('outbound', 'Send Money'),
|
||||
('inbound', 'Receive Money'),
|
||||
], string='Payment Type', store=True, copy=False,
|
||||
compute='_compute_from_lines')
|
||||
partner_type = fields.Selection([
|
||||
('customer', 'Customer'),
|
||||
('supplier', 'Vendor'),
|
||||
], store=True, copy=False,
|
||||
compute='_compute_from_lines')
|
||||
source_amount = fields.Monetary(
|
||||
string="Amount to Pay (company currency)", store=True, copy=False,
|
||||
currency_field='company_currency_id',
|
||||
compute='_compute_from_lines')
|
||||
source_amount_currency = fields.Monetary(
|
||||
string="Amount to Pay (foreign currency)", store=True, copy=False,
|
||||
currency_field='source_currency_id',
|
||||
compute='_compute_from_lines')
|
||||
source_currency_id = fields.Many2one('res.currency',
|
||||
string='Source Currency', store=True, copy=False,
|
||||
compute='_compute_from_lines',
|
||||
help="The payment's currency.")
|
||||
can_edit_wizard = fields.Boolean(store=True, copy=False,
|
||||
compute='_compute_from_lines',
|
||||
help="Technical field used to indicate the user can edit the wizard content such as the amount.")
|
||||
can_group_payments = fields.Boolean(store=True, copy=False,
|
||||
compute='_compute_from_lines',
|
||||
help="Technical field used to indicate the user can see the 'group_payments' box.")
|
||||
company_id = fields.Many2one('res.company', store=True, copy=False,
|
||||
compute='_compute_from_lines')
|
||||
partner_id = fields.Many2one('res.partner',
|
||||
string="Customer/Vendor", store=True, copy=False, ondelete='restrict',
|
||||
compute='_compute_from_lines')
|
||||
|
||||
# == Payment methods fields ==
|
||||
payment_method_id = fields.Many2one('account.payment.method', string='Payment Method',
|
||||
readonly=False, store=True,
|
||||
compute='_compute_payment_method_id',
|
||||
domain="[('id', 'in', available_payment_method_ids)]",
|
||||
help="Manual: Get paid by cash, check or any other method outside of Odoo.\n"\
|
||||
"Electronic: Get paid automatically through a payment acquirer by requesting a transaction on a card saved by the customer when buying or subscribing online (payment token).\n"\
|
||||
"Check: Pay bill by check and print it from Odoo.\n"\
|
||||
"Batch Deposit: Encase several customer checks at once by generating a batch deposit to submit to your bank. When encoding the bank statement in Odoo, you are suggested to reconcile the transaction with the batch deposit.To enable batch deposit, module account_batch_payment must be installed.\n"\
|
||||
"SEPA Credit Transfer: Pay bill from a SEPA Credit Transfer file you submit to your bank. To enable sepa credit transfer, module account_sepa must be installed ")
|
||||
available_payment_method_ids = fields.Many2many('account.payment.method',
|
||||
compute='_compute_payment_method_fields')
|
||||
hide_payment_method = fields.Boolean(
|
||||
compute='_compute_payment_method_fields',
|
||||
help="Technical field used to hide the payment method if the selected journal has only one available which is 'manual'")
|
||||
|
||||
# == Payment difference fields ==
|
||||
payment_difference = fields.Monetary(
|
||||
compute='_compute_payment_difference')
|
||||
payment_difference_handling = fields.Selection([
|
||||
('open', 'Keep open'),
|
||||
('reconcile', 'Mark invoice as fully paid'),
|
||||
], default='open', string="Payment Difference Handling")
|
||||
writeoff_account_id = fields.Many2one('account.account', string="Difference Account", copy=False,
|
||||
domain="[('deprecated', '=', False), ('company_id', '=', company_id)]")
|
||||
writeoff_label = fields.Char(string='Journal Item Label', default='Write-Off',
|
||||
help='Change label of the counterpart that will hold the payment difference')
|
||||
|
||||
# == Display purpose fields ==
|
||||
show_partner_bank_account = fields.Boolean(
|
||||
compute='_compute_show_require_partner_bank',
|
||||
help="Technical field used to know whether the field `partner_bank_id` needs to be displayed or not in the payments form views")
|
||||
require_partner_bank_account = fields.Boolean(
|
||||
compute='_compute_show_require_partner_bank',
|
||||
help="Technical field used to know whether the field `partner_bank_id` needs to be required or not in the payments form views")
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# HELPERS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def _get_batch_communication(self, batch_result):
|
||||
''' Helper to compute the communication based on the batch.
|
||||
:param batch_result: A batch returned by '_get_batches'.
|
||||
:return: A string representing a communication to be set on payment.
|
||||
'''
|
||||
return ' '.join(label for label in batch_result['lines'].mapped('name') if label)
|
||||
|
||||
@api.model
|
||||
def _get_line_batch_key(self, line):
|
||||
''' Turn the line passed as parameter to a dictionary defining on which way the lines
|
||||
will be grouped together.
|
||||
:return: A python dictionary.
|
||||
'''
|
||||
return {
|
||||
'partner_id': line.partner_id.id,
|
||||
'account_id': line.account_id.id,
|
||||
'currency_id': (line.currency_id or line.company_currency_id).id,
|
||||
'partner_bank_id': line.move_id.partner_bank_id.id,
|
||||
'partner_type': 'customer' if line.account_internal_type == 'receivable' else 'supplier',
|
||||
'payment_type': 'inbound' if line.balance > 0.0 else 'outbound',
|
||||
}
|
||||
|
||||
def _get_batches(self):
|
||||
''' Group the account.move.line linked to the wizard together.
|
||||
:return: A list of batches, each one containing:
|
||||
* key_values: The key as a dictionary used to group the journal items together.
|
||||
* moves: An account.move recordset.
|
||||
'''
|
||||
self.ensure_one()
|
||||
|
||||
lines = self.line_ids
|
||||
|
||||
if len(lines.company_id) > 1:
|
||||
raise UserError(_("You can't create payments for entries belonging to different companies."))
|
||||
if not lines:
|
||||
raise UserError(_("You can't open the register payment wizard without at least one receivable/payable line."))
|
||||
|
||||
batches = {}
|
||||
for line in lines:
|
||||
batch_key = self._get_line_batch_key(line)
|
||||
|
||||
serialized_key = '-'.join(str(v) for v in batch_key.values())
|
||||
batches.setdefault(serialized_key, {
|
||||
'key_values': batch_key,
|
||||
'lines': self.env['account.move.line'],
|
||||
})
|
||||
batches[serialized_key]['lines'] += line
|
||||
return list(batches.values())
|
||||
|
||||
@api.model
|
||||
def _get_wizard_values_from_batch(self, batch_result):
|
||||
''' Extract values from the batch passed as parameter (see '_get_batches')
|
||||
to be mounted in the wizard view.
|
||||
:param batch_result: A batch returned by '_get_batches'.
|
||||
:return: A dictionary containing valid fields
|
||||
'''
|
||||
key_values = batch_result['key_values']
|
||||
lines = batch_result['lines']
|
||||
company = lines[0].company_id
|
||||
|
||||
source_amount = abs(sum(lines.mapped('amount_residual')))
|
||||
if key_values['currency_id'] == company.currency_id.id:
|
||||
source_amount_currency = source_amount
|
||||
else:
|
||||
source_amount_currency = abs(sum(lines.mapped('amount_residual_currency')))
|
||||
|
||||
return {
|
||||
'company_id': company.id,
|
||||
'partner_id': key_values['partner_id'],
|
||||
'partner_type': key_values['partner_type'],
|
||||
'payment_type': key_values['payment_type'],
|
||||
'source_currency_id': key_values['currency_id'],
|
||||
'source_amount': source_amount,
|
||||
'source_amount_currency': source_amount_currency,
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# COMPUTE METHODS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@api.depends('line_ids')
|
||||
def _compute_from_lines(self):
|
||||
''' Load initial values from the account.moves passed through the context. '''
|
||||
for wizard in self:
|
||||
batches = wizard._get_batches()
|
||||
|
||||
if len(batches) == 1:
|
||||
# == Single batch to be mounted on the view ==
|
||||
batch_result = batches[0]
|
||||
wizard.update(wizard._get_wizard_values_from_batch(batch_result))
|
||||
|
||||
wizard.can_edit_wizard = True
|
||||
wizard.can_group_payments = len(batch_result['lines']) != 1
|
||||
else:
|
||||
# == Multiple batches: The wizard is not editable ==
|
||||
wizard.update({
|
||||
'company_id': batches[0]['lines'][0].company_id.id,
|
||||
'partner_id': False,
|
||||
'partner_type': False,
|
||||
'payment_type': False,
|
||||
'source_currency_id': False,
|
||||
'source_amount': False,
|
||||
'source_amount_currency': False,
|
||||
})
|
||||
|
||||
wizard.can_edit_wizard = False
|
||||
wizard.can_group_payments = any(len(batch_result['lines']) != 1 for batch_result in batches)
|
||||
|
||||
@api.depends('can_edit_wizard')
|
||||
def _compute_communication(self):
|
||||
# The communication can't be computed in '_compute_from_lines' because
|
||||
# it's a compute editable field and then, should be computed in a separated method.
|
||||
for wizard in self:
|
||||
if wizard.can_edit_wizard:
|
||||
batches = self._get_batches()
|
||||
wizard.communication = wizard._get_batch_communication(batches[0])
|
||||
else:
|
||||
wizard.communication = False
|
||||
|
||||
@api.depends('can_edit_wizard')
|
||||
def _compute_group_payment(self):
|
||||
for wizard in self:
|
||||
if wizard.can_edit_wizard:
|
||||
batches = wizard._get_batches()
|
||||
wizard.group_payment = len(batches[0]['lines'].move_id) == 1
|
||||
else:
|
||||
wizard.group_payment = False
|
||||
|
||||
@api.depends('company_id', 'source_currency_id')
|
||||
def _compute_journal_id(self):
|
||||
for wizard in self:
|
||||
domain = [
|
||||
('type', 'in', ('bank', 'cash')),
|
||||
('company_id', '=', wizard.company_id.id),
|
||||
]
|
||||
journal = None
|
||||
if wizard.source_currency_id:
|
||||
journal = self.env['account.journal'].search(domain + [('currency_id', '=', wizard.source_currency_id.id)], limit=1)
|
||||
if not journal:
|
||||
journal = self.env['account.journal'].search(domain, limit=1)
|
||||
wizard.journal_id = journal
|
||||
|
||||
@api.depends('journal_id')
|
||||
def _compute_currency_id(self):
|
||||
for wizard in self:
|
||||
wizard.currency_id = wizard.journal_id.currency_id or wizard.source_currency_id or wizard.company_id.currency_id
|
||||
|
||||
@api.depends('partner_id')
|
||||
def _compute_partner_bank_id(self):
|
||||
''' The default partner_bank_id will be the first available on the partner. '''
|
||||
for wizard in self:
|
||||
available_partner_bank_accounts = wizard.partner_id.bank_ids
|
||||
if available_partner_bank_accounts:
|
||||
wizard.partner_bank_id = available_partner_bank_accounts[0]._origin
|
||||
else:
|
||||
wizard.partner_bank_id = False
|
||||
|
||||
@api.depends('journal_id')
|
||||
def _compute_payment_method_id(self):
|
||||
for wizard in self:
|
||||
batches = wizard._get_batches()
|
||||
payment_type = batches[0]['key_values']['payment_type']
|
||||
|
||||
if payment_type == 'inbound':
|
||||
available_payment_methods = wizard.journal_id.inbound_payment_method_ids
|
||||
else:
|
||||
available_payment_methods = wizard.journal_id.outbound_payment_method_ids
|
||||
|
||||
# Select the first available one by default.
|
||||
if available_payment_methods:
|
||||
wizard.payment_method_id = available_payment_methods[0]._origin
|
||||
else:
|
||||
wizard.payment_method_id = False
|
||||
|
||||
@api.depends('payment_type',
|
||||
'journal_id.inbound_payment_method_ids',
|
||||
'journal_id.outbound_payment_method_ids')
|
||||
def _compute_payment_method_fields(self):
|
||||
for wizard in self:
|
||||
if wizard.payment_type == 'inbound':
|
||||
wizard.available_payment_method_ids = wizard.journal_id.inbound_payment_method_ids
|
||||
else:
|
||||
wizard.available_payment_method_ids = wizard.journal_id.outbound_payment_method_ids
|
||||
|
||||
wizard.hide_payment_method = len(wizard.available_payment_method_ids) == 1 and wizard.available_payment_method_ids.code == 'manual'
|
||||
|
||||
@api.depends('payment_type',
|
||||
'journal_id.inbound_payment_method_ids',
|
||||
'journal_id.outbound_payment_method_ids')
|
||||
def _compute_payment_method_id(self):
|
||||
for wizard in self:
|
||||
if wizard.payment_type == 'inbound':
|
||||
available_payment_methods = wizard.journal_id.inbound_payment_method_ids
|
||||
else:
|
||||
available_payment_methods = wizard.journal_id.outbound_payment_method_ids
|
||||
|
||||
# Select the first available one by default.
|
||||
if available_payment_methods:
|
||||
wizard.payment_method_id = available_payment_methods[0]._origin
|
||||
else:
|
||||
wizard.payment_method_id = False
|
||||
|
||||
@api.depends('payment_method_id')
|
||||
def _compute_show_require_partner_bank(self):
|
||||
""" Computes if the destination bank account must be displayed in the payment form view. By default, it
|
||||
won't be displayed but some modules might change that, depending on the payment type."""
|
||||
for wizard in self:
|
||||
wizard.show_partner_bank_account = wizard.payment_method_id.code in self.env['account.payment']._get_method_codes_using_bank_account()
|
||||
wizard.require_partner_bank_account = wizard.payment_method_id.code in self.env['account.payment']._get_method_codes_needing_bank_account()
|
||||
|
||||
@api.depends('source_amount', 'source_amount_currency', 'source_currency_id', 'company_id', 'currency_id', 'payment_date')
|
||||
def _compute_amount(self):
|
||||
for wizard in self:
|
||||
if wizard.source_currency_id == wizard.currency_id:
|
||||
# Same currency.
|
||||
wizard.amount = wizard.source_amount_currency
|
||||
elif wizard.currency_id == wizard.company_id.currency_id:
|
||||
# Payment expressed on the company's currency.
|
||||
wizard.amount = wizard.source_amount
|
||||
else:
|
||||
# Foreign currency on payment different than the one set on the journal entries.
|
||||
amount_payment_currency = wizard.company_id.currency_id._convert(wizard.source_amount, wizard.currency_id, wizard.company_id, wizard.payment_date)
|
||||
wizard.amount = amount_payment_currency
|
||||
|
||||
@api.depends('amount')
|
||||
def _compute_payment_difference(self):
|
||||
for wizard in self:
|
||||
if wizard.source_currency_id == wizard.currency_id:
|
||||
# Same currency.
|
||||
wizard.payment_difference = wizard.source_amount_currency - wizard.amount
|
||||
elif wizard.currency_id == wizard.company_id.currency_id:
|
||||
# Payment expressed on the company's currency.
|
||||
wizard.payment_difference = wizard.source_amount - wizard.amount
|
||||
else:
|
||||
# Foreign currency on payment different than the one set on the journal entries.
|
||||
amount_payment_currency = wizard.company_id.currency_id._convert(wizard.source_amount, wizard.currency_id, wizard.company_id, wizard.payment_date)
|
||||
wizard.payment_difference = amount_payment_currency - wizard.amount
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# LOW-LEVEL METHODS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
# OVERRIDE
|
||||
res = super().default_get(fields_list)
|
||||
|
||||
if 'line_ids' in fields_list and 'line_ids' not in res:
|
||||
|
||||
# Retrieve moves to pay from the context.
|
||||
|
||||
if self._context.get('active_model') == 'account.move':
|
||||
lines = self.env['account.move'].browse(self._context.get('active_ids', [])).line_ids
|
||||
elif self._context.get('active_model') == 'account.move.line':
|
||||
lines = self.env['account.move.line'].browse(self._context.get('active_ids', []))
|
||||
else:
|
||||
raise UserError(_(
|
||||
"The register payment wizard should only be called on account.move or account.move.line records."
|
||||
))
|
||||
|
||||
# Keep lines having a residual amount to pay.
|
||||
available_lines = self.env['account.move.line']
|
||||
for line in lines:
|
||||
if line.move_id.state != 'posted':
|
||||
raise UserError(_("You can only register payment for posted journal entries."))
|
||||
|
||||
if line.account_internal_type not in ('receivable', 'payable'):
|
||||
continue
|
||||
if line.currency_id:
|
||||
if line.currency_id.is_zero(line.amount_residual_currency):
|
||||
continue
|
||||
else:
|
||||
if line.company_currency_id.is_zero(line.amount_residual):
|
||||
continue
|
||||
available_lines |= line
|
||||
|
||||
# Check.
|
||||
if not available_lines:
|
||||
raise UserError(_("You can't register a payment because there is nothing left to pay on the selected journal items."))
|
||||
if len(lines.company_id) > 1:
|
||||
raise UserError(_("You can't create payments for entries belonging to different companies."))
|
||||
if len(set(available_lines.mapped('account_internal_type'))) > 1:
|
||||
raise UserError(_("You can't register payments for journal items being either all inbound, either all outbound."))
|
||||
|
||||
res['line_ids'] = [(6, 0, available_lines.ids)]
|
||||
|
||||
return res
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# BUSINESS METHODS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def _create_payment_vals_from_wizard(self):
|
||||
payment_vals = {
|
||||
'date': self.payment_date,
|
||||
'amount': self.amount,
|
||||
'payment_type': self.payment_type,
|
||||
'partner_type': self.partner_type,
|
||||
'ref': self.communication,
|
||||
'journal_id': self.journal_id.id,
|
||||
'currency_id': self.currency_id.id,
|
||||
'partner_id': self.partner_id.id,
|
||||
'partner_bank_id': self.partner_bank_id.id,
|
||||
'payment_method_id': self.payment_method_id.id,
|
||||
'destination_account_id': self.line_ids[0].account_id.id
|
||||
}
|
||||
|
||||
if self.payment_difference and self.payment_difference_handling == 'reconcile':
|
||||
payment_vals['write_off_line_vals'] = {
|
||||
'name': self.writeoff_label,
|
||||
'amount': -self.payment_difference,
|
||||
'account_id': self.writeoff_account_id.id,
|
||||
}
|
||||
return payment_vals
|
||||
|
||||
def _create_payment_vals_from_batch(self, batch_result):
|
||||
batch_values = self._get_wizard_values_from_batch(batch_result)
|
||||
return {
|
||||
'date': self.payment_date,
|
||||
'amount': batch_values['source_amount_currency'],
|
||||
'payment_type': batch_values['payment_type'],
|
||||
'partner_type': batch_values['partner_type'],
|
||||
'ref': self._get_batch_communication(batch_result),
|
||||
'journal_id': self.journal_id.id,
|
||||
'currency_id': batch_values['source_currency_id'],
|
||||
'partner_id': batch_values['partner_id'],
|
||||
'partner_bank_id': batch_result['key_values']['partner_bank_id'],
|
||||
'payment_method_id': self.payment_method_id.id,
|
||||
'destination_account_id': batch_result['lines'][0].account_id.id
|
||||
}
|
||||
|
||||
def _create_payments(self):
|
||||
self.ensure_one()
|
||||
batches = self._get_batches()
|
||||
|
||||
to_reconcile = []
|
||||
if self.can_edit_wizard and (len(batches[0]['lines']) == 1 or self.group_payment):
|
||||
payment_vals = self._create_payment_vals_from_wizard()
|
||||
payment_vals_list = [payment_vals]
|
||||
to_reconcile.append(batches[0]['lines'])
|
||||
else:
|
||||
# Don't group payments: Create one batch per move.
|
||||
if not self.group_payment:
|
||||
new_batches = []
|
||||
for batch_result in batches:
|
||||
for line in batch_result['lines']:
|
||||
new_batches.append({
|
||||
**batch_result,
|
||||
'lines': line,
|
||||
})
|
||||
batches = new_batches
|
||||
|
||||
payment_vals_list = []
|
||||
for batch_result in batches:
|
||||
payment_vals_list.append(self._create_payment_vals_from_batch(batch_result))
|
||||
to_reconcile.append(batch_result['lines'])
|
||||
|
||||
payments = self.env['account.payment'].create(payment_vals_list)
|
||||
payments.action_post()
|
||||
|
||||
domain = [('account_internal_type', 'in', ('receivable', 'payable')), ('reconciled', '=', False)]
|
||||
for payment, lines in zip(payments, to_reconcile):
|
||||
payment_lines = payment.line_ids.filtered_domain(domain)
|
||||
for account in payment_lines.account_id:
|
||||
(payment_lines + lines)\
|
||||
.filtered_domain([('account_id', '=', account.id), ('reconciled', '=', False)])\
|
||||
.reconcile()
|
||||
|
||||
return payments
|
||||
|
||||
def action_create_payments(self):
|
||||
payments = self._create_payments()
|
||||
|
||||
if self._context.get('dont_redirect_to_payments'):
|
||||
return True
|
||||
|
||||
action = {
|
||||
'name': _('Payments'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'account.payment',
|
||||
'context': {'create': False},
|
||||
}
|
||||
if len(payments) == 1:
|
||||
action.update({
|
||||
'view_mode': 'form',
|
||||
'res_id': payments.id,
|
||||
})
|
||||
else:
|
||||
action.update({
|
||||
'view_mode': 'tree,form',
|
||||
'domain': [('id', 'in', payments.ids)],
|
||||
})
|
||||
return action
|
||||
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="view_account_payment_register_form" model="ir.ui.view">
|
||||
<field name="name">account.payment.register.form</field>
|
||||
<field name="model">account.payment.register</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Register Payment">
|
||||
<!-- Invisible fields -->
|
||||
<field name="line_ids" invisible="1"/>
|
||||
<field name="can_edit_wizard" invisible="1" force_save="1"/>
|
||||
<field name="can_group_payments" invisible="1" force_save="1"/>
|
||||
<field name="payment_type" invisible="1" force_save="1"/>
|
||||
<field name="partner_type" invisible="1" force_save="1"/>
|
||||
<field name="source_amount" invisible="1" force_save="1"/>
|
||||
<field name="source_amount_currency" invisible="1" force_save="1"/>
|
||||
<field name="source_currency_id" invisible="1" force_save="1"/>
|
||||
<field name="company_id" invisible="1" force_save="1"/>
|
||||
<field name="partner_id" invisible="1" force_save="1"/>
|
||||
|
||||
<field name="show_partner_bank_account" invisible="1"/>
|
||||
<field name="require_partner_bank_account" invisible="1"/>
|
||||
<field name="hide_payment_method" invisible="1"/>
|
||||
<field name="available_payment_method_ids" invisible="1"/>
|
||||
<field name="company_currency_id" invisible="1"/>
|
||||
|
||||
<group>
|
||||
<group name="group1">
|
||||
<field name="journal_id" widget="selection"/>
|
||||
<field name="payment_method_id" widget="radio"
|
||||
required="1"
|
||||
attrs="{'invisible': [('hide_payment_method', '=', True)]}"/>
|
||||
<field name="partner_bank_id"
|
||||
attrs="{'invisible': ['|', ('show_partner_bank_account', '=', False), '|', ('can_edit_wizard', '=', False), '&', ('can_group_payments', '=', True), ('group_payment', '=', False)],
|
||||
'required': [('require_partner_bank_account', '=', True), ('can_edit_wizard', '=', True), '|', ('can_group_payments', '=', False), ('group_payment', '=', False)]}"/>
|
||||
<field name="group_payment"
|
||||
attrs="{'invisible': [('can_group_payments', '=', False)]}"/>
|
||||
</group>
|
||||
<group name="group2">
|
||||
<label for="amount"
|
||||
attrs="{'invisible': ['|', ('can_edit_wizard', '=', False), '&', ('can_group_payments', '=', True), ('group_payment', '=', False)]}"/>
|
||||
<div name="amount_div" class="o_row"
|
||||
attrs="{'invisible': ['|', ('can_edit_wizard', '=', False), '&', ('can_group_payments', '=', True), ('group_payment', '=', False)]}">
|
||||
<field name="amount"/>
|
||||
<field name="currency_id"
|
||||
options="{'no_create': True, 'no_open': True}"
|
||||
groups="base.group_multi_currency"/>
|
||||
</div>
|
||||
<field name="payment_date"/>
|
||||
<field name="communication"
|
||||
attrs="{'invisible': ['|', ('can_edit_wizard', '=', False), '&', ('can_group_payments', '=', True), ('group_payment', '=', False)]}"/>
|
||||
</group>
|
||||
<group name="group3"
|
||||
attrs="{'invisible': ['|', ('payment_difference', '=', 0.0), '|', ('can_edit_wizard', '=', False), '&', ('can_group_payments', '=', True), ('group_payment', '=', False)]}"
|
||||
groups="account.group_account_readonly">
|
||||
<label for="payment_difference"/>
|
||||
<div>
|
||||
<field name="payment_difference"/>
|
||||
<field name="payment_difference_handling" widget="radio" nolabel="1"/>
|
||||
<div attrs="{'invisible': [('payment_difference_handling','=','open')]}">
|
||||
<label for="writeoff_account_id" string="Post Difference In" class="oe_edit_only"/>
|
||||
<field name="writeoff_account_id"
|
||||
string="Post Difference In"
|
||||
options="{'no_create': True}"
|
||||
attrs="{'required': [('payment_difference_handling', '=', 'reconcile')]}"/>
|
||||
<label for="writeoff_label" class="oe_edit_only" string="Label"/>
|
||||
<field name="writeoff_label" attrs="{'required': [('payment_difference_handling', '=', 'reconcile')]}"/>
|
||||
</div>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Create Payment" name="action_create_payments" type="object" class="oe_highlight"/>
|
||||
<button string="Cancel" class="btn btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -93,7 +93,7 @@ class SetupBarBankConfigWizard(models.TransientModel):
|
||||
def _onchange_new_journal_code(self):
|
||||
for record in self:
|
||||
if not record.linked_journal_id:
|
||||
record.new_journal_code = self.env['account.journal'].get_next_bank_cash_default_code('bank', self.env.company.id)
|
||||
record.new_journal_code = self.env['account.journal'].get_next_bank_cash_default_code('bank', self.env.company)
|
||||
else:
|
||||
record.new_journal_code = self.linked_journal_id.code
|
||||
|
||||
|
||||
@@ -7,31 +7,38 @@ from odoo.tools.misc import formatLang, format_date
|
||||
|
||||
INV_LINES_PER_STUB = 9
|
||||
|
||||
class AccountPaymentRegister(models.TransientModel):
|
||||
_inherit = "account.payment.register"
|
||||
|
||||
def _prepare_payment_vals(self, invoices):
|
||||
res = super(AccountPaymentRegister, self)._prepare_payment_vals(invoices)
|
||||
if self.payment_method_id == self.env.ref('account_check_printing.account_payment_method_check'):
|
||||
currency_id = self.env['res.currency'].browse(res['currency_id'])
|
||||
res.update({
|
||||
'check_amount_in_words': currency_id.amount_to_text(res['amount']),
|
||||
})
|
||||
return res
|
||||
|
||||
|
||||
class AccountPayment(models.Model):
|
||||
_inherit = "account.payment"
|
||||
|
||||
check_amount_in_words = fields.Char(string="Amount in Words")
|
||||
check_manual_sequencing = fields.Boolean(related='journal_id.check_manual_sequencing', readonly=1)
|
||||
check_number = fields.Char(string="Check Number", readonly=True, copy=False,
|
||||
check_amount_in_words = fields.Char(string="Amount in Words", store=True, readonly=False,
|
||||
compute='_compute_check_amount_in_words')
|
||||
check_manual_sequencing = fields.Boolean(related='journal_id.check_manual_sequencing')
|
||||
check_number = fields.Char(string="Check Number", store=True, readonly=True,
|
||||
compute='_compute_check_number',
|
||||
help="The selected journal is configured to print check numbers. If your pre-printed check paper already has numbers "
|
||||
"or if the current numbering is wrong, you can change it in the journal configuration page.")
|
||||
check_number_int = fields.Integer(string="Check Number Integer", store=True, compute='_compute_check_number')
|
||||
check_number_int = fields.Integer(string="Check Number Integer", store=True,
|
||||
compute='_compute_check_number_int')
|
||||
|
||||
@api.depends('payment_method_id', 'currency_id', 'amount')
|
||||
def _compute_check_amount_in_words(self):
|
||||
for pay in self:
|
||||
if pay.currency_id and pay.payment_method_id == self.env.ref('account_check_printing.account_payment_method_check'):
|
||||
pay.check_amount_in_words = pay.currency_id.amount_to_text(pay.amount)
|
||||
else:
|
||||
pay.check_amount_in_words = False
|
||||
|
||||
@api.depends('journal_id')
|
||||
def _compute_check_number(self):
|
||||
for pay in self:
|
||||
if pay.journal_id.check_manual_sequencing:
|
||||
pay.check_number = pay.journal_id.check_sequence_id.number_next_actual
|
||||
else:
|
||||
pay.check_number = False
|
||||
|
||||
@api.depends('check_number')
|
||||
def _compute_check_number(self):
|
||||
def _compute_check_number_int(self):
|
||||
# store check number as int to avoid doing a lot of checks and transformations
|
||||
# when calculating next_check_number
|
||||
for record in self:
|
||||
@@ -42,29 +49,8 @@ class AccountPayment(models.Model):
|
||||
number = 0
|
||||
record.check_number_int = number
|
||||
|
||||
@api.onchange('amount', 'currency_id')
|
||||
def _onchange_amount(self):
|
||||
res = super(AccountPayment, self)._onchange_amount()
|
||||
self.check_amount_in_words = self.currency_id.amount_to_text(self.amount) if self.currency_id else ''
|
||||
return res
|
||||
|
||||
@api.onchange('journal_id')
|
||||
def _onchange_journal_id(self):
|
||||
if hasattr(super(AccountPayment, self), '_onchange_journal_id'):
|
||||
super(AccountPayment, self)._onchange_journal_id()
|
||||
if self.journal_id.check_manual_sequencing:
|
||||
self.check_number = self.journal_id.check_sequence_id.number_next_actual
|
||||
|
||||
def _check_communication(self, payment_method_id, communication):
|
||||
super(AccountPayment, self)._check_communication(payment_method_id, communication)
|
||||
if payment_method_id == self.env.ref('account_check_printing.account_payment_method_check').id:
|
||||
if not communication:
|
||||
return
|
||||
if len(communication) > 60:
|
||||
raise ValidationError(_("A check memo cannot exceed 60 characters."))
|
||||
|
||||
def post(self):
|
||||
res = super(AccountPayment, self).post()
|
||||
def action_post(self):
|
||||
res = super(AccountPayment, self).action_post()
|
||||
payment_method_check = self.env.ref('account_check_printing.account_payment_method_check')
|
||||
for payment in self.filtered(lambda p: p.payment_method_id == payment_method_check and p.check_manual_sequencing):
|
||||
sequence = payment.journal_id.check_sequence_id
|
||||
@@ -105,8 +91,8 @@ class AccountPayment(models.Model):
|
||||
self.filtered(lambda r: r.state == 'draft').post()
|
||||
return self.do_print_checks()
|
||||
|
||||
def unmark_sent(self):
|
||||
self.write({'state': 'posted'})
|
||||
def action_unmark_sent(self):
|
||||
self.write({'is_move_sent': False})
|
||||
|
||||
def do_print_checks(self):
|
||||
check_layout = self[0].company_id.account_check_printing_layout
|
||||
@@ -115,13 +101,9 @@ class AccountPayment(models.Model):
|
||||
report_action = self.env.ref(check_layout, False)
|
||||
if not report_action:
|
||||
raise UserError(_("Something went wrong with Check Layout, please select another layout in Invoicing/Accounting Settings and try again."))
|
||||
self.write({'state': 'sent'})
|
||||
self.write({'is_move_sent': True})
|
||||
return report_action.report_action(self)
|
||||
|
||||
def set_check_amount_in_words(self):
|
||||
for payment in self:
|
||||
payment.check_amount_in_words = payment.currency_id.amount_to_text(payment.amount) if payment.currency_id else ''
|
||||
|
||||
#######################
|
||||
#CHECK PRINTING METHODS
|
||||
#######################
|
||||
@@ -132,15 +114,15 @@ class AccountPayment(models.Model):
|
||||
multi_stub = self.company_id.account_check_printing_multi_stub
|
||||
return {
|
||||
'sequence_number': self.check_number if (self.journal_id.check_manual_sequencing and self.check_number != 0) else False,
|
||||
'payment_date': format_date(self.env, self.payment_date),
|
||||
'date': format_date(self.env, self.date),
|
||||
'partner_id': self.partner_id,
|
||||
'partner_name': self.partner_id.name,
|
||||
'currency': self.currency_id,
|
||||
'state': self.state,
|
||||
'amount': formatLang(self.env, self.amount, currency_obj=self.currency_id) if i == 0 else 'VOID',
|
||||
'amount_in_word': self._check_fill_line(self.check_amount_in_words) if i == 0 else 'VOID',
|
||||
'memo': self.communication,
|
||||
'stub_cropped': not multi_stub and len(self.invoice_ids) > INV_LINES_PER_STUB,
|
||||
'memo': self.ref,
|
||||
'stub_cropped': not multi_stub and len(self.move_id._get_reconciled_invoices()) > INV_LINES_PER_STUB,
|
||||
# If the payment does not reference an invoice, there is no stub line to display
|
||||
'stub_lines': p,
|
||||
}
|
||||
@@ -158,12 +140,12 @@ class AccountPayment(models.Model):
|
||||
""" The stub is the summary of paid invoices. It may spill on several pages, in which case only the check on
|
||||
first page is valid. This function returns a list of stub lines per page.
|
||||
"""
|
||||
if len(self.reconciled_invoice_ids) == 0:
|
||||
if len(self.move_id._get_reconciled_invoices()) == 0:
|
||||
return None
|
||||
|
||||
multi_stub = self.company_id.account_check_printing_multi_stub
|
||||
|
||||
invoices = self.reconciled_invoice_ids.sorted(key=lambda r: r.invoice_date_due)
|
||||
invoices = self.move_id._get_reconciled_invoices().sorted(key=lambda r: r.invoice_date_due)
|
||||
debits = invoices.filtered(lambda r: r.move_type == 'in_invoice')
|
||||
credits = invoices.filtered(lambda r: r.move_type == 'in_refund')
|
||||
|
||||
@@ -201,10 +183,10 @@ class AccountPayment(models.Model):
|
||||
# Find the account.partial.reconcile which are common to the invoice and the payment
|
||||
if invoice.move_type in ['in_invoice', 'out_refund']:
|
||||
invoice_sign = 1
|
||||
invoice_payment_reconcile = invoice.line_ids.mapped('matched_debit_ids').filtered(lambda r: r.debit_move_id in self.move_line_ids)
|
||||
invoice_payment_reconcile = invoice.line_ids.mapped('matched_debit_ids').filtered(lambda r: r.debit_move_id in self.line_ids)
|
||||
else:
|
||||
invoice_sign = -1
|
||||
invoice_payment_reconcile = invoice.line_ids.mapped('matched_credit_ids').filtered(lambda r: r.credit_move_id in self.move_line_ids)
|
||||
invoice_payment_reconcile = invoice.line_ids.mapped('matched_credit_ids').filtered(lambda r: r.credit_move_id in self.line_ids)
|
||||
|
||||
if self.currency_id != self.journal_id.company_id.currency_id:
|
||||
amount_paid = abs(sum(invoice_payment_reconcile.mapped('amount_currency')))
|
||||
|
||||
@@ -1,81 +1,105 @@
|
||||
# Make sure / performs a floating point division even if environment is python 2
|
||||
from __future__ import division
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestCommon
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.addons.account_check_printing.models.account_payment import INV_LINES_PER_STUB
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import Form
|
||||
import time
|
||||
|
||||
import math
|
||||
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestPrintCheck(AccountTestCommon):
|
||||
class TestPrintCheck(AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestPrintCheck, cls).setUpClass()
|
||||
super().setUpClass()
|
||||
|
||||
cls.invoice_model = cls.env['account.move']
|
||||
cls.invoice_line_model = cls.env['account.move.line']
|
||||
cls.payment_model = cls.env['account.payment']
|
||||
|
||||
cls.partner_axelor = cls.env['res.partner'].create({'name': 'A Partner'})
|
||||
cls.product = cls.env['product.product'].create({'name': 'A test Product'})
|
||||
cls.payment_method_check = cls.env.ref("account_check_printing.account_payment_method_check")
|
||||
|
||||
cls.account_payable = cls.env['account.account'].search([('user_type_id', '=', cls.env.ref('account.data_account_type_payable').id)], limit=1)
|
||||
cls.account_expenses = cls.env['account.account'].search([('user_type_id', '=', cls.env.ref('account.data_account_type_expenses').id)], limit=1)
|
||||
|
||||
cls.bank_journal = cls.env['account.journal'].create({'name': 'Bank', 'type': 'bank', 'code': 'BNK67'})
|
||||
cls.bank_journal.check_manual_sequencing = True
|
||||
|
||||
def create_invoice(self, amount=100, is_refund=False):
|
||||
invoice = self.env['account.move'].with_context(default_move_type=is_refund and 'out_refund' or 'in_invoice').create({
|
||||
'partner_id': self.partner_axelor.id,
|
||||
'invoice_date': time.strftime('%Y') + '-06-26',
|
||||
'date': time.strftime('%Y') + '-06-26',
|
||||
'invoice_line_ids': [
|
||||
(0, 0, {
|
||||
'product_id': self.product.id,
|
||||
'quantity': 1,
|
||||
'price_unit': is_refund and amount / 4 or amount,
|
||||
})
|
||||
]
|
||||
cls.company_data['default_journal_bank'].write({
|
||||
'outbound_payment_method_ids': [(6, 0, (
|
||||
cls.env.ref('account.account_payment_method_manual_out').id,
|
||||
cls.payment_method_check.id,
|
||||
))],
|
||||
})
|
||||
invoice.post()
|
||||
return invoice
|
||||
|
||||
def create_payment(self, invoices):
|
||||
payment_register = Form(self.env['account.payment'].with_context(active_model='account.move', active_ids=invoices.ids))
|
||||
payment_register.payment_date = time.strftime('%Y') + '-07-15'
|
||||
payment_register.journal_id = self.bank_journal
|
||||
payment_register.payment_method_id = self.payment_method_check
|
||||
payment = payment_register.save()
|
||||
payment.post()
|
||||
return payment
|
||||
def test_inbound_check_manual_sequencing(self):
|
||||
''' Test the check generation for customer invoices. '''
|
||||
nb_invoices_to_test = INV_LINES_PER_STUB + 1
|
||||
|
||||
def test_print_check(self):
|
||||
# Make a payment for 10 invoices and 5 credit notes
|
||||
invoices = self.env['account.move']
|
||||
for i in range(0, 15):
|
||||
invoices |= self.create_invoice(is_refund=(i % 3 == 0))
|
||||
payment = self.create_payment(invoices)
|
||||
self.assertEqual(all(payment.mapped('check_amount_in_words')), True, 'The amount in words is not set on all the payments')
|
||||
self.assertEqual(all(payment.mapped('check_number')), True, 'The check number is not set on all the payments')
|
||||
self.company_data['default_journal_bank'].write({
|
||||
'check_manual_sequencing': True,
|
||||
'check_next_number': 42,
|
||||
})
|
||||
|
||||
# Check the data generated for the report
|
||||
self.env.ref('base.main_company').write({'account_check_printing_multi_stub': True})
|
||||
# Create 10 customer invoices.
|
||||
out_invoices = self.env['account.move'].create([{
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'date': '2017-01-01',
|
||||
'invoice_date': '2017-01-01',
|
||||
'invoice_line_ids': [(0, 0, {'product_id': self.product_a.id, 'price_unit': 100.0})]
|
||||
} for i in range(nb_invoices_to_test)])
|
||||
out_invoices.post()
|
||||
|
||||
# Create a single payment.
|
||||
payment = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=out_invoices.ids).create({
|
||||
'group_payment': True,
|
||||
'payment_method_id': self.payment_method_check.id,
|
||||
})._create_payments()
|
||||
|
||||
# Check created payment.
|
||||
self.assertRecordValues(payment, [{
|
||||
'payment_method_id': self.payment_method_check.id,
|
||||
'check_amount_in_words': payment.currency_id.amount_to_text(100.0 * nb_invoices_to_test),
|
||||
'check_number': '00042',
|
||||
}])
|
||||
|
||||
# Check pages.
|
||||
self.company_data['company'].account_check_printing_multi_stub = True
|
||||
report_pages = payment._check_get_pages()
|
||||
self.assertEqual(len(report_pages), int(math.ceil(len(payment.reconciled_invoice_ids) / INV_LINES_PER_STUB)))
|
||||
self.env.ref('base.main_company').write({'account_check_printing_multi_stub': False})
|
||||
self.assertEqual(len(report_pages), int(math.ceil(len(out_invoices) / INV_LINES_PER_STUB)))
|
||||
|
||||
self.company_data['company'].account_check_printing_multi_stub = False
|
||||
report_pages = payment._check_get_pages()
|
||||
self.assertEqual(len(report_pages), 1)
|
||||
|
||||
def test_from_register(self):
|
||||
invoices = self.env['account.move']
|
||||
for i in range(0, 3):
|
||||
invoices |= self.create_invoice(is_refund=(i % 3 == 0))
|
||||
payment = self.create_payment(invoices)
|
||||
self.assertEqual(all(payment.mapped('check_amount_in_words')), True, 'The amount in words is not set on all the payments')
|
||||
def test_outbound_check_manual_sequencing(self):
|
||||
''' Test the check generation for refunds. '''
|
||||
nb_invoices_to_test = INV_LINES_PER_STUB + 1
|
||||
|
||||
self.company_data['default_journal_bank'].write({
|
||||
'check_manual_sequencing': True,
|
||||
'check_next_number': 42,
|
||||
})
|
||||
|
||||
# Create 10 refunds.
|
||||
out_refunds = self.env['account.move'].create([{
|
||||
'move_type': 'out_refund',
|
||||
'partner_id': self.partner_a.id,
|
||||
'date': '2017-01-01',
|
||||
'invoice_date': '2017-01-01',
|
||||
'invoice_line_ids': [(0, 0, {'product_id': self.product_a.id, 'price_unit': 100.0})]
|
||||
} for i in range(nb_invoices_to_test)])
|
||||
out_refunds.post()
|
||||
|
||||
# Create a single payment.
|
||||
payment = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=out_refunds.ids).create({
|
||||
'group_payment': True,
|
||||
'payment_method_id': self.payment_method_check.id,
|
||||
})._create_payments()
|
||||
|
||||
# Check created payment.
|
||||
self.assertRecordValues(payment, [{
|
||||
'payment_method_id': self.payment_method_check.id,
|
||||
'check_amount_in_words': payment.currency_id.amount_to_text(100.0 * nb_invoices_to_test),
|
||||
'check_number': '00042',
|
||||
}])
|
||||
|
||||
# Check pages.
|
||||
self.company_data['company'].account_check_printing_multi_stub = True
|
||||
report_pages = payment._check_get_pages()
|
||||
self.assertEqual(len(report_pages), int(math.ceil(len(out_refunds) / INV_LINES_PER_STUB)))
|
||||
|
||||
self.company_data['company'].account_check_printing_multi_stub = False
|
||||
report_pages = payment._check_get_pages()
|
||||
self.assertEqual(len(report_pages), 1)
|
||||
|
||||
@@ -5,17 +5,16 @@
|
||||
<field name="model">account.payment</field>
|
||||
<field name="inherit_id" ref="account.view_account_payment_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='post']" position="before">
|
||||
<xpath expr="//button[@name='action_post']" position="before">
|
||||
<button name="print_checks" class="oe_highlight" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('state', '!=', 'posted')]}" string="Print Check" type="object"/>
|
||||
<button name="unmark_sent" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('state', '!=', 'sent')]}" string="Unmark Sent" type="object"/>
|
||||
<button name="set_check_amount_in_words" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('check_amount_in_words', '!=', False)]}" string="Reset Amount in Words" type="object" groups="base.group_no_one"/>
|
||||
<button name="action_unmark_sent" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('is_move_sent', '=', False)]}" string="Unmark Sent" type="object"/>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='amount_div']" position="after">
|
||||
<field name="check_amount_in_words" attrs="{'invisible': [('payment_method_code', '!=', 'check_printing')], 'readonly': [('state', '!=', 'draft')]}" groups="base.group_no_one"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='communication']" position="after">
|
||||
<field name="check_manual_sequencing" invisible="1" readonly="1"/>
|
||||
<field name="check_number" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('check_number', '=', '0')]}"/>
|
||||
<xpath expr="//field[@name='ref']" position="after">
|
||||
<field name="check_manual_sequencing" invisible="1"/>
|
||||
<field name="check_number" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('check_number', '=', False)]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -21,7 +21,7 @@ class PrintPreNumberedChecks(models.TransientModel):
|
||||
def print_checks(self):
|
||||
check_number = int(self.next_check_number)
|
||||
payments = self.env['account.payment'].browse(self.env.context['payment_ids'])
|
||||
payments.filtered(lambda r: r.state == 'draft').post()
|
||||
payments.filtered(lambda r: r.state == 'draft').action_post()
|
||||
payments.filtered(lambda r: r.state not in ('sent', 'cancelled')).write({'state': 'sent'})
|
||||
for payment in payments:
|
||||
payment.check_number = check_number
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
|
||||
<!-- Reference. -->
|
||||
<ram:BuyerOrderReferencedDocument>
|
||||
<ram:IssuerAssignedID t-esc="'%s: %s' % (record.name, record.invoice_payment_ref) if record.invoice_payment_ref else record.name"/>
|
||||
<ram:IssuerAssignedID t-esc="'%s: %s' % (record.name, record.payment_reference) if record.payment_reference else record.name"/>
|
||||
</ram:BuyerOrderReferencedDocument>
|
||||
</ram:ApplicableHeaderTradeAgreement>
|
||||
|
||||
@@ -184,9 +184,9 @@
|
||||
<ram:ApplicableHeaderTradeSettlement>
|
||||
|
||||
<!-- Bank account. -->
|
||||
<ram:SpecifiedTradeSettlementPaymentMeans t-if="record.invoice_partner_bank_id.acc_type == 'iban'">
|
||||
<ram:SpecifiedTradeSettlementPaymentMeans t-if="record.partner_bank_id.acc_type == 'iban'">
|
||||
<ram:PayeePartyCreditorFinancialAccount>
|
||||
<ram:IBANID t-esc="record.invoice_partner_bank_id.sanitized_acc_number"/>
|
||||
<ram:IBANID t-esc="record.partner_bank_id.sanitized_acc_number"/>
|
||||
</ram:PayeePartyCreditorFinancialAccount>
|
||||
</ram:SpecifiedTradeSettlementPaymentMeans>
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ class AccountMove(models.Model):
|
||||
# Name.
|
||||
elements = tree.xpath('//ram:BuyerOrderReferencedDocument/ram:IssuerAssignedID', namespaces=tree.nsmap)
|
||||
if elements:
|
||||
invoice_form.invoice_payment_ref = elements[0].text
|
||||
invoice_form.payment_reference = elements[0].text
|
||||
|
||||
# Comment.
|
||||
elements = tree.xpath('//ram:IncludedNote/ram:Content', namespaces=tree.nsmap)
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
<div t-if="invoice.transaction_ids and invoice.amount_total and not success and not error" class="o_account_payment_tx_status" t-att-data-invoice-id="invoice.id">
|
||||
<t t-call="payment.payment_confirmation_status">
|
||||
<t t-set="payment_tx_id" t-value="invoice.get_portal_last_transaction()"/>
|
||||
<t t-set="reference" t-value="invoice.invoice_payment_ref"/>
|
||||
<t t-set="reference" t-value="invoice.payment_reference"/>
|
||||
</t>
|
||||
</div>
|
||||
<t t-set="tx_ids" t-value="invoice.transaction_ids.filtered(lambda tx: tx.state in ('authorized', 'done'))"/>
|
||||
|
||||
@@ -26,7 +26,7 @@ class TestSEPAQRCode(AccountTestInvoicingCommon):
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': cls.partner_a.id,
|
||||
'currency_id': cls.env.ref('base.EUR').id,
|
||||
'invoice_partner_bank_id': cls.acc_sepa_iban.id,
|
||||
'partner_bank_id': cls.acc_sepa_iban.id,
|
||||
'company_id': cls.company_data['company'].id,
|
||||
'invoice_line_ids': [
|
||||
(0, 0, {'quantity': 1, 'price_unit': 100})
|
||||
@@ -43,12 +43,12 @@ class TestSEPAQRCode(AccountTestInvoicingCommon):
|
||||
self.sepa_qr_invoice.generate_qr_code()
|
||||
|
||||
# Using a non-SEPA IBAN shouldn't
|
||||
self.sepa_qr_invoice.invoice_partner_bank_id = self.acc_non_sepa_iban
|
||||
self.sepa_qr_invoice.partner_bank_id = self.acc_non_sepa_iban
|
||||
with self.assertRaises(UserError, msg="It shouldn't be possible to generate a SEPA QR-code for IBAN of countries outside SEPA zone."):
|
||||
self.sepa_qr_invoice.generate_qr_code()
|
||||
|
||||
# Changing the currency should break it as well
|
||||
self.sepa_qr_invoice.invoice_partner_bank_id = self.acc_sepa_iban
|
||||
self.sepa_qr_invoice.partner_bank_id = self.acc_sepa_iban
|
||||
self.sepa_qr_invoice.currency_id = self.env.ref('base.USD').id
|
||||
with self.assertRaises(UserError, msg="It shouldn't be possible to generate a SEPA QR-code for another currency as EUR."):
|
||||
self.sepa_qr_invoice.generate_qr_code()
|
||||
|
||||
@@ -81,10 +81,10 @@ class StockMoveInvoice(AccountTestCommon):
|
||||
self.invoice.post()
|
||||
self.journal = self.AccountJournal.search([('type', '=', 'cash'), ('company_id', '=', self.sale_prepaid.company_id.id)], limit=1)
|
||||
|
||||
register_payments = self.env['account.payment.register'].with_context(active_ids=self.invoice.ids).create({
|
||||
register_payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=self.invoice.ids).create({
|
||||
'journal_id': self.journal.id,
|
||||
})
|
||||
register_payments.create_payments()
|
||||
register_payments._create_payments()
|
||||
|
||||
# Check the SO after paying the invoice
|
||||
self.assertNotEqual(self.sale_prepaid.invoice_count, 0, 'order not invoiced')
|
||||
|
||||
@@ -482,7 +482,7 @@ class HrExpense(models.Model):
|
||||
'partner_id': expense.employee_id.address_home_id.commercial_partner_id.id,
|
||||
'partner_type': 'supplier',
|
||||
'journal_id': journal.id,
|
||||
'payment_date': expense.date,
|
||||
'date': expense.date,
|
||||
'state': 'reconciled',
|
||||
'currency_id': expense.currency_id.id if different_currency else journal_currency.id,
|
||||
'amount': abs(total_amount_currency) if different_currency else abs(total_amount),
|
||||
|
||||
@@ -92,14 +92,14 @@ class HrExpenseSheetRegisterPaymentWizard(models.TransientModel):
|
||||
'partner_type': 'supplier',
|
||||
'payment_type': 'outbound',
|
||||
'partner_id': self.partner_id.id,
|
||||
'partner_bank_account_id': self.partner_bank_account_id.id,
|
||||
'partner_bank_id': self.partner_bank_account_id.id,
|
||||
'journal_id': self.journal_id.id,
|
||||
'company_id': self.company_id.id,
|
||||
'payment_method_id': self.payment_method_id.id,
|
||||
'amount': self.amount,
|
||||
'currency_id': self.currency_id.id,
|
||||
'payment_date': self.payment_date,
|
||||
'communication': self.communication
|
||||
'date': self.payment_date,
|
||||
'ref': self.communication
|
||||
}
|
||||
|
||||
def expense_post_payment(self):
|
||||
@@ -112,7 +112,7 @@ class HrExpenseSheetRegisterPaymentWizard(models.TransientModel):
|
||||
|
||||
# Create payment and post it
|
||||
payment = self.env['account.payment'].create(self._get_payment_vals())
|
||||
payment.post()
|
||||
payment.action_post()
|
||||
|
||||
# Log the payment in the chatter
|
||||
body = (_("A payment of %s %s with the reference <a href='/mail/view?%s'>%s</a> related to your expense %s has been made.") % (payment.amount, payment.currency_id.symbol, url_encode({'model': 'account.payment', 'res_id': payment.id}), payment.name, expense_sheet.name))
|
||||
@@ -120,7 +120,7 @@ class HrExpenseSheetRegisterPaymentWizard(models.TransientModel):
|
||||
|
||||
# Reconcile the payment and the expense, i.e. lookup on the payable account move lines
|
||||
account_move_lines_to_reconcile = self.env['account.move.line']
|
||||
for line in payment.move_line_ids + expense_sheet.account_move_id.line_ids:
|
||||
for line in payment.line_ids + expense_sheet.account_move_id.line_ids:
|
||||
if line.account_id.internal_type == 'payable' and not line.reconciled:
|
||||
account_move_lines_to_reconcile |= line
|
||||
account_move_lines_to_reconcile.reconcile()
|
||||
|
||||
@@ -12,5 +12,6 @@
|
||||
<field name="property_tax_payable_account_id" ref="a4512"/>
|
||||
<field name="property_tax_receivable_account_id" ref="a4112"/>
|
||||
<field name="default_pos_receivable_account_id" ref="a4001" />
|
||||
<field name="account_journal_suspense_account_id" ref="a4990"/>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -7,11 +7,6 @@ from odoo import api, models, fields
|
||||
class AccountChartTemplate(models.Model):
|
||||
_inherit = 'account.chart.template'
|
||||
|
||||
def get_countries_posting_at_bank_rec(self):
|
||||
rslt = super(AccountChartTemplate, self).get_countries_posting_at_bank_rec()
|
||||
rslt.append('BE')
|
||||
return rslt
|
||||
|
||||
@api.model
|
||||
def _prepare_all_journals(self, acc_template_ref, company, journals_dict=None):
|
||||
journal_data = super(AccountChartTemplate, self)._prepare_all_journals(
|
||||
|
||||
@@ -53,7 +53,7 @@ class AccountMove(models.Model):
|
||||
invoice_form.ref = elements[0].text
|
||||
elements = tree.xpath('//cbc:InstructionID', namespaces=namespaces)
|
||||
if elements:
|
||||
invoice_form.invoice_payment_ref = elements[0].text
|
||||
invoice_form.payment_reference = elements[0].text
|
||||
|
||||
# Dates
|
||||
elements = tree.xpath('//cbc:IssueDate', namespaces=namespaces)
|
||||
|
||||
@@ -28,7 +28,7 @@ class AccountMove(models.Model):
|
||||
l10n_ch_isr_sent = fields.Boolean(default=False, help="Boolean value telling whether or not the ISR corresponding to this invoice has already been printed or sent by mail.")
|
||||
l10n_ch_currency_name = fields.Char(related='currency_id.name', readonly=True, string="Currency Name", help="The name of this invoice's currency") #This field is used in the "invisible" condition field of the 'Print ISR' button.
|
||||
|
||||
@api.depends('invoice_partner_bank_id.l10n_ch_isr_subscription_eur', 'invoice_partner_bank_id.l10n_ch_isr_subscription_chf')
|
||||
@api.depends('partner_bank_id.l10n_ch_isr_subscription_eur', 'partner_bank_id.l10n_ch_isr_subscription_chf')
|
||||
def _compute_l10n_ch_isr_subscription(self):
|
||||
""" Computes the ISR subscription identifying your company or the bank that allows to generate ISR. And formats it accordingly"""
|
||||
def _format_isr_subscription(isr_subscription):
|
||||
@@ -46,11 +46,11 @@ class AccountMove(models.Model):
|
||||
for record in self:
|
||||
record.l10n_ch_isr_subscription = False
|
||||
record.l10n_ch_isr_subscription_formatted = False
|
||||
if record.invoice_partner_bank_id:
|
||||
if record.partner_bank_id:
|
||||
if record.currency_id.name == 'EUR':
|
||||
isr_subscription = record.invoice_partner_bank_id.l10n_ch_isr_subscription_eur
|
||||
isr_subscription = record.partner_bank_id.l10n_ch_isr_subscription_eur
|
||||
elif record.currency_id.name == 'CHF':
|
||||
isr_subscription = record.invoice_partner_bank_id.l10n_ch_isr_subscription_chf
|
||||
isr_subscription = record.partner_bank_id.l10n_ch_isr_subscription_chf
|
||||
else:
|
||||
#we don't format if in another currency as EUR or CHF
|
||||
continue
|
||||
@@ -60,7 +60,7 @@ class AccountMove(models.Model):
|
||||
record.l10n_ch_isr_subscription = _format_isr_subscription_scanline(isr_subscription)
|
||||
record.l10n_ch_isr_subscription_formatted = _format_isr_subscription(isr_subscription)
|
||||
|
||||
@api.depends('name', 'invoice_partner_bank_id.l10n_ch_postal')
|
||||
@api.depends('name', 'partner_bank_id.l10n_ch_postal')
|
||||
def _compute_l10n_ch_isr_number(self):
|
||||
""" The ISR reference number is 27 characters long. The first 12 of them
|
||||
contain the postal account number of this ISR's issuer, removing the zeros
|
||||
@@ -72,8 +72,8 @@ class AccountMove(models.Model):
|
||||
of a recursive modulo 10 on its first 26 characters.
|
||||
"""
|
||||
for record in self:
|
||||
if record.name and record.invoice_partner_bank_id and record.invoice_partner_bank_id.l10n_ch_postal:
|
||||
invoice_issuer_ref = record.invoice_partner_bank_id.l10n_ch_postal.ljust(l10n_ch_ISR_NUMBER_ISSUER_LENGTH, '0')
|
||||
if record.name and record.partner_bank_id and record.partner_bank_id.l10n_ch_postal:
|
||||
invoice_issuer_ref = record.partner_bank_id.l10n_ch_postal.ljust(l10n_ch_ISR_NUMBER_ISSUER_LENGTH, '0')
|
||||
invoice_ref = re.sub('[^\d]', '', record.name)
|
||||
#We only keep the last digits of the sequence number if it is too long
|
||||
invoice_ref = invoice_ref[-l10n_ch_ISR_NUMBER_ISSUER_LENGTH:]
|
||||
@@ -95,7 +95,7 @@ class AccountMove(models.Model):
|
||||
return res
|
||||
|
||||
for record in self:
|
||||
if record.name and record.invoice_partner_bank_id and record.invoice_partner_bank_id.l10n_ch_postal:
|
||||
if record.name and record.partner_bank_id and record.partner_bank_id.l10n_ch_postal:
|
||||
record.l10n_ch_isr_number_spaced = _space_isr_number(record.l10n_ch_isr_number)
|
||||
else:
|
||||
record.l10n_ch_isr_number_spaced = False
|
||||
@@ -103,9 +103,9 @@ class AccountMove(models.Model):
|
||||
|
||||
@api.depends(
|
||||
'currency_id.name', 'amount_residual', 'name',
|
||||
'invoice_partner_bank_id.l10n_ch_postal',
|
||||
'invoice_partner_bank_id.l10n_ch_isr_subscription_eur',
|
||||
'invoice_partner_bank_id.l10n_ch_isr_subscription_chf')
|
||||
'partner_bank_id.l10n_ch_postal',
|
||||
'partner_bank_id.l10n_ch_isr_subscription_eur',
|
||||
'partner_bank_id.l10n_ch_isr_subscription_chf')
|
||||
def _compute_l10n_ch_isr_optical_line(self):
|
||||
""" The optical reading line of the ISR looks like this :
|
||||
left>isr_ref+ bank_ref>
|
||||
@@ -142,16 +142,16 @@ class AccountMove(models.Model):
|
||||
|
||||
@api.depends(
|
||||
'move_type', 'name', 'currency_id.name',
|
||||
'invoice_partner_bank_id.l10n_ch_postal',
|
||||
'invoice_partner_bank_id.l10n_ch_isr_subscription_eur',
|
||||
'invoice_partner_bank_id.l10n_ch_isr_subscription_chf')
|
||||
'partner_bank_id.l10n_ch_postal',
|
||||
'partner_bank_id.l10n_ch_isr_subscription_eur',
|
||||
'partner_bank_id.l10n_ch_isr_subscription_chf')
|
||||
def _compute_l10n_ch_isr_valid(self):
|
||||
"""Returns True if all the data required to generate the ISR are present"""
|
||||
for record in self:
|
||||
record.l10n_ch_isr_valid = record.move_type == 'out_invoice' and\
|
||||
record.name and \
|
||||
record.l10n_ch_isr_subscription and \
|
||||
record.invoice_partner_bank_id.l10n_ch_postal and \
|
||||
record.partner_bank_id.l10n_ch_postal and \
|
||||
record.l10n_ch_currency_name in ['EUR', 'CHF']
|
||||
|
||||
def split_total_amount(self):
|
||||
@@ -171,7 +171,7 @@ class AccountMove(models.Model):
|
||||
"""
|
||||
self.ensure_one()
|
||||
return self.display_qr_code and \
|
||||
self.invoice_partner_bank_id._eligible_for_qr_code('ch_qr', self.partner_id, self.currency_id)
|
||||
self.partner_bank_id._eligible_for_qr_code('ch_qr', self.partner_id, self.currency_id)
|
||||
|
||||
def isr_print(self):
|
||||
""" Triggered by the 'Print ISR' button.
|
||||
|
||||
@@ -62,9 +62,9 @@
|
||||
<t t-if="print_bank">
|
||||
<div id="voucher-for-bank">
|
||||
<p t-if="not invoice.company_id.l10n_ch_isr_preprinted_bank">
|
||||
<t t-esc="invoice.invoice_partner_bank_id.bank_id.name"/><br />
|
||||
<t t-esc="invoice.invoice_partner_bank_id.bank_id.zip"/>
|
||||
<t t-esc="invoice.invoice_partner_bank_id.bank_id.city"/>
|
||||
<t t-esc="invoice.partner_bank_id.bank_id.name"/><br />
|
||||
<t t-esc="invoice.partner_bank_id.bank_id.zip"/>
|
||||
<t t-esc="invoice.partner_bank_id.bank_id.city"/>
|
||||
</p>
|
||||
</div>
|
||||
<!--Zugunsten von/En faveur de/A favore di-->
|
||||
@@ -103,9 +103,9 @@
|
||||
<t t-if="print_bank">
|
||||
<div id="slip-for-bank">
|
||||
<p t-if="not invoice.company_id.l10n_ch_isr_preprinted_bank">
|
||||
<t t-esc="invoice.invoice_partner_bank_id.bank_id.name"/><br />
|
||||
<t t-esc="invoice.invoice_partner_bank_id.bank_id.zip"/>
|
||||
<t t-esc="invoice.invoice_partner_bank_id.bank_id.city"/>
|
||||
<t t-esc="invoice.partner_bank_id.bank_id.name"/><br />
|
||||
<t t-esc="invoice.partner_bank_id.bank_id.zip"/>
|
||||
<t t-esc="invoice.partner_bank_id.bank_id.city"/>
|
||||
</p>
|
||||
</div>
|
||||
<!--Zugunsten von/En faveur de/A favore di-->
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<xpath expr="//div[@id='qrcode']" position="after">
|
||||
<link type="text/css" rel="stylesheet" href="/l10n_ch/static/src/scss/report_swissqr.css"/>
|
||||
<div class="swissqr_content" t-if="o.display_swiss_qr_code()">
|
||||
<div class="swissqr_body" t-if="o.invoice_partner_bank_id._eligible_for_qr_code('ch_qr', o.partner_id, o.currency_id)">
|
||||
<div class="swissqr_body" t-if="o.partner_bank_id._eligible_for_qr_code('ch_qr', o.partner_id, o.currency_id)">
|
||||
<div id="title_zone" class="swissqr_column_left title_zone">
|
||||
<span class="swissqr_text title title_zone">QR-bill payment part</span><br/>
|
||||
</div>
|
||||
@@ -19,11 +19,11 @@
|
||||
<span class="swissqr_text content">Credit transfer</span>
|
||||
</div>
|
||||
|
||||
<img class="swissqr" t-att-src="o.invoice_partner_bank_id.build_qr_code_url(o.amount_residual, o.ref or o.name, o.invoice_payment_ref, o.currency_id, o.partner_id, 'ch_qr')"/>
|
||||
<img class="swissqr" t-att-src="o.partner_bank_id.build_qr_code_url(o.amount_residual, o.ref or o.name, o.payment_reference, o.currency_id, o.partner_id, 'ch_qr')"/>
|
||||
|
||||
<div id="indications_zone" class="swissqr_column_right indication_zone">
|
||||
<span class="swissqr_text title">Account</span><br/>
|
||||
<span class="swissqr_text content" t-field="o.invoice_partner_bank_id.acc_number"/><br/>
|
||||
<span class="swissqr_text content" t-field="o.partner_bank_id.acc_number"/><br/>
|
||||
|
||||
<span class="swissqr_text title">Creditor</span><br/>
|
||||
<span class="swissqr_text content" t-field="o.company_id.name"/><br/>
|
||||
@@ -33,11 +33,11 @@
|
||||
<span class="swissqr_text content" t-field="o.company_id.city"/><br/>
|
||||
|
||||
<span class="swissqr_text title">Reference number</span><br/>
|
||||
<t t-if="o.invoice_partner_bank_id.currency_id.name == 'EUR'">
|
||||
<span class="swissqr_text content" t-field="o.invoice_partner_bank_id.l10n_ch_isr_subscription_eur"/><br/>
|
||||
<t t-if="o.partner_bank_id.currency_id.name == 'EUR'">
|
||||
<span class="swissqr_text content" t-field="o.partner_bank_id.l10n_ch_isr_subscription_eur"/><br/>
|
||||
</t>
|
||||
<t t-if="o.invoice_partner_bank_id.currency_id.name == 'CHF'">
|
||||
<span class="swissqr_text content" t-field="o.invoice_partner_bank_id.l10n_ch_isr_subscription_chf"/><br/>
|
||||
<t t-if="o.partner_bank_id.currency_id.name == 'CHF'">
|
||||
<span class="swissqr_text content" t-field="o.partner_bank_id.l10n_ch_isr_subscription_chf"/><br/>
|
||||
</t>
|
||||
|
||||
<span class="swissqr_text title">Additional information</span><br/>
|
||||
|
||||
@@ -28,9 +28,9 @@ class TestSwissQRCode(AccountTestInvoicingCommon):
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': cls.partner_a.id,
|
||||
'currency_id': cls.env.ref('base.CHF').id,
|
||||
'invoice_partner_bank_id': cls.swiss_iban.id,
|
||||
'partner_bank_id': cls.swiss_iban.id,
|
||||
'company_id': cls.company_data['company'].id,
|
||||
'invoice_payment_ref': "Papa a vu le fifi de lolo",
|
||||
'payment_reference': "Papa a vu le fifi de lolo",
|
||||
'invoice_line_ids': [
|
||||
(0, 0, {'quantity': 1, 'price_unit': 100})
|
||||
],
|
||||
@@ -61,13 +61,13 @@ class TestSwissQRCode(AccountTestInvoicingCommon):
|
||||
self.ch_qr_invoice.generate_qr_code()
|
||||
|
||||
# Now, check with a QR-IBAN as the payment account
|
||||
self.ch_qr_invoice.invoice_partner_bank_id = self.swiss_qr_iban
|
||||
self.ch_qr_invoice.partner_bank_id = self.swiss_qr_iban
|
||||
|
||||
with self.assertRaises(UserError, msg="It shouldn't be possible to generate a Swiss QR-cde for a QR-IBAN without giving it a valid QR-reference as payment reference."):
|
||||
self.ch_qr_invoice.generate_qr_code()
|
||||
|
||||
# Assigning a QR reference should fix it
|
||||
self.ch_qr_invoice.invoice_payment_ref = '210000000003139471430009017'
|
||||
self.ch_qr_invoice.payment_reference = '210000000003139471430009017'
|
||||
|
||||
def test_ch_qr_code_detection(self):
|
||||
""" Checks Swiss QR-code auto-detection when no specific QR-method
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='invoice_sent']" position="after">
|
||||
<xpath expr="//field[@name='is_move_sent']" position="after">
|
||||
<field name="l10n_ch_isr_sent" invisible="1"/>
|
||||
<field name="l10n_ch_currency_name" invisible="1" readonly="1"/>
|
||||
</xpath>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<field name="invoice_date" optional="show"/>
|
||||
<field name="invoice_date_due" optional="show"/>
|
||||
<field name="date" string="Accounting Date" optional="show"/>
|
||||
<field name="invoice_payment_ref" optional="hide"/>
|
||||
<field name="payment_reference" optional="hide"/>
|
||||
<field name="invoice_user_id" optional="show" invisible="context.get('default_move_type') not in ('out_invoice', 'out_refund','out_receipt')" string="Sales Person"/>
|
||||
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}" optional="show"/>
|
||||
<field name="invoice_origin" optional="show" string="Source Document"/>
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', obj().env.company.id)]"/>
|
||||
<field name="date" eval="time.strftime('%Y')+'-01-01'"/>
|
||||
<field name="name" eval="'BNK/%s/00001' % time.strftime('%Y')"/>
|
||||
<field name="balance_end_real">8998.2</field>
|
||||
<field name="balance_end_real">9944.87</field>
|
||||
<field name="balance_start">5103.0</field>
|
||||
</record>
|
||||
|
||||
@@ -15,7 +14,7 @@
|
||||
<field name="ref"></field>
|
||||
<field name="statement_id" ref="l10n_generic_coa.demo_bank_statement_1"/>
|
||||
<field name="sequence">1</field>
|
||||
<field name="name" eval="'INV/%s/00002 and INV/%s/00003' % (time.strftime('%Y'), time.strftime('%Y'))"/>
|
||||
<field name="payment_ref" eval="'INV/%s/00002 and INV/%s/00003' % (time.strftime('%Y'), time.strftime('%Y'))"/>
|
||||
<field name="journal_id" model="account.journal" search="[
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', obj().env.company.id)]"/>
|
||||
@@ -28,7 +27,7 @@
|
||||
<field name="ref"></field>
|
||||
<field name="statement_id" ref="l10n_generic_coa.demo_bank_statement_1"/>
|
||||
<field name="sequence">2</field>
|
||||
<field name="name">Bank fees</field>
|
||||
<field name="payment_ref">Bank fees</field>
|
||||
<field name="journal_id" model="account.journal" search="[
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', obj().env.company.id)]"/>
|
||||
@@ -40,7 +39,7 @@
|
||||
<field name="ref"></field>
|
||||
<field name="statement_id" ref="l10n_generic_coa.demo_bank_statement_1"/>
|
||||
<field name="sequence">3</field>
|
||||
<field name="name">Prepayment</field>
|
||||
<field name="payment_ref">Prepayment</field>
|
||||
<field name="journal_id" model="account.journal" search="[
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', obj().env.company.id)]"/>
|
||||
@@ -53,7 +52,7 @@
|
||||
<field name="ref"></field>
|
||||
<field name="statement_id" ref="l10n_generic_coa.demo_bank_statement_1"/>
|
||||
<field name="sequence">4</field>
|
||||
<field name="name" eval="'First 2000 $ of invoice %s/00001' % time.strftime('%Y')"/>
|
||||
<field name="payment_ref" eval="'First 2000 $ of invoice %s/00001' % time.strftime('%Y')"/>
|
||||
<field name="journal_id" model="account.journal" search="[
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', obj().env.company.id)]"/>
|
||||
@@ -66,23 +65,19 @@
|
||||
<field name="ref"></field>
|
||||
<field name="statement_id" ref="l10n_generic_coa.demo_bank_statement_1"/>
|
||||
<field name="sequence">5</field>
|
||||
<field name="name">Last Year Interests</field>
|
||||
<field name="payment_ref">Last Year Interests</field>
|
||||
<field name="journal_id" model="account.journal" search="[
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', obj().env.company.id)]"/>
|
||||
<field name="amount">102.78</field>
|
||||
<field name="date" eval="time.strftime('%Y')+'-01-01'"/>
|
||||
<field name="account_id" model="account.account" search="[
|
||||
('user_type_id', '=', ref('account.data_account_type_revenue')),
|
||||
('tag_ids', 'in', [ref('account.account_tag_financing')]),
|
||||
('company_id', '=', obj().env.company.id)]"/>
|
||||
</record>
|
||||
|
||||
<record id="demo_bank_statement_line_6" model="account.bank.statement.line">
|
||||
<field name="ref"></field>
|
||||
<field name="statement_id" ref="l10n_generic_coa.demo_bank_statement_1"/>
|
||||
<field name="sequence">1</field>
|
||||
<field name="name" eval="'INV/'+time.strftime('%Y')+'/00002'"/>
|
||||
<field name="payment_ref" eval="'INV/'+time.strftime('%Y')+'/00002'"/>
|
||||
<field name="journal_id" model="account.journal" search="[
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', obj().env.company.id)]"/>
|
||||
@@ -95,7 +90,7 @@
|
||||
<field name="ref"></field>
|
||||
<field name="statement_id" ref="l10n_generic_coa.demo_bank_statement_1"/>
|
||||
<field name="sequence">7</field>
|
||||
<field name="name">R:9772938 10/07 AX 9415116318 T:5 BRT: 100,00€ C/ croip</field>
|
||||
<field name="payment_ref">R:9772938 10/07 AX 9415116318 T:5 BRT: 100,00€ C/ croip</field>
|
||||
<field name="journal_id" model="account.journal" search="[
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', obj().env.company.id)]"/>
|
||||
@@ -104,10 +99,6 @@
|
||||
<field name="partner_id" ref="base.res_partner_2"/>
|
||||
</record>
|
||||
|
||||
<function model="account.bank.statement.line" name="fast_counterpart_creation">
|
||||
<value eval="[ref('demo_bank_statement_line_5')]"/>
|
||||
</function>
|
||||
|
||||
<!-- Also create a pdf attachment in chatter -->
|
||||
|
||||
<record id="ir_attachment_bank_statement_1" model="ir.attachment">
|
||||
@@ -131,5 +122,8 @@
|
||||
<field name="attachment_ids" eval="[(6, 0, [ref('ir_attachment_bank_statement_1')])]"/>
|
||||
</record>
|
||||
|
||||
<function model="account.bank.statement" name="button_post">
|
||||
<value eval="[ref('demo_bank_statement_1')]"/>
|
||||
</function>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
<!-- Demo of at(Tax liability on advances). Advance payment need to consideration for which invoices have not been issued in the same month.-->
|
||||
<record id="demo_payment_at" model="account.payment">
|
||||
<field name="partner_id" ref="l10n_in.res_partner_registered_customer"/>
|
||||
|
||||
<field name="partner_type">customer</field>
|
||||
<field name="amount">10000</field>
|
||||
<field name="payment_type">inbound</field>
|
||||
<field name="payment_date" eval="time.strftime('%Y-%m')+'-01'"/>
|
||||
<field name="date" eval="time.strftime('%Y-%m')+'-01'"/>
|
||||
<field name="journal_id" model="account.journal"
|
||||
eval="obj().search([
|
||||
('type', '=', 'cash'),
|
||||
@@ -18,7 +17,7 @@
|
||||
('company_id', '=', obj().env.company.id)], limit=1).inbound_payment_method_ids[0].id"/>
|
||||
</record>
|
||||
|
||||
<function model="account.payment" name="post">
|
||||
<function model="account.payment" name="action_post">
|
||||
<value eval="[ref('demo_payment_at')]"/>
|
||||
</function>
|
||||
</odoo>
|
||||
@@ -65,7 +65,7 @@ class L10nInPaymentReport(models.AbstractModel):
|
||||
tax.amount AS tax_rate,
|
||||
am.partner_id,
|
||||
am.amount_total AS payment_amount,
|
||||
ap.journal_id,
|
||||
am.journal_id,
|
||||
aml.currency_id,
|
||||
(CASE WHEN ps.l10n_in_tin IS NOT NULL
|
||||
THEN concat(ps.l10n_in_tin,'-',ps.name)
|
||||
@@ -137,7 +137,7 @@ class AdvancesPaymentReport(models.Model):
|
||||
def _select(self):
|
||||
select_str = super(AdvancesPaymentReport, self)._select()
|
||||
select_str += """,
|
||||
ap.payment_date as date,
|
||||
am.date as date,
|
||||
(SELECT sum(amount) FROM account_partial_reconcile AS apr
|
||||
WHERE (apr.credit_move_id = aml.id OR apr.debit_move_id = aml.id)
|
||||
AND (to_char(apr.max_date, 'MM-YYYY') = to_char(aml.date_maturity, 'MM-YYYY'))
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
<template id="account_invoice_it_FatturaPA_export">
|
||||
<t t-set="currency" t-value="record.currency_id or record.company_currency_id"/>
|
||||
<t t-set="bank" t-value="record.invoice_partner_bank_id"/>
|
||||
<t t-set="bank" t-value="record.partner_bank_id"/>
|
||||
<p:FatturaElettronica t-att-versione="formato_trasmissione" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:p="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2 http://www.fatturapa.gov.it/export/fatturazione/sdi/fatturapa/v1.2/Schema_del_file_xml_FatturaPA_versione_1.2.xsd">
|
||||
<FatturaElettronicaHeader>
|
||||
<DatiTrasmissione>
|
||||
@@ -168,14 +168,14 @@
|
||||
<t t-set="payments" t-value="record.line_ids.filtered(lambda line: line.account_id.user_type_id.type in ('receivable', 'payable'))"/>
|
||||
<CondizioniPagamento><t t-if="len(payments) == 1">TP02</t><t t-else="">TP01</t></CondizioniPagamento>
|
||||
<DettaglioPagamento>
|
||||
<t t-set="company_bank_account" t-value="record.invoice_partner_bank_id"/>
|
||||
<t t-set="company_bank_account" t-value="record.partner_bank_id"/>
|
||||
<ModalitaPagamento t-if="company_bank_account">MP05</ModalitaPagamento>
|
||||
<DataScadenzaPagamento t-esc="format_date(record.invoice_date_due)"/>
|
||||
<ImportoPagamento t-esc="format_numbers_two(record.amount_total)"/>
|
||||
<IstitutoFinanziario t-if="company_bank_account.bank_id" t-esc="company_bank_account.bank_id.name[:80]"/>
|
||||
<IBAN t-if="company_bank_account.acc_type == 'iban'" t-esc="company_bank_account.sanitized_acc_number"/>
|
||||
<BIC t-if="company_bank_account.acc_type == 'bank' and company_bank_account.bank_id.bic" t-esc="company_bank_account.bank_id.bic"/>
|
||||
<CodicePagamento t-esc="record.invoice_payment_ref[:60]"/>
|
||||
<CodicePagamento t-esc="record.payment_reference[:60]"/>
|
||||
</DettaglioPagamento>
|
||||
</DatiPagamento>
|
||||
<Allegati t-if="pdf">
|
||||
|
||||
@@ -384,7 +384,7 @@ class AccountMove(models.Model):
|
||||
# Numbering attributed by the transmitter. <1.1.2>
|
||||
elements = tree.xpath('//ProgressivoInvio')
|
||||
if elements:
|
||||
invoice_form.invoice_payment_ref = elements[0].text
|
||||
invoice_form.payment_reference = elements[0].text
|
||||
|
||||
elements = body_tree.xpath('.//DatiGeneraliDocumento//Numero')
|
||||
if elements:
|
||||
@@ -490,7 +490,7 @@ class AccountMove(models.Model):
|
||||
else:
|
||||
bank = self.env['res.partner.bank'].search([('acc_number', '=', elements[0].text)])
|
||||
if bank:
|
||||
invoice_form.invoice_partner_bank_id = bank
|
||||
invoice_form.partner_bank_id = bank
|
||||
else:
|
||||
message_to_log.append("%s<br/>%s" % (
|
||||
_("Bank account not found, useful informations from XML file:"),
|
||||
|
||||
@@ -7,11 +7,6 @@ from odoo import api, models, fields
|
||||
class AccountChartTemplate(models.Model):
|
||||
_inherit = 'account.chart.template'
|
||||
|
||||
def get_countries_posting_at_bank_rec(self):
|
||||
rslt = super(AccountChartTemplate, self).get_countries_posting_at_bank_rec()
|
||||
rslt.append('LU')
|
||||
return rslt
|
||||
|
||||
@api.model
|
||||
def _prepare_all_journals(self, acc_template_ref, company, journals_dict=None):
|
||||
journal_data = super(AccountChartTemplate, self)._prepare_all_journals(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from . import account_tax
|
||||
from . import account_move
|
||||
from . import account_journal
|
||||
from . import account_chart_template
|
||||
from . import l10n_latam_identification_type
|
||||
from . import res_partner
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
from odoo import models, api
|
||||
|
||||
|
||||
class AccountJournal(models.Model):
|
||||
_inherit = "account.journal"
|
||||
|
||||
@api.model
|
||||
def _get_sequence_prefix(self, code, refund=False):
|
||||
"""For peruvian companies we can not use sequences with **/** due to the edi generation which need in the
|
||||
sequence a plain text ended by a *-* and the length of this prefix"""
|
||||
if self.env.company.country_id != self.env.ref('base.pe'):
|
||||
return super()._get_sequence_prefix(code, refund=refund)
|
||||
prefix = code.upper()
|
||||
if len(prefix) > 3:
|
||||
prefix = prefix[:3]
|
||||
prefix = prefix.ljust(3, 'X')
|
||||
if refund:
|
||||
prefix = 'R' + prefix[:-1]
|
||||
return prefix + '-'
|
||||
|
||||
@api.model
|
||||
def _create_sequence(self, vals, refund=False):
|
||||
"""For Peruvian companies, a number reset by date do not make sense due to the fact that we can not use a free
|
||||
prefix the format does not have enough space to put a year on it or any other char, then with this approach we
|
||||
are avoiding the default behavior there."""
|
||||
res = super()._create_sequence(vals, refund=refund)
|
||||
# NOTE: the self element is coming filled just when write and not on create (which is Ok)
|
||||
journal_type = self.type if not vals.get('type') else vals.get('type')
|
||||
if self.env.company.country_id == self.env.ref('base.pe') or journal_type in ['sale', 'purchase']:
|
||||
res.write({'use_date_range': False})
|
||||
return res
|
||||
@@ -53,14 +53,14 @@ class TestMembership(TestMembershipCommon):
|
||||
|
||||
# the invoice is paid -> customer goes to paid status
|
||||
bank_journal = self.env['account.journal'].create({'name': 'Bank', 'type': 'bank', 'code': 'BNK67'})
|
||||
self.env['account.payment'].create({
|
||||
'payment_method_id': self.env.ref("account.account_payment_method_manual_in").id,
|
||||
'payment_type': 'inbound',
|
||||
'invoice_ids': [(6, False, invoice.ids)],
|
||||
'amount': 86.25,
|
||||
'journal_id': bank_journal.id,
|
||||
'partner_type': 'customer',
|
||||
}).post()
|
||||
|
||||
payment = self.env['account.payment.register']\
|
||||
.with_context(active_model='account.move', active_ids=invoice.ids)\
|
||||
.create({
|
||||
'amount': 86.25,
|
||||
'journal_id': bank_journal.id,
|
||||
})\
|
||||
._create_payments()
|
||||
|
||||
self.assertEqual(
|
||||
self.partner_1.membership_state, 'paid',
|
||||
|
||||
@@ -88,7 +88,7 @@ class AccountPayment(models.Model):
|
||||
self.mapped('payment_transaction_id').filtered(lambda x: x.state == 'done' and not x.is_processed)._post_process_after_done()
|
||||
return res
|
||||
|
||||
def post(self):
|
||||
def action_post(self):
|
||||
# Post the payments "normally" if no transactions are needed.
|
||||
# If not, let the acquirer updates the state.
|
||||
# __________ ______________
|
||||
@@ -109,7 +109,7 @@ class AccountPayment(models.Model):
|
||||
payments_need_trans = self.filtered(lambda pay: pay.payment_token_id and not pay.payment_transaction_id)
|
||||
transactions = payments_need_trans._create_payment_transaction()
|
||||
|
||||
res = super(AccountPayment, self - payments_need_trans).post()
|
||||
res = super(AccountPayment, self - payments_need_trans).action_post()
|
||||
|
||||
transactions.s2s_do_transaction()
|
||||
|
||||
|
||||
@@ -639,22 +639,42 @@ class PaymentTransaction(models.Model):
|
||||
for trans in self:
|
||||
trans.invoice_ids_nbr = len(trans.invoice_ids)
|
||||
|
||||
def _prepare_account_payment_vals(self):
|
||||
def _create_payment(self, add_payment_vals={}):
|
||||
''' Create an account.payment record for the current payment.transaction.
|
||||
If the transaction is linked to some invoices, the reconciliation will be done automatically.
|
||||
:param add_payment_vals: Optional additional values to be passed to the account.payment.create method.
|
||||
:return: An account.payment record.
|
||||
'''
|
||||
self.ensure_one()
|
||||
return {
|
||||
|
||||
payment_vals = {
|
||||
'amount': self.amount,
|
||||
'payment_type': 'inbound' if self.amount > 0 else 'outbound',
|
||||
'currency_id': self.currency_id.id,
|
||||
'partner_id': self.partner_id.id,
|
||||
'partner_type': 'customer',
|
||||
'invoice_ids': [(6, 0, self.invoice_ids.ids)],
|
||||
'journal_id': self.acquirer_id.journal_id.id,
|
||||
'company_id': self.acquirer_id.company_id.id,
|
||||
'payment_method_id': self.env.ref('payment.account_payment_method_electronic_in').id,
|
||||
'payment_token_id': self.payment_token_id and self.payment_token_id.id or None,
|
||||
'payment_transaction_id': self.id,
|
||||
'communication': self.reference,
|
||||
'ref': self.reference,
|
||||
**add_payment_vals,
|
||||
}
|
||||
payment = self.env['account.payment'].create(payment_vals)
|
||||
payment.action_post()
|
||||
|
||||
# Track the payment to make a one2one.
|
||||
self.payment_id = payment
|
||||
|
||||
if self.invoice_ids:
|
||||
self.invoice_ids.filtered(lambda move: move.state == 'draft').post()
|
||||
|
||||
(payment.line_ids + self.invoice_ids.line_ids)\
|
||||
.filtered(lambda line: line.account_id == payment.destination_account_id and not line.reconciled)\
|
||||
.reconcile()
|
||||
|
||||
return payment
|
||||
|
||||
def get_last_transaction(self):
|
||||
transactions = self.filtered(lambda t: t.state != 'draft')
|
||||
@@ -791,21 +811,11 @@ class PaymentTransaction(models.Model):
|
||||
invoices.post()
|
||||
|
||||
# Create & Post the payments.
|
||||
payments = defaultdict(lambda: self.env['account.payment'])
|
||||
for trans in self:
|
||||
if trans.payment_id:
|
||||
payments[trans.acquirer_id.company_id.id] += trans.payment_id
|
||||
continue
|
||||
|
||||
payment_vals = trans._prepare_account_payment_vals()
|
||||
payment = self.env['account.payment'].create(payment_vals)
|
||||
payments[trans.acquirer_id.company_id.id] += payment
|
||||
|
||||
# Track the payment to make a one2one.
|
||||
trans.payment_id = payment
|
||||
|
||||
for company in payments:
|
||||
payments[company].with_company(company).with_context(company_id=company).post()
|
||||
trans._create_payment()
|
||||
|
||||
def _set_transaction_cancel(self):
|
||||
'''Move the transaction's payment to the cancel state(e.g. Paypal).'''
|
||||
|
||||
@@ -24,7 +24,7 @@ class PaymentLinkWizard(models.TransientModel):
|
||||
if res_id and res_model == 'account.move':
|
||||
record = self.env[res_model].browse(res_id)
|
||||
res.update({
|
||||
'description': record.invoice_payment_ref,
|
||||
'description': record.payment_reference,
|
||||
'amount': record[amount_field],
|
||||
'currency_id': record.currency_id.id,
|
||||
'partner_id': record.partner_id.id,
|
||||
|
||||
@@ -11,11 +11,12 @@ class AccountBankStatement(models.Model):
|
||||
pos_session_id = fields.Many2one('pos.session', string="Session", copy=False)
|
||||
account_id = fields.Many2one('account.account', related='journal_id.default_debit_account_id', readonly=True)
|
||||
|
||||
def check_confirm_bank(self):
|
||||
for bs in self:
|
||||
if bs.pos_session_id.state in ('opened', 'closing_control') and bs.state == 'open':
|
||||
def button_validate_or_action(self):
|
||||
# OVERRIDE to check the consistency of the statement's state regarding the session's state.
|
||||
for statement in self:
|
||||
if statement.pos_session_id.state in ('opened', 'closing_control') and statement.state == 'open':
|
||||
raise UserError(_("You can't validate a bank statement that is used in an opened Session of a Point of Sale."))
|
||||
return super(AccountBankStatement, self).check_confirm_bank()
|
||||
return super(AccountBankStatement, self).button_validate_or_action()
|
||||
|
||||
def unlink(self):
|
||||
for bs in self:
|
||||
|
||||
@@ -384,7 +384,7 @@ class PosOrder(models.Model):
|
||||
raise UserError(_('Please provide a partner for the sale.'))
|
||||
|
||||
move_vals = {
|
||||
'invoice_payment_ref': order.name,
|
||||
'payment_reference': order.name,
|
||||
'invoice_origin': order.name,
|
||||
'journal_id': order.session_id.config_id.invoice_journal_id.id,
|
||||
'move_type': 'out_invoice' if order.amount_total >= 0 else 'out_refund',
|
||||
|
||||
@@ -207,7 +207,6 @@ class PosSession(models.Model):
|
||||
'journal_id': cash_journal.id,
|
||||
'user_id': self.env.user.id,
|
||||
'name': pos_name,
|
||||
'balance_start': self.env["account.bank.statement"]._get_opening_balance(cash_journal.id) if cash_journal.type == 'cash' else 0
|
||||
}
|
||||
statement_ids |= statement_ids.with_context(ctx).create(st_values)
|
||||
|
||||
@@ -251,7 +250,6 @@ class PosSession(models.Model):
|
||||
values['start_at'] = fields.Datetime.now()
|
||||
values['state'] = 'opened'
|
||||
session.write(values)
|
||||
session.statement_ids.button_open()
|
||||
return True
|
||||
|
||||
def action_pos_session_closing_control(self):
|
||||
@@ -300,14 +298,13 @@ class PosSession(models.Model):
|
||||
self._create_picking_at_end_of_session()
|
||||
self.with_company(self.company_id)._create_account_move()
|
||||
if self.move_id.line_ids:
|
||||
self.move_id.post()
|
||||
# Set the uninvoiced orders' state to 'done'
|
||||
self.env['pos.order'].search([('session_id', '=', self.id), ('state', '=', 'paid')]).write({'state': 'done'})
|
||||
else:
|
||||
# The cash register needs to be confirmed for cash diffs
|
||||
# made thru cash in/out when sesion is in cash_control.
|
||||
if self.config_id.cash_control:
|
||||
self.cash_register_id.button_confirm_bank()
|
||||
self.cash_register_id.button_validate()
|
||||
self.move_id.unlink()
|
||||
self.write({'state': 'closed'})
|
||||
return {
|
||||
@@ -340,7 +337,6 @@ class PosSession(models.Model):
|
||||
pickings = self.env['stock.picking']._create_picking_from_pos_order_lines(location_dest_id, lines, picking_type)
|
||||
pickings.write({'pos_session_id': self.id, 'origin': self.name})
|
||||
|
||||
|
||||
def _create_account_move(self):
|
||||
""" Create account.move and account.move.line records for this session.
|
||||
|
||||
@@ -365,6 +361,10 @@ class PosSession(models.Model):
|
||||
data = self._create_cash_statement_lines_and_cash_move_lines(data)
|
||||
data = self._create_invoice_receivable_lines(data)
|
||||
data = self._create_stock_output_lines(data)
|
||||
|
||||
if account_move.line_ids:
|
||||
account_move.post()
|
||||
|
||||
data = self._reconcile_account_move_lines(data)
|
||||
|
||||
def _accumulate_amounts(self, data):
|
||||
@@ -633,10 +633,11 @@ class PosSession(models.Model):
|
||||
for statement in self.statement_ids:
|
||||
if not self.config_id.cash_control:
|
||||
statement.write({'balance_end_real': statement.balance_end})
|
||||
statement.button_confirm_bank()
|
||||
statement.button_post()
|
||||
statement.button_validate()
|
||||
all_lines = (
|
||||
split_cash_statement_lines[statement].mapped('journal_entry_ids').filtered(lambda aml: aml.account_id.internal_type == 'receivable')
|
||||
| combine_cash_statement_lines[statement].mapped('journal_entry_ids').filtered(lambda aml: aml.account_id.internal_type == 'receivable')
|
||||
split_cash_statement_lines[statement].mapped('move_id.line_ids').filtered(lambda aml: aml.account_id.internal_type == 'receivable')
|
||||
| combine_cash_statement_lines[statement].mapped('move_id.line_ids').filtered(lambda aml: aml.account_id.internal_type == 'receivable')
|
||||
| split_cash_receivable_lines[statement]
|
||||
| combine_cash_receivable_lines[statement]
|
||||
)
|
||||
@@ -772,11 +773,11 @@ class PosSession(models.Model):
|
||||
|
||||
def _get_statement_line_vals(self, statement, receivable_account, amount):
|
||||
return {
|
||||
'date': fields.Date.context_today(self),
|
||||
'amount': amount,
|
||||
'name': self.name,
|
||||
'payment_ref': self.name,
|
||||
'statement_id': statement.id,
|
||||
'account_id': receivable_account.id,
|
||||
'journal_id': statement.journal_id.id,
|
||||
'counterpart_account_id': receivable_account.id,
|
||||
}
|
||||
|
||||
def _update_amounts(self, old_amounts, amounts_to_add, date, round=True, force_company_currency=False):
|
||||
|
||||
@@ -31,9 +31,9 @@ class TestPointOfSaleCommon(StockAccountTestCommon):
|
||||
'company_id': cls.company_id})
|
||||
cls.company.write({
|
||||
'anglo_saxon_accounting': coa.use_anglo_saxon,
|
||||
'bank_account_code_prefix': coa.bank_account_code_prefix,
|
||||
'cash_account_code_prefix': coa.cash_account_code_prefix,
|
||||
'transfer_account_code_prefix': coa.transfer_account_code_prefix,
|
||||
'bank_account_code_prefix': 'test' + coa.bank_account_code_prefix,
|
||||
'cash_account_code_prefix': 'test' + coa.cash_account_code_prefix,
|
||||
'transfer_account_code_prefix': 'test' + coa.transfer_account_code_prefix,
|
||||
'chart_template_id': coa.id,})
|
||||
cls.product3 = cls.env['product.product'].create({
|
||||
'name': 'Product 3',
|
||||
|
||||
@@ -472,6 +472,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
|
||||
# I test that the total of the attached invoice is correct
|
||||
invoice = self.env['account.move'].browse(res['res_id'])
|
||||
invoice.post()
|
||||
self.assertAlmostEqual(
|
||||
invoice.amount_total, self.pos_order_pos1.amount_total, places=2, msg="Invoice not correct")
|
||||
|
||||
@@ -505,7 +506,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
'amount': 1000,
|
||||
'partner_id': self.partner4.id,
|
||||
'statement_id': account_statement.id,
|
||||
'name': 'EXT001'
|
||||
'payment_ref': 'EXT001'
|
||||
})
|
||||
# I modify the bank statement and set the Closing Balance.
|
||||
account_statement.write({
|
||||
@@ -522,7 +523,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
|
||||
# I confirm the bank statement using Confirm button
|
||||
|
||||
self.AccountBankStatement.button_confirm_bank()
|
||||
self.AccountBankStatement.button_validate()
|
||||
|
||||
def test_create_from_ui(self):
|
||||
"""
|
||||
@@ -869,6 +870,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
||||
|
||||
# I test that the total of the attached invoice is correct
|
||||
invoice = self.env['account.move'].browse(res['res_id'])
|
||||
invoice.post()
|
||||
self.assertAlmostEqual(
|
||||
invoice.amount_total, self.pos_order_pos1.amount_total, places=2, msg="Invoice not correct")
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<field name="priority">1000</field>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='check_confirm_bank']" position="replace"/>
|
||||
<xpath expr="//button[@name='button_validate_or_action']" position="replace"/>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -59,13 +59,13 @@ class AccountMove(models.Model):
|
||||
refs = [ref for ref in refs if ref]
|
||||
self.ref = ','.join(refs)
|
||||
|
||||
# Compute invoice_payment_ref.
|
||||
# Compute payment_reference.
|
||||
if len(refs) == 1:
|
||||
self.invoice_payment_ref = refs[0]
|
||||
self.payment_reference = refs[0]
|
||||
|
||||
self.purchase_id = False
|
||||
self._onchange_currency()
|
||||
self.invoice_partner_bank_id = self.bank_partner_id.bank_ids and self.bank_partner_id.bank_ids[0]
|
||||
self.partner_bank_id = self.bank_partner_id.bank_ids and self.bank_partner_id.bank_ids[0]
|
||||
|
||||
@api.onchange('partner_id', 'company_id')
|
||||
def _onchange_partner_id(self):
|
||||
|
||||
@@ -457,12 +457,12 @@ class PurchaseOrder(models.Model):
|
||||
else:
|
||||
ref_invoice_vals['invoice_line_ids'] += invoice_vals['invoice_line_ids']
|
||||
origins.add(invoice_vals['invoice_origin'])
|
||||
payment_refs.add(invoice_vals['invoice_payment_ref'])
|
||||
payment_refs.add(invoice_vals['payment_reference'])
|
||||
refs.add(invoice_vals['ref'])
|
||||
ref_invoice_vals.update({
|
||||
'ref': ', '.join(refs)[:2000],
|
||||
'invoice_origin': ', '.join(origins),
|
||||
'invoice_payment_ref': len(payment_refs) == 1 and payment_refs.pop() or False,
|
||||
'payment_reference': len(payment_refs) == 1 and payment_refs.pop() or False,
|
||||
})
|
||||
new_invoice_vals_list.append(ref_invoice_vals)
|
||||
invoice_vals_list = new_invoice_vals_list
|
||||
@@ -499,8 +499,8 @@ class PurchaseOrder(models.Model):
|
||||
'invoice_user_id': self.user_id and self.user_id.id,
|
||||
'partner_id': partner_invoice_id,
|
||||
'fiscal_position_id': (self.fiscal_position_id or self.fiscal_position_id.get_fiscal_position(partner_invoice_id)).id,
|
||||
'invoice_payment_ref': self.partner_ref or '',
|
||||
'invoice_partner_bank_id': self.partner_id.bank_ids[:1].id,
|
||||
'payment_reference': self.partner_ref or '',
|
||||
'partner_bank_id': self.partner_id.bank_ids[:1].id,
|
||||
'invoice_origin': self.name,
|
||||
'invoice_payment_term_id': self.payment_term_id.id,
|
||||
'invoice_line_ids': [],
|
||||
|
||||
@@ -82,7 +82,7 @@ class AccountMove(models.Model):
|
||||
|
||||
for invoice in self.filtered(lambda move: move.is_invoice()):
|
||||
payments = invoice.mapped('transaction_ids.payment_id')
|
||||
move_lines = payments.mapped('move_line_ids').filtered(lambda line: not line.reconciled and line.credit > 0.0)
|
||||
move_lines = payments.line_ids.filtered(lambda line: line.account_internal_type in ('receivable', 'payable') and not line.reconciled)
|
||||
for line in move_lines:
|
||||
invoice.js_assign_outstanding_line(line.id)
|
||||
return res
|
||||
|
||||
@@ -555,11 +555,11 @@ class SaleOrder(models.Model):
|
||||
'partner_id': self.partner_invoice_id.id,
|
||||
'partner_shipping_id': self.partner_shipping_id.id,
|
||||
'fiscal_position_id': (self.fiscal_position_id or self.fiscal_position_id.get_fiscal_position(self.partner_invoice_id.id)).id,
|
||||
'invoice_partner_bank_id': self.company_id.partner_id.bank_ids[:1].id,
|
||||
'partner_bank_id': self.company_id.partner_id.bank_ids[:1].id,
|
||||
'journal_id': journal.id, # company comes from the journal
|
||||
'invoice_origin': self.name,
|
||||
'invoice_payment_term_id': self.payment_term_id.id,
|
||||
'invoice_payment_ref': self.reference,
|
||||
'payment_reference': self.reference,
|
||||
'transaction_ids': [(6, 0, self.transaction_ids.ids)],
|
||||
'invoice_line_ids': [],
|
||||
'company_id': self.company_id.id,
|
||||
@@ -697,12 +697,12 @@ Reason(s) of this behavior could be:
|
||||
else:
|
||||
ref_invoice_vals['invoice_line_ids'] += invoice_vals['invoice_line_ids']
|
||||
origins.add(invoice_vals['invoice_origin'])
|
||||
payment_refs.add(invoice_vals['invoice_payment_ref'])
|
||||
payment_refs.add(invoice_vals['payment_reference'])
|
||||
refs.add(invoice_vals['ref'])
|
||||
ref_invoice_vals.update({
|
||||
'ref': ', '.join(refs)[:2000],
|
||||
'invoice_origin': ', '.join(origins),
|
||||
'invoice_payment_ref': len(payment_refs) == 1 and payment_refs.pop() or False,
|
||||
'payment_reference': len(payment_refs) == 1 and payment_refs.pop() or False,
|
||||
})
|
||||
new_invoice_vals_list.append(ref_invoice_vals)
|
||||
invoice_vals_list = new_invoice_vals_list
|
||||
|
||||
@@ -48,7 +48,7 @@ class TestSaleTransaction(AccountTestCommon):
|
||||
invoice = self.order._create_invoices()
|
||||
invoice.post()
|
||||
|
||||
self.assertEqual(invoice.payment_state, 'paid')
|
||||
self.assertTrue(invoice.payment_state in ('in_payment', 'paid'), "Invoice should be paid")
|
||||
|
||||
def test_sale_transaction_mismatch(self):
|
||||
"""Test that a transaction for the incorrect amount does not validate the SO."""
|
||||
|
||||
@@ -81,9 +81,9 @@ class SaleAdvancePaymentInv(models.TransientModel):
|
||||
'fiscal_position_id': (order.fiscal_position_id or order.fiscal_position_id.get_fiscal_position(order.partner_id.id)).id,
|
||||
'partner_shipping_id': order.partner_shipping_id.id,
|
||||
'currency_id': order.pricelist_id.currency_id.id,
|
||||
'invoice_payment_ref': order.reference,
|
||||
'payment_reference': order.reference,
|
||||
'invoice_payment_term_id': order.payment_term_id.id,
|
||||
'invoice_partner_bank_id': order.company_id.partner_id.bank_ids[:1].id,
|
||||
'partner_bank_id': order.company_id.partner_id.bank_ids[:1].id,
|
||||
'team_id': order.team_id.id,
|
||||
'campaign_id': order.campaign_id.id,
|
||||
'medium_id': order.medium_id.id,
|
||||
|
||||
@@ -49,7 +49,7 @@ class AccountInvoiceSend(models.TransientModel):
|
||||
})
|
||||
letters |= letter
|
||||
|
||||
self.invoice_ids.filtered(lambda inv: not inv.invoice_sent).write({'invoice_sent': True})
|
||||
self.invoice_ids.filtered(lambda inv: not inv.is_move_sent).write({'is_move_sent': True})
|
||||
if len(self.invoice_ids) == 1:
|
||||
letters._snailmail_print()
|
||||
else:
|
||||
|
||||
@@ -970,7 +970,7 @@ tour.stepUtils.mobileModifier(tour.stepUtils.autoExpandMoreButtons('.o_control_p
|
||||
trigger: ".o_field_widget[name=balance_end_real] input",
|
||||
content: _t("Let's calculate the ending balance."),
|
||||
position: 'right',
|
||||
run: 'text 9010.85', // + 12.65
|
||||
run: 'text 9955.87',
|
||||
}, {
|
||||
// FIXME convert list in kanban + form
|
||||
edition: "enterprise",
|
||||
@@ -1010,7 +1010,7 @@ tour.stepUtils.mobileModifier(tour.stepUtils.autoExpandMoreButtons('.o_control_p
|
||||
position: 'right',
|
||||
}, {
|
||||
edition: "enterprise",
|
||||
trigger: ".o_selected_row .o_field_widget[name=name]",
|
||||
trigger: ".o_selected_row .o_field_widget[name=payment_ref]",
|
||||
content: _t("Let's enter a name."),
|
||||
position: "bottom",
|
||||
run: "text the_flow.statement.line",
|
||||
@@ -1020,7 +1020,8 @@ tour.stepUtils.mobileModifier(tour.stepUtils.autoExpandMoreButtons('.o_control_p
|
||||
content: _t('Save.'),
|
||||
position: 'bottom',
|
||||
},
|
||||
...tour.stepUtils.statusbarButtonsSteps('Reconcile', _t('<p><b>Reconcile</p>'), ".breadcrumb-item.active:contains('the_flow.statement')").map(tour.stepUtils.editionEnterpriseModifier),
|
||||
...tour.stepUtils.statusbarButtonsSteps('Post', _t('Processing'), ".breadcrumb-item.active:contains('the_flow.statement')").map(tour.stepUtils.editionEnterpriseModifier),
|
||||
...tour.stepUtils.statusbarButtonsSteps('Reconcile', _t('Reconcile'), ".o_statusbar_status .btn.dropdown-toggle:contains(Processing)").map(tour.stepUtils.editionEnterpriseModifier),
|
||||
{
|
||||
edition: "enterprise",
|
||||
trigger: "button.o_reconcile, button.o_validate",
|
||||
|
||||
@@ -47,8 +47,6 @@ class BaseTestUi(odoo.tests.HttpCase):
|
||||
'name': 'Vendor Bills - Test',
|
||||
'code': 'TEXJ',
|
||||
'type': 'purchase',
|
||||
'default_credit_account_id': a_expense.id,
|
||||
'default_debit_account_id': a_expense.id,
|
||||
'refund_sequence': True,
|
||||
})
|
||||
self.bank_journal = self.env['account.journal'].create({
|
||||
|
||||
Reference in New Issue
Block a user