[ADD] sale_payment_web: add further functionalities to register payment wizard

This commit is contained in:
Connor Christian
2020-11-23 18:32:26 -05:00
committed by Cedric Collins
parent d1a686c13f
commit 003c3bc769
6 changed files with 105 additions and 35 deletions

View File

@@ -1,4 +1,4 @@
from odoo import api, models from odoo import api, fields, models
class PaymentTransaction(models.Model): class PaymentTransaction(models.Model):
@@ -10,3 +10,9 @@ class PaymentTransaction(models.Model):
if active_ids and self._context.get('active_model') == 'sale.order': if active_ids and self._context.get('active_model') == 'sale.order':
values['sale_order_ids'] = [(6, 0, active_ids)] values['sale_order_ids'] = [(6, 0, active_ids)]
return super(PaymentTransaction, self).create(values) return super(PaymentTransaction, self).create(values)
class AccountPayment(models.Model):
_inherit = 'account.payment'
sale_order_id = fields.Many2one('sale.order', string="Sale Order")

View File

@@ -1,9 +1,29 @@
from odoo import models, _ from odoo import api, fields, models, _
from odoo.tools.safe_eval import safe_eval
class SaleOrder(models.Model): class SaleOrder(models.Model):
_inherit = 'sale.order' _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): def action_payment_register(self):
return { return {
'name': _('Register Payment'), 'name': _('Register Payment'),

View File

@@ -1,4 +1,5 @@
from odoo.addons.sale.tests.test_sale_to_invoice import TestSaleToInvoice from odoo.addons.sale.tests.test_sale_to_invoice import TestSaleToInvoice
from odoo.exceptions import UserError
class TestSalePayment(TestSaleToInvoice): class TestSalePayment(TestSaleToInvoice):
@@ -13,6 +14,17 @@ class TestSalePayment(TestSaleToInvoice):
def test_payment(self): def test_payment(self):
self.sale_order.action_confirm() self.sale_order.action_confirm()
payment_wizard = self.env['account.payment.register'].with_context(self.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.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.context).create({}) payment_wizard = self.env['account.payment.register'].with_context(self.context).create({})
self.assertTrue(payment_wizard.journal_id) self.assertTrue(payment_wizard.journal_id)
@@ -21,3 +33,10 @@ class TestSalePayment(TestSaleToInvoice):
payment = self.env[payment_action['res_model']].browse(payment_action['res_id']) payment = self.env[payment_action['res_model']].browse(payment_action['res_id'])
self.assertTrue(payment.exists()) self.assertTrue(payment.exists())
self.assertEqual(payment.amount, self.sale_order.amount_total) self.assertEqual(payment.amount, self.sale_order.amount_total)
self.assertEqual(payment.sale_order_id, self.sale_order)
payment_wizard = self.env['account.payment.register'].with_context(self.context).create({'amount': 15})
self.assertTrue(payment_wizard.journal_id)
with self.assertRaises(UserError):
payment_wizard.create_payments()

View File

@@ -9,6 +9,14 @@
<xpath expr="//button[@name='action_draft']" position="after"> <xpath expr="//button[@name='action_draft']" position="after">
<button name="action_payment_register" type="object" string="Register Payment"/> <button name="action_payment_register" type="object" string="Register Payment"/>
</xpath> </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> </field>
</record> </record>

View File

@@ -5,8 +5,7 @@ from odoo.exceptions import UserError
class AccountPaymentRegister(models.TransientModel): class AccountPaymentRegister(models.TransientModel):
_inherit = 'account.payment.register' _inherit = 'account.payment.register'
sale_order_ids = fields.Many2many('sale.order', 'sale_order_payment_rel_transient', 'payment_id', 'sale_order_id', sale_order_id = fields.Many2one('sale.order', string='Sale Order')
string="Sale Orders", copy=False, readonly=True)
payment_token_id = fields.Many2one('payment.token', string="Saved payment token", payment_token_id = fields.Many2one('payment.token', string="Saved payment token",
domain="[('acquirer_id.capture_manually', '=', False), ('company_id', '=', company_id)]", domain="[('acquirer_id.capture_manually', '=', False), ('company_id', '=', company_id)]",
help="Note that tokens from acquirers set to only authorize transactions " help="Note that tokens from acquirers set to only authorize transactions "
@@ -16,6 +15,13 @@ class AccountPaymentRegister(models.TransientModel):
currency_id = fields.Many2one(related='journal_id.currency_id') currency_id = fields.Many2one(related='journal_id.currency_id')
amount = fields.Monetary('Amount') amount = fields.Monetary('Amount')
so_amount_registered_payment = fields.Monetary(related='sale_order_id.manual_amount_registered_payment')
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.amount > self.so_amount_remaining
@api.model @api.model
def default_get(self, fields): def default_get(self, fields):
@@ -25,26 +31,25 @@ class AccountPaymentRegister(models.TransientModel):
return super(AccountPaymentRegister, self).default_get(fields) return super(AccountPaymentRegister, self).default_get(fields)
rec = super(AccountPaymentRegister, self.with_context(active_ids=None)).default_get(fields) rec = super(AccountPaymentRegister, self.with_context(active_ids=None)).default_get(fields)
sale_orders = self.env['sale.order'].browse(active_ids) sale_order = self.env['sale.order'].browse(active_ids)
company = sale_orders.mapped('company_id') if len(sale_order) != 1:
if len(company) != 1: raise UserError('Register payment must be called on a sale order.')
raise UserError('Can only register sale order payments for the sales in the same company.')
partner = sale_orders.mapped('partner_id') if 'sale_order_id' not in rec:
if len(partner) != 1: rec['sale_order_id'] = sale_order.id
raise UserError('Can only register sale order payments for the same customer.') rec['so_amount_registered_payment'] = sale_order.manual_amount_registered_payment
account_receivable = partner.property_account_receivable_id rec['so_amount_remaining'] = sale_order.manual_amount_remaining
if 'amount' not in rec:
rec['amount'] = sale_order.manual_amount_remaining
if 'company_id' not in rec:
rec['company_id'] = sale_order.company_id.id
if 'partner_id' not in rec:
rec['partner_id'] = sale_order.partner_id.id
account_receivable = sale_order.partner_id.property_account_receivable_id
if not account_receivable: if not account_receivable:
raise UserError('Your partner must have an Account Receivable setup.') raise UserError('Your partner must have an Account Receivable setup.')
if 'sale_order_ids' not in rec:
rec['sale_order_ids'] = [(6, 0, sale_orders.ids)]
if 'amount' not in rec:
rec['amount'] = sum(sale_orders.mapped('amount_total'))
if 'company_id' not in rec:
rec['company_id'] = sale_orders[0].company_id.id
if 'partner_id' not in rec:
rec['partner_id'] = sale_orders[0].partner_id.id
if 'journal_id' not in rec: if 'journal_id' not in rec:
rec['journal_id'] = self.env['account.journal'].search([('company_id', '=', company.id), ('type', 'in', ('bank', 'cash'))], limit=1).id rec['journal_id'] = self.env['account.journal'].search([('company_id', '=', sale_order.company_id.id), ('type', 'in', ('bank', 'cash'))], limit=1).id
if 'payment_method_id' not in rec: if 'payment_method_id' not in rec:
domain = [('payment_type', '=', 'inbound')] domain = [('payment_type', '=', 'inbound')]
rec['payment_method_id'] = self.env['account.payment.method'].search(domain, limit=1).id rec['payment_method_id'] = self.env['account.payment.method'].search(domain, limit=1).id
@@ -65,7 +70,7 @@ class AccountPaymentRegister(models.TransientModel):
res['domain'] = {'payment_token_id': domain} res['domain'] = {'payment_token_id': domain}
return res return res
@api.onchange('journal_id', 'invoice_ids', 'sale_order_ids') @api.onchange('journal_id', 'invoice_ids', 'sale_order_id')
def _onchange_journal(self): def _onchange_journal(self):
active_ids = self._context.get('active_ids') active_ids = self._context.get('active_ids')
if self._context.get('active_model') != 'sale.order' or not active_ids: if self._context.get('active_model') != 'sale.order' or not active_ids:
@@ -75,50 +80,54 @@ class AccountPaymentRegister(models.TransientModel):
domain_payment = [('payment_type', '=', 'inbound'), ('id', 'in', journal.inbound_payment_method_ids.ids)] domain_payment = [('payment_type', '=', 'inbound'), ('id', 'in', journal.inbound_payment_method_ids.ids)]
return {'domain': {'payment_method_id': domain_payment}} return {'domain': {'payment_method_id': domain_payment}}
def _prepare_communication_sale_orders(self, sale_orders): def _prepare_communication_sale_orders(self, sale_order):
return " ".join(o.reference or o.name for o in sale_orders) return " ".join(o.reference or o.name for o in sale_order)
def _prepare_payment_vals_sale_orders(self, sale_orders): def _prepare_payment_vals_sale_orders(self, sale_order):
'''Create the payment values. '''Create the payment values.
:param sale_orders: The sale orders to pay. In case of multiple :param sale_order: The sale orders to pay. In case of multiple
documents. documents.
:return: The payment values as a dictionary. :return: The payment values as a dictionary.
''' '''
if self.amount <= 0: if self.amount <= 0:
raise UserError("You must enter a positive amount.") 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)
values = { values = {
'journal_id': self.journal_id.id, 'journal_id': self.journal_id.id,
'payment_method_id': self.payment_method_id.id, 'payment_method_id': self.payment_method_id.id,
'payment_date': self.payment_date, 'payment_date': self.payment_date,
'communication': self._prepare_communication_sale_orders(sale_orders), 'communication': self._prepare_communication_sale_orders(sale_order),
# TODO sale orders need to get to transactions somehow # TODO sale orders need to get to transactions somehow
# 'invoice_ids': [(6, 0, invoices.ids)], # 'invoice_ids': [(6, 0, invoices.ids)],
'payment_type': 'inbound', # if amount can be negative we need to allow this to be outbound 'payment_type': 'inbound', # if amount can be negative we need to allow this to be outbound
'amount': self.amount, 'amount': self.amount,
'currency_id': sale_orders[0].currency_id.id, 'currency_id': sale_order.currency_id.id,
'partner_id': sale_orders[0].partner_id.id, 'partner_id': sale_order.partner_id.id,
'partner_type': 'customer', 'partner_type': 'customer',
'payment_token_id': self.payment_token_id.id, 'payment_token_id': self.payment_token_id.id,
} }
return values return values
def get_payments_vals(self): def get_payments_vals(self):
if self.sale_order_ids: if self.sale_order_id:
return [self._prepare_payment_vals_sale_orders(self.sale_order_ids)] return [self._prepare_payment_vals_sale_orders(self.sale_order_id)]
return super(AccountPaymentRegister, self).get_payments_vals() return super(AccountPaymentRegister, self).get_payments_vals()
def create_payments(self): def create_payments(self):
if self.sale_order_ids: if self.sale_order_id:
# user may not be able to create payment # user may not be able to create payment
res = super(AccountPaymentRegister, self.sudo()).create_payments() res = super(AccountPaymentRegister, self.sudo()).create_payments()
else: else:
res = super(AccountPaymentRegister, self).create_payments() res = super(AccountPaymentRegister, self).create_payments()
if res and 'res_id' in res and self.sale_order_ids: if res and 'res_id' in res and self.sale_order_id:
payment = self.env['account.payment'].browse(res['res_id']) payment = self.env['account.payment'].browse(res['res_id'])
self.sale_order_id.manual_payment_ids += payment
if payment.name: # if we don't have a name, then it started a transaction and that will be in chatter if payment.name: # if we don't have a name, then it started a transaction and that will be in chatter
payment_link = payment._get_payment_chatter_link() payment_link = payment._get_payment_chatter_link()
for order in self.sale_order_ids: for order in self.sale_order_id:
order.message_post(body=_('A %s payment has been registered: %s') % (payment.payment_method_code, payment_link)) order.message_post(body=_('A %s payment has been registered: %s') % (payment.payment_method_code, payment_link))
# return a 'dummy' action like object for tests # return a 'dummy' action like object for tests
return {'res_id': payment.id, 'res_model': payment._name} return {'res_id': payment.id, 'res_model': payment._name}

View File

@@ -14,6 +14,14 @@
<xpath expr="//field[@name='payment_date']" position="after"> <xpath expr="//field[@name='payment_date']" position="after">
<field name="currency_id" invisible="True"/> <field name="currency_id" invisible="True"/>
<field name="amount"/> <field name="amount"/>
<field name="so_amount_remaining" invisible="1"/>
<field name="so_amount_over" invisible="1"/>
<div 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>
</div>
</xpath> </xpath>
</field> </field>
</record> </record>