Skip to main content

Complete Product Lifecycle Guide

This guide provides an end-to-end walkthrough of the product lifecycle in OmniCRM, from creating a product definition through provisioning services, adding addons, and deprovisioning. We'll cover pricing strategy, Ansible integration, and provide real-world examples throughout.

Overview: The Product-to-Service Journey

The lifecycle of a product in OmniCRM follows these stages:

  1. Product Definition - Administrator creates product template with pricing and provisioning rules
  2. Service Creation - Customer orders product, system provisions service instance
  3. Service Lifecycle - Customer uses service, adds addons/topups, modifies service
  4. Deprovisioning - Service is terminated, resources are released

Understanding Pricing: Wholesale vs Retail

Every product and service in OmniCRM has two pricing dimensions: wholesale and retail.

Wholesale Cost

The wholesale cost represents the actual cost to deliver the service:

  • Infrastructure and bandwidth costs
  • Licensing fees
  • Equipment costs
  • Operational expenses

Retail Cost

The retail cost is the amount charged to the customer.

Setup Costs

Both wholesale and retail have setup cost variants for one-time provisioning charges:

  • wholesale_setup_cost - Your cost to provision
  • retail_setup_cost - Amount charged to customer for activation

Example:

{
"retail_cost": 15.00,
"wholesale_cost": 5.00,
"retail_setup_cost": 0.00,
"wholesale_setup_cost": 1.00
}

Stage 1: Creating a Product Definition

Products are templates that define what gets provisioned and how customers are charged.

Creating a Mobile SIM Product

Let's create a prepaid mobile SIM product with 20GB data per month.

Step 1: Navigate to Product Management

From the admin UI, go to ProductsCreate Product.

Step 2: Define Basic Information

{
"product_name": "Prepaid Mobile 20GB",
"product_slug": "prepaid-mobile-20gb",
"category": "standalone",
"service_type": "mobile",
"enabled": true,
"icon": "fa-solid fa-sim-card",
"comment": "Prepaid mobile SIM with 20GB data, unlimited calls & texts"
}

Field Explanations:

  • product_name - Customer-facing name shown in catalog
  • product_slug - URL-safe identifier used in API calls and links
  • category - "standalone" means this creates a new service (vs addon/bundle)
  • service_type - Groups related products, used for addon filtering
  • enabled - Must be true for product to be orderable
  • icon - FontAwesome icon displayed in UI
  • comment - Internal notes for staff reference

Step 3: Set Pricing

{
"retail_cost": 15.00,
"wholesale_cost": 5.00,
"retail_setup_cost": 0.00,
"wholesale_setup_cost": 1.00,
"contract_days": 30
}

Pricing Breakdown:

  • Monthly revenue per customer: £15.00
  • Monthly cost to deliver: £5.00
  • Monthly profit margin: £10.00 (200% markup, 67% margin)
  • Setup profit: -£1.00 (subsidized to attract customers)
  • Contract length: 30 days (monthly renewal)

Step 4: Define Customer Eligibility

{
"residential": true,
"business": false,
"customer_can_purchase": true,
"available_from": "2025-01-01T00:00:00Z",
"available_until": null
}
  • Residential customers can order
  • Business customers cannot (different product line)
  • Self-service purchase enabled
  • Available from Jan 1, 2025 onwards
  • No end date (ongoing offer)

Step 5: Configure Auto-Renewal

{
"auto_renew": "prompt",
"allow_auto_renew": true
}
  • "prompt" - Ask customer if they want auto-renewal at purchase
  • "true" - Automatically renew without asking
  • "false" - Never auto-renew (manual top-up only)
  • allow_auto_renew: true - Customer can enable/disable auto-renewal later

Step 6: Specify Inventory Requirements

Inventory requirements define which physical or virtual resources must be allocated when provisioning this product. This is a critical step that connects your product catalog to your Inventory Management System .

{
"inventory_items_list": "['SIM Card', 'Mobile Number']"
}

What Are Inventory Items?

Inventory items are trackable resources stored in the OmniCRM inventory system. Each item has:

  • Type - Defined by the Inventory Template (e.g., "SIM Card", "Mobile Number", "Modem")
  • Unique attributes - Serial numbers, MAC addresses, phone numbers, etc.
  • State - In Stock, Assigned, Decommissioned, etc.
  • Location - Physical or logical location

How Inventory Requirements Work:

The inventory_items_list is a Python list (as a string) containing inventory type names. Each name must exactly match an existing Inventory Template name.

Example Inventory Requirements:

# Mobile SIM product
inventory_items_list: "['SIM Card', 'Mobile Number']"

# Fixed internet service
inventory_items_list: "['Modem Router', 'Static IP Address']"

# Digital service (no physical items)
inventory_items_list: "[]"

# Fixed wireless with CPE
inventory_items_list: "['Fixed Wireless CPE', 'IPv4 Address', 'IPv6 Prefix']"

The Inventory Picker Process

When a user provisions a product with inventory requirements, the system enforces a mandatory selection process:

1. Provision Button Clicked

After selecting the product, the user clicks "Provision". Instead of immediately provisioning, the system checks inventory_items_list.

2. Inventory Picker Modal Appears

If inventory is required, a modal dialog appears with a separate dropdown for each inventory type:

3. Filtering Available Inventory

The dropdown for each inventory type only shows items that are:

  • Correct Type - Matches the inventory template name exactly
  • Available Status - item_state is "New" or "In Stock" (not "Assigned" or "Damaged")
  • Not Assigned - service_id and customer_id are NULL
  • In Stock at Location - Optionally filtered by warehouse/store location

Example Dropdown Options:

For "SIM Card" inventory type, the dropdown might show:

Each option displays:

  • Inventory ID or reference number
  • Primary identifier (itemtext1 - e.g., ICCID for SIM, number for phone)
  • Current location (item_location)

4. Selection Required to Proceed

Critical Rule: Provisioning CANNOT proceed without selecting all required inventory items.

  • "Continue" button is disabled until all dropdowns have selections
  • User must select one item for each inventory type
  • System validates selections before proceeding

5. Selected Inventory Passed to Ansible

Once user clicks "Continue", the selected inventory IDs are passed to the Ansible playbook as variables:

