Skip to content

How-To Guides

This page provides actionable, step-by-step guides for common development tasks within the BSS platform.

Adding a New BSS Entity

If you need to introduce a new business concept (e.g., a "Promotional Campaign" or a "Loyalty Point" tracker), follow these architectural layers from the DB up to the CRM via gRPC.

Step 1: Protocol Buffers Definition

Start by defining the data structure to ensure cross-language compatibility.

  1. Open billing.proto (shared by both repositories).
  2. Add your new message defining the entity fields.
  3. If this entity requires CRUD operations from the CRM, add new rpc endpoints to the CRMService definition in crm.proto.
  4. Compile the protobufs to generate the C++ and PHP headers.

Step 2: Database Schema

Add the required tables to the MySQL instance.

  1. Create a migration script (e.g., V2026.1__add_loyalty_points.sql).
  2. Ensure the table utilizes foreign keys appropriately (e.g., linking subscriber_id back to the main subscribers table).

Step 3: server.micro.bss DB Layer

Encapsulate the raw SQL access within the DB_layer class.

  1. Open include/db.hpp and declare the new CRUD functions:
    // Example
    int insert(const billing::LoyaltyTracker &tracker);
    
  2. Implement using the X DevAPI in src/db.cpp, mapping the Protobuf object fields to the MySQL table columns.

Transaction Safety

If updating a subscriber's balance and their loyalty points simultaneously, wrap the X DevAPI queries within a standard MySQL transaction (session.startTransaction()) to ensure atomicity.

Step 4: Exposing via gRPC

Implement the new logic handler.

  1. Open src/crm/crm_server.cpp.
  2. Override the newly generated virtual function in the CRMServiceImpl class.
  3. Implement the logic: read the incoming Protobuf request, validate the data, instantiate a DB_layer, perform the operation, and return a CRMResponse.

Step 5: crm2.micro.bss Frontend

Reflect the capabilities on the admin panel.

  1. Create a new view (e.g., loyalty-manage.php).
  2. Instantiate the PHP gRPC client.
  3. Call the new endpoint and render the data into your new datatable.

Modifying Existing Rating Logic

The rating engine is sensitive and heavily tested. Modifications must be handled with care.

  1. Open src/rater.cpp.
  2. Determine which flow you are modifying:
  3. Prepaid Rating: Modifications primarily occur inside Rater::rate(billing::Charge &cr) and Rater::rate(billing::Balance_reserve &br).
  4. Postpaid Rating: Modifications primarily occur inside Rater::rate(billing::CDR &cdr).
  5. Rating Hooks: Both prepaid and postpaid flows utilize a 3-step pipeline. You can utilize these as hooks to safely modify rating behavior:
  6. pre_rate(): Use this hook for initial lookups, validations, mapping price plans, or applying early discounts before the core rating logic executes.
  7. rate(): The core business logic where time-of-day offsets, multipliers, and free-unit consumption are applied. If adding a new Service ID type (e.g., SVC_5G_DATA), update the switch/case statements here.
  8. post_rate(): Use this hook to finalize discounts, apply specific taxes, or adjust the final computed price before database persistence.
  9. Testing Pre-Paid: After modifying the rater, for pre-paid use-cases use the MockDiameterClient to send dummy CCRs representing the new service type. Verify the returning CCA grants the correct units and deducts the correct balance relative to the configured REF_Price_plan.
  10. Testing Post-Paid: After modifying postpaid rating logic, drop dummy CDR files into the ingestion directory and verify that the CDRLoader correctly processes and rates them according to your new rules.

Troubleshooting PDF Layouts

The server.micro.bss uses plutobook (which is based on the litehtml engine) to render invoices. This engine is lightweight but does not support modern CSS Flexbox correctly when calculating page breaks.

  1. Missing Page Breaks: If page-break-before: always is ignored, ensure the element is NOT inside a display: flex container.
  2. The Fix: In your HTML template's @media print block, forcefully override Bootstrap layout structures (e.g., #kt_body, .d-flex, .card) back to display: block !important.
  3. Utility Classes: Use dedicated utility classes instead of inline styles to ensure the engine parser registers the forced page boundary:
    .force-page-break {
        page-break-before: always !important;
        display: block !important;
        clear: both !important;
        margin-top: 40px !important;
    }