diff --git a/sale_payment_web/models/payment.py b/sale_payment_web/models/payment.py index 275fbbd5..b205575c 100644 --- a/sale_payment_web/models/payment.py +++ b/sale_payment_web/models/payment.py @@ -1,4 +1,4 @@ -from odoo import api, models +from odoo import api, fields, models class PaymentTransaction(models.Model): @@ -10,3 +10,9 @@ class PaymentTransaction(models.Model): 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") diff --git a/sale_payment_web/models/sale.py b/sale_payment_web/models/sale.py index 667c1bcd..eaa7b9fc 100644 --- a/sale_payment_web/models/sale.py +++ b/sale_payment_web/models/sale.py @@ -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): _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'), diff --git a/sale_payment_web/tests/test_sale_payment.py b/sale_payment_web/tests/test_sale_payment.py index e5255f1a..38bd2edf 100644 --- a/sale_payment_web/tests/test_sale_payment.py +++ b/sale_payment_web/tests/test_sale_payment.py @@ -1,4 +1,5 @@ from odoo.addons.sale.tests.test_sale_to_invoice import TestSaleToInvoice +from odoo.exceptions import UserError class TestSalePayment(TestSaleToInvoice): @@ -13,6 +14,17 @@ class TestSalePayment(TestSaleToInvoice): def test_payment(self): 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({}) 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']) self.assertTrue(payment.exists()) 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() diff --git a/sale_payment_web/views/sale_views.xml b/sale_payment_web/views/sale_views.xml index b95fdff1..3842dc5b 100644 --- a/sale_payment_web/views/sale_views.xml +++ b/sale_payment_web/views/sale_views.xml @@ -7,7 +7,15 @@ - + + + + + + + + diff --git a/sale_payment_web/wizard/account_payment_register.py b/sale_payment_web/wizard/account_payment_register.py index 91dbcc65..53092f00 100644 --- a/sale_payment_web/wizard/account_payment_register.py +++ b/sale_payment_web/wizard/account_payment_register.py @@ -5,8 +5,7 @@ from odoo.exceptions import UserError class AccountPaymentRegister(models.TransientModel): _inherit = 'account.payment.register' - sale_order_ids = fields.Many2many('sale.order', 'sale_order_payment_rel_transient', 'payment_id', 'sale_order_id', - string="Sale Orders", copy=False, readonly=True) + sale_order_id = fields.Many2one('sale.order', string='Sale Order') payment_token_id = fields.Many2one('payment.token', string="Saved payment token", domain="[('acquirer_id.capture_manually', '=', False), ('company_id', '=', company_id)]", 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') 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 def default_get(self, fields): @@ -25,26 +31,25 @@ class AccountPaymentRegister(models.TransientModel): return super(AccountPaymentRegister, self).default_get(fields) rec = super(AccountPaymentRegister, self.with_context(active_ids=None)).default_get(fields) - sale_orders = self.env['sale.order'].browse(active_ids) - company = sale_orders.mapped('company_id') - if len(company) != 1: - raise UserError('Can only register sale order payments for the sales in the same company.') - partner = sale_orders.mapped('partner_id') - if len(partner) != 1: - raise UserError('Can only register sale order payments for the same customer.') - account_receivable = partner.property_account_receivable_id - if not account_receivable: - 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)] + 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 + rec['so_amount_registered_payment'] = sale_order.manual_amount_registered_payment + rec['so_amount_remaining'] = sale_order.manual_amount_remaining if 'amount' not in rec: - rec['amount'] = sum(sale_orders.mapped('amount_total')) + rec['amount'] = sale_order.manual_amount_remaining if 'company_id' not in rec: - rec['company_id'] = sale_orders[0].company_id.id + rec['company_id'] = sale_order.company_id.id if 'partner_id' not in rec: - rec['partner_id'] = sale_orders[0].partner_id.id + rec['partner_id'] = sale_order.partner_id.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.') 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: domain = [('payment_type', '=', 'inbound')] 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} return res - @api.onchange('journal_id', 'invoice_ids', 'sale_order_ids') + @api.onchange('journal_id', 'invoice_ids', 'sale_order_id') def _onchange_journal(self): active_ids = self._context.get('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)] return {'domain': {'payment_method_id': domain_payment}} - def _prepare_communication_sale_orders(self, sale_orders): - return " ".join(o.reference or o.name for o in sale_orders) + def _prepare_communication_sale_orders(self, sale_order): + 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. - :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. :return: The payment values as a dictionary. ''' 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) values = { 'journal_id': self.journal_id.id, 'payment_method_id': self.payment_method_id.id, '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 # '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, - 'currency_id': sale_orders[0].currency_id.id, - 'partner_id': sale_orders[0].partner_id.id, + 'currency_id': sale_order.currency_id.id, + 'partner_id': sale_order.partner_id.id, 'partner_type': 'customer', 'payment_token_id': self.payment_token_id.id, } return values def get_payments_vals(self): - if self.sale_order_ids: - return [self._prepare_payment_vals_sale_orders(self.sale_order_ids)] + if self.sale_order_id: + return [self._prepare_payment_vals_sale_orders(self.sale_order_id)] return super(AccountPaymentRegister, self).get_payments_vals() def create_payments(self): - if self.sale_order_ids: + if self.sale_order_id: # user may not be able to create payment res = super(AccountPaymentRegister, self.sudo()).create_payments() else: 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']) + 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 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)) # return a 'dummy' action like object for tests return {'res_id': payment.id, 'res_model': payment._name} diff --git a/sale_payment_web/wizard/account_payment_register_views.xml b/sale_payment_web/wizard/account_payment_register_views.xml index a5fdba24..02eb97f3 100644 --- a/sale_payment_web/wizard/account_payment_register_views.xml +++ b/sale_payment_web/wizard/account_payment_register_views.xml @@ -14,6 +14,14 @@ + + + + + You cannot make a payment for more than the difference of the total amount and existing payments: + + +
+ You cannot make a payment for more than the difference of the total amount and existing payments: + +