# User selected:
# - SIM Card inventory_id: 5001
# - Mobile Number inventory_id: 5002

# Variables passed to Ansible:
{
"product_id": 42,
"customer_id": 123,
"SIM Card": 5001, # Inventory ID
"Mobile Number": 5002, # Inventory ID
"access_token": "eyJ..."
}

Note: The variable name matches the inventory type exactly. The playbook uses hostvars[inventory_hostname]['SIM Card'] to access the inventory ID.

6. Playbook Fetches Full Inventory Details

The Ansible playbook uses the inventory ID to fetch complete details:

- name: Get SIM Card details from inventory
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ hostvars[inventory_hostname]['SIM Card'] }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
register: api_response_sim

- name: Extract ICCID and IMSI
set_fact:
iccid: "{{ api_response_sim.json.itemtext1 }}"
imsi: "{{ api_response_sim.json.itemtext2 }}"

Now the playbook has all SIM details (ICCID, IMSI, etc.) to provision the subscriber in the HSS.

7. Inventory State Changed to "Assigned"

After the service record is created, the playbook updates inventory to link it to the service:

- name: Assign SIM Card to Service
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ hostvars[inventory_hostname]['SIM Card'] }}"
method: PATCH
body:
{
"service_id": "{{ service_creation_response.json.service_id }}",
"customer_id": "{{ customer_id }}",
"item_state": "Assigned"
}

Important: Inventory assignment happens during playbook execution as a specific task, NOT when the provision button is clicked. This means:

  • Risk of Double-Allocation: Between clicking "Provision" and inventory being assigned, another user could theoretically select the same inventory item
  • Best Practice: For high-volume operations, implement inventory locking or use database transactions
  • Rollback on Failure: If the playbook fails before inventory assignment, inventory remains unassigned and available for reuse

Why Not Assign Earlier?

Inventory isn't assigned when "Provision" is clicked because:

  1. Service ID Needed: The service_id doesn't exist until the service is created in the playbook
  2. Rollback Simplicity: If provisioning fails early (e.g., OCS account creation fails), inventory doesn't need cleanup
  3. Flexibility: Playbook can decide not to assign inventory based on conditional logic

Handling Failed Provisions:

When a provision fails after inventory is assigned, the rescue block should release inventory:

rescue:
- name: Release inventory on failure
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ hostvars[inventory_hostname]['SIM Card'] }}"
method: PATCH
body:
{
"service_id": null,
"customer_id": null,
"item_state": "In Stock"
}
when: service_id is defined # Only if service was created

This ensures inventory isn't left in an "Assigned" state for a non-existent or failed service.

When Inventory List is Empty

If inventory_items_list: "[]" (empty list), the inventory picker is skipped entirely and provisioning proceeds immediately. This is common for:

  • Digital products - Software licenses, VPN accounts
  • Service addons - Data top-ups that don't need new hardware
  • Virtual services - That don't consume trackable resources

Example: A "5GB Data Boost" addon has inventory_items_list: "[]" because it just adds balance to an existing service without needing new hardware.

Inventory Template Setup

Before using an inventory type in inventory_items_list, you must create the Inventory Template:

  1. Navigate to AdministrationInventoryTemplates
  2. Create template with exact name (e.g., "SIM Card")
  3. Define fields:
    • itemtext1_label: "ICCID"
    • itemtext2_label: "IMSI"
    • itemtext3_label: "PUK Code"
  4. Add inventory items of this type to stock

For complete details on creating and managing inventory templates, see Inventory Management .

Multiple Items of Same Type

While the inventory_items_list is an array, having duplicate types (e.g., "['SIM Card', 'SIM Card']") is not recommended as it may cause confusion in the UI and playbook variable naming.

For scenarios requiring multiple similar items:

Option 1: Create distinct inventory template names

# Dual-SIM phone service
inventory_items_list: "['Primary SIM Card', 'Secondary SIM Card', 'Mobile Number']"

Create separate templates: "Primary SIM Card" and "Secondary SIM Card" with same fields but different names.

Option 2: Use a single bundled inventory item

# Dual-SIM kit
inventory_items_list: "['Dual SIM Kit', 'Mobile Number']"

Where "Dual SIM Kit" inventory template has fields for both SIMs (itemtext1: Primary ICCID, itemtext2: Secondary ICCID, etc.).

Common Inventory Scenarios

Mobile Service:

inventory_items_list: "['SIM Card', 'Mobile Number']"
  • SIM Card: Physical or eSIM with ICCID/IMSI
  • Mobile Number: Phone number (MSISDN)

Fixed Internet:

inventory_items_list: "['Modem Router', 'Static IP Address']"
  • Modem Router: CPE device with MAC address
  • Static IP Address: IPv4 from address pool

Fixed Wireless:

inventory_items_list: "['Fixed Wireless CPE', 'IPv4 Address', 'IPv6 Prefix']"
  • CPE: Customer premises equipment (antenna, modem)
  • IPv4: Public IP address
  • IPv6 Prefix: /56 or /64 prefix

Note: Appointments and scheduling are not inventory items. Use separate scheduling/calendar systems for installation appointments.

VoIP Service:

inventory_items_list: "['DID Number']"
  • DID Number: Direct Inward Dialing phone number

Note: SIP usernames, passwords, and account configurations are generated programmatically by the provisioning playbook, not selected from inventory.

GPON/Fiber:

inventory_items_list: "['ONT Device', 'GPON Port', 'IPv4 Address', 'Fiber Drop Cable']"
  • ONT Device: Optical Network Terminal with serial number
  • GPON Port: Specific port on OLT with fiber connection
  • IPv4 Address: Public or private IP
  • Fiber Drop Cable: Physical fiber cable from street to premises (tracked for asset management)

Equipment Rental:

inventory_items_list: "['Rental Modem']"
  • Tracks which modem is with which customer
  • Important for recovering equipment on cancellation

Why Inventory Requirements Matter

1. Prevent Double-Allocation

Without inventory tracking, you could accidentally:

  • Assign same SIM card to two customers
  • Allocate same IP address to multiple services
  • Ship same equipment serial to different locations

Inventory picker ensures each item is assigned to exactly one service.

2. Audit Trail

