Skip to content

Commit 3e1800d

Browse files
committed
[IMP] subscription_oca: recurrent payment
1 parent 69b97e0 commit 3e1800d

File tree

7 files changed

+216
-24
lines changed

7 files changed

+216
-24
lines changed

subscription_oca/README.rst

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
.. image:: https://odoo-community.org/readme-banner-image
2-
:target: https://odoo-community.org/get-involved?utm_source=readme
3-
:alt: Odoo Community Association
4-
51
=======================
62
Subscription management
73
=======================
@@ -17,7 +13,7 @@ Subscription management
1713
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
1814
:target: https://odoo-community.org/page/development-status
1915
:alt: Beta
20-
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
16+
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
2117
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
2218
:alt: License: AGPL-3
2319
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fcontract-lightgray.png?logo=github
@@ -91,6 +87,13 @@ Contributors
9187

9288
* Ilyas <irazor147@gmail.com>
9389

90+
91+
* `Binhex <https://binhex.cloud>`__:
92+
93+
* Adasat Torres de Leon <a.torres@binhex.cloud>
94+
95+
* Chris Mann <chrisandrewmann>
96+
9497
Maintainers
9598
~~~~~~~~~~~
9699

subscription_oca/models/sale_subscription.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,26 @@ class SaleSubscription(models.Model):
103103
store=True,
104104
ondelete="restrict",
105105
)
106+
payment_token_id = fields.Many2one(
107+
comodel_name="payment.token",
108+
string="Payment Token",
109+
default=lambda self: self._get_default_payment_token_id(),
110+
domain="[('partner_id', '=', partner_id)]",
111+
)
112+
invoicing_mode = fields.Selection(related="template_id.invoicing_mode")
113+
114+
def _get_default_payment_token_id(self):
115+
return (
116+
self.env["payment.token"]
117+
.sudo()
118+
.search(
119+
[
120+
("partner_id", "=", self.partner_id.id),
121+
],
122+
limit=1,
123+
order="write_date desc",
124+
)
125+
)
106126

107127
@api.model
108128
def _read_group_stage_ids(self, stages, domain, order):
@@ -328,10 +348,17 @@ def create_sale_order(self):
328348
def generate_invoice(self):
329349
invoice_number = ""
330350
msg_static = _("Created invoice with reference")
331-
if self.template_id.invoicing_mode in ["draft", "invoice", "invoice_send"]:
351+
if self.template_id.invoicing_mode in [
352+
"draft",
353+
"invoice",
354+
"invoice_send",
355+
"invoice_and_payment",
356+
]:
332357
invoice = self.create_invoice()
333358
if self.template_id.invoicing_mode != "draft":
334359
invoice.action_post()
360+
if self.template_id.invoicing_mode == "invoice_and_payment":
361+
self.create_payment(invoice)
335362
if self.template_id.invoicing_mode == "invoice_send":
336363
mail_template = self.template_id.invoice_mail_template_id
337364
invoice.with_context(force_send=True).message_post_with_template(
@@ -363,6 +390,54 @@ def generate_invoice(self):
363390
self.calculate_recurring_next_date(self.recurring_next_date)
364391
self.message_post(body=message_body)
365392

393+
def create_payment(self, invoice):
394+
invoice.ensure_one()
395+
if not self.payment_token_id:
396+
self.message_post(
397+
body=_(
398+
"No payment token found for partner %s" % invoice.partner_id.name
399+
)
400+
)
401+
return
402+
provider = self.payment_token_id.provider_id
403+
method_line = self.env["account.payment.method.line"].search(
404+
[
405+
("payment_method_id.code", "=", provider.code),
406+
("company_id", "=", invoice.company_id.id),
407+
],
408+
limit=1,
409+
)
410+
411+
if not method_line:
412+
self.message_post(
413+
body=_(
414+
"No payment method line found for payment provider %s"
415+
% provider.name
416+
)
417+
)
418+
return
419+
payment_register = self.env["account.payment.register"]
420+
payment_vals = {
421+
"currency_id": invoice.currency_id.id,
422+
"journal_id": provider.journal_id.id,
423+
"company_id": invoice.company_id.id,
424+
"partner_id": invoice.partner_id.id,
425+
"communication": invoice.name,
426+
"payment_type": "inbound",
427+
"partner_type": "customer",
428+
"payment_difference_handling": "open",
429+
"writeoff_label": "Write-Off",
430+
"payment_date": fields.Date.today(),
431+
"amount": invoice.amount_total,
432+
"payment_method_line_id": method_line.id,
433+
"payment_token_id": self.payment_token_id.id,
434+
}
435+
payment_register.with_context(
436+
active_model="account.move",
437+
active_ids=invoice.ids,
438+
active_id=invoice.id,
439+
).create(payment_vals).action_create_payments()
440+
366441
def manual_invoice(self):
367442
invoice_id = self.create_invoice()
368443
self.calculate_recurring_next_date(self.recurring_next_date)

subscription_oca/models/sale_subscription_template.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class SaleSubscriptionTemplate(models.Model):
3535
("invoice", "Invoice"),
3636
("invoice_send", "Invoice & send"),
3737
("sale_and_invoice", "Sale order & Invoice"),
38+
("invoice_and_payment", "Invoice & Recurring Payment"),
3839
],
3940
)
4041
code = fields.Char()

