Auth Module
app/Modules/Auth/
Handles all authentication concerns â login, logout, token management, 2FA, device sessions, password reset, and OAuth2 integration using Laravel Sanctum.
File Structure
Auth/
âââ Controllers/
â âââ AuthController.php # login, logout, refresh
â âââ PasswordController.php # forgot, reset password
â âââ TwoFactorController.php # 2FA setup/verify
âââ Services/
â âââ AuthService.php
â âââ TokenService.php
âââ Actions/
â âââ LoginAction.php
â âââ LogoutAction.php
âââ DTOs/
â âââ LoginDTO.php
âââ Events/
â âââ UserLoggedIn.php
â âââ UserLoggedOut.php
âââ Listeners/
â âââ LogAuthActivity.php
âââ Requests/
â âââ LoginRequest.php
â âââ ResetPasswordRequest.php
âââ Enums/
â âââ TokenAbilityEnum.php
âââ Middleware/
â âââ EnsureTokenIsValid.php
â âââ ThrottleLoginAttempts.php
âââ routes.php
Key Service Code
// app/Modules/Auth/Services/AuthService.php
class AuthService
{
public function login(LoginDTO $dto): array
{
if (!Auth::attempt($dto->credentials())) {
throw new AuthenticationException('Invalid credentials');
}
$user = Auth::user();
$this->auditLog->record($user, 'login');
return [
'token' => $this->tokenService->issue($user, $dto->abilities),
'user' => new UserResource($user),
'expires' => now()->addHours(config('auth.token_ttl')),
];
}
}
Features
Sanctum Tokens
Scoped API token abilities with expiry
2FA (TOTP)
Google Authenticator / Authy compatible
Token Refresh
Sliding expiry, refresh without re-login
Device Sessions
Per-device token management & revocation
Brute Force Guard
Redis-backed exponential throttling
Password Reset
Signed URL email with 60min expiry
API Endpoints
POST /api/v1/auth/login
POST /api/v1/auth/logout
POST /api/v1/auth/refresh
POST /api/v1/auth/password/forgot
POST /api/v1/auth/password/reset
POST /api/v1/auth/2fa/setup
POST /api/v1/auth/2fa/verify
GET /api/v1/auth/devices
Users Module
app/Modules/Users/
Complete user lifecycle management â onboarding, profile, territory assignment, hierarchy mapping, manager relationships, and BPE/ABM/RBM role-specific profile data.
Key Models & Relationships
// app/Modules/Users/Models/User.php
class User extends Authenticatable
{
use HasApiTokens, HasRoles, HasAuditLogs, SoftDeletes;
protected $casts = [
'metadata' => 'array',
'territory_codes' => 'array',
'email_verified_at' => 'datetime',
];
// Hierarchy: BPE â ABM â RBM â SRBM â ZBM â AGM â COO â MD
public function manager(): BelongsTo
{ return $this->belongsTo(User::class, 'manager_id'); }
public function subordinates(): HasMany
{ return $this->hasMany(User::class, 'manager_id'); }
public function claims(): HasMany
{ return $this->hasMany(Claim::class, 'claimant_id'); }
public function territories(): HasMany
{ return $this->hasMany(UserTerritory::class); }
}
Features
Hierarchy Tree
Recursive manager chain with depth limits
Territory Mapping
Zone â Region â Area â Territory assignment
Profile Types
BPE/BPEC/BPEP specific profile metadata
Bulk Import
Excel upload for batch user onboarding
Roles & RBAC Module
app/Modules/Roles/
Spatie-powered RBAC with hierarchical roles, granular permission matrices, policy guards, and approval threshold configuration per role level.
Role Hierarchy & Approval Thresholds
// app/Modules/Roles/Enums/RoleEnum.php
enum RoleEnum: string
{
case BPE = 'bpe'; // L1 â Submit only
case BPEC = 'bpec'; // L1 â Channel
case BPEP = 'bpep'; // L1 â Pharma
case ABM = 'abm'; // L2 â Area approval
case RBM = 'rbm'; // L3 â Regional
case SRBM = 'srbm'; // L4 â Senior Regional
case ZBM = 'zbm'; // L5 â Zonal
case AGM = 'agm'; // L6 â âš5L limit
case COO = 'coo'; // L7 â âš25L limit
case MD = 'md'; // L8 â No limit
case BIS = 'bis_team'; // SYS â Admin
case HO = 'ho'; // SYS â Head Office
public function approvalLimit(): ?int
{
return match($this) {
self::AGM => 500000,
self::COO => 2500000,
self::MD => null,
default => 0,
};
}
}
Permissions Matrix
claims.submit
BPE, BPEC, BPEP
claims.validate
ABM, RBM, BIS
claims.approve
ABM â MD (threshold)
rates.manage
BIS, HO, AGM+
disbursement.process
Finance, AGM+
reports.export
RBM+ all roles
system.configure
BIS, MD only
audit.view
AGM, COO, MD, BIS
Rate Management Module
app/Modules/RateManagement/
Manages primary and secondary rate masters with product-wise rate quotation, effective date management, version control, and approval workflow for rate changes.
Rate Master Service
// app/Modules/RateManagement/Services/RateMasterService.php
class RateMasterService
{
public function getEffectiveRate(
int $productId,
RateTypeEnum $type,
Carbon $date = null
): RateMaster {
$date ??= now();
return Cache::remember(
"rate:{$productId}:{$type->value}:{$date->format('Y-m-d')}",
now()->addHours(6),
fn() => $this->rateRepo->findEffective($productId, $type, $date)
);
}
public function createRateVersion(CreateRateDTO $dto): RateMaster
{
// Supersede current rate, create new version
DB::transaction(function() use ($dto) {
$this->rateRepo->expireCurrent($dto->productId, $dto->rateType, $dto->effectiveFrom);
$rate = $this->rateRepo->create($dto);
event(new RateMasterUpdated($rate));
Cache::tags('rates')->flush();
return $rate;
});
}
}
Features
Rate Versioning
Full history with effective date ranges
Rate Quotation Workflow
New rate proposal â ABM/RBM/AGM approval
Redis Cache
Effective rate cached with auto-invalidation
Bulk Import
Excel/CSV rate import with validation
Rate Change Alerts
Notify affected BPEs on rate updates
Zone-wise Rates
Territory-specific rate overrides
Claims Module
app/Modules/Claims/
Central claim lifecycle management for BPE/BPEC/BPEP workflows. Handles draft creation, submission, validation, multi-level approval, and final disbursement with complete state machine management.
Claim State Machine
// app/Modules/Claims/Enums/ClaimStatusEnum.php
enum ClaimStatusEnum: string
{
case DRAFT = 'draft';
case SUBMITTED = 'submitted';
case UNDER_VALIDATION = 'under_validation';
case VALIDATION_FAILED = 'validation_failed';
case PENDING_APPROVAL = 'pending_approval';
case PARTIALLY_APPROVED = 'partially_approved';
case APPROVED = 'approved';
case REJECTED = 'rejected';
case PENDING_DISBURSEMENT = 'pending_disbursement';
case DISBURSED = 'disbursed';
case CANCELLED = 'cancelled';
case ON_HOLD = 'on_hold';
public function allowedTransitions(): array
{
return match($this) {
self::DRAFT => [self::SUBMITTED, self::CANCELLED],
self::SUBMITTED => [self::UNDER_VALIDATION, self::CANCELLED],
self::UNDER_VALIDATION => [self::PENDING_APPROVAL, self::VALIDATION_FAILED],
self::PENDING_APPROVAL => [self::APPROVED, self::REJECTED, self::ON_HOLD],
self::APPROVED => [self::PENDING_DISBURSEMENT],
self::PENDING_DISBURSEMENT => [self::DISBURSED],
default => [],
};
}
}
Claim Service Core
// app/Modules/Claims/Services/ClaimService.php
class ClaimService
{
public function submit(CreateClaimDTO $dto, User $claimant): Claim
{
return DB::transaction(function() use ($dto, $claimant) {
$claim = $this->claimRepo->create($dto, $claimant);
// Attach line items with rate snapshot
$this->lineItemService->attachWithRateSnapshot($claim, $dto->lineItems);
// Queue async: validation â calculation â approval routing
ProcessClaimJob::dispatch($claim)->onQueue('claims');
event(new ClaimSubmitted($claim));
return $claim;
});
}
}
API Endpoints
POST /api/v1/claims
GET /api/v1/claims
GET /api/v1/claims/{id}
PUT /api/v1/claims/{id}
DELETE /api/v1/claims/{id}
POST /api/v1/claims/{id}/submit
POST /api/v1/claims/{id}/approve
POST /api/v1/claims/{id}/reject
GET /api/v1/claims/{id}/timeline
GET /api/v1/claims/{id}/calculation
Calculation Engine Module
app/Modules/CalculationEngine/
Financial precision calculation engine handling primary vs secondary rate comparison, stockist margin calculations, sales return deductions, NRV adjustments, percentage-based approvals, and advance deductions using BCMath for exact decimal arithmetic.
Calculation Pipeline
// app/Modules/CalculationEngine/Services/CalculationService.php
class CalculationService
{
use MoneyPrecisionTrait; // BCMath wrapper for 4dp precision
public function calculate(Claim $claim): CalculationResultDTO
{
$lineItems = $claim->lineItems->map(fn($item) =>
$this->processLineItem($item)
);
$grossAmount = $lineItems->sum('gross_amount');
$salesReturns = $this->salesReturnDeduction->calculate($claim);
$stockistMargin = $this->marginCalculator->calculate($claim);
$nrvAdjustment = $this->nrvService->adjust($claim);
$advanceDeduction = $this->advanceService->getPreviousAdvance($claim->claimant_id);
$netPayable = $this->bcSub(
$this->bcSub($grossAmount, $salesReturns),
$this->bcAdd($stockistMargin, $advanceDeduction)
);
return new CalculationResultDTO(
grossAmount: $this->bcRound($grossAmount, 4),
salesReturnDeduction: $salesReturns,
stockistMarginDeduction: $stockistMargin,
nrvAdjustment: $nrvAdjustment,
advanceDeduction: $advanceDeduction,
netPayable: $this->bcRound($netPayable, 4),
breakdown: $lineItems,
);
}
private function processLineItem(ClaimLineItem $item): array
{
// Use higher of primary or secondary rate
$applicableRate = $this->bcMax($item->primary_rate, $item->secondary_rate);
$grossAmount = $this->bcMul($item->quantity, $applicableRate);
return [
'line_item_id' => $item->id,
'applicable_rate' => $applicableRate,
'rate_basis' => $item->primary_rate > $item->secondary_rate ? 'primary' : 'secondary',
'gross_amount' => $grossAmount,
];
}
}
Calculation Components
Primary vs Secondary Rate
Automatic selection of higher applicable rate per line item
Stockist Margin
Configurable margin % deduction per stockist type
Sales Return Deduction
Cross-reference returns with ERP data
NRV Adjustment
Net Realisable Value adjustments for pharma claims
Advance Deduction
Previous cycle advance recovery from current claim
% Approval
Percentage-based partial approval with justification
Approval Workflow Module
app/Modules/ApprovalWorkflow/
Configurable multi-level approval chains with dynamic routing based on claim amount, type, and territory. Includes SLA tracking, auto-escalation, delegation, and reminder automation.
Workflow Engine
// app/Modules/ApprovalWorkflow/Services/ApprovalRoutingService.php
class ApprovalRoutingService
{
public function buildApprovalChain(Claim $claim): Collection
{
$chain = collect();
$amount = $claim->net_payable;
$claimant = $claim->claimant;
// Level 1: ABM always required
$chain->push($this->buildLevel($claimant->manager, level: 1, slaHours: 48));
// Level 2: RBM required for amounts > âš10,000
if ($amount > 10000) {
$chain->push($this->buildLevel($claimant->manager->manager, level: 2, slaHours: 72));
}
// Level 3: ZBM for amounts > âš50,000
if ($amount > 50000) {
$zbm = $this->userRepo->findZBM($claimant->zone_id);
$chain->push($this->buildLevel($zbm, level: 3, slaHours: 96));
}
// Finance gate: AGM/COO/MD based on thresholds
$chain->push(...$this->buildFinanceGate($amount));
return $chain;
}
}
Features
SLA Tracking
Per-level SLA with escalation on breach
Auto-Escalation
Skip to next level on SLA timeout
Delegation
Temporary approval authority delegation
Reminders
Scheduled reminder notifications for pending approvals
Validation Engine Module
app/Modules/ValidationEngine/
Comprehensive stockist-wise and pharmacy-wise validation with supply proof verification, cross-reference with ERP/sync data, duplicate detection, and configurable validation rules per claim type.
Validation Pipeline
// app/Modules/ValidationEngine/Services/ValidationPipelineService.php
class ValidationPipelineService
{
private array $validators = [
StockistExistenceValidator::class,
PharmacyMappingValidator::class,
SupplyProofValidator::class,
DuplicateClaimValidator::class,
ERPDataCrossCheckValidator::class,
ClaimPeriodValidator::class,
DocumentCompletenessValidator::class,
];
public function run(Claim $claim): ValidationResult
{
$errors = collect();
foreach ($this->validators as $validatorClass) {
$validator = app($validatorClass);
$result = $validator->validate($claim);
if (!$result->passed) {
$errors->push(...$result->errors);
if ($result->isCritical) break; // Stop on critical failure
}
}
return new ValidationResult(
passed: $errors->isEmpty(),
errors: $errors->all(),
);
}
}
Auto-Sync Engine Module
app/Modules/AutoSync/
Selective data synchronization from ERP systems, Excel uploads, and external APIs. Handles field-level mapping, conflict resolution, retry logic, and detailed sync audit logs with selective field overwrite control.
Sync Configuration
// config/sync.php â Selective field sync rules
return [
'entities' => [
'stockists' => [
'source' => 'erp',
'sync_fields' => ['name', 'code', 'address', 'gst_number'],
'protected_fields' => ['custom_margin', 'internal_notes'],
'conflict_strategy' => 'erp_wins', // erp_wins | local_wins | manual
'schedule' => '0 2 * * *', // 2 AM daily
],
'products' => [
'source' => 'erp',
'sync_fields' => ['name', 'sku', 'hsn_code', 'mrp'],
'protected_fields' => ['primary_rate', 'secondary_rate'],
'conflict_strategy' => 'manual',
'schedule' => '0 */6 * * *', // Every 6 hours
],
],
];
Features
Selective Field Sync
Field-level control over what ERP can overwrite
Conflict Resolution
ERP-wins, local-wins, or manual review strategies
Retry Logic
Exponential backoff with dead letter queue
Sync Audit Log
Full before/after record of every sync operation
Excel Import
Batch Excel upload with row-level error reporting
Sync Alerts
Notify BIS team on sync failures or conflicts
Disbursement Module
app/Modules/Disbursement/
Manages all payment disbursement methods â reimbursement, cheque issuance, NEFT/IMPS online transfer, and gift card allocation. Supports batch disbursement with reconciliation and payment confirmation tracking.
Disbursement Service
// app/Modules/Disbursement/Services/DisbursementService.php
class DisbursementService
{
public function process(Claim $claim, DisbursementDTO $dto): Disbursement
{
$handler = $this->resolveHandler($dto->method);
return DB::transaction(function() use ($claim, $dto, $handler) {
$disbursement = $this->repo->create($claim, $dto);
// Process via method-specific handler
$result = $handler->process($disbursement);
$disbursement->update([
'reference_number' => $result->referenceNumber,
'status' => DisbursementStatusEnum::PROCESSED,
'disbursed_at' => now(),
]);
event(new ClaimDisbursed($claim, $disbursement));
return $disbursement;
});
}
private function resolveHandler(DisbursementMethodEnum $method): DisbursementHandlerInterface
{
return match($method) {
DisbursementMethodEnum::CHEQUE => app(ChequeHandler::class),
DisbursementMethodEnum::NEFT => app(NeftHandler::class),
DisbursementMethodEnum::GIFT_CARD => app(GiftCardHandler::class),
DisbursementMethodEnum::REIMBURSEMENT => app(ReimbursementHandler::class),
};
}
}
Documents Module
app/Modules/Documents/
Secure document management for supply proofs, invoices, pharmacy bills, and signed forms. Stores on S3/MinIO with virus scanning, optional watermarking, OCR metadata extraction, and signed URL generation.
Features
S3/MinIO Storage
Presigned upload URLs, private buckets
Virus Scanning
ClamAV integration before storage
OCR Extraction
Invoice number, amount, date parsing
Watermarking
Stamp APPROVED/REJECTED on documents
Signed URLs
Time-limited access URLs for download
Version Control
Document revision history with reasons
Reports Module
app/Modules/Reports/
Role-scoped dashboards and analytics with claim summaries, financial reports, territory performance, disbursement tracking, Excel/PDF exports, and scheduled report delivery.
Available Reports
Claim Summary
By status, type, period, territory
Financial Report
Gross vs net payable, deduction breakdown
Territory Performance
Zone/region/area-wise claim analytics
TAT Analysis
Turn-around time by approval level
Disbursement Report
Payment method wise disbursement tracking
Scheduled Delivery
Auto-email reports on cron schedule
Audit Logs Module
app/Modules/AuditLogs/
Immutable audit trail for all financial transactions, RBAC changes, approval decisions, and data modifications. Stores before/after snapshots in append-only table with IP, user agent, and geolocation context.
Audit Trait Implementation
// app/Shared/Traits/HasAuditLogs.php
trait HasAuditLogs
{
public static function bootHasAuditLogs(): void
{
static::updated(function (Model $model) {
if ($model->wasChanged()) {
AuditLog::create([
'user_id' => auth()->id(),
'event' => 'updated',
'auditable_type' => $model::class,
'auditable_id' => $model->getKey(),
'old_values' => $model->getOriginal(),
'new_values' => $model->getChanges(),
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
]);
}
});
}
}
Notifications Module
app/Modules/Notifications/
Multi-channel notification system with email, SMS, push, and in-app channels. Template management, delivery tracking, user preferences, bulk notifications, and retry on failure.
Notification Channels
đ§ Email (SMTP/SES)
đą SMS (Twilio/MSG91)
đ Push (FCM/APNs)
đŦ In-App (Database)
đŦ WhatsApp (optional)
đ´ Slack (Internal alerts)
Integrations Module
app/Modules/Integrations/
Pluggable adapter architecture for ERP systems (SAP/Tally), payment gateways, cloud storage providers, and email services. Implements circuit breaker pattern for fault tolerance and automatic retry with exponential backoff.
Adapter Pattern
// app/Modules/Integrations/Contracts/ERPAdapterInterface.php
interface ERPAdapterInterface
{
public function fetchStockists(array $filters = []): Collection;
public function fetchProducts(array $filters = []): Collection;
public function fetchSalesData(Carbon $from, Carbon $to): Collection;
public function pushDisbursement(Disbursement $disbursement): bool;
public function healthCheck(): bool;
}
// Implementations: SAPAdapter, TallyAdapter, MockERPAdapter
// Selected via config('integrations.erp.driver')
Supported Integrations
SAP / Tally ERP
Bidirectional sync with circuit breaker
Excel Import
Laravel Excel with queue processing
Payment Gateway
NEFT/IMPS via bank API integration
AWS S3 / MinIO
Document storage with CDN delivery