Inventory assignment creates complete audit trail:

  • Which SIM card is with which customer
  • When was it assigned
  • Which service is using which phone number
  • Equipment history (who had it, when, for what service)

3. Resource Planning

Track inventory levels:

  • Alert when SIM cards running low
  • Reorder before stockout
  • Plan technician schedules based on CPE availability
  • Manage IP address space allocation

4. Cost Tracking

Link wholesale cost to specific item:

  • Track cost of each SIM card
  • Calculate equipment depreciation
  • Identify lost or stolen items
  • Accurate COGS (Cost of Goods Sold)

5. Deprovisioning

When service is cancelled, inventory can be:

  • Released back to stock (SIM cards, modems)
  • Retired (damaged equipment)
  • Returned to vendor (rental equipment)
  • Kept for grace period (phone numbers before release)

Troubleshooting Inventory Picker Issues

Problem: "No inventory available" message appears

Causes:

  • No inventory items of required type exist in database
  • All items are already "Assigned" to other services
  • Items are marked as "Damaged" or "Out Of Service"
  • Inventory template name doesn't match exactly (case-sensitive)

Solution:

  1. Verify inventory template exists: AdministrationInventoryTemplates
  2. Check template name matches exactly (including spaces, case)
  3. Add inventory items of this type: AdministrationInventoryAdd Item
  4. Verify items are in "New" or "In Stock" state
  5. Check items aren't already assigned (service_id should be NULL)

Problem: Inventory picker doesn't appear

Causes:

  • inventory_items_list is empty: "[]"
  • inventory_items_list is NULL or not set
  • Product category is "addon" and inherits parent service inventory

Solution:

  • If inventory is needed, set inventory_items_list: "['Type1', 'Type2']"
  • Verify product definition saved correctly
  • Check API response for product includes inventory_items_list

Problem: Playbook fails with "inventory not found"

Causes:

  • Playbook references wrong variable name
  • Inventory ID not passed correctly
  • Inventory was deleted between selection and provisioning

Solution:

  • Verify playbook uses correct variable: hostvars[inventory_hostname]['SIM Card']
  • Check variable is integer: {{ hostvars[inventory_hostname]['SIM Card'] | int }}
  • Add error handling in playbook for missing inventory

See Inventory Management for complete details on creating templates, adding items, and managing stock levels.

Step 7: Define Features and Terms

Features and terms are customer-facing marketing and legal content that helps customers understand what they're buying and the obligations involved.

{
"features_list": "20GB High-Speed Data. Unlimited Calls & Texts. EU Roaming Included. No Contract. 30-Day Expiry",
"terms": "Credit expires after 30 days. Data, calls, and texts valid only within expiry period. Fair use policy applies. See website for full terms."
}

Purpose and Business Value

Features List - Marketing & Sales:

The features list serves multiple critical business functions:

  1. Product Differentiation - Helps customers quickly compare products and choose the right one
    • "Prepaid Mobile 20GB" vs "Prepaid Mobile 50GB" - features clearly show the difference
    • Without features, customers only see price, missing value proposition
  2. Marketing Communication - Key selling points prominently displayed
    • "EU Roaming Included" attracts international travelers
    • "No Contract" appeals to commitment-averse customers
    • Features drive purchase decisions
  3. Customer Expectations - Sets clear expectations about what's included
    • Reduces support calls ("Does this include calls?" → clearly listed)
    • Prevents misunderstandings and refund requests
    • Builds trust through transparency
  4. Self-Service - Enables customers to self-select appropriate products
    • Customer reads features, understands offering, makes informed choice
    • Reduces need for sales staff explanation
    • Speeds up purchase process
  5. SEO and Discoverability - Features can be indexed for search
    • Customer searches "unlimited calls mobile plan" → product appears
    • Improves product catalog searchability

Terms and Conditions - Legal & Compliance:

Terms serve legal and operational purposes:

  1. Legal Protection - Protects business from disputes and liability
    • "Credit expires after 30 days" - customer cannot demand refund at 31 days
    • "Fair use policy applies" - prevents abuse (tethering entire office on mobile plan)
    • Creates binding agreement
  2. Expectation Management - Prevents customer dissatisfaction
    • "Valid only within expiry period" - customer knows usage deadline
    • "Cannot be refunded" (for addons) - prevents fraudulent purchases
    • Reduces chargebacks and complaints
  3. Regulatory Compliance - Meets legal requirements
    • Consumer protection laws require clear terms
    • Telecommunications regulations mandate disclosure
    • GDPR/privacy terms can be referenced
  4. Operational Boundaries - Defines service scope and limitations
    • "Subject to network coverage" - not liable for dead zones
    • "Speed may vary" - manages expectations on "up to" speeds
    • "Equipment must be returned" - ensures rental equipment recovery
  5. Audit Trail - Proves customer was informed
    • Customer accepted terms at purchase
    • System logs acceptance timestamp
    • Defensible in disputes or legal proceedings

Real-World Example:

Customer buys "Unlimited Calls & Texts" plan, then uses it for telemarketing (10,000 calls/day). Without terms:

  • Customer: "You said unlimited!"
  • Provider: "We meant personal use..."
  • Customer: "That's not what you advertised!"
  • Result: Dispute, potential regulator complaint, brand damage

With terms: "Fair use policy applies. Service is for personal use only. Commercial use prohibited."

  • Provider: Points to terms customer accepted
  • Customer cannot claim ignorance
  • Legal basis to suspend service
  • Dispute resolved in provider's favor

Features List Format:

Understanding the correct format is critical because improper formatting breaks the UI display. Features might appear as one long string instead of bullet points, or not display at all.

The features_list field can be formatted in two ways:

Option 1: Period-Separated String (Recommended)

Features are separated by a period and space (". "). The UI splits on this delimiter and renders each feature as a bullet point.

Why this format?

  • Simple to edit - just type features with periods between them
  • No special characters to escape
  • Works reliably across all UI components
  • Easy to update without breaking JSON syntax

Correct vs Incorrect:

Option 2: JSON Array String

"['20GB High-Speed Data', 'Unlimited Calls & Texts', 'EU Roaming Included']"

The UI can also parse JSON arrays. Note this is a string containing JSON, not an actual JSON array in the database.

