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.
- Open
billing.proto(shared by both repositories). - Add your new message defining the entity fields.
- If this entity requires CRUD operations from the CRM, add new
rpcendpoints to theCRMServicedefinition incrm.proto. - Compile the protobufs to generate the C++ and PHP headers.
Step 2: Database Schema
Add the required tables to the MySQL instance.
- Create a migration script (e.g.,
V2026.1__add_loyalty_points.sql). - Ensure the table utilizes foreign keys appropriately (e.g., linking
subscriber_idback to the mainsubscriberstable).
Step 3: server.micro.bss DB Layer
Encapsulate the raw SQL access within the DB_layer class.
- Open
include/db.hppand declare the new CRUD functions:// Example int insert(const billing::LoyaltyTracker &tracker); - 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.
- Open
src/crm/crm_server.cpp. - Override the newly generated virtual function in the
CRMServiceImplclass. - Implement the logic: read the incoming Protobuf request, validate the data, instantiate a
DB_layer, perform the operation, and return aCRMResponse.
Step 5: crm2.micro.bss Frontend
Reflect the capabilities on the admin panel.
- Create a new view (e.g.,
loyalty-manage.php). - Instantiate the PHP gRPC client.
- 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.
- Open
src/rater.cpp. - Determine which flow you are modifying:
- Prepaid Rating: Modifications primarily occur inside
Rater::rate(billing::Charge &cr)andRater::rate(billing::Balance_reserve &br). - Postpaid Rating: Modifications primarily occur inside
Rater::rate(billing::CDR &cdr). - Rating Hooks: Both prepaid and postpaid flows utilize a 3-step pipeline. You can utilize these as hooks to safely modify rating behavior:
pre_rate(): Use this hook for initial lookups, validations, mapping price plans, or applying early discounts before the core rating logic executes.rate(): The core business logic where time-of-day offsets, multipliers, and free-unit consumption are applied. If adding a newService IDtype (e.g.,SVC_5G_DATA), update the switch/case statements here.post_rate(): Use this hook to finalize discounts, apply specific taxes, or adjust the final computed price before database persistence.- Testing Pre-Paid: After modifying the rater, for pre-paid use-cases use the
MockDiameterClientto send dummy CCRs representing the new service type. Verify the returning CCA grants the correct units and deducts the correct balance relative to the configuredREF_Price_plan. - Testing Post-Paid: After modifying postpaid rating logic, drop dummy CDR files into the ingestion directory and verify that the
CDRLoadercorrectly 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.
- Missing Page Breaks: If
page-break-before: alwaysis ignored, ensure the element is NOT inside adisplay: flexcontainer. - The Fix: In your HTML template's
@media printblock, forcefully override Bootstrap layout structures (e.g.,#kt_body,.d-flex,.card) back todisplay: block !important. - 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; }