[IMP] account_payment_disperse: refactor code

H4805
This commit is contained in:
Leo Pinedo
2022-10-11 00:28:28 +00:00
parent c4fba00361
commit 75ad1f9147
6 changed files with 177 additions and 121 deletions

View File

@@ -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,

View File

@@ -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.")

View File

@@ -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
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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_payment_disperse_account_payment_disperse account_payment_disperse.access_account_register_payments_invoice_line account_payment_disperse.account_payment_disperse access_account_register_payments_invoice_line model_account_payment_disperse_account_payment_disperse account_payment_disperse.model_account_register_payments_invoice_line base.group_user 1 1 1 1

View File

@@ -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)

View File

@@ -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

View File

@@ -1,20 +1,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- <record id="view_account_payment_from_invoices_inherited" model="ir.ui.view">
<field name="name">account.register.payments.wizard.inherited</field>
<field name="model">account.register.payments</field>
<field name="inherit_id" ref="account.view_account_payment_from_invoices"/>
<record id="view_account_payment_register_form_inherit" model="ir.ui.view">
<field name="name">account.payment.register.form.inherited</field>
<field name="model">account.payment.register</field>
<field name="inherit_id" ref="payment.view_account_payment_register_form_inherit_payment"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='amount']" position="attributes">
<attribute name="attrs">{'readonly': [('multi', '=', True), ('is_manual_disperse', '!=', True)]}</attribute>
</xpath>
<xpath expr="//field[@name='payment_method_code']" position="after">
<field name="is_manual_disperse"/>
</xpath>
<xpath expr="//group[3]" position="attributes">
<xpath expr="//field[@name='amount']" position="attributes">
<!-- <attribute name="attrs">{'readonly': [('multi', '=', True), ('is_manual_disperse', '!=', True)]}</attribute> -->
<attribute name="attrs">{'readonly': [('is_manual_disperse', '!=', True)]}</attribute>
</xpath>
<xpath expr="//group[@name='group3']" position="attributes">
<attribute name="attrs">{'invisible': ['|', ('payment_difference', '=', 0.0), ('is_manual_disperse', '=', True)]}</attribute>
</xpath>
<xpath expr="//form/group[1]" position="after">
<xpath expr="//group[@name='group1']" position="after">
<group name="invoice_lines" attrs="{'invisible': [('is_manual_disperse', '=', False)]}">
<group>
<button type="object" name="action_fill_residual"
@@ -44,5 +46,6 @@
</group>
</xpath>
</field>
</record> -->
</record>
</odoo>