Why this format exists?

  • Allows features with periods in them (e.g., "Up to 100Mbps. Subject to availability.")
  • Programmatic generation from scripts/API is easier
  • Imported from external product catalogs that use arrays

Important: This must be valid Python list syntax as a string. Single quotes around each item, double quotes around the whole string.

Which Format to Use?

  • Period-separated - For manual product creation in UI (simpler, less error-prone)
  • JSON array - For API/script-based product creation (more robust for complex features)

Both formats produce identical output in the UI - they just affect how you input the data.

Where Features Appear in the UI:

1. Product Catalog (Customer View)

When customers browse available products, features are displayed as bullet points on each product card:

2. Product Details Page

Clicking "View Details" shows full product information including:

  • Product name and icon
  • Pricing (monthly cost, setup cost)
  • Full features list (bullet points)
  • Terms and conditions (see below)
  • Availability and eligibility

3. Provisioning Confirmation

During provisioning, features are shown for user to review before confirming:

Features: • 20GB High-Speed Data • Unlimited Calls & Texts • EU Roaming Included • No Contract • 30-Day Expiry

Cost: £15.00/month Setup: £0.00

[Cancel] [Confirm & Provision]

4. Service Details (After Provisioning)

After service is active, features are displayed on the service detail page for customer reference.

Terms and Conditions Format:

The terms field is plain text that can include newlines:

Where Terms Appear in the UI:

1. Product Details Page

Terms are displayed in a collapsed section that expands when clicked:

2. Order Confirmation

During provisioning, a checkbox requires user to accept terms:

[Provision] button disabled until checked

3. Invoices

Service terms may be included on invoices as footnotes for clarity.

Best Practices:

  • Features: Keep concise (under 50 characters each), focus on key benefits
  • Terms: Include critical legal requirements, expiration policies, fair use policies
  • Both: Update when product changes to keep customers informed

Step 8: Link Ansible Provisioning Playbook

{
"provisioning_play": "play_local_mobile_sim",
"provisioning_json_vars": "{
\"days\": 30,
\"data_gb\": 20,
\"voice_minutes\": \"unlimited\",
\"sms_count\": \"unlimited\"
}"
}
  • provisioning_play - Name of Ansible playbook (without .yaml extension)
  • provisioning_json_vars - Default variables passed to playbook
  • Playbook must exist at: OmniCRM-API/Provisioners/plays/play_local_mobile_sim.yaml

Complete Product Definition

{
"product_name": "Prepaid Mobile 20GB",
"product_slug": "prepaid-mobile-20gb",
"category": "standalone",
"service_type": "mobile",
"enabled": true,
"icon": "fa-solid fa-sim-card",
"comment": "Prepaid mobile SIM with 20GB data, unlimited calls & texts",

"retail_cost": 15.00,
"wholesale_cost": 5.00,
"retail_setup_cost": 0.00,
"wholesale_setup_cost": 1.00,
"contract_days": 30,

"residential": true,
"business": false,
"customer_can_purchase": true,
"available_from": "2025-01-01T00:00:00Z",
"available_until": null,

"auto_renew": "prompt",
"allow_auto_renew": true,

"inventory_items_list": "['SIM Card', 'Mobile Number']",

"features_list": "[
'20GB High-Speed Data',
'Unlimited Calls & Texts',
'EU Roaming Included',
'No Contract',
'30-Day Expiry'
]",
"terms": "Credit expires after 30 days. Data, calls, and texts valid only within expiry period. Fair use policy applies.",

"provisioning_play": "play_local_mobile_sim",
"provisioning_json_vars": "{
\"days\": 30,
\"data_gb\": 20,
\"voice_minutes\": \"unlimited\",
\"sms_count\": \"unlimited\"
}"
}

Creating an Addon Product

Addons enhance or modify existing services. They come in two types: virtual addons (no physical resources) and hardware addons (require inventory).

Example 1: Virtual Addon (5GB Data Boost)

A digital addon that adds data to an existing mobile service:

{
"product_name": "5GB Data Boost",
"product_slug": "5gb-data-boost",
"category": "addon",
"service_type": "mobile",
"enabled": true,
"icon": "fa-solid fa-plus",
"comment": "Add 5GB extra data to existing mobile service",

"retail_cost": 5.00,
"wholesale_cost": 1.50,
"retail_setup_cost": 0.00,
"wholesale_setup_cost": 0.00,
"contract_days": 0,

"residential": true,
"business": true,
"customer_can_purchase": true,

"auto_renew": "false",
"allow_auto_renew": false,

"inventory_items_list": "[]",
"relies_on_list": "",

"features_list": "5GB High-Speed Data. Valid for 7 Days",
"terms": "Data expires after 7 days or when exhausted. Cannot be refunded.",

"provisioning_play": "play_topup_charge_then_action",
"provisioning_json_vars": "{
\"data_gb\": 5,
\"days\": 7
}"
}

Example 2: Hardware Addon (Modem Rental)

An addon that provides physical equipment for an existing fiber service:

{
"product_name": "WiFi 6 Modem Rental",
"product_slug": "wifi6-modem-rental",
"category": "addon",
"service_type": "internet",
"enabled": true,
"icon": "fa-solid fa-router",
"comment": "Add WiFi 6 modem to fiber service - rental",

"retail_cost": 10.00,
"wholesale_cost": 3.00,
"retail_setup_cost": 0.00,
"wholesale_setup_cost": 45.00,
"contract_days": 30,

"residential": true,
"business": true,
"customer_can_purchase": true,

"auto_renew": "true",
"allow_auto_renew": true,

"inventory_items_list": "['Rental Modem']",
"relies_on_list": "",

"features_list": "WiFi 6 (802.11ax). Dual-band 2.4GHz + 5GHz. Up to 40 devices. Parental controls",
"terms": "Equipment rental. Must be returned on service cancellation or £150 replacement fee applies. Equipment remains property of provider.",

"provisioning_play": "play_addon_assign_modem",
"provisioning_json_vars": "{
\"device_type\": \"modem_router\",
\"requires_configuration\": true
}"
}

