mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
Finalise merge of account_banking_payment_transfer into account_payment_order
Add support for transfer moves Display transfer moves in a dedicated tab in payment order view Code cleanup
This commit is contained in:
committed by
Enric Tobella
parent
469af80ee7
commit
8736d5722f
@@ -4,6 +4,7 @@ from . import account_payment_mode
|
||||
from . import account_payment_order
|
||||
from . import account_payment_line
|
||||
from . import bank_payment_line
|
||||
from . import account_move
|
||||
from . import account_move_line
|
||||
from . import account_invoice
|
||||
from . import res_bank
|
||||
|
||||
@@ -38,7 +38,8 @@ class AccountInvoice(models.Model):
|
||||
}
|
||||
if self.payment_mode_id.bank_account_link == 'fixed':
|
||||
vals['journal_id'] = self.payment_mode_id.fixed_journal_id.id
|
||||
# TODO : else: no filter on allowed bank accounts, because onchange not played ??
|
||||
# TODO : else: no filter on allowed bank accounts,
|
||||
# because onchange not played ??
|
||||
return vals
|
||||
|
||||
@api.multi
|
||||
|
||||
13
account_payment_order/models/account_move.py
Normal file
13
account_payment_order/models/account_move.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from openerp import models, fields
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
payment_order_id = fields.Many2one(
|
||||
'account.payment.order', string='Payment Order', copy=False,
|
||||
readonly=True)
|
||||
@@ -4,41 +4,17 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from openerp import models, fields, api
|
||||
from openerp.tools import float_is_zero
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = 'account.move.line'
|
||||
|
||||
# TODO Should we keep this field ?
|
||||
# journal_entry_ref = fields.Char(compute='_get_journal_entry_ref',
|
||||
# string='Journal Entry Ref')
|
||||
partner_bank_id = fields.Many2one(
|
||||
'res.partner.bank', string='Partner Bank Account',
|
||||
help='Bank account on which we should pay the supplier')
|
||||
|
||||
# @api.one
|
||||
# def _get_journal_entry_ref(self):
|
||||
# if self.move_id.state == 'draft':
|
||||
# if self.invoice.id:
|
||||
# self.journal_entry_ref = self.invoice.number
|
||||
# else:
|
||||
# self.journal_entry_ref = '*' + str(self.move_id.id)
|
||||
# else:
|
||||
# self.journal_entry_ref = self.move_id.name
|
||||
|
||||
@api.multi
|
||||
def get_balance(self):
|
||||
"""
|
||||
Return the balance of any set of move lines.
|
||||
|
||||
Not to be confused with the 'balance' field on this model, which
|
||||
returns the account balance that the move line applies to.
|
||||
"""
|
||||
total = 0.0
|
||||
for line in self:
|
||||
total += (line.debit or 0.0) - (line.credit or 0.0)
|
||||
return total
|
||||
bank_payment_line_id = fields.Many2one(
|
||||
'bank.payment.line', string='Bank Payment Line',
|
||||
readonly=True)
|
||||
|
||||
@api.multi
|
||||
def _prepare_payment_line_vals(self, payment_order):
|
||||
@@ -67,8 +43,8 @@ class AccountMoveLine(models.Model):
|
||||
else:
|
||||
currency_id = self.company_id.currency_id.id
|
||||
amount_currency = self.amount_residual
|
||||
# TODO : check that self.amount_residual_currency is 0 in this case
|
||||
precision = self.env['decimal.precision'].precision_get('Account')
|
||||
# TODO : check that self.amount_residual_currency is 0
|
||||
# in this case
|
||||
if payment_order.payment_type == 'outbound':
|
||||
amount_currency *= -1
|
||||
vals = {
|
||||
|
||||
@@ -80,6 +80,9 @@ class AccountPaymentOrder(models.Model):
|
||||
bank_line_count = fields.Integer(
|
||||
compute='_bank_line_count', string='Number of Bank Lines',
|
||||
readonly=True)
|
||||
move_ids = fields.One2many(
|
||||
'account.move', 'payment_order_id', string='Transfer Journal Entries',
|
||||
readonly=True)
|
||||
|
||||
@api.multi
|
||||
@api.constrains('payment_type', 'payment_mode_id')
|
||||
@@ -271,135 +274,108 @@ class AccountPaymentOrder(models.Model):
|
||||
# Generation of transfer move
|
||||
@api.multi
|
||||
def _prepare_transfer_move(self):
|
||||
if self.payment_type == 'outbound':
|
||||
ref = _('Payment order %s') % self.name
|
||||
else:
|
||||
ref = _('Debit order %s') % self.name
|
||||
vals = {
|
||||
'journal_id': self.mode.transfer_journal_id.id,
|
||||
'ref': '%s %s' % (
|
||||
self.payment_order_type[:3].upper(), self.reference)
|
||||
'journal_id': self.payment_mode_id.transfer_journal_id.id,
|
||||
'ref': ref,
|
||||
'payment_order_id': self.id,
|
||||
'line_ids': [],
|
||||
}
|
||||
return vals
|
||||
|
||||
@api.multi
|
||||
def _prepare_move_line_transfer_account(
|
||||
self, amount, move, bank_payment_lines, labels):
|
||||
if len(bank_payment_lines) == 1:
|
||||
partner_id = bank_payment_lines[0].partner_id.id
|
||||
name = _('%s bank line %s') % (labels[self.payment_order_type],
|
||||
bank_payment_lines[0].name)
|
||||
self, amount, bank_payment_lines):
|
||||
if self.payment_type == 'outbound':
|
||||
name = _('Payment order %s') % self.name
|
||||
else:
|
||||
partner_id = False
|
||||
name = '%s %s' % (
|
||||
labels[self.payment_order_type], self.reference)
|
||||
name = _('Debit order %s') % self.name
|
||||
date_maturity = bank_payment_lines[0].date
|
||||
vals = {
|
||||
'name': name,
|
||||
'move_id': move.id,
|
||||
'partner_id': partner_id,
|
||||
'account_id': self.mode.transfer_account_id.id,
|
||||
'credit': (self.payment_order_type == 'payment' and
|
||||
'partner_id': False,
|
||||
'account_id': self.payment_mode_id.transfer_account_id.id,
|
||||
'credit': (self.payment_type == 'outbound' and
|
||||
amount or 0.0),
|
||||
'debit': (self.payment_order_type == 'debit' and
|
||||
'debit': (self.payment_type == 'inbound' and
|
||||
amount or 0.0),
|
||||
'date_maturity': date_maturity,
|
||||
}
|
||||
return vals
|
||||
|
||||
@api.multi
|
||||
def _prepare_move_line_partner_account(self, bank_line, move, labels):
|
||||
def _prepare_move_line_partner_account(self, bank_line):
|
||||
# TODO : ALEXIS check don't group if move_line_id.account_id
|
||||
# is not the same
|
||||
if bank_line.payment_line_ids[0].move_line_id:
|
||||
account_id =\
|
||||
bank_line.payment_line_ids[0].move_line_id.account_id.id
|
||||
else:
|
||||
if self.payment_order_type == 'debit':
|
||||
if self.payment_type == 'inbound':
|
||||
account_id =\
|
||||
bank_line.partner_id.property_account_receivable.id
|
||||
bank_line.partner_id.property_account_receivable_id.id
|
||||
else:
|
||||
account_id = bank_line.partner_id.property_account_payable.id
|
||||
account_id =\
|
||||
bank_line.partner_id.property_account_payable_id.id
|
||||
if self.payment_type == 'outbound':
|
||||
name = _('Payment bank line %s') % bank_line.name
|
||||
else:
|
||||
name = _('Debit bank line %s') % bank_line.name
|
||||
vals = {
|
||||
'name': _('%s line %s') % (
|
||||
labels[self.payment_order_type], bank_line.name),
|
||||
'move_id': move.id,
|
||||
'name': name,
|
||||
'bank_payment_line_id': bank_line.id,
|
||||
'partner_id': bank_line.partner_id.id,
|
||||
'account_id': account_id,
|
||||
'credit': (self.payment_order_type == 'debit' and
|
||||
'credit': (self.payment_type == 'inbound' and
|
||||
bank_line.amount_currency or 0.0),
|
||||
'debit': (self.payment_order_type == 'payment' and
|
||||
'debit': (self.payment_type == 'outbound' and
|
||||
bank_line.amount_currency or 0.0),
|
||||
}
|
||||
return vals
|
||||
|
||||
@api.multi
|
||||
def action_sent_no_move_line_hook(self, pay_line):
|
||||
"""This function is designed to be inherited"""
|
||||
return
|
||||
|
||||
@api.multi
|
||||
def _create_move_line_partner_account(self, bank_line, move, labels):
|
||||
"""This method is designed to be inherited in a custom module"""
|
||||
# TODO: take multicurrency into account
|
||||
company_currency = self.env.user.company_id.currency_id
|
||||
if bank_line.currency != company_currency:
|
||||
raise UserError(_(
|
||||
"Cannot generate the transfer move when "
|
||||
"the currency of the payment (%s) is not the "
|
||||
"same as the currency of the company (%s). This "
|
||||
"is not supported for the moment.")
|
||||
% (bank_line.currency.name, company_currency.name))
|
||||
aml_obj = self.env['account.move.line']
|
||||
# create the payment/debit counterpart move line
|
||||
# on the partner account
|
||||
partner_ml_vals = self._prepare_move_line_partner_account(
|
||||
bank_line, move, labels)
|
||||
partner_move_line = aml_obj.create(partner_ml_vals)
|
||||
|
||||
# register the payment/debit move line
|
||||
# on the payment line and call reconciliation on it
|
||||
bank_line.write({'transit_move_line_id': partner_move_line.id})
|
||||
|
||||
@api.multi
|
||||
def _reconcile_payment_lines(self, bank_payment_lines):
|
||||
for bline in bank_payment_lines:
|
||||
if all([pline.move_line_id for pline in bline.payment_line_ids]):
|
||||
bline.debit_reconcile()
|
||||
else:
|
||||
self.action_sent_no_move_line_hook(bline)
|
||||
|
||||
@api.multi
|
||||
def generate_transfer_move(self):
|
||||
"""
|
||||
Create the moves that pay off the move lines from
|
||||
the debit order.
|
||||
the payment/debit order.
|
||||
"""
|
||||
self.ensure_one()
|
||||
am_obj = self.env['account.move']
|
||||
aml_obj = self.env['account.move.line']
|
||||
labels = {
|
||||
'outbound': _('Payment'),
|
||||
'inbound': _('Direct debit'),
|
||||
}
|
||||
# prepare a dict "trfmoves" that can be used when
|
||||
# self.mode.transfer_move_option = date or line
|
||||
# self.payment_mode_id.transfer_move_option = date or line
|
||||
# key = unique identifier (date or True or line.id)
|
||||
# value = [pay_line1, pay_line2, ...]
|
||||
# value = bank_pay_lines (recordset that can have several entries)
|
||||
trfmoves = {}
|
||||
for bline in self.bank_line_ids:
|
||||
hashcode = bline.move_line_transfer_account_hashcode()
|
||||
if hashcode in trfmoves:
|
||||
trfmoves[hashcode].append(bline)
|
||||
trfmoves[hashcode] += bline
|
||||
else:
|
||||
trfmoves[hashcode] = [bline]
|
||||
trfmoves[hashcode] = bline
|
||||
|
||||
company_currency = self.env.user.company_id.currency_id
|
||||
for hashcode, blines in trfmoves.iteritems():
|
||||
mvals = self._prepare_transfer_move()
|
||||
move = am_obj.create(mvals)
|
||||
total_amount = 0
|
||||
for bline in blines:
|
||||
total_amount += bline.amount_currency
|
||||
self._create_move_line_partner_account(bline, move, labels)
|
||||
# create the payment/debit move line on the transfer account
|
||||
if bline.currency_id != company_currency:
|
||||
raise UserError(_(
|
||||
"Cannot generate the transfer move when "
|
||||
"the currency of the payment (%s) is not the "
|
||||
"same as the currency of the company (%s). This "
|
||||
"is not supported for the moment.")
|
||||
% (bline.currency_id.name, company_currency.name))
|
||||
|
||||
partner_ml_vals = self._prepare_move_line_partner_account(
|
||||
bline)
|
||||
mvals['line_ids'].append((0, 0, partner_ml_vals))
|
||||
trf_ml_vals = self._prepare_move_line_transfer_account(
|
||||
total_amount, move, blines, labels)
|
||||
aml_obj.create(trf_ml_vals)
|
||||
self._reconcile_payment_lines(blines)
|
||||
total_amount, blines)
|
||||
mvals['line_ids'].append((0, 0, trf_ml_vals))
|
||||
move = am_obj.create(mvals)
|
||||
blines.reconcile_payment_lines()
|
||||
move.post()
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
# © 2015 Akretion - Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from openerp import models, fields, api
|
||||
from openerp import models, fields, api, _
|
||||
from openerp.exceptions import UserError
|
||||
|
||||
|
||||
class BankPaymentLine(models.Model):
|
||||
@@ -45,32 +46,10 @@ class BankPaymentLine(models.Model):
|
||||
related='payment_line_ids.communication_type', readonly=True)
|
||||
communication = fields.Char(
|
||||
string='Communication', required=True,
|
||||
readonly=True) #, states={'draft': [('readonly', False)]})
|
||||
readonly=True)
|
||||
company_id = fields.Many2one(
|
||||
related='order_id.payment_mode_id.company_id', store=True,
|
||||
readonly=True)
|
||||
# TODO : not shown in view ?
|
||||
# why on bank payment line and not on payment line ?
|
||||
transit_move_line_id = fields.Many2one(
|
||||
'account.move.line', string='Transfer Move Line', readonly=True,
|
||||
help="Move line through which the payment/debit order "
|
||||
"pays the invoice")
|
||||
transfer_move_line_id = fields.Many2one(
|
||||
'account.move.line', compute='_get_transfer_move_line',
|
||||
string='Transfer move line counterpart',
|
||||
help="Counterpart move line on the transfer account")
|
||||
|
||||
@api.multi
|
||||
def _get_transfer_move_line(self):
|
||||
for bank_line in self:
|
||||
if bank_line.transit_move_line_id:
|
||||
payment_type = bank_line.payment_type
|
||||
trf_lines = bank_line.transit_move_line_id.move_id.line_id
|
||||
for move_line in trf_lines:
|
||||
if payment_type == 'inbound' and move_line.debit > 0:
|
||||
bank_line.transfer_move_line_id = move_line
|
||||
elif payment_type == 'outbound' and move_line.credit > 0:
|
||||
bank_line.transfer_move_line_id = move_line
|
||||
|
||||
@api.model
|
||||
def same_fields_payment_line_and_bank_payment_line(self):
|
||||
@@ -113,3 +92,54 @@ class BankPaymentLine(models.Model):
|
||||
hashcode = unicode(self.id)
|
||||
return hashcode
|
||||
|
||||
@api.multi
|
||||
def reconcile_payment_lines(self):
|
||||
for bline in self:
|
||||
if all([pline.move_line_id for pline in bline.payment_line_ids]):
|
||||
bline.reconcile()
|
||||
else:
|
||||
bline.no_reconcile_hook()
|
||||
|
||||
@api.multi
|
||||
def no_reconcile_hook(self):
|
||||
"""This method is designed to be inherited if needed"""
|
||||
return
|
||||
|
||||
@api.multi
|
||||
def reconcile(self):
|
||||
self.ensure_one()
|
||||
amlo = self.env['account.move.line']
|
||||
transit_mlines = amlo.search([('bank_payment_line_id', '=', self.id)])
|
||||
assert len(transit_mlines) == 1, 'We should have only 1 move'
|
||||
transit_mline = transit_mlines[0]
|
||||
assert not transit_mline.reconciled,\
|
||||
'Transit move should not be reconciled'
|
||||
lines_to_rec = transit_mline
|
||||
for payment_line in self.payment_line_ids:
|
||||
|
||||
if not payment_line.move_line_id:
|
||||
raise UserError(_(
|
||||
"Can not reconcile: no move line for "
|
||||
"payment line %s of partner '%s'.") % (
|
||||
payment_line.name,
|
||||
payment_line.partner_id.name))
|
||||
if payment_line.move_line_id.reconciled:
|
||||
raise UserError(_(
|
||||
"Move line '%s' of partner '%s' has already "
|
||||
"been reconciled") % (
|
||||
payment_line.move_line_id.name,
|
||||
payment_line.partner_id.name))
|
||||
if (
|
||||
payment_line.move_line_id.account_id !=
|
||||
transit_mline.account_id):
|
||||
raise UserError(_(
|
||||
"For partner '%s', the account of the account "
|
||||
"move line to pay (%s) is different from the "
|
||||
"account of of the transit move line (%s).") % (
|
||||
payment_line.move_line_id.partner_id.name,
|
||||
payment_line.move_line_id.account_id.code,
|
||||
transit_mline.account_id.code))
|
||||
|
||||
lines_to_rec += payment_line.move_line_id
|
||||
|
||||
lines_to_rec.reconcile()
|
||||
|
||||
28
account_payment_order/test/data.yml
Normal file
28
account_payment_order/test/data.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
-
|
||||
I create a transfer account
|
||||
-
|
||||
!record {model: account.account, id: account_account_transfer0}:
|
||||
code: TRANSF
|
||||
name: Transfer
|
||||
user_type: account.data_account_type_liability
|
||||
type: other
|
||||
reconcile: True
|
||||
-
|
||||
I create a transfer journal
|
||||
-
|
||||
!record {model: account.journal, id: transfer_journal0}:
|
||||
name: Transfer journal
|
||||
code: TR
|
||||
type: general
|
||||
company_id: base.main_company
|
||||
-
|
||||
I create a payment mode
|
||||
-
|
||||
!record {model: payment.mode, id: payment_mode0}:
|
||||
name: Payment Mode Test
|
||||
journal: account.bank_journal
|
||||
bank_id: account_payment.partner_bank_1
|
||||
company_id: base.main_company
|
||||
transfer_account_id: account_account_transfer0
|
||||
transfer_journal_id: transfer_journal0
|
||||
type: account_banking_payment_export.manual_bank_tranfer
|
||||
134
account_payment_order/test/test_partial_payment_refunded.yml
Normal file
134
account_payment_order/test/test_partial_payment_refunded.yml
Normal file
@@ -0,0 +1,134 @@
|
||||
-
|
||||
I create a supplier invoice
|
||||
-
|
||||
!record {model: account.invoice, id: account_invoice_supplier_refunded, view: account.invoice_supplier_form}:
|
||||
check_total: 600.00
|
||||
partner_id: base.res_partner_12
|
||||
reference_type: none
|
||||
type: in_invoice
|
||||
account_id: account.a_pay
|
||||
company_id: base.main_company
|
||||
currency_id: base.EUR
|
||||
invoice_line:
|
||||
- account_id: account.a_expense
|
||||
name: 'Some contact lenses'
|
||||
price_unit: 600.00
|
||||
quantity: 1.0
|
||||
journal_id: account.expenses_journal
|
||||
-
|
||||
Make sure that the type is in_invoice
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
self.write(cr, uid, ref("account_invoice_supplier_refunded"), {'type': 'in_invoice'})
|
||||
-
|
||||
I change the state of invoice to open by clicking Validate button
|
||||
-
|
||||
!workflow {model: account.invoice, action: invoice_open, ref: account_invoice_supplier_refunded}
|
||||
-
|
||||
I create a supplier refund for this invoice
|
||||
-
|
||||
!record {model: account.invoice, id: account_refund_supplier_refunded, view: account.invoice_supplier_form}:
|
||||
check_total: 200.00
|
||||
partner_id: base.res_partner_12
|
||||
reference_type: none
|
||||
type: in_refund
|
||||
account_id: account.a_pay
|
||||
company_id: base.main_company
|
||||
currency_id: base.EUR
|
||||
invoice_line:
|
||||
- account_id: account.a_expense
|
||||
name: 'Some contact lenses'
|
||||
price_unit: 200.00
|
||||
quantity: 1.0
|
||||
journal_id: account.expenses_journal
|
||||
-
|
||||
Make sure that the type is in_refund
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
self.write(cr, uid, ref("account_refund_supplier_refunded"), {'type': 'in_refund'})
|
||||
-
|
||||
I change the state of invoice to open by clicking Validate button
|
||||
-
|
||||
!workflow {model: account.invoice, action: invoice_open, ref: account_refund_supplier_refunded}
|
||||
-
|
||||
I create a payment order
|
||||
-
|
||||
!record {model: payment.order, id: partial_payment_order_1}:
|
||||
mode: account_banking_payment_transfer.payment_mode0
|
||||
date_prefered: 'now'
|
||||
-
|
||||
I run the select move line to pay wizard
|
||||
-
|
||||
!python {model: payment.order.create}: |
|
||||
context = {
|
||||
"active_model": "payment.order",
|
||||
"active_ids": [ref("partial_payment_order_1")],
|
||||
"active_id": ref("partial_payment_order_1"),
|
||||
}
|
||||
wiz_id = self.create(cr, uid, {}, context=context)
|
||||
self.search_entries(cr, uid, [wiz_id], context=context)
|
||||
mline_ids = []
|
||||
invoice = self.pool.get('account.invoice').browse(cr, uid, ref("account_invoice_supplier_refunded"))
|
||||
for l in invoice.move_id.line_id:
|
||||
if not l.debit and l.credit:
|
||||
mline_ids.append(l.id)
|
||||
break
|
||||
refund = self.pool.get('account.invoice').browse(cr, uid, ref("account_refund_supplier_refunded"))
|
||||
for l in refund.move_id.line_id:
|
||||
if not l.credit and l.debit:
|
||||
mline_ids.append(l.id)
|
||||
break
|
||||
self.write(cr, uid, [wiz_id], {'entries': [(6, 0, mline_ids)]})
|
||||
self.create_payment(cr, uid, [wiz_id], context=context)
|
||||
pay_obj = self.pool.get('payment.order')
|
||||
pay = pay_obj.browse(cr, uid, ref('partial_payment_order_1'))
|
||||
assert len(pay.line_ids) == 2
|
||||
-
|
||||
I confirm the payment order.
|
||||
-
|
||||
!workflow {model: payment.order, action: open, ref: partial_payment_order_1}
|
||||
-
|
||||
I check that payment order is now "Confirmed".
|
||||
-
|
||||
!assert {model: payment.order, id: partial_payment_order_1, severity: error, string: Payment Order should be 'Confirmed'.}:
|
||||
- state == 'open'
|
||||
- total == 400.0
|
||||
-
|
||||
I create the wizard for paying the payment
|
||||
-
|
||||
!record {model: payment.manual, id: payment_manual_partial}:
|
||||
create_date: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I click OK
|
||||
-
|
||||
!python {model: payment.manual}: |
|
||||
if context is None:
|
||||
context = {}
|
||||
context.update({'active_ids': [ref("partial_payment_order_1")]})
|
||||
self.button_ok(cr, uid, ref("payment_manual_partial"), context)
|
||||
-
|
||||
I check that the payment order is now "Sent".
|
||||
-
|
||||
!assert {model: payment.order, id: partial_payment_order_1, severity: error, string: Payment Order should be 'Sent'.}:
|
||||
- state == 'sent'
|
||||
-
|
||||
I check that the invoice has payments associated
|
||||
-
|
||||
!assert {model: account.invoice, id: account_invoice_supplier_refunded, severity: error, string: payment_ids should be populated}:
|
||||
- payment_ids
|
||||
-
|
||||
I check the content of the payment of the invoice
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
inv = self.browse(cr, uid, ref("account_invoice_supplier_refunded"))
|
||||
payment1, payment2 = sorted(inv.payment_ids, key=lambda line: line.id)
|
||||
assert payment1.debit == 200
|
||||
assert payment2.debit == 400
|
||||
assert inv.payment_ids[0].reconcile_id.id != False
|
||||
-
|
||||
I check that the invoice balance (residual) is now 0 and the state is paid
|
||||
-
|
||||
!assert {model: account.invoice, id: account_invoice_supplier_refunded, severity: error, string: Invoice residual should be 0.}:
|
||||
- residual == 0
|
||||
- amount_total == 600
|
||||
- state == 'paid'
|
||||
252
account_payment_order/test/test_partial_payment_transfer.yml
Normal file
252
account_payment_order/test/test_partial_payment_transfer.yml
Normal file
@@ -0,0 +1,252 @@
|
||||
-
|
||||
I create a supplier invoice
|
||||
-
|
||||
!record {model: account.invoice, id: account_invoice_supplier_partial, view: account.invoice_supplier_form}:
|
||||
check_total: 1000.00
|
||||
partner_id: base.res_partner_12
|
||||
reference_type: none
|
||||
type: in_invoice
|
||||
account_id: account.a_pay
|
||||
company_id: base.main_company
|
||||
currency_id: base.EUR
|
||||
invoice_line:
|
||||
- account_id: account.a_expense
|
||||
name: 'Some glasses'
|
||||
price_unit: 1000.00
|
||||
quantity: 1.0
|
||||
journal_id: account.expenses_journal
|
||||
-
|
||||
Make sure that the type is in_invoice
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
self.write(cr, uid, ref("account_invoice_supplier_partial"), {'type': 'in_invoice'})
|
||||
-
|
||||
I change the state of invoice to open by clicking Validate button
|
||||
-
|
||||
!workflow {model: account.invoice, action: invoice_open, ref: account_invoice_supplier_partial}
|
||||
-
|
||||
I create a payment order
|
||||
-
|
||||
!record {model: payment.order, id: partial_payment_order_2}:
|
||||
mode: account_banking_payment_transfer.payment_mode0
|
||||
date_prefered: 'due'
|
||||
-
|
||||
I run the select move line to pay wizard
|
||||
-
|
||||
!python {model: payment.order.create}: |
|
||||
context = {
|
||||
"active_model": "payment.order",
|
||||
"active_ids": [ref("partial_payment_order_2")],
|
||||
"active_id": ref("partial_payment_order_2"),
|
||||
}
|
||||
wiz_id = self.create(cr, uid, {}, context=context)
|
||||
self.search_entries(cr, uid, [wiz_id], context=context)
|
||||
invoice = self.pool.get('account.invoice').browse(cr, uid, ref("account_invoice_supplier_partial"))
|
||||
move_line = invoice.move_id.line_id[0]
|
||||
self.write(cr, uid, [wiz_id], {'entries': [(6, 0, [move_line.id])]})
|
||||
self.create_payment(cr, uid, [wiz_id], context=context)
|
||||
pay_obj = self.pool.get('payment.order')
|
||||
pay = pay_obj.browse(cr, uid, ref('partial_payment_order_2'))
|
||||
assert pay.line_ids
|
||||
assert pay.line_ids[0].amount_currency == 1000.0
|
||||
-
|
||||
I change the amount paid to test the partial payment
|
||||
-
|
||||
!python {model: payment.order}: |
|
||||
line_ids = self.browse(cr, uid, ref('partial_payment_order_2')).line_ids
|
||||
line_to_change = line_ids[0]
|
||||
assert line_to_change.amount_currency == 1000.00
|
||||
self.pool.get('payment.line').write(cr, uid, line_to_change.id, {'amount_currency':100})
|
||||
-
|
||||
I confirm the payment order.
|
||||
-
|
||||
!workflow {model: payment.order, action: open, ref: partial_payment_order_2}
|
||||
-
|
||||
I check that payment order is now "Confirmed".
|
||||
-
|
||||
!assert {model: payment.order, id: partial_payment_order_2, severity: error, string: Payment Order should be 'Confirmed'.}:
|
||||
- state == 'open'
|
||||
-
|
||||
I assume that the document is sent to the bank and validate.
|
||||
-
|
||||
!record {model: payment.manual, id: payment_manual_1}:
|
||||
create_date: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I click OK
|
||||
-
|
||||
!python {model: payment.manual}: |
|
||||
if context is None:
|
||||
context = {}
|
||||
context.update({'active_ids': [ref("partial_payment_order_2")]})
|
||||
self.button_ok(cr, uid, ref("payment_manual_1"), context)
|
||||
-
|
||||
I check that the payment order is now "Sent".
|
||||
-
|
||||
!assert {model: payment.order, id: partial_payment_order_2, severity: error, string: Payment Order should be 'Sent'.}:
|
||||
- state == 'sent'
|
||||
-
|
||||
I check that the invoice has payments associated
|
||||
-
|
||||
!assert {model: account.invoice, id: account_invoice_supplier_partial, severity: error, string: payment_ids should be populated}:
|
||||
- payment_ids
|
||||
-
|
||||
I check the content of the payment of the invoice
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
inv = self.browse(cr, uid, ref("account_invoice_supplier_partial"))
|
||||
assert round(inv.payment_ids[0].debit, 2) == 100
|
||||
assert inv.payment_ids[0].credit == 0
|
||||
assert not inv.payment_ids[0].reconcile_id.id
|
||||
assert inv.payment_ids[0].reconcile_partial_id
|
||||
sum_debit = 0.0
|
||||
sum_credit = 0.0
|
||||
for line in inv.payment_ids[0].reconcile_partial_id.line_partial_ids:
|
||||
sum_debit += line.debit
|
||||
sum_credit += line.credit
|
||||
assert sum_debit == 100
|
||||
sum_credit == 1000
|
||||
assert inv.residual == 900
|
||||
assert inv.state == 'open'
|
||||
-
|
||||
I create a 2nd partial payment
|
||||
-
|
||||
!record {model: payment.order, id: partial_partial_payment_order_2}:
|
||||
mode: account_banking_payment_transfer.payment_mode0
|
||||
date_prefered: 'due'
|
||||
-
|
||||
I search for the invoice entries to make the payment.
|
||||
-
|
||||
!python {model: payment.order.create}: |
|
||||
context = {
|
||||
"active_model": "payment.order",
|
||||
"active_ids": [ref("partial_partial_payment_order_2")],
|
||||
"active_id": ref("partial_partial_payment_order_2"),
|
||||
}
|
||||
wiz_id = self.create(cr, uid, {}, context=context)
|
||||
self.search_entries(cr, uid, [wiz_id], context=context)
|
||||
invoice = self.pool.get('account.invoice').browse(cr, uid, ref("account_invoice_supplier_partial"))
|
||||
for l in invoice.move_id.line_id:
|
||||
if not l.debit and l.credit:
|
||||
move_line = l
|
||||
break
|
||||
self.write(cr, uid, [wiz_id], {'entries': [(6,0,[move_line.id])]})
|
||||
self.create_payment(cr, uid, [wiz_id], context=context)
|
||||
pay_obj = self.pool.get('payment.order')
|
||||
pay = pay_obj.browse(cr, uid, ref('partial_partial_payment_order_2'))
|
||||
assert len(pay.line_ids) == 1
|
||||
assert pay.line_ids[0].amount_currency == 900
|
||||
-
|
||||
I change the amount paid to test the partial payment
|
||||
-
|
||||
!python {model: payment.order}: |
|
||||
line_ids = self.browse(cr, uid, ref('partial_partial_payment_order_2')).line_ids
|
||||
line_to_change = line_ids[0]
|
||||
self.pool.get('payment.line').write(cr, uid, line_to_change.id, {'amount_currency':200})
|
||||
-
|
||||
I confirm the payment order.
|
||||
-
|
||||
!workflow {model: payment.order, action: open, ref: partial_partial_payment_order_2}
|
||||
-
|
||||
I assume that the document is sent to the bank and validate.
|
||||
-
|
||||
!record {model: payment.manual, id: payment_manual_1}:
|
||||
create_date: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I click OK
|
||||
-
|
||||
!python {model: payment.manual}: |
|
||||
if context is None:
|
||||
context = {}
|
||||
context.update({'active_ids': [ref("partial_partial_payment_order_2")]})
|
||||
self.button_ok(cr, uid, ref("payment_manual_1"), context)
|
||||
-
|
||||
I check that the payment order is now "Sent".
|
||||
-
|
||||
!assert {model: payment.order, id: partial_partial_payment_order_2, severity: error, string: Payment Order should be 'Sent'.}:
|
||||
- state == 'sent'
|
||||
-
|
||||
I check the content of the payment of the invoice
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
inv = self.browse(cr, uid, ref("account_invoice_supplier_partial"))
|
||||
assert len(inv.payment_ids) == 2
|
||||
assert inv.payment_ids[0].credit == 0
|
||||
assert not inv.payment_ids[0].reconcile_id.id
|
||||
assert inv.payment_ids[0].reconcile_partial_id
|
||||
sum_debit = 0.0
|
||||
sum_credit = 0.0
|
||||
for line in inv.payment_ids[0].reconcile_partial_id.line_partial_ids:
|
||||
sum_debit += line.debit
|
||||
sum_credit += line.credit
|
||||
assert sum_debit == 300
|
||||
assert sum_credit == 1000
|
||||
assert inv.residual == 700
|
||||
assert inv.state == 'open'
|
||||
-
|
||||
I create the last partial payment for completing the payment
|
||||
-
|
||||
!record {model: payment.order, id: partial_partial_payment_order_3}:
|
||||
mode: account_banking_payment_transfer.payment_mode0
|
||||
date_prefered: 'due'
|
||||
-
|
||||
I search for the invoice entries to make the payment.
|
||||
-
|
||||
!python {model: payment.order.create}: |
|
||||
context = {
|
||||
"active_model": "payment.order",
|
||||
"active_ids": [ref("partial_partial_payment_order_3")],
|
||||
"active_id": ref("partial_partial_payment_order_3"),
|
||||
}
|
||||
wiz_id = self.create(cr, uid, {}, context=context)
|
||||
self.search_entries(cr, uid, [wiz_id], context=context)
|
||||
invoice = self.pool.get('account.invoice').browse(cr, uid, ref("account_invoice_supplier_partial"))
|
||||
for l in invoice.move_id.line_id:
|
||||
if not l.debit and l.credit:
|
||||
move_line = l
|
||||
break
|
||||
self.write(cr, uid, [wiz_id], {'entries': [(6, 0, [move_line.id])]})
|
||||
self.create_payment(cr, uid, [wiz_id], context=context)
|
||||
pay_obj = self.pool.get('payment.order')
|
||||
pay = pay_obj.browse(cr, uid, ref('partial_partial_payment_order_3'))
|
||||
assert len(pay.line_ids) == 1
|
||||
assert pay.line_ids[0].amount_currency == 700
|
||||
-
|
||||
I confirm the payment order.
|
||||
-
|
||||
!workflow {model: payment.order, action: open, ref: partial_partial_payment_order_3}
|
||||
-
|
||||
I assume that the document is sent to the bank and validate.
|
||||
-
|
||||
!record {model: payment.manual, id: payment_manual_3}:
|
||||
create_date: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I click OK
|
||||
-
|
||||
!python {model: payment.manual}: |
|
||||
if context is None:
|
||||
context = {}
|
||||
context.update({'active_ids': [ref("partial_partial_payment_order_3")]})
|
||||
self.button_ok(cr, uid, ref("payment_manual_3"), context)
|
||||
-
|
||||
I check that the payment order is now "Sent".
|
||||
-
|
||||
!assert {model: payment.order, id: partial_partial_payment_order_3, severity: error, string: Payment Order should be 'Sent'.}:
|
||||
- state == 'sent'
|
||||
-
|
||||
I check the content of the payment of the invoice
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
inv = self.browse(cr, uid, ref("account_invoice_supplier_partial"))
|
||||
assert len(inv.payment_ids) == 3
|
||||
assert inv.payment_ids[0].credit == 0
|
||||
assert inv.payment_ids[0].reconcile_id.id
|
||||
#assert not inv.payment_ids[0].reconcile_partial_id ?? should we remove it?
|
||||
sum_debit = 0.0
|
||||
sum_credit = 0.0
|
||||
for line in inv.payment_ids:
|
||||
sum_debit += line.debit
|
||||
sum_credit += line.credit
|
||||
assert sum_debit == 1000
|
||||
assert sum_credit == 0
|
||||
assert inv.residual == 0
|
||||
assert inv.state == 'paid'
|
||||
159
account_payment_order/test/test_payment_method.yml
Normal file
159
account_payment_order/test/test_payment_method.yml
Normal file
@@ -0,0 +1,159 @@
|
||||
-
|
||||
I create a supplier invoice
|
||||
-
|
||||
!record {model: account.invoice, id: account_invoice_supplier0, view: account.invoice_supplier_form}:
|
||||
check_total: 1005.55
|
||||
partner_id: base.res_partner_4
|
||||
reference_type: none
|
||||
type: in_invoice
|
||||
account_id: account.a_pay
|
||||
company_id: base.main_company
|
||||
currency_id: base.EUR
|
||||
invoice_line:
|
||||
- account_id: account.a_expense
|
||||
name: 'Some expenses'
|
||||
price_unit: 450.0
|
||||
quantity: 1.0
|
||||
- account_id: account.a_expense
|
||||
name: 'Some other expenses'
|
||||
price_unit: 555.55
|
||||
quantity: 1.0
|
||||
journal_id: account.expenses_journal
|
||||
-
|
||||
Make sure that the type is in_invoice
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
self.write(cr, uid, ref("account_invoice_supplier0"), {'type': 'in_invoice'})
|
||||
-
|
||||
I change the state of invoice to open by clicking Validate button
|
||||
-
|
||||
!workflow {model: account.invoice, action: invoice_open, ref: account_invoice_supplier0}
|
||||
-
|
||||
I check that the invoice state is now "Open"
|
||||
-
|
||||
!assert {model: account.invoice, id: account_invoice_supplier0}:
|
||||
- state == 'open'
|
||||
- type == 'in_invoice'
|
||||
-
|
||||
I create a payment order
|
||||
-
|
||||
!record {model: payment.order, id: payment_order_0}:
|
||||
mode: account_banking_payment_transfer.payment_mode0
|
||||
date_prefered: 'due'
|
||||
-
|
||||
I run the select move line to pay wizard
|
||||
-
|
||||
!python {model: payment.order.create}: |
|
||||
context = {
|
||||
"active_model": "payment.order",
|
||||
"active_ids": [ref("payment_order_0")],
|
||||
"active_id": ref("payment_order_0"),
|
||||
}
|
||||
wiz_id = self.create(cr, uid, {}, context=context)
|
||||
self.search_entries(cr, uid, [wiz_id], context=context)
|
||||
invoice = self.pool.get('account.invoice').browse(cr, uid, ref("account_invoice_supplier0"))
|
||||
entries = []
|
||||
for move_line in invoice.move_id.line_id:
|
||||
if move_line.credit and not move_line.debit:
|
||||
entries.append((6, 0, [move_line.id]))
|
||||
self.write(cr, uid, [wiz_id], {'entries': entries})
|
||||
self.create_payment(cr, uid, [wiz_id], context=context)
|
||||
pay_obj = self.pool.get('payment.order')
|
||||
pay = pay_obj.browse(cr, uid, ref('payment_order_0'))
|
||||
for line in pay.line_ids:
|
||||
assert line.amount != 0.0
|
||||
-
|
||||
I confirm the payment order.
|
||||
-
|
||||
!workflow {model: payment.order, action: open, ref: payment_order_0}
|
||||
-
|
||||
I check that payment order is now "Confirmed".
|
||||
-
|
||||
!assert {model: payment.order, id: payment_order_0, severity: error, string: Payment Order should be 'Confirmed'.}:
|
||||
- state == 'open'
|
||||
-
|
||||
I create the wizard for paying the payment
|
||||
-
|
||||
!record {model: payment.manual, id: payment_manual_0}:
|
||||
create_date: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I click OK
|
||||
-
|
||||
!python {model: payment.manual}: |
|
||||
if context is None:
|
||||
context = {}
|
||||
context.update({'active_ids': [ref("payment_order_0")]})
|
||||
self.button_ok(cr, uid, ref("payment_manual_0"), context)
|
||||
-
|
||||
I check that the payment order is now "Sent".
|
||||
-
|
||||
!assert {model: payment.order, id: payment_order_0, severity: error, string: Payment Order should be 'Sent'.}:
|
||||
- state == 'sent'
|
||||
-
|
||||
I check that the invoice has payments associated
|
||||
-
|
||||
!assert {model: account.invoice, id: account_invoice_supplier0, severity: error, string: payment_ids should be populated}:
|
||||
- payment_ids
|
||||
-
|
||||
I check the content of the payment of the invoice
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
inv = self.browse(cr, uid, ref("account_invoice_supplier0"))
|
||||
assert round(inv.payment_ids[0].debit, 2) == 1005.55
|
||||
assert inv.payment_ids[0].credit == 0
|
||||
assert inv.payment_ids[0].reconcile_id.id != False
|
||||
assert inv.payment_ids[0].reconcile_ref != False
|
||||
assert inv.state == 'paid'
|
||||
-
|
||||
I create the bank statement to reconcile the transfer account move
|
||||
-
|
||||
!record {model: account.bank.statement, id: bank_statement_0}:
|
||||
name: BK test
|
||||
balance_end_real: 0.0
|
||||
balance_start: 0.0
|
||||
date: !eval time.strftime('%Y-%m-%d')
|
||||
journal_id: account.bank_journal
|
||||
-
|
||||
I create bank statement line
|
||||
-
|
||||
!python {model: account.bank.statement.line}: |
|
||||
vals = {
|
||||
'amount': -1005.55,
|
||||
'partner_id': ref('base.res_partner_4'),
|
||||
'statement_id': ref('bank_statement_0'),
|
||||
'name': 'Pay invoice',
|
||||
'journal_id': ref("account.bank_journal"),
|
||||
}
|
||||
line_id = self.create(cr, uid, vals)
|
||||
assert line_id, "Account bank statement line has not been created"
|
||||
-
|
||||
I reconcile the move transfer (not the invoice) with the payment.
|
||||
-
|
||||
!python {model: account.bank.statement}: |
|
||||
inv_obj = self.pool.get('account.invoice')
|
||||
statement_obj = self.pool.get('account.bank.statement.line')
|
||||
transfer_entry = inv_obj.browse(cr, uid, ref("account_invoice_supplier0")).payment_ids[0].move_id
|
||||
for line in transfer_entry.line_id:
|
||||
if not line.reconcile_id and line.credit:
|
||||
counterpart_move_line = line
|
||||
break
|
||||
browse_payment = self.browse(cr, uid, ref("bank_statement_0"))
|
||||
for line in browse_payment.line_ids:
|
||||
statement_obj.process_reconciliation(cr, uid, line.id, [{
|
||||
'counterpart_move_line_id': counterpart_move_line.id,
|
||||
'credit':0,
|
||||
'debit': counterpart_move_line.credit,
|
||||
'name': line.name,
|
||||
}])
|
||||
self.write(cr, uid, ref("bank_statement_0"), {'balance_end_real': -1005.55})
|
||||
self.button_confirm_bank(cr, uid, ref("bank_statement_0"))
|
||||
-
|
||||
I check that the bank statement is confirm
|
||||
-
|
||||
!assert {model: account.bank.statement, id: bank_statement_0, severity: error, string: Bank Statement should be confirm}:
|
||||
- state == 'confirm'
|
||||
-
|
||||
I check that the payment is done
|
||||
-
|
||||
!assert {model: payment.order, id: payment_order_0, severity: error, string: Payment Order should be done}:
|
||||
- state == 'done'
|
||||
@@ -18,6 +18,7 @@
|
||||
<group name="payments" position="inside">
|
||||
<field name="partner_bank_id"
|
||||
domain="[('partner_id', '=', partner_id)]"/>
|
||||
<field name="bank_payment_line_id"/>
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<field name="transfer_move"/>
|
||||
<field name="transfer_account_id"
|
||||
attrs="{'invisible': [('transfer_move', '=', False)], 'required': [('transfer_move', '=', True)]}"
|
||||
context="{'default_internal_type': 'other', 'default_reconcile': True, 'default_company_id': company_id}"/>
|
||||
context="{'default_reconcile': True, 'default_company_id': company_id}"/> <!-- We can't put a default vue to user_type_id... -->
|
||||
<field name="transfer_journal_id"
|
||||
attrs="{'invisible': [('transfer_move', '=', False)], 'required': [('transfer_move', '=', True)]}"/>
|
||||
<field name="transfer_move_option"
|
||||
|
||||
@@ -59,6 +59,9 @@
|
||||
<field name="bank_line_ids"
|
||||
context="{'default_payment_type': payment_type}"/>
|
||||
</page>
|
||||
<page name="moves" string="Transfer Journal Entries">
|
||||
<field name="move_ids"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
|
||||
@@ -91,11 +91,11 @@ class AccountPaymentLineCreate(models.TransientModel):
|
||||
# will not be refunded with a payment.
|
||||
domain += [
|
||||
('credit', '>', 0),
|
||||
#'|',
|
||||
# '|',
|
||||
('account_id.internal_type', '=', 'payable'),
|
||||
#'&',
|
||||
#('account_id.internal_type', '=', 'receivable'),
|
||||
#('reconcile_partial_id', '=', False), # TODO uncomment
|
||||
# '&',
|
||||
# ('account_id.internal_type', '=', 'receivable'),
|
||||
# ('reconcile_partial_id', '=', False), # TODO uncomment
|
||||
]
|
||||
elif self.order_id.payment_type == 'inbound':
|
||||
domain += [
|
||||
|
||||
Reference in New Issue
Block a user