From 19ebc1ef0a17a4df15fe8d07df4a4123f289c6ae Mon Sep 17 00:00:00 2001 From: Matt Choplin Date: Fri, 6 Mar 2015 10:12:38 +0000 Subject: [PATCH 1/6] [test_for_account_banking_payment_transfer] add tests according to the use case given in https://github.com/OCA/bank-payment/issues/93 --- .../__openerp__.py | 6 + .../test/data.yml | 28 +++ .../test/test_partial_payment_refunded.yml | 162 +++++++++++++++++ .../test/test_payment_method.yml | 164 ++++++++++++++++++ 4 files changed, 360 insertions(+) create mode 100644 account_banking_payment_transfer/test/data.yml create mode 100644 account_banking_payment_transfer/test/test_partial_payment_refunded.yml create mode 100644 account_banking_payment_transfer/test/test_payment_method.yml diff --git a/account_banking_payment_transfer/__openerp__.py b/account_banking_payment_transfer/__openerp__.py index 3d36a5af2..f42d83539 100644 --- a/account_banking_payment_transfer/__openerp__.py +++ b/account_banking_payment_transfer/__openerp__.py @@ -38,6 +38,12 @@ 'view/payment_mode.xml', 'workflow/account_payment.xml', ], + 'test': [ + 'test/data.yml', + 'test/test_payment_method.yml', + 'test/test_partial_payment_refunded.yml', + + ], 'description': '''Payment order reconciliation infrastructure This module reconciles invoices as soon as the payment order diff --git a/account_banking_payment_transfer/test/data.yml b/account_banking_payment_transfer/test/data.yml new file mode 100644 index 000000000..c317b2059 --- /dev/null +++ b/account_banking_payment_transfer/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_banking_payment_transfer/test/test_partial_payment_refunded.yml b/account_banking_payment_transfer/test/test_partial_payment_refunded.yml new file mode 100644 index 000000000..cf476f155 --- /dev/null +++ b/account_banking_payment_transfer/test/test_partial_payment_refunded.yml @@ -0,0 +1,162 @@ +- + 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_invoice +- + !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 reconcile the invoice and the refund +- + !record {model: account.move.line.reconcile, id: account_move_line_reconcile0}: + trans_nbr: 2 + credit: 600.0 + debit: 200.0 + writeoff: -400.0 +- + Then I click on the 'Partial Reconcile' button +- + !python {model: account.move.line.reconcile}: | + move_line_obj = self.pool.get('account.move.line') + inv_obj = self.pool.get('account.invoice') + invoice_move_id = inv_obj.browse(cr, uid, ref("account_invoice_supplier_refunded")).move_id.id + refund_move_id = inv_obj.browse(cr, uid, ref("account_refund_supplier_refunded")).move_id.id + debit_line_id = move_line_obj.search(cr, uid, [('move_id', '=', refund_move_id),('debit', '=', 200)])[0] + credit_line_id = move_line_obj.search(cr, uid, [('move_id', '=', invoice_move_id),('credit', '=', 600)])[0] + ids = [debit_line_id, credit_line_id] + partial_reconcile = self.trans_rec_reconcile_partial_reconcile(cr, uid, [ref('account_move_line_reconcile0')], { + 'active_model': 'account.move.line', 'active_ids': ids, 'tz': False, 'active_id': ids[1]}) + move_line = move_line_obj.browse(cr, uid, ids) + assert move_line[0].reconcile_partial_id, "Partial reconcilation is not done" +- + I check that the invoice balance (residual) is now 400 +- + !assert {model: account.invoice, id: account_invoice_supplier_refunded, severity: error, string: Invoice residual should be 400.}: + - residual == 400 + - amount_total == 600 +- + I create a payment order on which I will select the invoice I created +- + !record {model: payment.order, id: partial_payment_order_1}: + mode: account_banking_payment_transfer.payment_mode0 + date_prefered: 'due' +- + !record {model: payment.order.create, id: payment_order_create_1}: + duedate: !eval time.strftime('%Y-%m-%d') +- + I search for the invoice entries to make the payment. +- + !python {model: payment.order.create}: | + self.search_entries(cr, uid, [ref("payment_order_create_1")], { + "active_model": "payment.order", "active_ids": [ref("partial_payment_order_1")], + "active_id": ref("partial_payment_order_1"), }) +- + I create payment lines entries. +- + !python {model: payment.order.create}: | + 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: + move_line = l + break + self.write(cr, uid, [ref("payment_order_create_1")], {'entries': [(6,0,[move_line.id])]}) + self.create_payment(cr, uid, [ref("payment_order_create_1")], { + "active_model": "payment.order", "active_ids": [ref("partial_payment_order_1")], + "active_id": ref("partial_payment_order_1")}) + pay_obj = self.pool.get('payment.order') + pay = pay_obj.browse(cr, uid, ref('partial_payment_order_1')) + assert pay.line_ids[0].amount_currency == 400 + assert pay.total == 400 +- + 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' +- + 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' \ No newline at end of file diff --git a/account_banking_payment_transfer/test/test_payment_method.yml b/account_banking_payment_transfer/test/test_payment_method.yml new file mode 100644 index 000000000..a20db04d4 --- /dev/null +++ b/account_banking_payment_transfer/test/test_payment_method.yml @@ -0,0 +1,164 @@ +- + I create a supplier invoice +- + !record {model: account.invoice, id: account_invoice_supplier0, view: account.invoice_supplier_form}: + check_total: 450.0 + 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 on which I will select the invoice I created +- + !record {model: payment.order, id: payment_order_0}: + mode: account_banking_payment_transfer.payment_mode0 + date_prefered: 'due' +- + !record {model: payment.order.create, id: payment_order_create_0}: + duedate: !eval time.strftime('%Y-%m-%d') +- + I search for the invoice entries to make the payment. +- + !python {model: payment.order.create}: | + self.search_entries(cr, uid, [ref("payment_order_create_0")], { + "active_model": "payment.order", "active_ids": [ref("payment_order_0")], + "active_id": ref("payment_order_0"), }) +- + I create payment lines entries. +- + !python {model: payment.order.create}: | + 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, [ref("payment_order_create_0")], {'entries': entries}) + self.create_payment(cr, uid, [ref("payment_order_create_0")], { + "active_model": "payment.order", "active_ids": [ref("payment_order_0")], + "active_id": ref("payment_order_0")}) + 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' \ No newline at end of file From 0f91f276ac9881a2729556042af9854d17c3be11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Wed, 14 Jan 2015 10:50:50 +0100 Subject: [PATCH 2/6] [IMP] improve selection of invoices to pay This fixes #93. - filter invoices that are already in draft and open payment orders. - use residual amount to care for partial reconciliations and align with official 8.0 --- .../models/account_move_line.py | 76 +------------------ .../wizard/payment_order_create.py | 31 +++++++- 2 files changed, 29 insertions(+), 78 deletions(-) diff --git a/account_banking_payment_export/models/account_move_line.py b/account_banking_payment_export/models/account_move_line.py index d8ca2fd1f..54adf2ca8 100644 --- a/account_banking_payment_export/models/account_move_line.py +++ b/account_banking_payment_export/models/account_move_line.py @@ -19,8 +19,7 @@ # ############################################################################## -from openerp.osv import orm, fields -from operator import itemgetter +from openerp.osv import orm class AccountMoveLine(orm.Model): @@ -40,76 +39,3 @@ class AccountMoveLine(orm.Model): cr, uid, ids, ['debit', 'credit'], context=context): total += (line['debit'] or 0.0) - (line['credit'] or 0.0) return total - - # All the code below aims at fixing one small issue in _to_pay_search() - # But _to_pay_search() is the search function of the field 'amount_to_pay' - # which is a field.function and these functions are not inheritable in - # OpenERP. - # So we have to inherit the field 'amount_to_pay' and duplicate the related - # functions - # If the patch that I proposed in this bug report - # https://bugs.launchpad.net/openobject-addons/+bug/1275478 - # is integrated in addons/account_payment, then we will be able to remove - # this file. -- Alexis de Lattre - - def _amount_to_pay(self, cr, uid, ids, name, arg=None, context=None): - """ Return the amount still to pay regarding all the payment orders - (excepting cancelled orders)""" - if not ids: - return {} - cr.execute("""SELECT ml.id, - CASE WHEN ml.amount_currency < 0 - THEN - ml.amount_currency - ELSE ml.credit - END - - (SELECT coalesce(sum(amount_currency),0) - FROM payment_line pl - INNER JOIN payment_order po - ON (pl.order_id = po.id) - WHERE move_line_id = ml.id - AND po.state != 'cancel') AS amount - FROM account_move_line ml - WHERE id IN %s""", (tuple(ids),)) - r = dict(cr.fetchall()) - return r - - def _to_pay_search(self, cr, uid, obj, name, args, context=None): - if not args: - return [] - line_obj = self.pool.get('account.move.line') - query = line_obj._query_get(cr, uid, context={}) - where = ' and '.join(map(lambda x: '''(SELECT - CASE WHEN l.amount_currency < 0 - THEN - l.amount_currency - ELSE l.credit - END - coalesce(sum(pl.amount_currency), 0) - FROM payment_line pl - INNER JOIN payment_order po ON (pl.order_id = po.id) - WHERE move_line_id = l.id - AND po.state != 'cancel' - ) %(operator)s %%s ''' % {'operator': x[1]}, args)) - sql_args = tuple(map(itemgetter(2), args)) - cr.execute( - '''SELECT id - FROM account_move_line l - WHERE account_id IN (select id - FROM account_account - WHERE type in %s AND active) - AND reconcile_id IS null - AND credit > 0 - AND ''' + where + ' and ' + query, - (('payable', 'receivable'),) + sql_args) - # The patch we have compared to the original function in - # addons/account_payment is just above : - # original code : type = 'payable' - # fixed code : type in ('payable', 'receivable') - res = cr.fetchall() - if not res: - return [('id', '=', '0')] - return [('id', 'in', map(lambda x:x[0], res))] - - _columns = { - 'amount_to_pay': fields.function( - _amount_to_pay, type='float', string='Amount to pay', - fnct_search=_to_pay_search), - } diff --git a/account_banking_payment_export/wizard/payment_order_create.py b/account_banking_payment_export/wizard/payment_order_create.py index 90be9a94c..88e9b3424 100644 --- a/account_banking_payment_export/wizard/payment_order_create.py +++ b/account_banking_payment_export/wizard/payment_order_create.py @@ -3,6 +3,7 @@ # # Copyright (C) 2009 EduSense BV (). # (C) 2011 - 2013 Therp BV (). +# (C) 2014 - 2015 ACSONE SA/NV (). # # All other contributions are (C) by their respective contributors # @@ -45,9 +46,33 @@ class PaymentOrderCreate(models.TransientModel): def extend_payment_order_domain(self, payment_order, domain): if payment_order.payment_order_type == 'payment': domain += [('account_id.type', 'in', ('payable', 'receivable')), - ('amount_to_pay', '>', 0)] + ('credit', '>', 0)] return True + @api.model + def filter_lines(self, lines): + """ Filter move lines before proposing them for inclusion + in the payment order. + + This implementation filters out move lines that are already + included in draft or open payment orders. This prevents the + user to include the same line in two different open payment + orders. When the payment order is sent, it is assumed that + the move will be reconciled soon (or immediately with + account_banking_payment_transfer), so it will not be + proposed anymore for payment. + + See also https://github.com/OCA/bank-payment/issues/93. + + :param lines: recordset of move lines + :returns: list of move line ids + """ + payment_lines = self.env['payment.line'].\ + search([('order_id.state', 'in', ('draft', 'open')), + ('move_line_id', 'in', lines.ids)]) + to_exclude = set([l.move_line_id.id for l in payment_lines]) + return [l.id for l in lines if l.id not in to_exclude] + @api.multi def search_entries(self): """This method taken from account_payment module. @@ -69,7 +94,7 @@ class PaymentOrderCreate(models.TransientModel): # -- end account_direct_debit -- lines = line_obj.search(domain) context = self.env.context.copy() - context['line_ids'] = lines.ids + context['line_ids'] = self.filter_lines(lines) context['populate_results'] = self.populate_results model_datas = model_data_obj.search( [('model', '=', 'ir.ui.view'), @@ -129,7 +154,7 @@ class PaymentOrderCreate(models.TransientModel): 'amount_to_receive' in line): amount_currency = line.amount_to_receive else: - amount_currency = line.amount_to_pay + amount_currency = line.amount_residual_currency line2bank = line.line2bank(payment.mode.id) # -- end account banking res = {'move_line_id': line.id, From 291962316696b6f92b1451a68dd699b7095fe8be Mon Sep 17 00:00:00 2001 From: "Adrien Peiffer (ACSONE)" Date: Tue, 20 Jan 2015 14:18:07 +0100 Subject: [PATCH 3/6] [ADD] Add the possibility to show partial reconcilations from payment order --- .../__openerp__.py | 1 + .../model/account_payment.py | 34 +++++++++++++++++++ .../view/account_payment.xml | 20 +++++++++++ 3 files changed, 55 insertions(+) create mode 100644 account_banking_payment_transfer/view/account_payment.xml diff --git a/account_banking_payment_transfer/__openerp__.py b/account_banking_payment_transfer/__openerp__.py index f42d83539..d197f8e5f 100644 --- a/account_banking_payment_transfer/__openerp__.py +++ b/account_banking_payment_transfer/__openerp__.py @@ -37,6 +37,7 @@ 'data': [ 'view/payment_mode.xml', 'workflow/account_payment.xml', + 'view/account_payment.xml', ], 'test': [ 'test/data.yml', diff --git a/account_banking_payment_transfer/model/account_payment.py b/account_banking_payment_transfer/model/account_payment.py index 6dff5bfd3..4199c90e3 100644 --- a/account_banking_payment_transfer/model/account_payment.py +++ b/account_banking_payment_transfer/model/account_payment.py @@ -34,6 +34,18 @@ class PaymentOrder(models.Model): ''' _inherit = 'payment.order' + @api.multi + def get_partial_reconcile_ids(self): + self.ensure_one() + reconcile_partial_ids = [line.move_line_id.reconcile_partial_id.id + for line in self.line_ids if + line.move_line_id.reconcile_partial_id.id] + return reconcile_partial_ids + + @api.one + def get_partial_reconcile_count(self): + self.partial_reconcile_count = len(self.get_partial_reconcile_ids()) + date_scheduled = fields.Date(states={ 'sent': [('readonly', True)], 'rejected': [('readonly', True)], @@ -75,6 +87,9 @@ class PaymentOrder(models.Model): 'done': [('readonly', True)] }) date_sent = fields.Date(string='Send date', readonly=True) + partial_reconcile_count = fields\ + .Integer(string='Partial Reconciles Counter', + compute='get_partial_reconcile_count') def action_rejected(self, cr, uid, ids, context=None): return True @@ -253,3 +268,22 @@ class PaymentOrder(models.Model): # State field is written by act_sent_wait self.write({'date_sent': fields.Date.context_today(self)}) return True + + @api.multi + def partial(self): + self.ensure_one() + view_id = self.env.ref('account.view_move_line_tree').id + reconcile_partial_ids = self.get_partial_reconcile_ids() + reconcile_partial_domain = [('reconcile_partial_id', 'in', + reconcile_partial_ids)] + return { + 'name': _('Partial Reconcile Moves Line'), + 'context': self.env.context, + 'domain': reconcile_partial_domain, + 'view_type': 'form', + 'view_mode': 'tree,form', + 'res_model': 'account.move.line', + 'views': [(view_id, 'tree')], + 'type': 'ir.actions.act_window', + 'target': 'current', + } diff --git a/account_banking_payment_transfer/view/account_payment.xml b/account_banking_payment_transfer/view/account_payment.xml new file mode 100644 index 000000000..8bb537622 --- /dev/null +++ b/account_banking_payment_transfer/view/account_payment.xml @@ -0,0 +1,20 @@ + + + + + account.payment.order.form (account_banking_payment_transfer) + payment.order + + + + + + + + + From bc19e1863a13bca0f20861c504f233a30a92122a Mon Sep 17 00:00:00 2001 From: "Adrien Peiffer (ACSONE)" Date: Thu, 29 Jan 2015 16:41:35 +0100 Subject: [PATCH 4/6] [FIX] If move line linked with payment line is already partially reconciled then write new values on this partial reconcile --- account_banking_payment_transfer/model/payment_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account_banking_payment_transfer/model/payment_line.py b/account_banking_payment_transfer/model/payment_line.py index 3dc640a20..a0116ace2 100644 --- a/account_banking_payment_transfer/model/payment_line.py +++ b/account_banking_payment_transfer/model/payment_line.py @@ -177,7 +177,7 @@ class PaymentLine(orm.Model): if torec_move_line.reconcile_partial_id: reconcile_obj.write( - cr, uid, transit_move_line.reconcile_partial_id.id, + cr, uid, [torec_move_line.reconcile_partial_id.id], vals, context=context) else: reconcile_obj.create( From c288d6264fab3d53ce1f64364913e24d37e68fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Wed, 4 Mar 2015 11:01:45 +0100 Subject: [PATCH 5/6] [IMP] improve selection of customer credit notes to pay --- .../wizard/payment_order_create.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/account_banking_payment_export/wizard/payment_order_create.py b/account_banking_payment_export/wizard/payment_order_create.py index 88e9b3424..40a98457d 100644 --- a/account_banking_payment_export/wizard/payment_order_create.py +++ b/account_banking_payment_export/wizard/payment_order_create.py @@ -45,9 +45,22 @@ class PaymentOrderCreate(models.TransientModel): @api.model def extend_payment_order_domain(self, payment_order, domain): if payment_order.payment_order_type == 'payment': - domain += [('account_id.type', 'in', ('payable', 'receivable')), - ('credit', '>', 0)] - return True + # For payables, propose all unreconciled credit lines, + # including partially reconciled ones. + # If they are partially reconciled with a supplier refund, + # the residual will be added to the payment order. + # + # For receivables, propose all unreconciled credit lines. + # (ie customer refunds): they can be refunded with a payment. + # Do not propose partially reconciled credit lines, + # as they are deducted from a customer invoice, and + # will not be refunded with a payment. + domain += [('credit', '>', 0), + '|', + ('account_id.type', '=', 'payable'), + '&', + ('account_id.type', '=', 'receivable'), + ('reconcile_partial_id', '=', False)] @api.model def filter_lines(self, lines): From 0e02b7e13187d84a502b164d8488e31957fd040a Mon Sep 17 00:00:00 2001 From: Matt Choplin Date: Fri, 6 Mar 2015 12:01:02 +0000 Subject: [PATCH 6/6] [test_for_account_banking_payment_transfer] add test for the partial payment without refund --- .../__openerp__.py | 2 + .../test/test_partial_payment_transfer.yml | 276 ++++++++++++++++++ 2 files changed, 278 insertions(+) create mode 100644 account_banking_payment_transfer/test/test_partial_payment_transfer.yml diff --git a/account_banking_payment_transfer/__openerp__.py b/account_banking_payment_transfer/__openerp__.py index d197f8e5f..e9d37c1d6 100644 --- a/account_banking_payment_transfer/__openerp__.py +++ b/account_banking_payment_transfer/__openerp__.py @@ -43,6 +43,8 @@ 'test/data.yml', 'test/test_payment_method.yml', 'test/test_partial_payment_refunded.yml', + 'test/test_partial_payment_transfer.yml', + ], 'description': '''Payment order reconciliation infrastructure diff --git a/account_banking_payment_transfer/test/test_partial_payment_transfer.yml b/account_banking_payment_transfer/test/test_partial_payment_transfer.yml new file mode 100644 index 000000000..f32b9a84d --- /dev/null +++ b/account_banking_payment_transfer/test/test_partial_payment_transfer.yml @@ -0,0 +1,276 @@ +- + 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 on which I will select the invoice I created +- + !record {model: payment.order, id: partial_payment_order_2}: + mode: account_banking_payment_transfer.payment_mode0 + date_prefered: 'due' +- + !record {model: payment.order.create, id: payment_order_create_2}: + duedate: !eval time.strftime('%Y-%m-%d') +- + I search for the invoice entries to make the payment. +- + !python {model: payment.order.create}: | + self.search_entries(cr, uid, [ref("payment_order_create_2")], { + "active_model": "payment.order", "active_ids": [ref("partial_payment_order_2")], + "active_id": ref("partial_payment_order_2"), }) +- + I create payment lines entries. +- + !python {model: payment.order.create}: | + 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, [ref("payment_order_create_2")], {'entries': [(6,0,[move_line.id])]}) + self.create_payment(cr, uid, [ref("payment_order_create_2")], { + "active_model": "payment.order", "active_ids": [ref("partial_payment_order_2")], + "active_id": ref("partial_payment_order_2")}) + 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' +- + !record {model: payment.order.create, id: partial_payment_order_create_2}: + duedate: !eval time.strftime('%Y-%m-%d') +- + I search for the invoice entries to make the payment. +- + !python {model: payment.order.create}: | + self.search_entries(cr, uid, [ref("partial_payment_order_create_2")], { + "active_model": "payment.order", "active_ids": [ref("partial_partial_payment_order_2")], + "active_id": ref("partial_partial_payment_order_2"), }) +- + I create payment lines entries. +- + !python {model: payment.order.create}: | + 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, [ref("partial_payment_order_create_2")], {'entries': [(6,0,[move_line.id])]}) + self.create_payment(cr, uid, [ref("partial_payment_order_create_2")], { + "active_model": "payment.order", "active_ids": [ref("partial_partial_payment_order_2")], + "active_id": ref("partial_partial_payment_order_2")}) + 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' +- + !record {model: payment.order.create, id: partial_payment_order_create_3}: + duedate: !eval time.strftime('%Y-%m-%d') +- + I search for the invoice entries to make the payment. +- + !python {model: payment.order.create}: | + self.search_entries(cr, uid, [ref("partial_payment_order_create_3")], { + "active_model": "payment.order", "active_ids": [ref("partial_partial_payment_order_3")], + "active_id": ref("partial_partial_payment_order_3"), }) +- + I create payment lines entries. +- + !python {model: payment.order.create}: | + 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, [ref("partial_payment_order_create_3")], {'entries': [(6,0,[move_line.id])]}) + self.create_payment(cr, uid, [ref("partial_payment_order_create_3")], { + "active_model": "payment.order", "active_ids": [ref("partial_partial_payment_order_3")], + "active_id": ref("partial_partial_payment_order_3")}) + 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' + + + + + + + + +