From 8736d5722f816534695a878960b444c88e2044b6 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 6 May 2016 22:14:56 +0200 Subject: [PATCH] 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 --- account_payment_order/models/__init__.py | 1 + .../models/account_invoice.py | 3 +- account_payment_order/models/account_move.py | 13 + .../models/account_move_line.py | 34 +-- .../models/account_payment_order.py | 130 ++++----- .../models/bank_payment_line.py | 78 ++++-- account_payment_order/test/data.yml | 28 ++ .../test/test_partial_payment_refunded.yml | 134 ++++++++++ .../test/test_partial_payment_transfer.yml | 252 ++++++++++++++++++ .../test/test_payment_method.yml | 159 +++++++++++ .../views/account_move_line.xml | 1 + .../views/account_payment_mode.xml | 2 +- .../views/account_payment_order.xml | 3 + .../wizard/account_payment_line_create.py | 8 +- 14 files changed, 710 insertions(+), 136 deletions(-) create mode 100644 account_payment_order/models/account_move.py create mode 100644 account_payment_order/test/data.yml create mode 100644 account_payment_order/test/test_partial_payment_refunded.yml create mode 100644 account_payment_order/test/test_partial_payment_transfer.yml create mode 100644 account_payment_order/test/test_payment_method.yml diff --git a/account_payment_order/models/__init__.py b/account_payment_order/models/__init__.py index 48e597645..650494570 100644 --- a/account_payment_order/models/__init__.py +++ b/account_payment_order/models/__init__.py @@ -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 diff --git a/account_payment_order/models/account_invoice.py b/account_payment_order/models/account_invoice.py index 79290ca53..7431e12b2 100644 --- a/account_payment_order/models/account_invoice.py +++ b/account_payment_order/models/account_invoice.py @@ -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 diff --git a/account_payment_order/models/account_move.py b/account_payment_order/models/account_move.py new file mode 100644 index 000000000..1fda02896 --- /dev/null +++ b/account_payment_order/models/account_move.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# © 2016 Akretion (Alexis de Lattre ) +# 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) diff --git a/account_payment_order/models/account_move_line.py b/account_payment_order/models/account_move_line.py index ab8a58aea..1a7870950 100644 --- a/account_payment_order/models/account_move_line.py +++ b/account_payment_order/models/account_move_line.py @@ -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 = { diff --git a/account_payment_order/models/account_payment_order.py b/account_payment_order/models/account_payment_order.py index 2503b813c..99dd15de2 100644 --- a/account_payment_order/models/account_payment_order.py +++ b/account_payment_order/models/account_payment_order.py @@ -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() diff --git a/account_payment_order/models/bank_payment_line.py b/account_payment_order/models/bank_payment_line.py index 6eab672dd..8126709cf 100644 --- a/account_payment_order/models/bank_payment_line.py +++ b/account_payment_order/models/bank_payment_line.py @@ -2,7 +2,8 @@ # © 2015 Akretion - Alexis de Lattre # 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() diff --git a/account_payment_order/test/data.yml b/account_payment_order/test/data.yml new file mode 100644 index 000000000..c317b2059 --- /dev/null +++ b/account_payment_order/test/data.yml @@ -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 diff --git a/account_payment_order/test/test_partial_payment_refunded.yml b/account_payment_order/test/test_partial_payment_refunded.yml new file mode 100644 index 000000000..529a04e88 --- /dev/null +++ b/account_payment_order/test/test_partial_payment_refunded.yml @@ -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' diff --git a/account_payment_order/test/test_partial_payment_transfer.yml b/account_payment_order/test/test_partial_payment_transfer.yml new file mode 100644 index 000000000..2e7a05a7c --- /dev/null +++ b/account_payment_order/test/test_partial_payment_transfer.yml @@ -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' diff --git a/account_payment_order/test/test_payment_method.yml b/account_payment_order/test/test_payment_method.yml new file mode 100644 index 000000000..4a74dd895 --- /dev/null +++ b/account_payment_order/test/test_payment_method.yml @@ -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' diff --git a/account_payment_order/views/account_move_line.xml b/account_payment_order/views/account_move_line.xml index d9e606d11..1179fe817 100644 --- a/account_payment_order/views/account_move_line.xml +++ b/account_payment_order/views/account_move_line.xml @@ -18,6 +18,7 @@ + diff --git a/account_payment_order/views/account_payment_mode.xml b/account_payment_order/views/account_payment_mode.xml index b6eb332e7..47ba96f79 100644 --- a/account_payment_order/views/account_payment_mode.xml +++ b/account_payment_order/views/account_payment_mode.xml @@ -23,7 +23,7 @@ + context="{'default_reconcile': True, 'default_company_id': company_id}"/> + + + diff --git a/account_payment_order/wizard/account_payment_line_create.py b/account_payment_order/wizard/account_payment_line_create.py index 7703d885d..043fd3565 100644 --- a/account_payment_order/wizard/account_payment_line_create.py +++ b/account_payment_order/wizard/account_payment_line_create.py @@ -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 += [