Key Differences for Addons:

  • category: "addon" - Applied to existing service, not standalone
  • contract_days: 0 (virtual) or 30 (recurring rental) - Billing frequency
  • inventory_items_list: "[]" (virtual) or "['Rental Modem']" (hardware) - Physical resources
  • auto_renew: "false" (one-time) or "true" (rental) - Recurring behavior
  • relies_on_list: "" - Empty means applies to any service of matching service_type

Why Hardware Addons Need Inventory:

Hardware addons require inventory_items_list because:

  1. Track Equipment - Know which modem is with which customer
  2. Prevent Stockouts - Can't provision addon if no modems in stock
  3. Recovery - When customer cancels, know which equipment to recover
  4. Cost Tracking - Link wholesale cost to specific serial number
  5. Depreciation - Track equipment value over rental period
  6. Warranty - Identify defective units by serial number

Addon Provisioning Flow with Inventory:

When a customer adds "WiFi 6 Modem Rental" to their fiber service:

  1. Addon Selected - Customer clicks "Add to Service"
  2. Inventory Picker Appears - Just like standalone services:
  3. Payment Processed - £10.00 monthly rental charged
  4. Modem Assigned - Inventory updated:
    • service_id: Linked to fiber service
    • customer_id: Linked to customer
    • item_state: "Assigned"
  5. Shipping Triggered - Fulfillment system notified to ship modem
  6. Installation - Customer receives modem, plugs into ONT
  7. Recurring Billing - £10/month charged until addon cancelled

Deprovisioning Hardware Addons:

When customer cancels modem rental:

  1. Cancellation Initiated - Customer clicks "Remove Addon"
  2. Return Process Started:
    • Email sent with return instructions
    • Prepaid shipping label generated
    • 14-day grace period before penalty
  3. Equipment Returned:
    • Inventory updated: item_state = "In Stock" (after refurbishment)
    • Or item_state = "Damaged" (if defective)
    • Linked to next customer once refurbished
  4. No Return:
    • After 14 days, £150 replacement fee charged
    • Inventory marked: item_state = "Lost"
    • Wholesale cost (£45) + replacement value recovered

Pricing for Addons:

Addons can be priced differently from standalone services:

  • Virtual addons typically have no setup costs
  • Hardware addons may have wholesale setup costs for equipment
  • Recurring rental addons use contract_days for billing frequency

Stage 2: The Provisioning Process

When a customer orders the "Prepaid Mobile 20GB" product, OmniCRM orchestrates provisioning through Ansible.

Provisioning Flow Diagram

Customer Orders → Inventory Selection → Provisioning Job Created ↓ ↓ Payment Authorized ← Variables Assembled ← Ansible Playbook Executed ↓ ↓ Service Record Created → OCS Account Setup → Inventory Assigned → Service Active

Step-by-Step Provisioning Flow

1. Customer Initiates Order

From customer page:

  • Staff clicks "Add Service"
  • Selects "Prepaid Mobile 20GB" from product carousel
  • Product details and pricing displayed

2. Inventory Selection

System prompts for required inventory:

  • SIM Card - Dropdown shows available SIM cards in stock
    • Example: "SIM-00123 - ICCID: 8944..."
  • Mobile Number - Dropdown shows available phone numbers
    • Example: "+44 7700 900123"

Staff or customer selects items from available inventory.

3. Pricing Confirmation

System displays final pricing:

  • Setup cost: £0.00 (free activation)
  • Monthly cost: £15.00
  • Due today: £15.00 (first month)
  • Renewal date: 30 days from today

If auto-renew prompting enabled, customer chooses:

  • Automatically renew this service every 30 days

4. Provision Button Clicked

When "Provision" is clicked, the API:

  • Creates Provision record with status "Running" (status=1)
  • Merges variables from product + request + inventory selections
  • Spawns background thread to execute Ansible playbook
  • Returns provision_id to UI for status tracking

5. Variables Assembled

System merges variables from multiple sources:

From Product:

{
"days": 30,
"data_gb": 20,
"voice_minutes": "unlimited",
"sms_count": "unlimited"
}

From Request:

{
"product_id": 42,
"customer_id": 123,
"SIM Card": 5001,
"Mobile Number": 5002
}

System-Added:

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"initiating_user": 7
}

Final Variables Passed to Ansible:

{
"product_id": 42,
"customer_id": 123,
"SIM Card": 5001,
"Mobile Number": 5002,
"days": 30,
"data_gb": 20,
"voice_minutes": "unlimited",
"sms_count": "unlimited",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"initiating_user": 7
}

6. Ansible Playbook Execution

The playbook play_local_mobile_sim.yaml executes with these variables.

Understanding the Ansible Provisioning Playbook

Let's examine a real provisioning playbook to understand what happens behind the scenes.

Mobile SIM Provisioning Playbook Example

Location: OmniCRM-API/Provisioners/plays/play_local_mobile_sim.yaml

High-Level Structure:

- name: Mobile SIM Provisioning
hosts: localhost
gather_facts: no
become: False

tasks:
- name: Main block
block:
# 1. Load configuration
# 2. Fetch product details from API
# 3. Fetch customer details from API
# 4. Fetch inventory details from API
# 5. Create account in OCS (CGRateS)
# 6. Add balances and allowances to OCS
# 7. Create service record in CRM
# 8. Assign inventory to service
# 9. Record transactions
# 10. Send welcome notifications

rescue:
# Rollback on failure
# - Remove OCS account
# - Release inventory
# - Log error

Detailed Playbook Walkthrough:

Task 1: Load Configuration

- name: Include vars of crm_config
ansible.builtin.include_vars:
file: "../../crm_config.yaml"
name: crm_config

Loads system configuration including:

  • OCS/CGRateS URL and credentials
  • CRM base URL
  • Tenant configuration

Task 2: Fetch Product Details

- name: Get Product information from CRM API
uri:
url: "{{ crm_config.crm.base_url }}/crm/product/product_id/{{ product_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
return_content: yes
register: api_response_product

What This Does:

  • Calls GET /crm/product/product_id/42
  • Retrieves complete product definition
  • Stores in api_response_product variable

Why: Even though we have provisioning_json_vars from the product, we fetch the full product to get:

  • Latest pricing (may have changed since order started)
  • Product name for service naming
  • Features list for documentation
  • Wholesale costs for margin tracking

