The subscriptions module in src/cluster_erp/modules/subscriptions/ records recurring business: plans that the company offers, subscriptions of customers to those plans, and subscriptions the company itself holds with vendors. It tracks billing periods, renewals, and expiry and emits lifecycle events; it produces no invoices. It is a business module, not the SaaS membership of a tenant, which is explained in multi-tenancy.
At a glance¶
| Item | Value | Source |
|---|---|---|
| Package | src/cluster_erp/modules/subscriptions/ |
folder |
| Module class | Module |
src/cluster_erp/modules/subscriptions/module.py:15 |
name / version |
subscriptions / 0.1.0 |
src/cluster_erp/modules/subscriptions/module.py:16-22 |
| API prefix | /api/v1/subscriptions (ModuleBase.api_prefix) |
src/cluster_erp/core/module_base.py:37-38 |
| Sub-modules | 2: plans, customer_subscriptions |
folder listing |
| Tables | 2: sub_plans, sub_customer_subscriptions |
src/cluster_erp/modules/subscriptions/plans/models.py:23, src/cluster_erp/modules/subscriptions/customer_subscriptions/models.py:34 |
| Entities in metadata | 2: customer_subscription, subscription_plan |
src/cluster_erp/modules/subscriptions/module.py:149-152 |
| Web app presence | Catalogue app subscriptions |
src/cluster_erp/modules/app_store/catalog.json (entry subscriptions) |
| Desktop presence | Generic widgets only; no folder in src/cluster_erp/desktop/modules/ |
folder |
What the module does¶
- A plan states what recurring product is sold, how often it bills, and at what price (
src/cluster_erp/modules/subscriptions/plans/models.py:13-21). - A sales subscription takes its terms from an active plan; a purchase subscription has its terms entered directly against a vendor (
src/cluster_erp/modules/subscriptions/customer_subscriptions/services.py:129-206). - Activation snapshots the plan's terms so that later plan changes do not rewrite history (
src/cluster_erp/modules/subscriptions/customer_subscriptions/services.py:247-275). - A daily sweep renews or expires subscriptions whose period has ended (
src/cluster_erp/modules/subscriptions/customer_subscriptions/services.py:348-390,src/cluster_erp/server/app.py:504-546).
Sub-modules¶
| Sub-module | Purpose | Main entities | Router | Source |
|---|---|---|---|---|
plans |
Sellable recurring plans | SubscriptionPlan |
/api/v1/subscriptions/subscription-plans |
src/cluster_erp/modules/subscriptions/plans/routes.py:25 |
customer_subscriptions |
Customer and purchase subscriptions | CustomerSubscription |
/api/v1/subscriptions (module root) |
src/cluster_erp/modules/subscriptions/customer_subscriptions/routes.py:31-34 |
Data model¶
| Entity | Table | Key fields | Notes | Source |
|---|---|---|---|---|
SubscriptionPlan |
sub_plans |
code, name, product_id, product_model_id, product_version_id, billing_cycle, interval_count, price_per_cycle, currency, setup_fee, trial_days, min_term_cycles, status |
product triple required; money columns Numeric(18, 4); tenant fields in extra |
src/cluster_erp/modules/subscriptions/plans/models.py:13-59 |
CustomerSubscription |
sub_customer_subscriptions |
subscription_no, direction, customer_id, vendor_id, plan_id, product triple, snapshot terms, period dates, auto_renew, status |
a check constraint requires a customer for sales and a vendor for purchase |
src/cluster_erp/modules/subscriptions/customer_subscriptions/models.py:23-109 |
Money values are Numeric columns and Decimal in schemas, and events carry amounts as strings (src/cluster_erp/modules/subscriptions/plans/models.py:43-47, src/cluster_erp/modules/subscriptions/plans/schemas.py:22-24, src/cluster_erp/modules/subscriptions/customer_subscriptions/services.py:415-444). See money and Decimal.
Screens and metadata¶
get_metadata declares a sidebar group Subscriptions (Turkish Abonelikler) with two list sections: Subscriptions for customer_subscription and Plans (Turkish Planlar) for subscription_plan (src/cluster_erp/modules/subscriptions/module.py:121-153). Billing cycle choices are Monthly, Quarterly, Semi-annual, and Annual (src/cluster_erp/modules/subscriptions/customer_subscriptions/metadata.py:40-45).
Status and actions¶
Plan statuses go draft to active to retired, with no way back (src/cluster_erp/modules/subscriptions/plans/services.py:101-117). Subscription statuses are draft, active, paused, cancelled, and expired (src/cluster_erp/modules/subscriptions/customer_subscriptions/models.py:97-100).
| Action | Label | Condition | Endpoint | Permission | Source |
|---|---|---|---|---|---|
plan activate |
Activate | status is draft |
POST /subscription-plans/{id}/activate |
subscription_plan.activate |
src/cluster_erp/modules/subscriptions/plans/metadata.py:100-107, src/cluster_erp/modules/subscriptions/plans/routes.py:99-108 |
plan retire |
Retire | status is active |
POST /subscription-plans/{id}/retire |
subscription_plan.retire |
src/cluster_erp/modules/subscriptions/plans/metadata.py:108-115, src/cluster_erp/modules/subscriptions/plans/routes.py:111-120 |
activate |
Activate | status is draft |
POST /{id}/activate |
customer_subscription.activate |
src/cluster_erp/modules/subscriptions/customer_subscriptions/metadata.py:141, src/cluster_erp/modules/subscriptions/customer_subscriptions/routes.py:125-130 |
pause |
Pause | status is active |
POST /{id}/pause |
customer_subscription.pause |
src/cluster_erp/modules/subscriptions/customer_subscriptions/metadata.py:142, src/cluster_erp/modules/subscriptions/customer_subscriptions/routes.py:131-136 |
resume |
Resume | status is paused |
POST /{id}/resume |
customer_subscription.resume |
src/cluster_erp/modules/subscriptions/customer_subscriptions/metadata.py:143 |
renew |
Renew Now | status is active |
POST /{id}/renew |
customer_subscription.renew |
src/cluster_erp/modules/subscriptions/customer_subscriptions/metadata.py:144 |
cancel |
Cancel | status is draft, active, or paused |
POST /{id}/cancel |
customer_subscription.cancel |
src/cluster_erp/modules/subscriptions/customer_subscriptions/metadata.py:146-151, src/cluster_erp/modules/subscriptions/customer_subscriptions/routes.py:151-161 |
The state diagram shows a subscription's lifecycle.
stateDiagram-v2
[*] --> draft: create
draft --> active: activate
active --> paused: pause
paused --> active: resume
active --> active: renew or sweep renewal
active --> expired: sweep, end reached or renewal off
draft --> cancelled: cancel
active --> cancelled: cancel
paused --> cancelled: cancel
cancelled --> [*]
expired --> [*]In prose: a subscription is created as draft; activate starts the first period; pause and resume move between active and paused; renew or the sweep advances the period of an active subscription; the sweep expires an active subscription past its end date or whose period ended with renewal off; cancel ends any subscription that is not already cancelled or expired (src/cluster_erp/modules/subscriptions/customer_subscriptions/services.py:247-399). Labels come from the metadata label dictionaries.
Business rules¶
- A sales subscription needs a customer and an active plan; a purchase subscription needs a vendor, the full product triple, a price, a cycle, and a currency (
src/cluster_erp/modules/subscriptions/customer_subscriptions/services.py:129-190). subscription_noand the plancodeare yearly business keys fromnext_code(src/cluster_erp/modules/subscriptions/customer_subscriptions/services.py:112,src/cluster_erp/modules/subscriptions/plans/services.py:65).- A period is the cycle length in months (1, 3, 6, or 12) times
interval_count, anchored on the first period's day of month (src/cluster_erp/modules/subscriptions/customer_subscriptions/services.py:35-53,src/cluster_erp/modules/subscriptions/customer_subscriptions/services.py:328-339). - The sweep catches up every period due and emits one renewal per period (
src/cluster_erp/modules/subscriptions/customer_subscriptions/services.py:365-385). - The amount in an event is unit price times quantity (
src/cluster_erp/modules/subscriptions/customer_subscriptions/services.py:426-444).
Events and queries¶
| Direction | Contract class | Handler or emitter | Source |
|---|---|---|---|
| emits | SubscriptionActivated, SubscriptionRenewed, SubscriptionCancelled, SubscriptionExpired |
SubscriptionService._emit |
src/cluster_erp/modules/subscriptions/customer_subscriptions/services.py:415-444, src/cluster_erp/core/contracts.py:2318-2352 |
| answers query | GetSubscriptionPlanById |
_handle_get_plan |
src/cluster_erp/modules/subscriptions/module.py:53 |
| answers query | GetCustomerSubscriptionById |
_handle_get_subscription |
src/cluster_erp/modules/subscriptions/module.py:54-56 |
A grep for the four event classes outside the module finds no subscriber at this commit.
Permissions¶
| Permission key | Used by | Source |
|---|---|---|
subscription_plan.read, .create, .update, .delete, .bulk_delete, .activate, .retire |
plan routes | src/cluster_erp/modules/subscriptions/plans/routes.py:42-140 |
customer_subscription.read, .create, .update, .delete, .bulk_delete, .activate, .pause, .resume, .renew, .cancel |
subscription routes; .renew also guards the on-demand sweep |
src/cluster_erp/modules/subscriptions/customer_subscriptions/routes.py:51-197 |
The lifecycle codes are seeded as extra codes (src/cluster_erp/server/app.py:837-847).
Privacy, retention, and history¶
The module stores party ids of customers and vendors but no personal fields of its own, and it has no privacy_handlers.py. Changes are captured by change history; see change history. Tenant-defined values in extra are filtered by permission on read (src/cluster_erp/modules/subscriptions/customer_subscriptions/routes.py:42-48).
Analytics facts¶
This module has no analytics_facts.py.
Settings and reference data¶
The module reads no dedicated settings. Currencies are three-letter codes validated by pattern in the plan schema (src/cluster_erp/modules/subscriptions/plans/schemas.py:23). The daily sweep runs per tenant database under a leader lock (src/cluster_erp/server/app.py:504-546).
Tests¶
Found by file name in tests/ and web-client/.
| Kind | File | Covers |
|---|---|---|
| pytest | tests/test_subscription_math.py |
period math and event payloads (5 tests) |
| e2e | web-client/_e2e_subscriptions.mjs |
plans and subscriptions in the web app |
Discrepancies¶
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| subscription plan | SubscriptionPlan |
A sellable recurring product with its cycle and price. | Plan | src/cluster_erp/modules/subscriptions/plans/models.py:13 |
| customer subscription | CustomerSubscription |
A live recurring agreement with a customer or a vendor. | Abonelik | src/cluster_erp/modules/subscriptions/customer_subscriptions/models.py:23 |
| subscription direction | direction |
Whether a subscription is a sale to a customer or a purchase from a vendor. | abonelik yönü | src/cluster_erp/modules/subscriptions/customer_subscriptions/models.py:55 |
Related pages¶
- Modules index
- API reference for this module
- Identity, access, tenancy, privacy, and licensing
- Money and Decimal
Verified against commit 92f8051 on 2026-11-13.