From b1c86269c4573448ae303210f65f37a431e3ed40 Mon Sep 17 00:00:00 2001 From: Adasat Torres de Leon Date: Wed, 10 Dec 2025 15:02:12 +0000 Subject: [PATCH] [IMP] subscription_oca: recurrent payment --- subscription_oca/README.rst | 28 +++--- subscription_oca/models/sale_subscription.py | 88 +++++++++++++++++- .../models/sale_subscription_template.py | 1 + subscription_oca/readme/CONTRIBUTORS.md | 3 + .../static/description/index.html | 5 + .../tests/test_subscription_oca.py | 93 ++++++++++++++++++- .../sale_subscription_template_views.xml | 5 +- .../views/sale_subscription_views.xml | 7 ++ 8 files changed, 213 insertions(+), 17 deletions(-) diff --git a/subscription_oca/README.rst b/subscription_oca/README.rst index c705b9c5fd..4d268c29e7 100644 --- a/subscription_oca/README.rst +++ b/subscription_oca/README.rst @@ -70,9 +70,9 @@ To create subscriptions with the sale of a product: Known issues / Roadmap ====================== -- Refactor all the onchanges that have business logic to computed - write-able fields when possible. Keep onchanges only for UI purposes. -- Add tests. +- Refactor all the onchanges that have business logic to computed + write-able fields when possible. Keep onchanges only for UI purposes. +- Add tests. Bug Tracker =========== @@ -95,17 +95,23 @@ Authors Contributors ------------ -- Carlos Martínez -- Carolina Ferrer -- `Ooops404 `__: +- Carlos Martínez +- Carolina Ferrer +- `Ooops404 `__: - - Ilyas + - Ilyas -- `Sygel `__: +- `Sygel `__: - - Harald Panten - - Valentin Vinagre - - Alberto Martínez + - Harald Panten + - Valentin Vinagre + - Alberto Martínez + +- `Binhex `__: + + - Adasat Torres de León + +- Chris Mann Maintainers ----------- diff --git a/subscription_oca/models/sale_subscription.py b/subscription_oca/models/sale_subscription.py index f99e4a8dbd..81af12575a 100644 --- a/subscription_oca/models/sale_subscription.py +++ b/subscription_oca/models/sale_subscription.py @@ -104,6 +104,37 @@ class SaleSubscription(models.Model): ondelete="restrict", ) + payment_token_id = fields.Many2one( + comodel_name="payment.token", + string="Payment Token", + store=True, + compute="_compute_payment_token_id", + domain="[('partner_id', '=', partner_id)]", + ) + invoicing_mode = fields.Selection(related="template_id.invoicing_mode") + + @api.depends("partner_id", "template_id") + def _compute_payment_token_id(self): + for record in self: + if record.template_id.invoicing_mode not in [ + "invoice_and_payment", + ]: + record.payment_token_id = False + continue + payment_token = ( + self.env["payment.token"] + .sudo() + .with_company(record.company_id) + .search( + [ + ("partner_id", "=", record.partner_id.id), + ], + limit=1, + order="write_date desc", + ) + ) + record.payment_token_id = payment_token.id if payment_token else False + @api.model def _read_group_stage_ids(self, stages, domain, order): stage_ids = stages.search([], order=order) @@ -311,10 +342,17 @@ def create_sale_order(self): def generate_invoice(self): invoice_number = "" msg_static = _("Created invoice with reference") - if self.template_id.invoicing_mode in ["draft", "invoice", "invoice_send"]: + if self.template_id.invoicing_mode in [ + "draft", + "invoice", + "invoice_send", + "invoice_and_payment", + ]: invoice = self.create_invoice() if self.template_id.invoicing_mode != "draft": invoice.action_post() + if self.template_id.invoicing_mode == "invoice_and_payment": + self.create_payment(invoice) mail_template = self.template_id.invoice_mail_template_id invoice.with_context(force_send=True)._generate_pdf_and_send_invoice( mail_template @@ -466,3 +504,51 @@ def create(self, vals_list): .id ) return super().create(vals_list) + + def create_payment(self, invoice): + invoice.ensure_one() + if not self.payment_token_id: + self.message_post( + body=_( + "No payment token found for partner %s" % invoice.partner_id.name + ) + ) + return + provider = self.payment_token_id.provider_id + method_line = self.env["account.payment.method.line"].search( + [ + ("payment_method_id.code", "=", provider.code), + ("company_id", "=", invoice.company_id.id), + ], + limit=1, + ) + + if not method_line: + self.message_post( + body=_( + "No payment method line found for payment provider %s" + % provider.name + ) + ) + return + payment_register = self.env["account.payment.register"] + payment_vals = { + "currency_id": invoice.currency_id.id, + "journal_id": provider.journal_id.id, + "company_id": invoice.company_id.id, + "partner_id": invoice.partner_id.id, + "communication": invoice.name, + "payment_type": "inbound", + "partner_type": "customer", + "payment_difference_handling": "open", + "writeoff_label": "Write-Off", + "payment_date": fields.Date.today(), + "amount": invoice.amount_total, + "payment_method_line_id": method_line.id, + "payment_token_id": self.payment_token_id.id, + } + payment_register.with_context( + active_model="account.move", + active_ids=invoice.ids, + active_id=invoice.id, + ).create(payment_vals).action_create_payments() diff --git a/subscription_oca/models/sale_subscription_template.py b/subscription_oca/models/sale_subscription_template.py index df89f401b0..b5fe7610a8 100644 --- a/subscription_oca/models/sale_subscription_template.py +++ b/subscription_oca/models/sale_subscription_template.py @@ -35,6 +35,7 @@ class SaleSubscriptionTemplate(models.Model): ("invoice", "Invoice"), ("invoice_send", "Invoice & send"), ("sale_and_invoice", "Sale order & Invoice"), + ("invoice_and_payment", "Invoice & Recurring Payment"), ], ) code = fields.Char() diff --git a/subscription_oca/readme/CONTRIBUTORS.md b/subscription_oca/readme/CONTRIBUTORS.md index ffcb907145..d137d98766 100644 --- a/subscription_oca/readme/CONTRIBUTORS.md +++ b/subscription_oca/readme/CONTRIBUTORS.md @@ -6,3 +6,6 @@ - Harald Panten - Valentin Vinagre - Alberto Martínez +- [Binhex](https://www.binhex.cloud): + - Adasat Torres de León \<\> +- Chris Mann \ No newline at end of file diff --git a/subscription_oca/static/description/index.html b/subscription_oca/static/description/index.html index f8c337475f..a85d909a82 100644 --- a/subscription_oca/static/description/index.html +++ b/subscription_oca/static/description/index.html @@ -456,6 +456,11 @@

Contributors

  • Alberto Martínez
  • +
  • Binhex: +
  • +
  • Chris Mann
  • diff --git a/subscription_oca/tests/test_subscription_oca.py b/subscription_oca/tests/test_subscription_oca.py index 8c2cab964b..42ac1263fc 100644 --- a/subscription_oca/tests/test_subscription_oca.py +++ b/subscription_oca/tests/test_subscription_oca.py @@ -112,6 +112,13 @@ def setUpClass(cls): "recurring_rule_type": "days", } ) + cls.tmpl6 = cls.create_sub_template( + { + "recurring_rule_boundary": "unlimited", + "invoicing_mode": "invoice_and_payment", + "recurring_rule_type": "years", + } + ) cls.stage = cls.env["sale.subscription.stage"].create( { @@ -181,6 +188,13 @@ def setUpClass(cls): "journal_id": cls.cash_journal.id, } ) + cls.sub10 = cls.create_sub( + { + "template_id": cls.tmpl6.id, + "recurring_rule_boundary": False, + "date_start": fields.Date.today(), + } + ) cls.sub_line = cls.create_sub_line(cls.sub1) cls.sub_line2 = cls.env["sale.subscription.line"].create( @@ -199,6 +213,7 @@ def setUpClass(cls): cls.sub_line52 = cls.create_sub_line(cls.sub5, cls.product_2.id) cls.sub_line71 = cls.create_sub_line(cls.sub7) cls.sub_line72 = cls.create_sub_line(cls.sub7, cls.product_2.id) + cls.sub_line102 = cls.create_sub_line(cls.sub10, cls.product_2.id) cls.close_reason = cls.env["sale.subscription.close.reason"].create( { @@ -516,7 +531,7 @@ def test_subscription_oca_sub_stage(self): def test_x_subscription_oca_pricelist_related(self): res = self.partner.read(["subscription_count", "subscription_ids"]) - self.assertEqual(res[0]["subscription_count"], 8) + self.assertEqual(res[0]["subscription_count"], 9) res = self.partner.action_view_subscription_ids() self.assertIsInstance(res, dict) sale_order = self.sub1.create_sale_order() @@ -686,3 +701,79 @@ def _collect_all_sub_test_results(self, subscription): ) test_res.append(group_stage_ids) return test_res + + def test_subscription_invoice_and_payment(self): + payment_method_unknown = self.env.ref("payment.payment_method_unknown") + + account_payment_method = self.env["account.payment.method"].create( + { + "name": "Test Payment Method", + "code": "none", + "payment_type": "inbound", + } + ) + + account_payment_method_line = self.env["account.payment.method.line"].create( + { + "payment_method_id": account_payment_method.id, + "company_id": self.env.ref("base.main_company").id, + "name": "Test Method Line", + } + ) + + journal = self.env["account.journal"].create( + { + "name": "Test Journal", + "type": "bank", + "company_id": self.env.ref("base.main_company").id, + "code": "TESTJNL", + } + ) + + provider_test = self.env["payment.provider"].create( + { + "name": "Test Provider for Subscriptions", + "code": "none", + "company_id": self.env.ref("base.main_company").id, + "journal_id": journal.id, + "state": "test", + } + ) + + subscription = self.sub10 + subscription.generate_invoice() + error_count = len( + self.sub10.message_ids.filtered( + lambda msg: "No payment token found for partner" in msg.body + ) + ) + self.assertEqual(error_count, 1) + self.assertEqual(len(subscription.invoice_ids), 1) + self.assertEqual(subscription.invoice_ids.state, "posted") + self.sub10.payment_token_id = self.env["payment.token"].create( + { + "payment_details": "1234", + "provider_id": provider_test.id, + "partner_id": self.partner.id, + "payment_method_id": payment_method_unknown.id, + "provider_ref": "provider Ref (TEST)", + "active": True, + } + ) + subscription.generate_invoice() + self.assertEqual(len(subscription.invoice_ids), 2) + last_invoice = subscription.invoice_ids[-1] + self.assertEqual(last_invoice.state, "posted") + error_count = len( + self.sub10.message_ids.filtered( + lambda msg: "No payment method line found for payment provider" + in msg.body + ) + ) + journal.write( + {"inbound_payment_method_line_ids": [(4, account_payment_method_line.id)]} + ) + subscription.generate_invoice() + self.assertEqual(len(subscription.invoice_ids), 3) + last_invoice = subscription.invoice_ids[-1] + self.assertEqual(last_invoice.state, "posted") diff --git a/subscription_oca/views/sale_subscription_template_views.xml b/subscription_oca/views/sale_subscription_template_views.xml index 5bdc73a2ce..776d0702f0 100644 --- a/subscription_oca/views/sale_subscription_template_views.xml +++ b/subscription_oca/views/sale_subscription_template_views.xml @@ -80,7 +80,7 @@ @@ -97,12 +97,9 @@ /> - - - diff --git a/subscription_oca/views/sale_subscription_views.xml b/subscription_oca/views/sale_subscription_views.xml index 5d96c9a8a1..b28977d0cd 100644 --- a/subscription_oca/views/sale_subscription_views.xml +++ b/subscription_oca/views/sale_subscription_views.xml @@ -87,6 +87,13 @@ + + +