Task 3: Set Package Facts

- name: Set package facts
set_fact:
package_name: "{{ api_response_product.json.product_name }}"
monthly_cost: "{{ api_response_product.json.retail_cost }}"
setup_cost: "{{ api_response_product.json.retail_setup_cost }}"

Extracts commonly-used values into simple variables for readability.

Task 4: Fetch Inventory Details

- name: Get SIM information from CRM API
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ hostvars[inventory_hostname]['SIM Card'] }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
register: api_response_sim

- name: Set IMSI from Inventory response
set_fact:
imsi: "{{ api_response_sim.json.itemtext2 }}"
iccid: "{{ api_response_sim.json.itemtext1 }}"

What This Does:

  • Looks up SIM Card inventory ID 5001
  • Retrieves SIM details:
    • itemtext1 = ICCID (SIM card number)
    • itemtext2 = IMSI (subscriber identity)
  • Does same for Mobile Number inventory (retrieves phone number)

Why This Matters:

  • IMSI is needed to provision subscriber in HSS (Home Subscriber Server)
  • ICCID is recorded in service notes for troubleshooting
  • Phone number (MSISDN) is displayed to customer and used for routing

Task 5: Generate Service UUID

- name: Generate UUID Fact
set_fact:
uuid: "{{ 99999999 | random | to_uuid }}"

- name: Set Service UUID
set_fact:
service_uuid: "Local_Mobile_SIM_{{ uuid[0:8] }}"

What This Does:

  • Generates random UUID
  • Creates service_uuid like Local_Mobile_SIM_a3f2c1d8

Why:

  • Service UUID is the unique identifier in OCS/CGRateS
  • Used for all charging operations
  • Must be globally unique across all services

Task 6: Create OCS Account

- name: Create account in OCS
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
headers:
Content-Type: "application/json"
body:
{
"method": "ApierV2.SetAccount",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"ActionPlanIds": [],
"ExtraOptions": {
"AllowNegative": false,
"Disabled": false
},
"ReloadScheduler": true
}]
}
register: ocs_create_response

What This Does:

  • Calls CGRateS JSON-RPC API
  • Creates new account with service_uuid
  • Sets account to active (not disabled)
  • Prevents negative balance (prepaid mode)

Why:

  • OCS account is where all charging happens
  • Balances (data, voice, SMS, money) are stored here
  • Usage is tracked and rated in real-time

Task 7: Add Data Balance

- name: Add 20GB Data Balance
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Tenant": "{{ crm_config.ocs.ocsTenant }}",
"Account": "{{ service_uuid }}",
"BalanceType": "*data",
"Balance": {
"ID": "DATA_20GB_Monthly",
"Value": 21474836480,
"ExpiryTime": "+720h",
"Weight": 10,
"DestinationIDs": "*any"
}
}]
}

What This Does:

  • Adds 20GB data balance to account
  • Value: 21474836480 bytes (20 * 1024 * 1024 * 1024)
  • Expires in 720 hours (30 days)
  • Weight 10 (higher weight consumed first)

Task 8: Add Unlimited Voice & SMS

- name: Add Unlimited Voice
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Account": "{{ service_uuid }}",
"BalanceType": "*voice",
"Balance": {
"ID": "VOICE_Unlimited",
"Value": 999999999,
"ExpiryTime": "+720h"
}
}]
}
  • Adds 999,999,999 seconds of voice (essentially unlimited)
  • Expires in 30 days

Task 9: Create Service Record in CRM

- name: Add Service via API
uri:
url: "{{ crm_config.crm.base_url }}/crm/service/"
method: PUT
body_format: json
headers:
Authorization: "Bearer {{ access_token }}"
body:
{
"customer_id": "{{ customer_id }}",
"product_id": "{{ product_id }}",
"service_name": "Mobile - {{ phone_number }}",
"service_type": "mobile",
"service_uuid": "{{ service_uuid }}",
"service_status": "Active",
"service_provisioned_date": "{{ provision_datetime }}",
"retail_cost": "{{ monthly_cost }}",
"wholesale_cost": "{{ api_response_product.json.wholesale_cost }}",
"icon": "fa-solid fa-sim-card"
}
register: service_creation_response

What This Creates:

  • Service record linked to customer
  • Links to OCS via service_uuid
  • Stores retail and wholesale costs
  • Sets status to "Active"
  • Returns service_id for subsequent operations

Task 10: Assign Inventory to Service

- name: Assign SIM Card to Service
uri:
url: "{{ crm_config.crm.base_url }}/crm/inventory/inventory_id/{{ hostvars[inventory_hostname]['SIM Card'] }}"
method: PATCH
body_format: json
headers:
Authorization: "Bearer {{ access_token }}"
body:
{
"service_id": "{{ service_creation_response.json.service_id }}",
"customer_id": "{{ customer_id }}",
"item_state": "Assigned"
}

What This Does:

  • Updates SIM Card inventory record
  • Sets service_id to link SIM to service
  • Changes state from "In Stock" to "Assigned"
  • Repeats for Mobile Number inventory

Why:

  • Tracks which SIM is assigned to which customer
  • Prevents double-allocation of inventory
  • Enables inventory reporting and auditing

Task 11: Record Setup Cost Transaction

- name: Add Setup Cost Transaction
uri:
url: "{{ crm_config.crm.base_url }}/crm/transaction/"
method: PUT
body_format: json
headers:
Authorization: "Bearer {{ access_token }}"
body:
{
"customer_id": "{{ customer_id }}",
"service_id": "{{ service_creation_response.json.service_id }}",
"title": "{{ package_name }} - Setup",
"description": "Activation fee",
"retail_cost": "{{ setup_cost }}",
"wholesale_cost": "{{ api_response_product.json.wholesale_setup_cost }}"
}

What This Does:

  • Records £0.00 setup charge to customer (retail)
  • Records £1.00 wholesale cost
  • Creates transaction record for invoicing

Task 12: Rescue Block (Error Handling)

rescue:
- name: Remove account in OCS on failure
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body:
{
"method": "ApierV2.RemoveAccount",
"params": [{
"Account": "{{ service_uuid }}"
}]
}

- name: Fail the provisioning
fail:
msg: "Provisioning failed, rolled back OCS account"