subscription_oca/readme/CONTRIBUTORS.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,10 @@
55
* `Ooops404 <https://www.ooops404.com>`__:
66

77
* Ilyas <irazor147@gmail.com>
8+
9+
10+
* `Binhex <https://binhex.cloud>`__:
11+
12+
* Adasat Torres de Leon <a.torres@binhex.cloud>
13+
14+
* Chris Mann <chrisandrewmann>

subscription_oca/static/description/index.html

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<head>
44
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
55
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
6-
<title>README.rst</title>
6+
<title>Subscription management</title>
77
<style type="text/css">
88

99
/*
@@ -360,21 +360,16 @@
360360
</style>
361361
</head>
362362
<body>
363-
<div class="document">
363+
<div class="document" id="subscription-management">
364+
<h1 class="title">Subscription management</h1>
364365

365-
366-
<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
367-
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
368-
</a>
369-
<div class="section" id="subscription-management">
370-
<h1>Subscription management</h1>
371366
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
372367
!! This file is generated by oca-gen-addon-readme !!
373368
!! changes will be overwritten. !!
374369
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
375370
!! source digest: sha256:0f74e3f2d1fce7423f268d9e6d62b9e9fe3af4629ec13a22c42fe64660c74bfe
376371
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
377-
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/contract/tree/16.0/subscription_oca"><img alt="OCA/contract" src="https://img.shields.io/badge/github-OCA%2Fcontract-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/contract-16-0/contract-16-0-subscription_oca"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/contract&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
372+
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/contract/tree/16.0/subscription_oca"><img alt="OCA/contract" src="https://img.shields.io/badge/github-OCA%2Fcontract-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/contract-16-0/contract-16-0-subscription_oca"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/contract&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
378373
<p>This module allows creating subscriptions that generate recurring invoices or orders. It also enables the sale of products that generate subscriptions.</p>
379374
<p><strong>Table of contents</strong></p>
380375
<div class="contents local topic" id="contents">
@@ -391,7 +386,7 @@ <h1>Subscription management</h1>
391386
</ul>
392387
</div>
393388
<div class="section" id="usage">
394-
<h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
389+
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
395390
<p>To make a subscription:</p>
396391
<ol class="arabic simple">
397392
<li>Go to <em>Subscriptions &gt; Configuration &gt; Subscription templates</em>.</li>
@@ -408,41 +403,46 @@ <h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
408403
</ol>
409404
</div>
410405
<div class="section" id="known-issues-roadmap">
411-
<h2><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h2>
406+
<h1><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h1>
412407
<ul class="simple">
413408
<li>Refactor all the onchanges that have business logic to computed write-able fields when possible. Keep onchanges only for UI purposes.</li>
414409
<li>Add tests.</li>
415410
</ul>
416411
</div>
417412
<div class="section" id="bug-tracker">
418-
<h2><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h2>
413+
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
419414
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/contract/issues">GitHub Issues</a>.
420415
In case of trouble, please check there if your issue has already been reported.
421416
If you spotted it first, help us to smash it by providing a detailed and welcomed
422417
<a class="reference external" href="https://github.com/OCA/contract/issues/new?body=module:%20subscription_oca%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
423418
<p>Do not contact contributors directly about support or help with technical issues.</p>
424419
</div>
425420
<div class="section" id="credits">
426-
<h2><a class="toc-backref" href="#toc-entry-4">Credits</a></h2>
421+
<h1><a class="toc-backref" href="#toc-entry-4">Credits</a></h1>
427422
<div class="section" id="authors">
428-
<h3><a class="toc-backref" href="#toc-entry-5">Authors</a></h3>
423+
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
429424
<ul class="simple">
430425
<li>Domatix</li>
431426
</ul>
432427
</div>
433428
<div class="section" id="contributors">
434-
<h3><a class="toc-backref" href="#toc-entry-6">Contributors</a></h3>
429+
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
435430
<ul class="simple">
436431
<li>Carlos Martínez &lt;<a class="reference external" href="mailto:carlos&#64;domatix.com">carlos&#64;domatix.com</a>&gt;</li>
437432
<li>Carolina Ferrer &lt;<a class="reference external" href="mailto:carolina&#64;domatix.com">carolina&#64;domatix.com</a>&gt;</li>
438433
<li><a class="reference external" href="https://www.ooops404.com">Ooops404</a>:<ul>
439434
<li>Ilyas &lt;<a class="reference external" href="mailto:irazor147&#64;gmail.com">irazor147&#64;gmail.com</a>&gt;</li>
440435
</ul>
441436
</li>
437+
<li><a class="reference external" href="https://binhex.cloud">Binhex</a>:<ul>
438+
<li>Adasat Torres de Leon &lt;<a class="reference external" href="mailto:a.torres&#64;binhex.cloud">a.torres&#64;binhex.cloud</a>&gt;</li>
439+
</ul>
440+
</li>
441+
<li>Chris Mann &lt;chrisandrewmann&gt;</li>
442442
</ul>
443443
</div>
444444
<div class="section" id="maintainers">
445-
<h3><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h3>
445+
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
446446
<p>This module is maintained by the OCA.</p>
447447
<a class="reference external image-reference" href="https://odoo-community.org">
448448
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
@@ -455,6 +455,5 @@ <h3><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h3>
455455
</div>
456456
</div>
457457
</div>
458-
</div>
459458
</body>
460459
</html>

subscription_oca/tests/test_subscription_oca.py

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ def setUpClass(cls):
3838
("company_id", "=", cls.env.ref("base.main_company").id),
3939
]
4040
)[0]
41+
42+
cls.bank_journal = cls.env["account.journal"].search(
43+
[
44+
("type", "=", "bank"),
45+
("company_id", "=", cls.env.ref("base.main_company").id),
46+
]
47+
)[0]
48+
4149
cls.pricelist1 = cls.env["product.pricelist"].create(
4250
{
4351
"name": "pricelist for contract test",
@@ -116,6 +124,14 @@ def setUpClass(cls):
116124
}
117125
)
118126

127+
cls.tmpl6 = cls.create_sub_template(
128+
{
129+
"recurring_rule_boundary": "unlimited",
130+
"invoicing_mode": "invoice_and_payment",
131+
"recurring_rule_type": "years",
132+
}
133+
)
134+
119135
cls.stage = cls.env["sale.subscription.stage"].create(
120136
{
121137
"name": "Test Sub Stage",
@@ -193,6 +209,14 @@ def setUpClass(cls):
193209
}
194210
)
195211

212+
cls.sub10 = cls.create_sub(
213+
{
214+
"template_id": cls.tmpl6.id,
215+
"recurring_rule_boundary": False,
216+
"date_start": fields.Date.today(),
217+
}
218+
)
219+
196220
cls.sub_line = cls.create_sub_line(cls.sub1)
197221
cls.sub_line2 = cls.env["sale.subscription.line"].create(
198222
{
@@ -210,6 +234,7 @@ def setUpClass(cls):
210234
cls.sub_line52 = cls.create_sub_line(cls.sub5, cls.product_2.id)
211235
cls.sub_line71 = cls.create_sub_line(cls.sub7)
212236
cls.sub_line72 = cls.create_sub_line(cls.sub7, cls.product_2.id)
237+
cls.sub_line102 = cls.create_sub_line(cls.sub10, cls.product_2.id)
213238

214239
cls.close_reason = cls.env["sale.subscription.close.reason"].create(
215240
{
@@ -541,7 +566,7 @@ def test_subscription_oca_sub_stage(self):
541566

542567
def test_x_subscription_oca_pricelist_related(self):
543568
res = self.partner.read(["subscription_count", "subscription_ids"])
544-
self.assertEqual(res[0]["subscription_count"], 9)
569+
self.assertEqual(res[0]["subscription_count"], 10)
545570
res = self.partner.action_view_subscription_ids()
546571
self.assertIsInstance(res, dict)
547572
sale_order = self.sub1.create_sale_order()
@@ -692,3 +717,77 @@ def _collect_all_sub_test_results(self, subscription):
692717
)
693718
test_res.append(group_stage_ids)
694719
return test_res
720+
721+
def test_subscription_invoice_and_payment(self):
722+
723+
account_payment_method = self.env["account.payment.method"].create(
724+
{
725+
"name": "Test Payment Method",
726+
"code": "none",
727+
"payment_type": "inbound",
728+
}
729+
)
730+
731+
account_payment_method_line = self.env["account.payment.method.line"].create(
732+
{
733+
"payment_method_id": account_payment_method.id,
734+
"company_id": self.env.ref("base.main_company").id,
735+
"name": "Test Method Line",
736+
}
737+
)
738+
739+
journal = self.env["account.journal"].create(
740+
{
741+
"name": "Test Journal",
742+
"type": "bank",
743+
"company_id": self.env.ref("base.main_company").id,
744+
"code": "TESTJNL",
745+
}
746+
)
747+
748+
provider_test = self.env["payment.provider"].create(
749+
{
750+
"name": "Test Provider for Subscriptions",
751+
"code": "none",
752+
"company_id": self.env.ref("base.main_company").id,
753+
"journal_id": journal.id,
754+
"state": "test",
755+
}
756+
)
757+
758+
subscription = self.sub10
759+
subscription.generate_invoice()
760+
error_count = len(
761+
self.sub10.message_ids.filtered(
762+
lambda msg: "No payment token found for partner" in msg.body
763+
)
764+
)
765+
self.assertEqual(error_count, 1)
766+
self.assertEqual(len(subscription.invoice_ids), 1)
767+
self.assertEqual(subscription.invoice_ids.state, "posted")
768+
self.sub10.payment_token_id = self.env["payment.token"].create(
769+
{
770+
"payment_details": "1234",
771+
"provider_id": provider_test.id,
772+
"partner_id": self.partner.id,
773+
"provider_ref": "provider Ref (TEST)",
774+
"active": True,
775+
}
776+
)
777+
subscription.generate_invoice()
778+
self.assertEqual(len(subscription.invoice_ids), 2)
779+
last_invoice = subscription.invoice_ids[-1]
780+
self.assertEqual(last_invoice.state, "posted")
781+
error_count = len(
782+
self.sub10.message_ids.filtered(
783+
lambda msg: "No payment method line found for payment provider"
784+
in msg.body
785+
)
786+
)
787+
journal.write(
788+
{"inbound_payment_method_line_ids": [(4, account_payment_method_line.id)]}
789+
)
790+
subscription.generate_invoice()
791+
self.assertEqual(len(subscription.invoice_ids), 3)
792+
last_invoice = subscription.invoice_ids[-1]
793+
self.assertEqual(last_invoice.state, "posted")

0 commit comments

Comments
 (0)