mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
Merge branch 'mig/14.0/sale_payment_web' into '14.0'
WIP: mig/14.0/sale_payment_web into 14.0 See merge request hibou-io/hibou-odoo/suite!1443
This commit is contained in:
2
sale_payment_web/__init__.py
Normal file
2
sale_payment_web/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import models
|
||||
from . import wizard
|
||||
28
sale_payment_web/__manifest__.py
Executable file
28
sale_payment_web/__manifest__.py
Executable file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
'name': 'Sale Payment Web',
|
||||
'author': 'Hibou Corp. <hello@hibou.io>',
|
||||
'category': 'Sales',
|
||||
'version': '14.0.1.0.0',
|
||||
'description':
|
||||
"""
|
||||
Sale Payment Web
|
||||
================
|
||||
|
||||
Allow sales people to register payments for sale orders.
|
||||
|
||||
Electronic payments will create transactions and automatically reconcile on the invoice.
|
||||
|
||||
""",
|
||||
'depends': [
|
||||
'payment',
|
||||
'sale',
|
||||
],
|
||||
'auto_install': False,
|
||||
'data': [
|
||||
'security/sale_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/payment_views.xml',
|
||||
'views/sale_views.xml',
|
||||
'wizard/account_payment_register_views.xml',
|
||||
],
|
||||
}
|
||||
2
sale_payment_web/models/__init__.py
Normal file
2
sale_payment_web/models/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import payment
|
||||
from . import sale
|
||||
18
sale_payment_web/models/payment.py
Normal file
18
sale_payment_web/models/payment.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class PaymentTransaction(models.Model):
|
||||
_inherit = 'payment.transaction'
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
active_ids = self._context.get('active_ids')
|
||||
if active_ids and self._context.get('active_model') == 'sale.order':
|
||||
values['sale_order_ids'] = [(6, 0, active_ids)]
|
||||
return super(PaymentTransaction, self).create(values)
|
||||
|
||||
|
||||
class AccountPayment(models.Model):
|
||||
_inherit = 'account.payment'
|
||||
|
||||
sale_order_id = fields.Many2one('sale.order', string="Sale Order")
|
||||
35
sale_payment_web/models/sale.py
Normal file
35
sale_payment_web/models/sale.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
manual_payment_ids = fields.One2many('account.payment', 'sale_order_id', string='Manual Payments')
|
||||
manual_amount_registered_payment = fields.Monetary('Manually Registered Amount', compute='_compute_manual_amount_registered_payment')
|
||||
manual_amount_remaining = fields.Monetary('Remaining Amount Due', compute='_compute_manual_amount_registered_payment')
|
||||
|
||||
@api.depends('manual_payment_ids.amount', 'amount_total')
|
||||
def _compute_manual_amount_registered_payment(self):
|
||||
for so in self:
|
||||
so.manual_amount_registered_payment = sum(so.manual_payment_ids.mapped('amount'))
|
||||
so.manual_amount_remaining = so.amount_total - so.manual_amount_registered_payment
|
||||
|
||||
|
||||
def action_manual_payments(self):
|
||||
action = self.env.ref('account.action_account_payments').read()[0]
|
||||
domain = action['domain'] or '[]'
|
||||
domain = safe_eval(domain)
|
||||
domain.append(('id', 'in', self.manual_payment_ids.ids))
|
||||
action['domain'] = domain
|
||||
return action
|
||||
|
||||
def action_payment_register(self):
|
||||
return {
|
||||
'name': _('Register Payment'),
|
||||
'res_model': 'account.payment.register',
|
||||
'view_mode': 'form',
|
||||
'context': {'active_ids': self.ids, 'active_model': 'sale.order'},
|
||||
'target': 'new',
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
3
sale_payment_web/security/ir.model.access.csv
Normal file
3
sale_payment_web/security/ir.model.access.csv
Normal file
@@ -0,0 +1,3 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_payment_method,account.payment.method,account.model_account_payment_method,group_payment_web,1,0,0,0
|
||||
access_payment,account.payment,account.model_account_payment,group_payment_web,1,1,1,1
|
||||
|
9
sale_payment_web/security/sale_security.xml
Normal file
9
sale_payment_web/security/sale_security.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="group_payment_web" model="res.groups">
|
||||
<field name="name">Sale Order Payments</field>
|
||||
<field name="category_id" ref="base.module_category_hidden"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
1
sale_payment_web/tests/__init__.py
Normal file
1
sale_payment_web/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_sale_payment
|
||||
49
sale_payment_web/tests/test_sale_payment.py
Normal file
49
sale_payment_web/tests/test_sale_payment.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from odoo.addons.sale.tests.test_sale_to_invoice import TestSaleToInvoice
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSalePayment(TestSaleToInvoice):
|
||||
|
||||
def _sale_context(self):
|
||||
return {
|
||||
'active_model': 'sale.order',
|
||||
'active_ids': [self.sale_order.id],
|
||||
'active_id': self.sale_order.id,
|
||||
}
|
||||
|
||||
def test_00_payment(self):
|
||||
self.sale_order.action_confirm()
|
||||
|
||||
payment_wizard = self.env['account.payment.register'].with_context(self._sale_context()).create({'amount': -15})
|
||||
self.assertTrue(payment_wizard.journal_id)
|
||||
with self.assertRaises(UserError):
|
||||
payment_wizard._create_payments()
|
||||
|
||||
payment_wizard = self.env['account.payment.register'].with_context(self._sale_context()).create({'amount': 0})
|
||||
self.assertTrue(payment_wizard.journal_id)
|
||||
with self.assertRaises(UserError):
|
||||
payment_wizard._create_payments()
|
||||
|
||||
payment_wizard = self.env['account.payment.register'].with_context(self._sale_context()).create({})
|
||||
self.assertTrue(payment_wizard.journal_id)
|
||||
|
||||
payment = payment_wizard._create_payments()
|
||||
self.assertEqual(payment.amount, self.sale_order.amount_total)
|
||||
self.assertEqual(payment.sale_order_id, self.sale_order)
|
||||
self.assertEqual(self.sale_order.manual_amount_remaining, 0.0)
|
||||
|
||||
payment_wizard = self.env['account.payment.register'].with_context(self._sale_context()).create({'amount': 15})
|
||||
self.assertTrue(payment_wizard.journal_id)
|
||||
with self.assertRaises(UserError):
|
||||
payment_wizard._create_payments()
|
||||
|
||||
def test_10_partial_payment(self):
|
||||
payment_wizard = self.env['account.payment.register'].with_context(self._sale_context()).create({'amount': 50})
|
||||
self.assertTrue(payment_wizard.journal_id)
|
||||
|
||||
payment = payment_wizard._create_payments()
|
||||
self.assertEqual(payment.amount, 50)
|
||||
self.assertEqual(payment.sale_order_id, self.sale_order)
|
||||
self.assertEqual(self.sale_order.manual_amount_remaining, self.sale_order.amount_total - 50)
|
||||
15
sale_payment_web/views/payment_views.xml
Normal file
15
sale_payment_web/views/payment_views.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_account_payment_form_inherit" model="ir.ui.view">
|
||||
<field name="name">account.payment.form.inherit</field>
|
||||
<field name="model">account.payment</field>
|
||||
<field name="inherit_id" ref="account.view_account_payment_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='move_id']" position="after">
|
||||
<field name="sale_order_id" attrs="{'invisible': [('state', '!=', 'draft'), ('sale_order_id', '=', False)], 'readonly': [('state', '!=', 'draft')]}" groups="account.group_account_readonly"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
27
sale_payment_web/views/sale_views.xml
Normal file
27
sale_payment_web/views/sale_views.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_order_form_inherit" model="ir.ui.view">
|
||||
<field name="name">sale.order.form.inherit</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="groups_id" eval="[(4, ref('group_payment_web'))]"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='action_draft']" position="after">
|
||||
<button name="action_payment_register"
|
||||
type="object"
|
||||
context="{'dont_redirect_to_payments': True}"
|
||||
string="Register Payment"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='amount_total']" position="after">
|
||||
<field name="manual_amount_remaining" widget="monetary"/>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<field name="manual_payment_ids" invisible="1"/>
|
||||
<button name="action_manual_payments" string="Manual Payments" type="object" class="oe_stat_button"
|
||||
icon="fa-dollar" attrs="{'invisible':[('manual_payment_ids', '=', [])]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
1
sale_payment_web/wizard/__init__.py
Normal file
1
sale_payment_web/wizard/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import account_payment_register
|
||||
129
sale_payment_web/wizard/account_payment_register.py
Normal file
129
sale_payment_web/wizard/account_payment_register.py
Normal file
@@ -0,0 +1,129 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AccountPaymentRegister(models.TransientModel):
|
||||
_inherit = 'account.payment.register'
|
||||
|
||||
sale_order_id = fields.Many2one('sale.order', string='Sale Order')
|
||||
so_amount_remaining = fields.Monetary(related='sale_order_id.manual_amount_remaining')
|
||||
so_amount_over = fields.Boolean()
|
||||
|
||||
@api.onchange('amount')
|
||||
def _compute_amount_remaining(self):
|
||||
self.so_amount_over = self.sale_order_id and self.amount > self.so_amount_remaining
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
# super implementation checks active_ids, but not active_model
|
||||
active_ids = self._context.get('active_ids')
|
||||
if self._context.get('active_model') == 'sale.order' and 'line_ids' in fields:
|
||||
fields.remove('line_ids')
|
||||
rec = super(AccountPaymentRegister, self).default_get(fields)
|
||||
if self._context.get('active_model') != 'sale.order':
|
||||
return rec
|
||||
sale_order = self.env['sale.order'].browse(active_ids)
|
||||
if len(sale_order) != 1:
|
||||
raise UserError('Register payment must be called on a sale order.')
|
||||
|
||||
if 'sale_order_id' not in rec:
|
||||
rec['sale_order_id'] = sale_order.id
|
||||
return rec
|
||||
|
||||
def _compute_from_lines(self):
|
||||
wizards = self.filtered('sale_order_id')
|
||||
super(AccountPaymentRegister, self - wizards)._compute_from_lines()
|
||||
for wizard in wizards:
|
||||
sale_order = wizard.sale_order_id
|
||||
account_receivable = sale_order.partner_id.property_account_receivable_id
|
||||
if not account_receivable:
|
||||
raise UserError('Your partner must have an Account Receivable setup.')
|
||||
company = sale_order.company_id
|
||||
amount_currency = sale_order.manual_amount_remaining
|
||||
if sale_order.currency_id == company.currency_id:
|
||||
amount = amount_currency
|
||||
else:
|
||||
amount = sale_order.currency_id._convert(amount_currency, company.currency_id, company, fields.Date.context_today(self))
|
||||
wizard.update({
|
||||
'company_id': company.id,
|
||||
'partner_id': sale_order.partner_id.id,
|
||||
'partner_type': 'customer',
|
||||
'payment_type': 'inbound',
|
||||
'source_currency_id': sale_order.currency_id.id,
|
||||
'source_amount': amount,
|
||||
'source_amount_currency': amount_currency,
|
||||
'can_edit_wizard': True,
|
||||
'can_group_payments': False,
|
||||
})
|
||||
|
||||
def _compute_communication(self):
|
||||
# The communication can't be computed in '_compute_from_lines' because
|
||||
# it's a compute editable field and then, should be computed in a separated method.
|
||||
wizards = self.filtered('sale_order_id')
|
||||
super(AccountPaymentRegister, self - wizards)._compute_communication()
|
||||
for wizard in wizards:
|
||||
so = wizard.sale_order_id
|
||||
wizards.communication = so.reference or so.name
|
||||
|
||||
def _compute_group_payment(self):
|
||||
wizards = self.filtered('sale_order_id')
|
||||
super(AccountPaymentRegister, self - wizards)._compute_group_payment()
|
||||
wizards.write({'group_payment': False})
|
||||
|
||||
def _compute_journal_id(self):
|
||||
wizards = self.filtered('sale_order_id')
|
||||
super(AccountPaymentRegister, self - wizards)._compute_journal_id()
|
||||
for wizard in wizards:
|
||||
wizard.journal_id = self.env['account.journal'].search([
|
||||
('type', 'in', ('bank', 'cash')),
|
||||
('company_id', '=', wizard.company_id.id),
|
||||
], limit=1)
|
||||
|
||||
def _compute_available_partner_bank_ids(self):
|
||||
wizards = self.filtered('sale_order_id')
|
||||
super(AccountPaymentRegister, self - wizards)._compute_available_partner_bank_ids()
|
||||
for wizard in wizards:
|
||||
wizard.available_partner_bank_ids = wizard.journal_id.bank_account_id
|
||||
|
||||
def _compute_partner_bank_id(self):
|
||||
wizards = self.filtered('sale_order_id')
|
||||
super(AccountPaymentRegister, self - wizards)._compute_partner_bank_id()
|
||||
wizards.write({'partner_bank_id': None})
|
||||
|
||||
def _compute_suitable_payment_token_partner_ids(self):
|
||||
""" Override from `payment_fix_register_token` """
|
||||
wizards = self.filtered('sale_order_id')
|
||||
super(AccountPaymentRegister, self - wizards)._compute_suitable_payment_token_partner_ids()
|
||||
for wizard in wizards:
|
||||
partners = wizard.sale_order_id.partner_id
|
||||
commercial_partners = partners.commercial_partner_id
|
||||
children_partners = commercial_partners.child_ids
|
||||
wizard.suitable_payment_token_partner_ids = (partners + commercial_partners + children_partners)._origin
|
||||
|
||||
def _create_payment_vals_from_sale_order(self):
|
||||
if self.amount <= 0:
|
||||
raise UserError("You must enter a positive amount.")
|
||||
elif self.amount > self.so_amount_remaining:
|
||||
raise UserError("You cannot make a payment for more than the difference of the total amount and existing "
|
||||
"payments: %.2f" % self.so_amount_remaining)
|
||||
return {
|
||||
'date': self.payment_date,
|
||||
'amount': self.amount,
|
||||
'payment_type': self.payment_type,
|
||||
'partner_type': self.partner_type,
|
||||
'ref': self.communication,
|
||||
'journal_id': self.journal_id.id,
|
||||
'currency_id': self.currency_id.id,
|
||||
'partner_id': self.partner_id.id,
|
||||
'partner_bank_id': self.partner_bank_id.id,
|
||||
'payment_method_id': self.payment_method_id.id,
|
||||
'sale_order_id': self.sale_order_id.id,
|
||||
}
|
||||
|
||||
def _create_payments(self):
|
||||
self.ensure_one()
|
||||
if not self.sale_order_id:
|
||||
return super(AccountPaymentRegister, self)._create_payments()
|
||||
payments = self.env['account.payment'].create(self._create_payment_vals_from_sale_order())
|
||||
payments.action_post()
|
||||
return payments
|
||||
28
sale_payment_web/wizard/account_payment_register_views.xml
Normal file
28
sale_payment_web/wizard/account_payment_register_views.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_account_payment_register_form_inherit" model="ir.ui.view">
|
||||
<field name="name">account.payment.form.multi.inherit</field>
|
||||
<field name="model">account.payment.register</field>
|
||||
<field name="inherit_id" ref="account.view_account_payment_register_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='company_currency_id']" position="after">
|
||||
<field name="sale_order_id" invisible="1"/>
|
||||
<field name="so_amount_over" invisible="1"/>
|
||||
<field name="so_amount_remaining" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='payment_difference_handling']" position="attributes">
|
||||
<attribute name="attrs">{'invisible': [('sale_order_id', '!=', False)]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='group3']" position="after">
|
||||
<group name="group_so_amount_over" attrs="{'invisible':[('so_amount_over', '=', False)]}" class="text-danger" colspan="4">
|
||||
<p>
|
||||
You cannot make a payment for more than the difference of the total amount and existing payments:
|
||||
<field name="so_amount_remaining"/>
|
||||
</p>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user