diff --git a/account_payment_disperse/__manifest__.py b/account_payment_disperse/__manifest__.py index 4c419f9d..d211622b 100644 --- a/account_payment_disperse/__manifest__.py +++ b/account_payment_disperse/__manifest__.py @@ -10,8 +10,10 @@ Pay multiple invoices with one Payment, and manually disperse the amount per inv 'website': 'https://hibou.io/', 'depends': [ 'account', + 'payment', ], 'data': [ + 'security/ir.model.access.csv', 'wizard/register_payment_wizard_views.xml', ], 'installable': True, diff --git a/account_payment_disperse/models/account.py b/account_payment_disperse/models/account.py index c1eca431..c71fcda2 100644 --- a/account_payment_disperse/models/account.py +++ b/account_payment_disperse/models/account.py @@ -4,19 +4,19 @@ from odoo import api, fields, models class AccountPayment(models.Model): _inherit = 'account.payment' - # def _create_payment_entry(self, amount): - # wizard_id = self.env.context.get('payment_wizard_id') - # if wizard_id: - # # wizard = self.env['account.register.payments'].browse(wizard_id) - # wizard = self.env['account.payment.register'].browse(wizard_id) - # assert wizard - # if wizard.is_manual_disperse: - # payment_amount = sum(wizard.invoice_line_ids.filtered(lambda p: p.partner_id == self.partner_id).mapped('amount')) - # if amount < 0: - # payment_amount = -payment_amount - # return self._create_payment_entry_manual_disperse(payment_amount, wizard) + def _create_payment_entry(self, amount): + wizard_id = self.env.context.get('payment_wizard_id') + if wizard_id: + # wizard = self.env['account.register.payments'].browse(wizard_id) + wizard = self.env['account.payment.register'].browse(wizard_id) + assert wizard + if wizard.is_manual_disperse: + payment_amount = sum(wizard.invoice_line_ids.filtered(lambda p: p.partner_id == self.partner_id).mapped('amount')) + if amount < 0: + payment_amount = -payment_amount + return self._create_payment_entry_manual_disperse(payment_amount, wizard) - # return super(AccountPayment, self)._create_payment_entry(amount) + return super(AccountPayment, self)._create_payment_entry(amount) def _create_payment_entry_manual_disperse(self, amount, wizard): # When registering payments for multiple partners at the same time, without setting @@ -67,3 +67,9 @@ class AccountPayment(models.Model): aml_ids.reconcile(writeoff_acc_id, wizard.writeoff_journal_id) return move + + +class AccountMove(models.Model): + _inherit = "account.move" + + account_id = fields.Many2one('account.account', string='Account', readonly=True, states={'draft': [('readonly', False)]}, domain=[('deprecated', '=', False)], help="The partner account used for this invoice.") diff --git a/account_payment_disperse/security/ir.model.access.csv b/account_payment_disperse/security/ir.model.access.csv index 5b4a26df..e9a8525d 100644 --- a/account_payment_disperse/security/ir.model.access.csv +++ b/account_payment_disperse/security/ir.model.access.csv @@ -1,2 +1,2 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_account_payment_disperse_account_payment_disperse,account_payment_disperse.account_payment_disperse,model_account_payment_disperse_account_payment_disperse,base.group_user,1,1,1,1 \ No newline at end of file +account_payment_disperse.access_account_register_payments_invoice_line,access_account_register_payments_invoice_line,account_payment_disperse.model_account_register_payments_invoice_line,base.group_user,1,1,1,1 diff --git a/account_payment_disperse/tests/test_payment_multi.py b/account_payment_disperse/tests/test_payment_multi.py index e07fcabb..c2bc62e4 100644 --- a/account_payment_disperse/tests/test_payment_multi.py +++ b/account_payment_disperse/tests/test_payment_multi.py @@ -1,10 +1,54 @@ -from odoo.addons.account.tests.test_payment import TestPayment +# from odoo.addons.account.tests.test_payment import TestPayment +from odoo.tests.common import TransactionCase from odoo.exceptions import ValidationError import time -class PaymentMultiTest(TestPayment): - +# class PaymentMultiTest(TestPayment): +class PaymentMultiTest(TransactionCase): + + # @classmethod + def setUp(self): + super().setUp() + self.register_payments_model = self.env['account.payment.register'].with_context(active_model='account.invoice') + self.payment_model = self.env['account.payment'] + self.invoice_model = self.env['account.move'] + self.invoice_line_model = self.env['account.move.line'] + + self.partner_agrolait = self.env.ref("base.res_partner_2") + self.partner_china_exp = self.env.ref("base.res_partner_3") + self.currency_eur_id = self.env.ref("base.EUR").id + + self.product = self.env.ref("product.product_product_4") + self.payment_method_manual_in = self.env.ref("account.account_payment_method_manual_in") + self.payment_method_manual_out = self.env.ref("account.account_payment_method_manual_out") + + self.account_receivable = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1) + self.account_revenue = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)], limit=1) + + self.bank_journal_euro = self.env['account.journal'].create({'name': 'Bank', 'type': 'bank', 'code': 'BNK67'}) + + def create_invoice(self, amount=100, move_type='out_invoice', currency_id=None, partner=None, account_id=None): + """ Returns an open invoice """ + invoice = self.invoice_model.create({ + 'partner_id': partner or self.partner_agrolait.id, + 'currency_id': currency_id or self.currency_eur_id, + 'name': move_type, + 'account_id': account_id or self.account_receivable.id, + 'move_type': move_type, + 'date': time.strftime('%Y') + '-06-26', + }) + self.invoice_line_model.create({ + 'product_id': self.product.id, + 'quantity': 1, + 'price_unit': amount, + 'move_id': invoice.id, + 'name': 'something', + 'account_id': self.account_revenue.id, + }) + invoice.action_invoice_open() + return invoice + def test_multiple_payments_partial(self): """ Create test to pay several vendor bills/invoices at once """ # One payment for inv_1 and inv_2 (same partner) @@ -24,14 +68,14 @@ class PaymentMultiTest(TestPayment): register_payments.create_payments() for line in register_payments.invoice_line_ids: - if line.invoice_id == inv_1: + if line.move_id == inv_1: line.amount = 99.0 - if line.invoice_id == inv_2: + if line.move_id == inv_2: line.amount = 300.0 register_payments.create_payments() - payment_ids = self.payment_model.search([('invoice_ids', 'in', ids)], order="id desc") + payment_ids = self.payment_model.search([('move_ids', 'in', ids)], order="id desc") self.assertEqual(len(payment_ids), 1, 'Need only one payment.') self.assertEqual(payment_ids.amount, 399.0) @@ -47,7 +91,7 @@ class PaymentMultiTest(TestPayment): register_payments.is_manual_disperse = True for line in register_payments.invoice_line_ids: - if line.invoice_id == inv_2: + if line.move_id == inv_2: line.amount = 200.0 register_payments.create_payments() @@ -72,15 +116,15 @@ class PaymentMultiTest(TestPayment): register_payments.create_payments() for line in register_payments.invoice_line_ids: - if line.invoice_id == inv_1: + if line.move_id == inv_1: line.amount = 100.0 - if line.invoice_id == inv_2: + if line.move_id == inv_2: line.amount = 300.0 line.writeoff_acc_id = self.account_revenue register_payments.create_payments() - payment_ids = self.payment_model.search([('invoice_ids', 'in', ids)], order="id desc") + payment_ids = self.payment_model.search([('move_ids', 'in', ids)], order="id desc") self.assertEqual(len(payment_ids), 1, 'Need only one payment.') self.assertEqual(payment_ids.amount, 400.0) @@ -103,14 +147,14 @@ class PaymentMultiTest(TestPayment): register_payments.is_manual_disperse = True for line in register_payments.invoice_line_ids: - if line.invoice_id == inv_1: + if line.move_id == inv_1: line.amount = 100.0 - if line.invoice_id == inv_2: + if line.move_id == inv_2: line.amount = 300.0 register_payments.create_payments() - payment_ids = self.payment_model.search([('invoice_ids', 'in', ids)], order="id desc") + payment_ids = self.payment_model.search([('move_ids', 'in', ids)], order="id desc") self.assertEqual(len(payment_ids), 2, 'Need two payments.') # Useful for logging amounts of payments and their accounting # for pay in payment_ids: @@ -125,8 +169,8 @@ class PaymentMultiTest(TestPayment): self.assertEqual(inv_2.residual_signed, 200.0) def test_vendor_multiple_payments_write_off(self): - inv_1 = self.create_invoice(amount=100, type='in_invoice', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id) - inv_2 = self.create_invoice(amount=500, type='in_invoice', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id) + inv_1 = self.create_invoice(amount=100, move_type='in_invoice', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id) + inv_2 = self.create_invoice(amount=500, move_type='in_invoice', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id) ids = [inv_1.id, inv_2.id] register_payments = self.register_payments_model.with_context(active_ids=ids).create({ 'payment_date': time.strftime('%Y') + '-07-15', @@ -141,15 +185,15 @@ class PaymentMultiTest(TestPayment): register_payments.create_payments() for line in register_payments.invoice_line_ids: - if line.invoice_id == inv_1: + if line.move_id == inv_1: line.amount = 100.0 - if line.invoice_id == inv_2: + if line.move_id == inv_2: line.amount = 300.0 line.writeoff_acc_id = self.account_revenue register_payments.create_payments() - payment_ids = self.payment_model.search([('invoice_ids', 'in', ids)], order="id desc") + payment_ids = self.payment_model.search([('move_ids', 'in', ids)], order="id desc") self.assertEqual(len(payment_ids), 1, 'Need only one payment.') self.assertEqual(payment_ids.amount, 400.0) diff --git a/account_payment_disperse/wizard/register_payment_wizard.py b/account_payment_disperse/wizard/register_payment_wizard.py index 67d156b8..42200680 100644 --- a/account_payment_disperse/wizard/register_payment_wizard.py +++ b/account_payment_disperse/wizard/register_payment_wizard.py @@ -1,98 +1,99 @@ -# from odoo import api, fields, models -# from odoo.exceptions import ValidationError +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError # class AccountRegisterPayments(models.TransientModel): +class AccountPaymentRegister(models.TransientModel): # # _inherit = 'account.register.payments' -# _inherit = 'account.payment.register' + _inherit = 'account.payment.register' -# is_manual_disperse = fields.Boolean(string='Disperse Manually') -# invoice_line_ids = fields.One2many('account.register.payments.invoice.line', 'wizard_id', string='Invoices') -# writeoff_journal_id = fields.Many2one('account.journal', string='Write-off Journal') -# due_date_cutoff = fields.Date(string='Due Date Cutoff', default=fields.Date.today) + is_manual_disperse = fields.Boolean(string='Disperse Manually') + invoice_line_ids = fields.One2many('account.register.payments.invoice.line', 'wizard_id', string='Invoices') + writeoff_journal_id = fields.Many2one('account.journal', string='Write-off Journal') + due_date_cutoff = fields.Date(string='Due Date Cutoff', default=fields.Date.today) -# @api.model -# def default_get(self, fields): -# rec = super(AccountRegisterPayments, self).default_get(fields) -# if 'invoice_ids' in rec: -# invoice_ids = rec['invoice_ids'][0][2] -# rec['invoice_line_ids'] = [(0, 0, {'invoice_id': i, 'amount': 0.0}) for i in invoice_ids] -# return rec + @api.model + def default_get(self, fields): + rec = super(AccountPaymentRegister, self).default_get(fields) + if 'invoice_ids' in rec: + invoice_ids = rec['invoice_ids'][0][2] + rec['invoice_line_ids'] = [(0, 0, {'invoice_id': i, 'amount': 0.0}) for i in invoice_ids] + return rec # # @api.multi -# def create_payments(self): -# for payment in self.filtered(lambda p: p.is_manual_disperse): -# line_amount = sum(payment.invoice_line_ids.mapped('amount')) -# if abs(line_amount - payment.amount) >= 0.01: -# raise ValidationError(_('Cannot pay for %0.2f worth of invoices with %0.2f total.' % -# (line_amount, payment.amount))) -# if not payment.writeoff_journal_id and payment.invoice_line_ids.filtered(lambda l: l.writeoff_acc_id): -# raise ValidationError(_('Cannot write off without a write off journal.')) -# new_self = self.with_context(payment_wizard_id=self.id) -# return super(AccountRegisterPayments, new_self).create_payments() + def create_payments(self): + for payment in self.filtered(lambda p: p.is_manual_disperse): + line_amount = sum(payment.invoice_line_ids.mapped('amount')) + if abs(line_amount - payment.amount) >= 0.01: + raise ValidationError(_('Cannot pay for %s worth of invoices with %s total.' % + (line_amount, payment.amount))) + if not payment.writeoff_journal_id and payment.invoice_line_ids.filtered(lambda l: l.writeoff_acc_id): + raise ValidationError(_('Cannot write off without a write off journal.')) + new_self = self.with_context(payment_wizard_id=self.id) + return super(AccountPaymentRegister, new_self).create_payments() # # @api.multi -# def action_fill_residual(self): -# for payment in self: -# for line in payment.invoice_line_ids: -# line.amount = line.residual -# action = self.env.ref('account.action_account_payment_from_invoices').read()[0] -# action['res_id'] = payment.id -# return action + def action_fill_residual(self): + for payment in self: + for line in payment.invoice_line_ids: + line.amount = line.residual + action = self.env.ref('account.action_account_payment_from_invoices').read()[0] + action['res_id'] = payment.id + return action # # @api.multi -# def action_fill_residual_due(self): -# for payment in self: -# for line in payment.invoice_line_ids: -# line.amount = line.residual_due -# action = self.env.ref('account.action_account_payment_from_invoices').read()[0] -# action['res_id'] = payment.id -# return action + def action_fill_residual_due(self): + for payment in self: + for line in payment.invoice_line_ids: + line.amount = line.residual_due + action = self.env.ref('account.action_account_payment_from_invoices').read()[0] + action['res_id'] = payment.id + return action -# class AccountRegisterPaymentsInvoiceLine(models.TransientModel): -# _name = 'account.register.payments.invoice.line' +class AccountRegisterPaymentsInvoiceLine(models.TransientModel): + _name = 'account.register.payments.invoice.line' -# wizard_id = fields.Many2one('account.register.payments') -# invoice_id = fields.Many2one('account.invoice', string='Invoice', required=True) -# partner_id = fields.Many2one('res.partner', string='Partner', compute='_compute_balances', compute_sudo=True) -# residual = fields.Float(string='Remaining', compute='_compute_balances', compute_sudo=True) -# residual_due = fields.Float(string='Due', compute='_compute_balances', compute_sudo=True) -# difference = fields.Float(string='Difference', default=0.0) -# amount = fields.Float(string='Amount') -# writeoff_acc_id = fields.Many2one('account.account', string='Write-off Account') + wizard_id = fields.Many2one('account.payment.register') + invoice_id = fields.Many2one('account.invoice', string='Invoice', required=True) + partner_id = fields.Many2one('res.partner', string='Partner', compute='_compute_balances', compute_sudo=True) + residual = fields.Float(string='Remaining', compute='_compute_balances', compute_sudo=True) + residual_due = fields.Float(string='Due', compute='_compute_balances', compute_sudo=True) + difference = fields.Float(string='Difference', default=0.0) + amount = fields.Float(string='Amount') + writeoff_acc_id = fields.Many2one('account.account', string='Write-off Account') -# @api.depends('invoice_id', 'wizard_id.due_date_cutoff', 'invoice_id.partner_id') -# def _compute_balances(self): -# for line in self: -# # Bug in the ORM 12.0? The invoice is set, but there is no residual -# # on anything other than the first invoice/line processed. -# invoice = line.invoice_id.browse(line.invoice_id.id) -# residual = invoice.residual + @api.depends('invoice_id', 'wizard_id.due_date_cutoff', 'invoice_id.partner_id') + def _compute_balances(self): + for line in self: + # Bug in the ORM 12.0? The invoice is set, but there is no residual + # on anything other than the first invoice/line processed. + invoice = line.invoice_id.browse(line.invoice_id.id) + residual = invoice.residual -# cutoff_date = line.wizard_id.due_date_cutoff -# total_amount = 0.0 -# total_reconciled = 0.0 -# for move_line in invoice.move_id.line_ids.filtered(lambda r: ( -# not r.reconciled -# and r.account_id.internal_type in ('payable', 'receivable') -# and r.date_maturity <= cutoff_date -# )): -# amount = abs(move_line.debit - move_line.credit) -# total_amount += amount -# for partial_line in move_line.matched_debit_ids: -# total_reconciled += partial_line.amount -# for partial_line in move_line.matched_credit_ids: -# total_reconciled += partial_line.amount -# values = { -# 'residual': residual, -# 'residual_due': total_amount - total_reconciled, -# 'difference': residual - (line.amount or 0.0), -# 'partner_id': invoice.partner_id.id, -# } -# line.update(values) + cutoff_date = line.wizard_id.due_date_cutoff + total_amount = 0.0 + total_reconciled = 0.0 + for move_line in invoice.move_id.line_ids.filtered(lambda r: ( + not r.reconciled + and r.account_id.internal_type in ('payable', 'receivable') + and r.date_maturity <= cutoff_date + )): + amount = abs(move_line.debit - move_line.credit) + total_amount += amount + for partial_line in move_line.matched_debit_ids: + total_reconciled += partial_line.amount + for partial_line in move_line.matched_credit_ids: + total_reconciled += partial_line.amount + values = { + 'residual': residual, + 'residual_due': total_amount - total_reconciled, + 'difference': residual - (line.amount or 0.0), + 'partner_id': invoice.partner_id.id, + } + line.update(values) -# @api.onchange('amount') -# def _onchange_amount(self): -# for line in self: -# line.difference = line.residual - line.amount + @api.onchange('amount') + def _onchange_amount(self): + for line in self: + line.difference = line.residual - line.amount diff --git a/account_payment_disperse/wizard/register_payment_wizard_views.xml b/account_payment_disperse/wizard/register_payment_wizard_views.xml index 6110f243..aa52d2c2 100644 --- a/account_payment_disperse/wizard/register_payment_wizard_views.xml +++ b/account_payment_disperse/wizard/register_payment_wizard_views.xml @@ -1,20 +1,22 @@ - + {'readonly': [('is_manual_disperse', '!=', True)]} + + {'invisible': ['|', ('payment_difference', '=', 0.0), ('is_manual_disperse', '=', True)]} - +