What This Does:

  • If any task fails, rescue block executes
  • Deletes OCS account that was partially created
  • Releases inventory back to "In Stock"
  • Fails the provision job with error message

Why:

  • Prevents orphaned accounts in OCS
  • Ensures clean rollback on errors
  • Maintains data consistency

Provisioning Complete: What Was Created

After successful provisioning, the system has:

1. OCS Account (CGRateS):

  • Account ID: Local_Mobile_SIM_a3f2c1d8
  • Balances:
    • 20GB data (expires in 30 days)
    • Unlimited voice (999M seconds, expires in 30 days)
    • Unlimited SMS (999M messages, expires in 30 days)

2. CRM Service Record:

  • Service ID: 1234
  • Customer: John Doe (customer_id: 123)
  • Product: Prepaid Mobile 20GB (product_id: 42)
  • Service Name: "Mobile - +44 7700 900123"
  • Service UUID: Local_Mobile_SIM_a3f2c1d8
  • Status: Active
  • Monthly Cost: £15.00 (retail), £5.00 (wholesale)
  • Profit: £10.00/month

3. Inventory Assignments:

  • SIM Card 5001: Assigned to service 1234, customer 123
  • Mobile Number 5002: Assigned to service 1234, customer 123

4. Transaction Records:

  • Setup cost transaction created
  • First month charge recorded

5. Customer Can Now:

  • View service in self-care portal
  • See 20GB data balance
  • Make calls and send SMS
  • Top up or add addons
  • View usage in real-time

Stage 3: Adding Addons and Topups

After a service is active, customers can purchase addons to enhance their service.

Addon Provisioning Flow

Let's say customer has used 18GB of their 20GB allowance and wants to buy the "5GB Data Boost" addon.

1. Customer Navigates to Service

  • Opens "Mobile - +44 7700 900123" service page
  • Sees current usage: 18GB of 20GB used (90%)
  • Clicks "Add Addon" or "Top Up"

2. System Filters Available Addons

Only shows addons where:

  • category = "addon"
  • service_type = "mobile" (matches service type)
  • residential = true (if customer is residential)
  • enabled = true

Customer sees: "5GB Data Boost - £5.00"

3. Customer Selects Addon

  • Clicks "5GB Data Boost"
  • Confirms purchase for £5.00
  • System captures payment authorization

4. Addon Provisioning Initiated

System calls play_topup_charge_then_action.yaml with variables:

{
"product_id": 43, # 5GB Data Boost product
"customer_id": 123,
"service_id": 1234, # Existing service
"access_token": "eyJ...",
"data_gb": 5, # From provisioning_json_vars
"days": 7 # From provisioning_json_vars
}

Key Difference from Standalone:

  • service_id is included (existing service to modify)
  • No inventory required
  • No service creation (modifies existing)

Addon Provisioning Playbook Walkthrough

Task 1: Fetch Service Details

- name: Get Service information from CRM API
uri:
url: "http://localhost:5000/crm/service/service_id/{{ service_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
register: api_response_service

- name: Set service facts
set_fact:
service_uuid: "{{ api_response_service.json.service_uuid }}"
customer_id: "{{ api_response_service.json.customer_id }}"

Why:

  • Need service_uuid to add balance to correct OCS account
  • Verifies service exists and is active
  • Ensures service belongs to the customer

Task 2: Charge Customer

- name: Get Customer's Default Payment Method
uri:
url: "http://localhost:5000/crm/stripe/customer_id/{{ customer_id }}"
method: GET
headers:
Authorization: "Bearer {{ access_token }}"
register: api_response_stripe

- name: Get default card ID
set_fact:
customer_stripe_id: "{{ api_response_stripe.json | json_query(query) }}"
vars:
query: "data[?default_payment_method==`true`].customer_stripe_id | [0]"

- name: Charge card
uri:
url: "http://localhost:5000/crm/stripe/charge_card/{{ customer_stripe_id }}"
method: POST
body_format: json
headers:
Authorization: "Bearer {{ access_token }}"
body:
{
"retail_cost": 5.00,
"description": "5GB Data Boost",
"customer_id": "{{ customer_id }}",
"service_id": "{{ service_id }}",
"product_id": "{{ product_id }}",
"wholesale_cost": 1.50,
"invoice": true
}
register: charge_response

- name: Assert payment successful
assert:
that:
- charge_response.status == 200

What This Does:

  • Finds customer's default Stripe payment method
  • Charges £5.00 to the card
  • Records wholesale cost £1.50 for margin tracking
  • Creates transaction linked to service
  • Adds to next invoice
  • Fails provision if payment fails

Why Charge First:

  • No credit delivered until payment confirmed
  • Prevents fraud
  • Matches payment to addon provision

Task 3: Add Data Balance to OCS

- name: Add 5GB Data Balance
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body_format: json
body:
{
"method": "ApierV1.AddBalance",
"params": [{
"Account": "{{ service_uuid }}",
"BalanceType": "*data",
"Balance": {
"ID": "DATA_5GB_Boost_{{ uuid }}",
"Value": 5368709120,
"ExpiryTime": "+168h",
"Weight": 20
}
}]
}

What This Does:

  • Adds 5GB (5368709120 bytes) to account
  • Expires in 168 hours (7 days)
  • Weight 20 (higher weight consumed first - boost before monthly allowance)

Customer Balance After Addon:

  • Original monthly: 2GB remaining (expires in 25 days)
  • New boost: 5GB (expires in 7 days)
  • Total available: 7GB
  • Usage order: Boost consumed first, then monthly

Task 4: Record Transaction

- name: Add Addon Transaction
uri:
url: "http://localhost:5000/crm/transaction/"
method: PUT
body_format: json
headers:
Authorization: "Bearer {{ access_token }}"
body:
{
"customer_id": "{{ customer_id }}",
"service_id": "{{ service_id }}",
"title": "5GB Data Boost",
"description": "Additional 5GB data valid for 7 days",
"retail_cost": 5.00,
"wholesale_cost": 1.50
}

What This Does:

  • Records £5.00 charge to customer
  • Records £1.50 wholesale cost
  • Links transaction to service for reporting

