Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# generated from manifests external_dependencies
phonenumbers
105 changes: 105 additions & 0 deletions subscription_oca_sms/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

====================
Subscription OCA SMS
====================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:4d098a9398ec3318bfe33e897e1021985f354663730d1b215d75d95aa2a26cf8
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fcontract-lightgray.png?logo=github
:target: https://github.com/OCA/contract/tree/19.0/subscription_oca_sms
:alt: OCA/contract
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/contract-19-0/contract-19-0-subscription_oca_sms
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/contract&target_branch=19.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

Bridge module between ``subscription_oca`` and the community ``sms``
addon. Ships two ready-to-use SMS templates (payment reminder, payment
failure) and two corresponding actions on the subscription form so a
salesman can send SMS notifications to the customer in one click.

**Table of contents**

.. contents::
:local:

Usage
=====

On a subscription form, salespeople (group *Sales / User*) get two
buttons next to *Close subscription*:

- **SMS payment reminder** — sends the customer an SMS reminding them
of the next invoice date.
- **SMS payment failure** — notifies the customer that a payment could
not be processed.

Both are **manual, one-click** actions: the salesperson decides when to
send. The buttons only appear when the customer has a valid phone
number. The SMS body is automatically translated to the customer's
language if a translation of the template exists.

Sending SMS relies on Odoo's SMS gateway (IAP), which has a per-message
cost. Make sure the gateway is configured and has credit before using
these actions.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/contract/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/contract/issues/new?body=module:%20subscription_oca_sms%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Domatix

Contributors
------------

- Domatix:

- Álvaro López <alvaro@domatix.com>

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/contract <https://github.com/OCA/contract/tree/19.0/subscription_oca_sms>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
1 change: 1 addition & 0 deletions subscription_oca_sms/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
19 changes: 19 additions & 0 deletions subscription_oca_sms/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2026 Domatix - Alvaro
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Subscription OCA SMS",
"summary": "Send SMS notifications from subscriptions.",
"version": "19.0.1.0.0",
"development_status": "Beta",
"category": "Subscription Management",
"website": "https://github.com/OCA/contract",
"license": "AGPL-3",
"author": "Domatix, Odoo Community Association (OCA)",
"depends": ["subscription_oca", "sms", "phone_validation"],
"external_dependencies": {"python": ["phonenumbers"]},
"data": [
"data/sms_template_data.xml",
"views/sale_subscription_views.xml",
],
"installable": True,
}
18 changes: 18 additions & 0 deletions subscription_oca_sms/data/sms_template_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="sms_template_payment_reminder" model="sms.template">
<field name="name">Subscription: Payment reminder</field>
<field name="model_id" ref="subscription_oca.model_sale_subscription" />
<field
name="body"
>Hi {{ object.partner_id.name }}, your subscription {{ object.name }} renews on {{ format_date(object.recurring_next_date) }}.</field>
</record>

<record id="sms_template_payment_failure" model="sms.template">
<field name="name">Subscription: Payment failure</field>
<field name="model_id" ref="subscription_oca.model_sale_subscription" />
<field
name="body"
>Hi {{ object.partner_id.name }}, the payment for {{ object.name }} failed. Please update your payment method.</field>
</record>
</odoo>
1 change: 1 addition & 0 deletions subscription_oca_sms/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import sale_subscription
66 changes: 66 additions & 0 deletions subscription_oca_sms/models/sale_subscription.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Copyright 2026 Domatix - Alvaro
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
from odoo.exceptions import UserError


class SaleSubscription(models.Model):
_inherit = "sale.subscription"

can_send_sms = fields.Boolean(
string="Can send SMS",
compute="_compute_can_send_sms",
help="Technical field: true when the customer has a valid phone number.",
)

@api.depends("partner_id.phone")
def _compute_can_send_sms(self):
for subscription in self:
partner = subscription.partner_id
subscription.can_send_sms = bool(
partner and partner._phone_format(fname="phone")
)

def _send_sms_template(self, template_xmlid):
self.ensure_one()
template = self.env.ref(template_xmlid, raise_if_not_found=False)
if not template:
raise UserError(
self.env._(
"SMS template %(xmlid)s is not available.",
xmlid=template_xmlid,
)
)
# Validate against the *sanitized* number, mirroring what the SMS
# composer does internally (it resolves and formats partner_id.phone).
# A non-empty but invalid number would otherwise pass a naive check
# and fail later inside the composer with a less helpful message.
if not self.partner_id._phone_format(fname="phone"):
raise UserError(
self.env._(
"Cannot send SMS: %(partner)s has no valid phone number.",
partner=self.partner_id.display_name,
)
)
composer = (
self.env["sms.composer"]
.with_context(active_id=self.id, active_model="sale.subscription")
.create(
{
"composition_mode": "comment",
"res_model": "sale.subscription",
"template_id": template.id,
}
)
)
composer.action_send_sms()

def action_send_sms_payment_reminder(self):
self.ensure_one()
self._send_sms_template("subscription_oca_sms.sms_template_payment_reminder")
self.message_post(body=self.env._("Payment reminder SMS sent."))

def action_send_sms_payment_failure(self):
self.ensure_one()
self._send_sms_template("subscription_oca_sms.sms_template_payment_failure")
self.message_post(body=self.env._("Payment failure SMS sent."))
3 changes: 3 additions & 0 deletions subscription_oca_sms/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
2 changes: 2 additions & 0 deletions subscription_oca_sms/readme/CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Domatix:
- Álvaro López \<<alvaro@domatix.com>\>
4 changes: 4 additions & 0 deletions subscription_oca_sms/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Bridge module between `subscription_oca` and the community `sms`
addon. Ships two ready-to-use SMS templates (payment reminder, payment
failure) and two corresponding actions on the subscription form so a
salesman can send SMS notifications to the customer in one click.
16 changes: 16 additions & 0 deletions subscription_oca_sms/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
On a subscription form, salespeople (group *Sales / User*) get two
buttons next to *Close subscription*:

- **SMS payment reminder** — sends the customer an SMS reminding them of
the next invoice date.
- **SMS payment failure** — notifies the customer that a payment could
not be processed.

Both are **manual, one-click** actions: the salesperson decides when to
send. The buttons only appear when the customer has a valid phone
number. The SMS body is automatically translated to the customer's
language if a translation of the template exists.

Sending SMS relies on Odoo's SMS gateway (IAP), which has a per-message
cost. Make sure the gateway is configured and has credit before using
these actions.
Loading
Loading