diff --git a/sale_payment_web/__init__.py b/sale_payment_web/__init__.py new file mode 100644 index 00000000..9b429614 --- /dev/null +++ b/sale_payment_web/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/sale_payment_web/__manifest__.py b/sale_payment_web/__manifest__.py new file mode 100755 index 00000000..87a26bd9 --- /dev/null +++ b/sale_payment_web/__manifest__.py @@ -0,0 +1,28 @@ +{ + 'name': 'Sale Payment Web', + 'author': 'Hibou Corp. ', + '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', + ], +} diff --git a/sale_payment_web/models/__init__.py b/sale_payment_web/models/__init__.py new file mode 100644 index 00000000..b70704c4 --- /dev/null +++ b/sale_payment_web/models/__init__.py @@ -0,0 +1,2 @@ +from . import payment +from . import sale diff --git a/sale_payment_web/models/payment.py b/sale_payment_web/models/payment.py new file mode 100644 index 00000000..b205575c --- /dev/null +++ b/sale_payment_web/models/payment.py @@ -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") diff --git a/sale_payment_web/models/sale.py b/sale_payment_web/models/sale.py new file mode 100644 index 00000000..dd002d90 --- /dev/null +++ b/sale_payment_web/models/sale.py @@ -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', + } diff --git a/sale_payment_web/security/ir.model.access.csv b/sale_payment_web/security/ir.model.access.csv new file mode 100644 index 00000000..1cd7e0de --- /dev/null +++ b/sale_payment_web/security/ir.model.access.csv @@ -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 diff --git a/sale_payment_web/security/sale_security.xml b/sale_payment_web/security/sale_security.xml new file mode 100644 index 00000000..7234cc38 --- /dev/null +++ b/sale_payment_web/security/sale_security.xml @@ -0,0 +1,9 @@ + + + + + Sale Order Payments + + + + diff --git a/sale_payment_web/tests/__init__.py b/sale_payment_web/tests/__init__.py new file mode 100644 index 00000000..35d768d5 --- /dev/null +++ b/sale_payment_web/tests/__init__.py @@ -0,0 +1 @@ +from . import test_sale_payment diff --git a/sale_payment_web/tests/test_sale_payment.py b/sale_payment_web/tests/test_sale_payment.py new file mode 100644 index 00000000..50d180cf --- /dev/null +++ b/sale_payment_web/tests/test_sale_payment.py @@ -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) diff --git a/sale_payment_web/views/payment_views.xml b/sale_payment_web/views/payment_views.xml new file mode 100644 index 00000000..ae63961a --- /dev/null +++ b/sale_payment_web/views/payment_views.xml @@ -0,0 +1,15 @@ + + + + + account.payment.form.inherit + account.payment + + + + + + + + + diff --git a/sale_payment_web/views/sale_views.xml b/sale_payment_web/views/sale_views.xml new file mode 100644 index 00000000..cf5a4869 --- /dev/null +++ b/sale_payment_web/views/sale_views.xml @@ -0,0 +1,27 @@ + + + + + sale.order.form.inherit + sale.order + + + + +