Complete Addon Flow Summary

  1. Customer selects addon from filtered list
  2. Payment authorized and charged
  3. Data balance added to OCS account
  4. Transaction recorded in CRM
  5. Customer immediately sees updated balance: 7GB available

Financial Tracking:

  • Service monthly charge: £15 retail, £5 wholesale
  • Addon purchase: £5 retail, £1.50 wholesale

Auto-Renewal: Recurring Addons

Some addons can be set to auto-renew (monthly data plans, subscriptions, etc).

Product Configuration:

{
"product_name": "Monthly 10GB Data Plan",
"category": "addon",
"retail_cost": 10.00,
"contract_days": 30,
"auto_renew": "true",
"provisioning_play": "play_topup_charge_then_action"
}

Provisioning Creates ActionPlan:

- name: Create ActionPlan for Auto-Renewal
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body:
{
"method": "ApierV1.SetActionPlan",
"params": [{
"Id": "ServiceID_{{ service_uuid }}__ProductID_{{ product_id }}__MonthlyRenewal",
"ActionPlan": [{
"ActionsId": "Action_{{ product_slug }}",
"Years": "*any",
"Months": "*any",
"MonthDays": "*any",
"WeekDays": "*any",
"Time": "00:00:00",
"Weight": 10
}],
"Overwrite": false,
"ReloadScheduler": true
}]
}

What This Does:

  • Creates scheduled task in OCS
  • Executes Action_{{ product_slug }} every 30 days
  • Action charges customer and re-applies data balance
  • Continues until customer cancels

Customer Management:

  • Customer sees "Next Renewal: Feb 1, 2025 - £10.00" in service view
  • Can click "Cancel Auto-Renewal" to stop future charges
  • Can click "Renew Now" to immediately apply next month's allowance

Stage 4: Deprovisioning Services

When a customer cancels service, the system must cleanly remove all resources.

Deprovisioning Triggers

Deprovisioning can be triggered by:

  1. Customer cancellation - Customer clicks "Cancel Service"
  2. Administrative action - Staff marks service for deactivation
  3. Non-payment - Service expires due to lack of renewal
  4. Contract end - Fixed-term contract reaches end date

Deprovisioning Flow

1. Customer Initiates Cancellation

  • Navigates to service
  • Clicks "Cancel Service"
  • System prompts: "Are you sure? Any remaining balance will be forfeited."
  • Customer confirms

2. Grace Period (Optional)

Some operators implement grace period:

  • Service marked "Pending Cancellation"
  • Remains active for 7-30 days
  • Customer can reverse cancellation during grace period
  • Automatic deprovisioning after grace period

3. Deprovisioning Job Created

System creates provision job with:

{
"action": "deprovision",
"service_id": 1234,
"customer_id": 123,
"service_uuid": "Local_Mobile_SIM_a3f2c1d8"
}

Calls playbook specified in service.deprovisioning_play or rescue block of original playbook.

4. Ansible Deprovision Playbook

- name: Deprovision Mobile Service
hosts: localhost
tasks:
- name: Disable OCS Account
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body:
{
"method": "ApierV2.SetAccount",
"params": [{
"Account": "{{ service_uuid }}",
"ExtraOptions": { "Disabled": true }
}]
}

- name: Remove ActionPlans (stop auto-renewals)
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body:
{
"method": "ApierV1.RemoveActionPlan",
"params": [{
"Id": "ServiceID_{{ service_uuid }}__*"
}]
}

- name: Update Service Status in CRM
uri:
url: "http://localhost:5000/crm/service/{{ service_id }}"
method: PATCH
body:
{
"service_status": "Deactivated",
"service_deactivate_date": "{{ current_datetime }}"
}

- name: Release Inventory to Stock
uri:
url: "http://localhost:5000/crm/inventory/inventory_id/{{ sim_card_id }}"
method: PATCH
body:
{
"service_id": null,
"customer_id": null,
"item_state": "Decommissioned"
}

What This Does:

  1. Disables OCS account - Stops all charging, usage blocked
  2. Removes ActionPlans - Cancels auto-renewals
  3. Updates CRM service - Status "Deactivated", date recorded
  4. Releases inventory - SIM marked "Decommissioned", available for reuse (after refurbishment)

5. Post-Deprovisioning

System performs cleanup:

  • Customer no longer sees service in self-care portal
  • Service remains in CRM for historical reporting
  • Transactions and invoices preserved for accounting
  • Inventory can be refurbished and reused
  • OCS account can be archived after retention period

Partial vs Full Deprovisioning

Partial Deprovisioning (Suspension):

  • Used for non-payment or temporary suspension
  • OCS account disabled but not deleted
  • Balances preserved
  • Can be re-enabled when payment received
- name: Suspend Service
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body:
{
"method": "ApierV2.SetAccount",
"params": [{
"Account": "{{ service_uuid }}",
"ExtraOptions": { "Disabled": true }
}]
}

Full Deprovisioning (Permanent Cancellation):

  • Used for permanent cancellation
  • OCS account deleted entirely
  • Balances forfeit
  • Cannot be re-enabled
- name: Remove OCS Account
uri:
url: "http://{{ crm_config.ocs.cgrates }}/jsonrpc"
method: POST
body:
{
"method": "ApierV2.RemoveAccount",
"params": [{
"Account": "{{ service_uuid }}"
}]
}

Best Practices for Product Management

Product Lifecycle Management

Product States:

  • enabled: true - Product available for new orders
  • enabled: false - Product disabled, existing services continue

Disabling Products:

  • Mark product as enabled: false to prevent new orders
  • Existing services remain active
  • Customers can still renew/modify existing services
  • Useful for sunsetting old products

Inventory Management

Inventory States:

  • New - Fresh stock, ready to assign
  • In Stock - Available for provisioning
  • Assigned - Linked to customer service
  • Decommissioned - Can be refurbished and reused
  • Damaged - Needs repair or disposal

Reusing Inventory:

After deprovisioning:

  • SIM cards: Refurbish and mark "In Stock"
  • Phone numbers: Release after porting period (30 days)
  • Equipment: Test, refurbish, mark "Used"

Provisioning Metrics

Monitor:

  • Provisioning success rate
  • Average provisioning time
  • Common failure points
  • Inventory turnover