Table of Contents

1Executive Summary & Scope
2Technology Stack Rationale
3System Architecture Overview
4Module Breakdown
5Database Schema — Core Tables
6Database Schema — Module Tables
7API Design Conventions
8Authentication & RBAC (Dynamic)
9Third-Party Integrations
10Infrastructure & DevOps
11Sprint Plan — Phase 1 (Weeks 1–11)
12Sprint Plan — Phase 2 (Weeks 12–18)
13Sprint Plan — Phase 3 (Weeks 19–23)
14Non-Functional Requirements
15Risk Register

1. Executive Summary & Scope

This document defines the complete technical architecture for the Mariinox CRM & Scrap Operations Platform, re-platformed from ERPNext/Frappe to a purpose-built Laravel 11 + React 18 stack. It is the primary reference for database schema design, API contracts, sprint planning, and infrastructure provisioning.

1.1 Scope — This Document

1.2 Out of Scope

1.3 Key Constraints

Constraint Requirement
Uptime 99.5% — EC2 Auto Scaling + RDS Multi-AZ + ALB
Lead Volume 50,000+ leads/month; 50+ concurrent users
Data Residency AWS ap-south-1 (Mumbai); S3 replica in ap-south-2 (Hyderabad)
Encryption AES-256 at rest (S3 SSE-KMS); TLS 1.2+ in transit
RPO / RTO RPO 4h (RDS snapshots every 4h); RTO 8h (tested restore runbook)
Audit Retention 3 years — immutable logs in DB + CloudWatch
Page Load < 2s P95 — Redis cache + CDN + DB indexing

2. Technology Stack Rationale

The platform is intentionally separated into a stateless API layer (Laravel) and a stateful UI layer (React SPA). This allows independent scaling and clear contract boundaries for future mobile apps or third-party integrations.

Layer Technology Rationale
API Backend Laravel 11 (PHP 8.3) Mature ecosystem; Eloquent ORM; queues; Sanctum auth; policy-based RBAC
Frontend React 18 + TypeScript Component reuse for 8 dashboards; React Query for server state; Zustand for local state
Database MySQL 8 (RDS Multi-AZ) JSON columns for flexible metadata; full-text search; proven at scale
Cache / Queue Redis (ElastiCache) Session store; Laravel queues; dashboard cache invalidation; RBAC permission cache
File Storage AWS S3 (SSE-KMS) AES-256; versioning; pre-signed URLs; no direct public access
Search (Phase 2) Laravel Scout + Meilisearch Fast lead search across 50k+ records; self-hosted on EC2
Scheduler Laravel Scheduler + Horizon SLA engine (hourly), assignment retry (15-min), advance payment trigger
CI/CD GitHub Actions + Docker Build → test → staging deploy → smoke test → prod deploy
Monitoring AWS CloudWatch + Sentry Infra metrics + application error tracking

3. System Architecture Overview

3.1 Deployment Architecture

Layer Components
DNS + CDN Route 53 → CloudFront (static assets) → ALB
Load Balancer Application Load Balancer — HTTPS termination, health checks, sticky sessions
App Tier EC2 Auto Scaling Group (min 2, max 10) — Laravel API + React SPA via Nginx + PHP-FPM
Queue Workers Dedicated EC2 worker fleet — Laravel Horizon (Redis-backed); separate from web tier
Data Tier RDS MariaDB 10.11 Multi-AZ (primary + standby); automated snapshots every 4h
Cache Tier ElastiCache Redis 7 cluster mode; sessions + queue + dashboard cache + RBAC permission cache
Storage S3 (ap-south-1) — private bucket; versioning; SSE-KMS; pre-signed URLs (15-min expiry)
Logging CloudWatch Logs — all app logs + audit stream; 3-year retention

3.2 Request Flow

Browser → CloudFront → ALB → Nginx (EC2) → PHP-FPM / Laravel Router → Controller → Service Layer → Repository → MySQL / Redis / S3

Background jobs: Laravel Horizon (Redis) → Job Class → External APIs (VAHAN, Textract, WhatsApp, SMS)

3.3 Laravel Application Structure

Directory Purpose
app/Modules/Lead Lead capture, queue, pipeline — self-contained module
app/Modules/Document Checklist engine, upload, OCR, fraud detection, verification workflow
app/Modules/Pricing Scrap value calculation, override workflow, version snapshots
app/Modules/Payment UTR capture, receipt storage, stage gating, advance trigger
app/Modules/SLA Hourly scheduler, breach detection, auto-reassignment, escalation
app/Modules/Notification WhatsApp / SMS hooks, template renderer, delivery tracking
app/Modules/Dashboard Aggregation queries powering all 8 React dashboard pages
app/Modules/Auth Sanctum tokens, RBAC policies, dynamic permission system, session tracking
app/Jobs/ Async jobs: VahanFetch, TextractOCR, SendWhatsApp, SlaCheck, AssignmentRetry
app/Services/ Shared: AuditLogger, S3Manager, FraudDetector, EligibilityScorer, PermissionService, FieldSyncService

4. Module Breakdown

4.1 Lead Capture Module

Feature Delivery Implementation Notes
Inbound Call Centre Custom Agent UI form → POST /api/leads; all Mariinox fields; VAHAN triggered on reg. no.
Website Form Custom + 3P Public React form → POST /api/leads/web; UTM + IP middleware; OTP pre-validated
WhatsApp Inbound 3rd Party Gupshup/Twilio webhook → POST /api/webhooks/whatsapp → LeadFactory
VAHAN API Auto-Fetch 3rd Party VahanFetchJob dispatched on lead save; auto-populates 11 vehicle fields; manual fallback
OTP Mobile Verify Custom + 3P POST /api/otp/send → SMS gateway; POST /api/otp/verify → mobile_verified = true
UTM / Attribution Custom UTM middleware parses query params + IP geolocation; stored on lead_attribution table
Preliminary Price Estimate Custom Triggered on assignment; Kerb Weight × Metal Rate × Condition Modifier stored on lead

4.2 Assignment & Queue Module

ℹ Note: Status: ~80% finalised (Phase 1). Remaining 20% subject to confirmation — flagged below.

Agent Status Model

Status Condition Assignment Eligibility
Active Bandwidth Available — active lead count < max_capacity Eligible for auto-assignment
Busy Bandwidth not available — at or above max_capacity Skipped by auto-assignment; Supervisor can override manually

Bandwidth (active lead count) = leads in stages: Fresh + Interested + Verbal Commitment assigned to that agent.

Agent Capacity Benchmarks (configurable per agent)

Metric Agent A Agent B Agent C
Calls / Day 30 30 30
Duration / Day (hrs) 5 5 5
Avg Duration / Call (min) 10 10 10
Current Pipeline (max_capacity) 20 25 28
  ↳ Fresh leads 14 18 20
  ↳ Non-Fresh (Interested + Verbal) 6 7 8
  ↳ Follow Up 3 4 3
  ↳ Repeat Attempt 2 3 3
  ↳ Re-assigned 1 0 2

5-Layer Assignment Rules Engine

Layers are evaluated in strict order. The engine stops at the first layer that produces a unique eligible agent.

Layer Rule Criterion Logic & Notes
L1 Duration / No. of Calls Rank eligible (Active) agents by call performance score: total connected call duration ÷ number of genuine attempts in the scoring window (default last 30 days). Highest score assigned first.
L2 Type of Call Match lead call-type to agent specialisation: Fresh leads → agents with higher Fresh conversion; Follow-up → agents with higher Follow-up conversion.
L3 Type of Vehicle Filter agents whose vehicle expertise matches: 4W (car/truck) or 2W (bike). Measured by Lead-to-Pay conversion rate per vehicle type. Key makes tracked: Honda, Maruti, Skoda, TVS (Admin-configurable).
L4 Language Match lead's preferred language (from IP state / chatbot selection) to agent's language profile. Multi-value JSON array e.g. ["hi", "kn", "en"]. Currently confirmed: Hindi.
L5 Manual Assignment Supervisor/Admin manually picks from Active agents list only. Busy agents not shown. Admin can bypass from Escalation Queue view.

Genuine Attempt Qualifier

Condition Classification & Action
Outgoing call rang for ≥ 30 seconds ✅ Genuine Attempt — credited to agent's performance score and call metrics
Outgoing call rang for < 30 seconds ❌ Fake Call — NOT credited; flagged in call_attempts.is_genuine = false; excluded from Leadboard and assignment scoring
The 30-second threshold is stored in system_configs.genuine_call_min_seconds and is Admin-configurable without code change.
⚠️ TBD: Maximum number of call attempts per unique customer-agent pair is to be confirmed before Sprint 2 kickoff.

4.3 Pipeline Module

Stage transitions are gated by: role permission check → mandatory field validation → business rule validation → audit log entry.

4.4 SLA Engine Module

Stage Default SLA Breach Action Job
Fresh 24h Auto-reassign SlaBreachJob (hourly)
Interested 48h Auto-reassign SlaBreachJob (hourly)
Document Initiated 96h Escalate to Supervisor SlaBreachJob (hourly)

5. Database Schema — Core Tables

All tables include: id (ULID PK), created_at, updated_at, deleted_at (soft delete). created_by and updated_by are FK to users.id.

5.1 users

Column Type Constraints Notes
id CHAR(26) PK ULID
name VARCHAR(120) NOT NULL Full name
email VARCHAR(191) UNIQUE, NOT NULL Login identifier
mobile VARCHAR(15) UNIQUE E.164 format
password VARCHAR(255) NOT NULL bcrypt hashed
role ENUM NOT NULL agent | supervisor | verification | finance | logistics | admin
max_capacity TINYINT DEFAULT 10 Max concurrent active leads for callers
is_active BOOLEAN DEFAULT true Soft disable without delete
language VARCHAR(20) DEFAULT 'hi' Preferred language for assignment matching
team_id CHAR(26) FK teams.id Nullable — for team-based reporting

Indexes: email (unique), role, is_active, team_id

5.2 leads

Column Type Constraints Notes
id CHAR(26) PK ULID
lead_number VARCHAR(20) UNIQUE, NOT NULL MRX-YYYYMMDD-NNNN format
source ENUM NOT NULL call_centre | web | whatsapp | chatbot
stage ENUM NOT NULL fresh | interested | verbal_commitment | doc_initiated | doc_verification_pending | doc_verified | scrap_value_paid | assigned_to_logistic | pickup_scheduled | assigned_to_agent | pickup_done
vehicle_type ENUM NOT NULL car | truck | bike | other
deal_type ENUM NULLABLE cod_with_trade | cod_without_trade | noc
case_type ENUM NULLABLE individual | business | death | hypothecation_individual | hypothecation_company | previous_owner
assigned_to CHAR(26) FK users.id Current assigned caller; nullable if in queue
stage_entered_at TIMESTAMP NOT NULL Used for SLA aging calculation
sla_breached BOOLEAN DEFAULT false Set true by SlaBreachJob
eligibility_score TINYINT NULLABLE 0–100 numeric score
estimated_scrap_value DECIMAL(12,2) NULLABLE Calculated on assignment
final_scrap_value DECIMAL(12,2) NULLABLE Confirmed value (may be overridden)
cod_value DECIMAL(12,2) NULLABLE Only for COD With Trade deal type
priority TINYINT DEFAULT 0 0=normal, 1=high, 2=urgent
exceptions_flag JSON NULLABLE Array: hypothecation, duplicate_aadhaar, vahan_mismatch, etc.

Indexes: lead_number, stage, vehicle_type, assigned_to, stage_entered_at, sla_breached, composite (stage+assigned_to)

5.3 lead_vehicle_details

One-to-one with leads. Stores all VAHAN + manual vehicle data. Pricing formula: Scrap Value = Kerb Weight (kg) × Metal Rate (₹/kg) × Condition Modifier

Column Type Notes
lead_id CHAR(26) PK + FK leads.id (1:1)
registration_number VARCHAR(20) SHA-256 hash stored separately in fraud_detection_index
make VARCHAR(60) From VAHAN or manual
model VARCHAR(80)
fuel_type ENUM petrol | diesel | cng | electric | hybrid
manufacture_year YEAR
kerb_weight_kg DECIMAL(8,2) Critical for pricing formula
registration_date DATE
registration_expiry DATE RC validity for eligibility scoring
hypothecation_status ENUM none | hypothecated | cleared | unknown
engine_number VARCHAR(40)
chassis_number VARCHAR(40)
vahan_raw JSON Full raw VAHAN API response for audit

5.4 lead_contacts & 5.5 lead_attribution

Table Key Columns Purpose
lead_contacts lead_id, name, mobile, alternate_mobile, email, pincode, city, state, full_address Customer contact info; mobile = OTP-verified primary
lead_attribution lead_id, utm_source, utm_medium, utm_campaign, utm_term, gclid, fbclid, ip_address, ip_city, ip_state, referrer_url Marketing attribution; IP geolocation via MaxMind GeoLite2

6. Database Schema — Module Tables

Key module-specific tables. Full 22-table ERD finalized in Sprint 1, Week 2.

Table Purpose Key Fields
master_queue All leads pending assignment lead_id, source, vehicle_type, priority, entered_at, assignment_status (pending | assigned | failed), wait_time_minutes
agent_capacity_config Per-agent capacity settings user_id, calls_per_day_target, max_pipeline_size, vehicle_type_expertise JSON, language_skills JSON, call_type_eligibility JSON
call_logs All outbound call records agent_id, lead_id, duration_seconds, is_genuine_attempt, call_type (fresh/followup/reassigned), called_at
lead_timeline Immutable audit log (INSERT-only) lead_id, event_type, from_stage, to_stage, changed_by, metadata JSON, created_at (no updated_at)
documents All uploaded documents lead_id, document_type, s3_key, file_hash_sha256, ocr_extracted JSON, ocr_corrections JSON, verification_status, tampering_flags JSON
document_checklist_configs Required docs per case×deal matrix deal_type, case_type, document_type, is_mandatory, is_active
fraud_detection_index SHA-256 hashes for duplicate detection hash_sha256, document_type, lead_id, first_seen_at
pricing_metal_rates Metal rate history metal_type, rate_per_kg, effective_date, source (manual/api), created_by
pricing_condition_rules Condition modifier rules vehicle_age_from_years, vehicle_age_to_years, condition, modifier_multiplier
pricing_snapshots Versioned pricing ruleset snapshots snapshot_data JSON, version, created_by, restored_from_id
payments Payment records (CRM side only) lead_id, payment_type, amount, utr_number UNIQUE, payment_mode, receipt_s3_key, receipt_hash_sha256, is_manual_review
sla_configurations SLA thresholds per stage stage, threshold_hours, breach_action (auto_reassign/escalate_supervisor), is_active
whatsapp_notifications WhatsApp message log lead_id, stage, template_id, message_id, status (sent/delivered/read/failed), delivery_at, fallback_triggered_at
agent_sessions Session tracking user_id, login_at, logout_at, active_minutes, idle_minutes, device_info
agent_daily_summaries Daily aggregates for Leadboard user_id, date, active_minutes, idle_minutes, leads_touched, genuine_call_count
system_configs Admin-configurable parameters key VARCHAR UNIQUE, value TEXT — genuine_call_min_seconds, queue_backlog_threshold, keyword_priority_list
customer_agent_mappings Returning customer → original agent mobile_hash SHA-256, agent_id, call_attempt_count, last_assigned_at

7. API Design Conventions

7.1 Base URL & Standards

7.2 Core API Endpoints

Method Endpoint Auth / Role Description
POST /api/v1/auth/login Public Returns Sanctum token + user profile
POST /api/v1/auth/logout Auth Revokes current token
POST /api/v1/otp/send Public Sends OTP to mobile via SMS gateway
POST /api/v1/otp/verify Public Validates OTP, returns verified=true
GET /api/v1/leads Agent, Admin, Supervisor Paginated list; scoped to own leads for agent role
POST /api/v1/leads Agent, Admin, Call Centre Create new lead; triggers VAHAN + pricing jobs
GET /api/v1/leads/{id} Role-scoped Full lead detail with timeline, documents, payments
PATCH /api/v1/leads/{id}/stage Role-scoped per stage Stage transition; gated by validation rules
PATCH /api/v1/leads/{id}/assign Admin, Supervisor Manual assignment/reassignment
POST /api/v1/leads/bulk-assign Admin, Supervisor Bulk assignment; array of lead IDs + caller_id
POST /api/v1/leads/{id}/documents Agent, Admin Upload document → S3; triggers TextractJob
GET /api/v1/documents/{id}/url Role-scoped Returns pre-signed S3 URL (15-min expiry); logs access
PATCH /api/v1/documents/{id}/verify Verification Team Mark verified | rejected | request_reupload
GET /api/v1/queue Admin, Supervisor Master queue with wait times, colour coding
POST /api/v1/queue/auto-assign Admin Triggers AssignmentEngine for all pending queue items
GET /api/v1/pricing/calculate Agent, Admin Returns calculated scrap value for given params
POST /api/v1/pricing/override Manager, Admin Request override → approval workflow
POST /api/webhooks/whatsapp HMAC-signed Gupshup/Twilio inbound message + delivery status
POST /api/v1/rbac/permissions/batch Admin Save permission matrix; busts Redis cache
POST /api/v1/rbac/fields/sync Admin FieldSyncService — sync DB columns to fields table

8. Authentication & RBAC

This section covers the complete RBAC implementation: 6 hardcoded pipeline roles plus a fully dynamic field-level permission system supporting any module, any field, and any role without code changes.

8.1 Pipeline Role Definitions

Role Stages Accessible Data Scope Config Access Sensitive Fields
agent Fresh → Doc Verification Pending Own leads only None Aadhaar ✗ PAN ✗ Bank ✗
supervisor All stages (view); Fresh→Doc Verif Pending (action) Team leads Reassign, escalation, priority Aadhaar ✗ PAN ✗ Bank ✗
verification Doc Verification Pending, Doc Verified Assigned verification queue Document review only Aadhaar ✓ PAN ✓ Bank ✓
finance Scrap Value Paid (action) Payment fields + receipts View pricing only Aadhaar ✗ PAN ✗ Bank ✓
logistics Assigned to Logistic → Pickup Done Assigned pickups only None Aadhaar ✗ PAN ✗ Bank ✗
admin All stages All records Full — users, SLA, pricing, RBAC Aadhaar ✓ PAN ✓ Bank ✓

8.2 Dynamic RBAC Architecture — Overview

In addition to the 6 pipeline roles, Mariinox CRM implements a fully dynamic field-level permission system. Admin can configure which roles can view, edit, or are hidden from any field on any module — without code changes or redeployment.

Layer Description
Module A feature area: customers, leads, orders, payments. Each maps to a DB table and a Laravel module folder.
Field A column inside a module's table. Fields are auto-synced from Schema::getColumnListing() and stored in the fields table with a UI label, type, and is_active flag.
Permission A per (role × module × field) record. Stores can_view and can_edit booleans. field_id = NULL means module-level permission.
PermissionService Central service: canAccessField($user, $moduleSlug, $fieldName, $action). Called from controllers, API Resources, Blade directives.
Middleware CheckModuleAccess: validates module-level can_view before the controller runs. Blocks 403 early.
Blade Directive @canField('customers', 'credit_limit', 'view') — renders or hides Blade sections based on field-level permission.
Admin UI Permission matrix table (role × field with View/Edit checkboxes). Saved via AJAX. No code deployment required.
Redis Cache All permissions cached per (role_id × module_id) in Redis with 5-minute TTL. Cache busted on any permission save.

8.3 Dynamic RBAC — Database Schema (Step 1)

Step 1Database Tables
Table Column Type Notes
roles id bigIncrements PK
name string(80) e.g. 'Sales Agent'
slug string(80) unique e.g. agent, finance, admin — lookup key
modules id bigIncrements PK
name string(80) e.g. 'Leads', 'Customers'
slug string(80) unique maps to DB table name
fields id bigIncrements PK
module_id FK → modules.id CASCADE delete
name string(80) DB column name e.g. credit_limit
label string(120) UI display label e.g. 'Credit Limit (₹)'
type enum text | number | select | date | boolean | file
is_active boolean default true Soft-disable without deleting permissions
permissions id bigIncrements PK
role_id FK → roles.id CASCADE delete
module_id FK → modules.id CASCADE delete
field_id FK nullable → fields.id NULL = module-level permission
can_view / can_edit boolean default false UNIQUE(role_id, module_id, field_id)
// Migration snippet — permissions table
Schema::create('permissions', function (Blueprint $table) {
    $table->id();
    $table->foreignId('role_id')->constrained()->cascadeOnDelete();
    $table->foreignId('module_id')->constrained()->cascadeOnDelete();
    $table->foreignId('field_id')->nullable()->constrained()->cascadeOnDelete();
    $table->boolean('can_view')->default(false);
    $table->boolean('can_edit')->default(false);
    $table->unique(['role_id', 'module_id', 'field_id']);
    $table->index(['role_id', 'module_id']);
});

8.4 Eloquent Models & Relationships (Step 2)

Step 2Models & Relationships
// Role.php
class Role extends Model {
    public function permissions(): HasMany {
        return $this->hasMany(Permission::class);
    }
}

// Module.php
class Module extends Model {
    public function fields(): HasMany {
        return $this->hasMany(Field::class);
    }
}

// Field.php
class Field extends Model {
    public function module(): BelongsTo {
        return $this->belongsTo(Module::class);
    }
}

// Permission.php
class Permission extends Model {
    public function role(): BelongsTo    { return $this->belongsTo(Role::class); }
    public function module(): BelongsTo  { return $this->belongsTo(Module::class); }
    public function field(): BelongsTo   { return $this->belongsTo(Field::class); }
}

8.5 FieldSyncService (Step 3)

Step 3FieldSyncService — Auto-sync DB Columns to fields Table
// App\Services\FieldSyncService.php
class FieldSyncService {
    public function sync(string $moduleSlug): array {
        $module = Module::where('slug', $moduleSlug)->firstOrFail();
        $columns = Schema::getColumnListing($moduleSlug);

        $synced = 0; $skipped = 0;
        foreach ($columns as $column) {
            $existing = Field::where('module_id', $module->id)
                             ->where('name', $column)->first();
            if ($existing) {
                // Preserve manual label/type overrides
                $skipped++; continue;
            }
            Field::create([
                'module_id' => $module->id,
                'name'      => $column,
                'label'     => Str::title(str_replace('_', ' ', $column)),
                'type'      => $this->inferType($column),
                'is_active' => true,
            ]);
            $synced++;
        }
        return ['synced' => $synced, 'skipped' => $skipped];
    }

    private function inferType(string $column): string {
        if (str_ends_with($column, '_at'))    return 'date';
        if (str_ends_with($column, '_id'))    return 'number';
        if (str_contains($column, 'amount') || str_contains($column, 'value')) return 'number';
        if (str_contains($column, 'is_') || str_contains($column, 'has_')) return 'boolean';
        return 'text';
    }
}

8.6 PermissionService (Step 4)

Step 4PermissionService — Central Permission Resolver
// App\Services\PermissionService.php
class PermissionService {
    public function canAccessField(User $user, string $moduleSlug, string $fieldName, string $action = 'view'): bool {
        $role = $user->role;
        if (!$role) return false;

        // Admin always has full access
        if ($role->slug === 'admin') return true;

        $map = $this->getPermissionMap($role->id, $moduleSlug);

        // Check module-level permission first (field_id = null)
        if (!($map['__module__']['can_view'] ?? false)) return false;

        // Check field-level permission
        $fieldPerms = $map[$fieldName] ?? null;
        if (!$fieldPerms) return false;

        return match($action) {
            'view' => (bool) $fieldPerms['can_view'],
            'edit' => (bool) $fieldPerms['can_edit'],
            default => false,
        };
    }

    private function getPermissionMap(int $roleId, string $moduleSlug): array {
        $cacheKey = "rbac:role:{$roleId}:module:{$moduleSlug}";

        return Cache::remember($cacheKey, now()->addMinutes(5), function () use ($roleId, $moduleSlug) {
            $module = Module::where('slug', $moduleSlug)->firstOrFail();
            $perms  = Permission::with('field')
                          ->where('role_id', $roleId)
                          ->where('module_id', $module->id)
                          ->get();
            $map = [];
            foreach ($perms as $perm) {
                $key = $perm->field_id ? $perm->field->name : '__module__';
                $map[$key] = ['can_view' => $perm->can_view, 'can_edit' => $perm->can_edit];
            }
            return $map;
        });
    }

    public function bustCache(int $roleId, string $moduleSlug): void {
        Cache::forget("rbac:role:{$roleId}:module:{$moduleSlug}");
    }

    // Bulk filter: removes inaccessible fields from an array
    public function filterFields(User $user, string $moduleSlug, array $data): array {
        return array_filter($data, function ($value, $key) use ($user, $moduleSlug) {
            return $this->canAccessField($user, $moduleSlug, $key, 'view');
        }, ARRAY_FILTER_USE_BOTH);
    }
}

8.7 CheckModuleAccess Middleware (Step 5)

Step 5Middleware
// App\Http\Middleware\CheckModuleAccess.php
class CheckModuleAccess {
    public function handle(Request $request, Closure $next, string $moduleSlug): Response {
        $user = $request->user();
        if (!$user) return response()->json(['error' => 'Unauthenticated'], 401);

        $service = app(PermissionService::class);
        if (!$service->canAccessField($user, $moduleSlug, '__module__', 'view')) {
            return response()->json(['error' => 'Access denied to module: ' . $moduleSlug], 403);
        }
        return $next($request);
    }
}

// routes/api.php — usage
Route::middleware(['auth:sanctum', 'module:leads'])->group(function () {
    Route::apiResource('leads', LeadController::class);
});
Route::middleware(['auth:sanctum', 'module:customers'])->group(function () {
    Route::apiResource('customers', CustomerController::class);
});

8.8 Blade Directive (Step 6)

Step 6canField() Blade Directive & Helper
// AppServiceProvider.php — register Blade directive
Blade::if('canField', function (string $module, string $field, string $action = 'view') {
    $user    = auth()->user();
    $service = app(PermissionService::class);
    return $user && $service->canAccessField($user, $module, $field, $action);
});

// Global helper (Helpers.php)
function canField(string $module, string $field, string $action = 'view'): bool {
    $user = auth()->user();
    if (!$user) return false;
    return app(PermissionService::class)->canAccessField($user, $module, $field, $action);
}

// Usage in Blade templates

                    @canField('customers', 'credit_limit', 'view')
                    
Credit Limit: {{ $customer->credit_limit }}
@endcanField @canField('leads', 'aadhaar_number', 'edit') @else **** **** **** @endcanField

8.9 Admin Permission Matrix UI (Step 7)

Step 7Admin Panel — Role Permission Matrix

React component inside Admin Command Centre. Role + Module selectors load the field matrix dynamically. All toggles save via AJAX to the permissions API.

UI Component Behaviour API Call Notes
Role Selector (dropdown) Loads all roles GET /rbac/roles Default: first role alphabetically
Module Selector (dropdown) Loads all modules GET /rbac/modules Triggers matrix refresh
Field Matrix Table (Field | View ☑ | Edit ☑) Pre-filled from DB GET /rbac/permissions?role_id=&module_id= Inactive fields greyed out
View checkbox toggle AJAX on change POST /rbac/permissions Unchecking View auto-unchecks Edit
Edit checkbox toggle AJAX on change POST /rbac/permissions Checking Edit auto-checks View
Save All button Batch save changed rows PUT /rbac/permissions/batch Busts Redis cache for role+module on save
Sync Fields button (per module) Triggers FieldSyncService POST /rbac/fields/sync Shows new columns from DB; does not delete existing

Example Permission Matrix (leads module)

Field Name Label agent — View agent — Edit verification — View admin — View
lead_number Lead Number
customer_name Customer Name
aadhaar_number Aadhaar Number
pan_number PAN Number
bank_account Bank Account
credit_limit Credit Limit (₹)
final_scrap_value Final Scrap Value

8.10 Controller Integration (Step 8)

Step 8Controller & API Resource — Dynamic Field Filtering
// LeadController.php — show()
public function show(Lead $lead): JsonResponse {
    $this->authorize('view', $lead); // Laravel Policy (pipeline role gate)

    $data = $lead->toArray();

    // Dynamic RBAC: strip fields user cannot view
    $filtered = app(PermissionService::class)->filterFields(auth()->user(), 'leads', $data);

    return response()->json(['data' => $filtered]);
}

// LeadController.php — update()
public function update(UpdateLeadRequest $request, Lead $lead): JsonResponse {
    $this->authorize('update', $lead);

    // Only allow fields where can_edit = true
    $allowedFields = array_filter($request->all(), function ($key) {
        return canField('leads', $key, 'edit');
    }, ARRAY_FILTER_USE_KEY);

    $lead->update($allowedFields);
    return response()->json(['data' => $lead->fresh()]);
}

8.11 API Response Protection (Step 9)

Step 9API Protection — Restricted Fields Removed Before JSON Response
// BaseApiController.php
protected function permissionFilteredResponse(User $user, string $module, array $data): array {
    return app(PermissionService::class)->filterFields($user, $module, $data);
}

// For collection responses (e.g. GET /leads index)
protected function filterCollection(User $user, string $module, Collection $items): array {
    return $items->map(fn($item) =>
        $this->permissionFilteredResponse($user, $module, $item->toArray())
    )->toArray();
}

// Pipeline: Controller → PermissionService::filterFields → JsonResponse
// No restricted field ever reaches the wire — server-side enforcement only

8.12 Test Cases (Step 10)

Step 10Testing — Role × Field × Action Matrix
Test ID Role Module Field Action Expected
TC-RBAC-01 admin leads aadhaar_number view true (admin bypass)
TC-RBAC-02 agent leads aadhaar_number view false (masked)
TC-RBAC-03 agent leads customer_name view true
TC-RBAC-04 agent leads credit_limit edit false (view-only)
TC-RBAC-05 finance payments bank_account view true
TC-RBAC-06 logistics payments bank_account view false
TC-RBAC-07 supervisor leads assigned_to edit true (reassign allowed)
TC-RBAC-08 verification leads pan_number view true
TC-RBAC-09 agent customers (module-level) view false (module blocked)
TC-RBAC-10 admin customers credit_limit edit true (admin bypass)

8.13 Advanced Enhancements

Enhancement Implementation Notes
Redis Permission Cache Cache::remember() with 5-min TTL per role×module. Busted automatically on permission save. php artisan rbac:clear-cache for emergency flush.
Read-Only Field Mode can_view=true, can_edit=false. API Resource returns value; Controller update() rejects writes with HTTP 403 + 'field_name is read-only for your role'.
Field Groups Add field_group column to fields table (e.g. 'financial', 'contact', 'vehicle'). Admin grants/revokes entire group at once. FieldSyncService auto-assigns group by naming convention.
Dynamic Form Builder GET /api/v1/rbac/form-schema?module=leads returns only fields current user can view/edit, with type + label + is_editable flag. React renders inputs dynamically from this schema.
Hidden vs Masked Extend with display_mode enum: hidden (field removed), masked (returned as '****'), visible. Useful for showing agents data exists but is restricted.
RBAC Audit Trail rbac_audit_logs table: who changed what permission, from/to values, timestamp. Streamed to CloudWatch.

8.14 Laravel Policy Layer — Pipeline Stage Gates

// LeadPolicy.php
public function view(User $user, Lead $lead): bool {
    if ($user->role->slug === 'agent')      return $lead->assigned_to === $user->id;
    if ($user->role->slug === 'supervisor') return $lead->team_id === $user->team_id;
    return in_array($user->role->slug, ['admin', 'verification', 'finance', 'logistics']);
}

public function transition(User $user, Lead $lead, string $toStage): bool {
    $allowedRoles = PipelineConfig::stageAllowedRoles($lead->vehicle_type, $toStage);
    return in_array($user->role->slug, $allowedRoles);
}

8.15 Session & Token Management

9. Third-Party Integrations

Service Provider Job / Hook Implementation Notes
VAHAN API MoRTH / NIC VahanFetchJob Dispatched async on lead save; 3 retries with 30s backoff; manual fallback if all fail
OCR AWS Textract TextractOCRJob Triggered on S3 upload; confidence < 85% flags field amber; corrections logged
SMS Gateway Gupshup / MSG91 SendSmsJob OTP delivery + WhatsApp fallback (5-min non-delivery trigger)
WhatsApp Gupshup / Twilio SendWhatsAppJob Template messages on every stage change; delivery status via webhook → lead_timeline
Payment GW API Team N/A (CRM side) CRM only stores UTR + receipt; disbursement handled by API Integration team
AI KYC (Phase 2) IDfy / Signzy KycVerifyJob Name fuzzy-match, format validation; photo matching Phase 2+
STT (Phase 2) AWS Transcribe / GCP Speech TranscribeCallJob Confirmation required by Week 10; Hindi + Kannada + Hinglish support
Metal Rate (Phase 2) LME / Metal Price API MetalRateSyncJob Scheduled daily; fallback to last known rate; Admin notified on failure
CSAT (Phase 3) WhatsApp survey CsatSurveyJob Triggered 2h after Pickup Done; response captured via webhook

10. Infrastructure & DevOps

10.1 AWS Resource Sizing — Phase 1 Launch

Resource Spec Notes
EC2 Web (ASG) t3.medium × 2 (min) Scale to 10× on CPU > 60%; Nginx + PHP-FPM; 8 vCPU / 32GB at peak
EC2 Workers (ASG) t3.small × 2 (min) Laravel Horizon; separate from web tier; scales on queue depth
RDS MariaDB db.t3.medium Multi-AZ Primary + standby; automated snapshots every 4h; 7-day retention
ElastiCache Redis cache.t3.small Sessions + queue + RBAC cache; 2-node cluster for HA
S3 Buckets Private + versioning mariinox-crm-docs-prod (ap-south-1); CRR to ap-south-2; SSE-KMS; pre-signed URLs only
ALB 1 ALB HTTPS termination; ACM certificate; HTTP→HTTPS redirect; sticky sessions
CloudFront 1 distribution React SPA static assets; API calls bypass CDN
CloudWatch Logs + Alarms App logs + audit stream + infra metrics; 3yr log retention; SNS alerts on P1 alarms

10.2 CI/CD Pipeline

GitHub Actions → Build PHP + React → Run PHPUnit + Jest → Build Docker image → Push to ECR → Deploy to Staging → Smoke test → Manual approval gate → Deploy to Production

10.3 Security Controls

11. Sprint Plan — Phase 1 (Weeks 1–11)

577
Person-Days
1,154
Story Points
8
Team Members
11
Weeks
6
Sprints

11.0 Team Composition

Team / Function Head Count Person-Days Story Points
Product / BA / Project Management 1 PM 21 42
DevOps / Infrastructure 1 DevOps Engineer 20 40
Backend Team (Laravel) 1 Tech Lead + 2 BEs 272 544
Frontend Team (React + TypeScript) 2 Frontend Engineers 210 420
QA / Testing 1 QA Engineer 36 72
UI/UX Design 1 Designer 18 36
TOTAL 8 people 577 PD 1,154 SP

Sprint Schedule Overview

Sprint Weeks Focus Primary Modules PD SP
S1 1–2 Foundation Project Setup, DB Design, UI/UX Wireframes 56 112
S2 3–4 Auth + Capture Auth + RBAC, Lead Capture all channels, VAHAN, OTP 40 80
S3 5–6 Assignment + Pipeline Assignment Engine, Pipeline State Machine, Deal/Case/Checklist 78 156
S4 7–8 Doc + Pricing Document Engine, Verification Workflow, Pricing Engine, Payment Triggers 82 164
S5 9–10 SLA + Notifs + Dashboards SLA Engine, Notifications, 8 Dashboards, Session Tracking, Audit Logs, QA 321 642
S6 11 UAT + Go-Live UAT, bug fixes, production deployment, hypercare start
TOTAL 577 PD 1,154 SP

Sprint 1 — Weeks 1–2: Foundation Setup | 56 PD | 112 SP

Module: Project Setup & Infrastructure | 20 PD | 40 SP | DevOps + Backend

Task Owner PD SP Acceptance Criteria Dependency
Provision AWS: EC2 ASG (min 2), RDS Multi-AZ, Redis, ALB, S3 (versioning+SSE-KMS), CloudWatch, ACM, WAF DevOps 4 8 All services healthy; HTTPS reachable AWS account access
VPC design: public (ALB), private (app+worker), isolated (RDS); NAT Gateway; security groups DevOps 2 5 No public DB/app exposure
Dev + Staging envs: separate RDS instances, shared Redis with DB index isolation, S3 bucket per env DevOps 2 5 Devs deploy to staging via CI in < 10 min Row 1
GitHub Actions CI/CD: build → PHPUnit → Jest → Docker → ECR push → staging deploy → smoke test → prod gate DevOps+TL 3 8 Green pipeline on main branch merge Row 3
Laravel 11 skeleton: app/Modules/* structure, Sanctum config, Horizon config, base .env via AWS Secrets Manager Tech Lead 3 5 php artisan route:list returns auth + health routes Row 1
React 18+TS+Vite scaffold: React Router v6, Tailwind, React Query v5, Zustand, auth context + sidebar layout FE Lead 3 5 Login page renders; API base URL configurable per env
S3 integration: Laravel Filesystem S3 disk + local fallback; pre-signed URL helper with 15-min expiry Tech Lead 3 4 Upload to S3 and retrieve via pre-signed URL on staging Row 1
Module Total 20 40

Module: Database Design & Architecture | 18 PD | 36 SP | Backend

Task Owner PD SP Acceptance Criteria Dependency
Finalise ERD: all 22 tables including users, leads, lead_vehicle_details, lead_contacts, lead_attribution, master_queue, assignment_rules, agent_capacity_config, call_logs, customer_agent_mappings, lead_timeline, documents, document_checklist_configs, fraud_detection_index, pricing tables, payments, sla_configurations, whatsapp_notifications, agent_sessions, system_configs Tech Lead 4 8 ERD signed off by Mariinox by Day 10 Business rules locked
Write all Laravel migration files: column definitions, ULID PKs, FK constraints, nullable/default, ENUM types BE1 5 8 php artisan migrate:fresh runs without errors ERD approval
Define all indexes: composite (stage, assigned_to), (assignment_status, priority, entered_at), (document_type, hash_sha256) Tech Lead 3 5 EXPLAIN SELECT shows index on all dashboard queries Migrations done
Eloquent models: relationships, casts, hidden fields (aadhaar/pan/bank masked), SoftDeletes, ULID trait BE1 3 5 Model factories + seeders for all 22 tables Migrations done
DB-level immutability: REVOKE UPDATE, DELETE on lead_timeline FROM app_db_user; GRANT INSERT only; test assertion Tech Lead 2 5 Direct UPDATE on lead_timeline returns DB error 1142
DB seeding: system_configs, pipeline_stages (11+10 stage definitions), default SLA configs BE1 1 5 Fresh migrate + seed completes in < 2 min Models done
Module Total 18 36
✅ Sprint 1 Milestone: AWS live + CI/CD green + ERD signed off + wireframes approved + business rules locked | 56 PD | 112 SP

Sprint 2 — Weeks 3–4: Auth + RBAC + Lead Capture | 40 PD | 80 SP

Module: Auth + RBAC | 16 PD | 32 SP | Backend

Task Owner PD SP Acceptance Criteria Dependency
Sanctum SPA auth: POST /login → HttpOnly token; POST /logout; POST /heartbeat (5-min extend); expiry 8h BE1 2 3 Token issued; logout clears cookie; 401 on expired token Sprint 1 done
User management CRUD (Admin): create/edit, assign role, max_capacity, language_skills, vehicle_type_expertise JSON BE1 2 5 Admin creates all 6 role types; inactive user cannot login DB migrations
Laravel Policy classes (6): LeadPolicy, DocumentPolicy, PricingPolicy, QueuePolicy, PaymentPolicy, DashboardPolicy Tech Lead 3 8 403 on all 47 endpoint × role mismatch combos (unit tested) User model
Field-level masking: Aadhaar, PAN, bank_account hidden in API Resource transformers for agent + logistics roles BE1 2 5 Agent endpoint returns masked '****' for sensitive fields Policies done
Agent session: heartbeat → active_minutes++; 10-min gap = idle; logout updates logout_at BE1 2 3 Session idle flag set after 10-min no-ping DB migrations
Frontend: Login page, role-based sidebar, protected route HOC, auto-refresh on 401, session timeout warning at 7h FE1 3 5 Agent sees only agent sidebar items; admin sees all Sanctum done
Admin user management UI: user list, create/edit form, role selector, capacity config, activate/deactivate toggle FE1 2 3 Admin manages users without backend console access User CRUD API
Module Total 16 32
✅ Sprint 2 Milestone: Lead capture live on staging (all 3 channels); VAHAN tested; RBAC enforced on all endpoints | 38 PD | 84 SP

Sprint 3 — Weeks 5–6: Assignment Engine + Pipeline + Checklist | 78 PD | 156 SP

Module: Assignment Engine | 32 PD | 64 SP | Backend (heavy logic)

Task Owner PD SP Acceptance Criteria Dependency
master_queue management: all leads enter on creation; priority sort (priority DESC, entered_at ASC); backlog alert Tech Lead 2 5 New lead in queue within 1s; backlog alert fires Lead model
Agent capacity engine: live active_lead_count per agent; compare vs max_capacity → Active/Busy; Redis cached BE1 2 5 Busy agent skipped by engine; cache updates < 500ms Redis setup
5-layer assignment rules engine (L1–L5): call score → call type specialisation → vehicle expertise → language match → manual fallback Tech Lead 5 13 All 5 layers pass 20+ unit test cases; correct agent selected agent_capacity_config seeded
Genuine attempt qualifier: call_logs.is_genuine_attempt = (duration_seconds >= genuine_call_min_seconds) BE1 2 3 Calls < 30s not counted in performance score call_logs model
AssignmentRetryJob: Scheduler every 15 min; re-evaluates pending queue; triggered on stage change via Observer BE1 2 5 Queue re-evaluated < 1 min after stage change Scheduler config
Customer-agent mapping: SHA-256(mobile) → preferential re-assign to original agent if Active BE2 2 5 Returning customer re-assigned to same agent if Active Assignment engine
Manual assign/reassign API: only Active agents in eligibles; Admin can override Busy from Escalation Queue BE1 2 5 Reassignment entry has old agent + new agent + reason code Timeline writer
Bulk assign: POST /api/v1/leads/bulk-assign; validates capacity per lead; partial success response BE2 2 3 50 leads bulk-assigned in < 3s Assign API
Frontend — Master Queue Dashboard: wait-time sorted list, red badge >2h, Assign button, Auto-Assign All, 60s refresh FE1 3 8 Queue shows correct wait times; badges accurate Queue API
Frontend — Manual assign modal: status dot (Active/Busy), capacity bar, today's calls; Busy agents greyed-out FE2 2 5 Busy agents visible but not selectable Queue dashboard
Module Total 26 60
✅ Sprint 3 Milestone: Assignment engine on staging; pipeline transitions gated for all 6 roles; checklist engine for all 18 case×deal combos | 66 PD | 143 SP

Sprint 4 — Weeks 7–8: Document Engine + Pricing + Payments | 82 PD | 164 SP

Module: Document Engine | 36 PD | 72 SP | Backend + Frontend

Task Owner PD SP Acceptance Criteria Dependency
S3 upload flow: pre-signed POST URL → client direct upload → S3 event → document record created; max 10MB; PDF/JPG/PNG/HEIC BE1 3 8 File in S3; document record with s3_key created S3 setup
File integrity: SHA-256 hash server-side on upload; re-verified on every pre-signed GET URL; mismatch = tampering flag BE1 2 5 Modified file hash mismatch detected on next access S3 upload
AWS Textract OCR: triggered post-upload; key-value pairs mapped to lead fields; confidence score per field; < 85% = amber BE2 4 8 RC upload populates vehicle fields with confidence scores AWS Textract access
SHA-256 fraud detection: document number hash checked against fraud_detection_index; duplicate → Exceptions Queue BE2 3 5 Duplicate Aadhaar triggers exception within 1s Fraud index table
Digital consent capture (immutable): timestamp + IP + agent ID + consent text version in lead_consents; REVOKE UPDATE/DELETE BE1 2 3 Consent record cannot be edited or deleted by any role Timeline writer
Document Access Log: every pre-signed GET URL generation writes to document_access_logs; streamed to CloudWatch BE1 1 2 Log entry on every URL generation; admin can view log S3 upload
Combined PDF: all verified docs merged into single PDF; stored in S3 combined/ prefix; link from lead detail BE2 2 3 Combined PDF downloadable from lead detail view Verification done
Frontend — Document upload UI: drag-and-drop per doc type; HEIC preview; OCR result panel with amber fields; correction input FE1 4 8 Agent uploads, views OCR, corrects amber fields end-to-end Upload + OCR BE done
Frontend — In-browser document viewer: PDF in iframe; image zoom; no download button; expired URL shows re-load prompt FE2 2 5 Document viewable in-browser; direct download prevented Pre-signed URL API
Module Total 27 55
✅ Sprint 4 Milestone: Document upload → OCR → verification → payment flow end-to-end on staging; pricing calculating correctly; payment gating enforced | 73 PD | 148 SP

Sprint 5 — Weeks 9–10: SLA + Notifications + Dashboards + QA | ~220 PD | ~430 SP

All 8 Dashboards Summary | 130 PD | 260 SP

Dashboard Owner PD SP Key Components Acceptance Criteria
Agent Day Briefing FE1+BE1 18 36 Day-at-a-glance strip; Priority Stack (5-item todo); Today's Schedule with Call buttons; personal conversion vs team avg All tasks in correct priority order on login
Admin Command Centre FE2+BE2 22 44 Zone 1: Pipeline Health; Zone 2: Team Health (per-agent live card); Zone 3: Action Required (3 alert cards); 60s auto-refresh All 3 zones populate with live data
Team Performance / Leadboard FE1+BE1 18 36 Date range filter; Call Metrics table; Pipeline Conversion table; sortable columns; top performer highlight All columns sortable; top performer visually distinct
SLA & Escalations FE2+BE1 14 28 3 tabs: Escalation Queue; Caller Capacity; SLA Rules Reference Escalation queue shows leads with overage in hours
Master Queue Dashboard FE1+BE2 14 28 Wait-time sorted; red badge >2h; Assign button; Auto-Assign All; capacity panel; 60s refresh Queue sorted correctly; modal shows Active agents only
Pipeline Analytics FE2+BE2 18 36 Source Conversion Quality table; Lead Ageing Distribution bar chart; weekly call volume chart Charts render correctly for selected date range
Agent Personal Performance FE1+BE1 16 32 KPI strip; conversion funnel with drop-off %; incentive tracker (target vs actual, projected month-end) Incentive estimate matches manual calculation
Notifications UI FE2+BE2 10 20 Bell icon with unread count; dropdown list; mark-as-read; toast alerts for SLA breach / new assignment Unread count accurate; toast appears on SLA breach
Module Total 130 260
✅ Sprint 5 Milestone: All 8 dashboards live; SLA engine running; WhatsApp on all 11 stage changes; full QA suite green; load test passed | ~220 PD | ~430 SP

Sprint 6 — Week 11: UAT & Production Go-Live

Activity Detail Owner Exit Criteria
Mariinox UAT All 6 roles tested across 100+ test scenarios; test case library provided by PM in Week 10 Mariinox + PM 0 critical, 0 high bugs open
Bug triage + fixes Critical/High fixed within 48h; Medium scheduled post go-live; all fixes verified on staging TL + BE1 UAT sign-off from Mariinox
Production deployment DB migration on Mariinox AWS account; Docker image to prod EC2 ASG; CloudWatch alarms active; rollback plan ready DevOps + TL Smoke test passes on prod
Go-live sign-off Written sign-off from Mariinox stakeholder; M2 payment milestone triggered (30% of contract) PM + Mariinox M2 invoice issued
Hypercare begins Dedicated on-call engineer; business hours; critical SLA = 4h response + 24h fix; daily status call for 2 weeks BE1 Hypercare runbook active
✅ Sprint 6 Milestone: Phase 1 Production Go-Live | M2 payment triggered

12. Sprint Plan — Phase 2 (Weeks 12–18) | 60 PD | 120 SP

Goal: Live metal rate feed, logistics CRM sync, government portal CRM layer, STT if confirmed by Week 10.
Weeks Module PD SP Prerequisite Acceptance Criteria
12–14 Live metal rate feed (MetalRateSyncJob daily; admin-confirmed API source; fallback to last known rate) 8 16 Metal rate API provider + credentials by Week 12 Rate auto-updates daily; fallback fires on failure
12–14 Logistics webhook receivers: Pickup Scheduled / Agent Assigned / Pickup Done → auto-stage progression in CRM 12 24 Logistics portal webhook event schema by Week 11 Stage advances on webhook receipt within 5s
12–14 STT integration (AWS Transcribe / GCP Speech): Hindi + Kannada + Hinglish; call recording → transcript → note appended 12 24 STT scope decision locked by Week 10 Transcript appended to lead note within 5 min
15–17 Government portal CRM layer: Ops UI to initiate scrapping application, poll status, capture CoD number; SLA escalation if overdue 14 28 Govt portal API docs by Week 12 CoD number captured; SLA escalation fires if overdue
17–18 Phase 2 UAT + full regression (Phase 1+2) + production deployment + hypercare 14 28 Logistics + Govt portal teams available M3 sign-off; 0 critical bugs open
TOTAL 60 PD 120 SP
✅ Phase 2 Milestone: Go-Live ✅ Week 18 | M3 payment triggered

13. Sprint Plan — Phase 3 (Weeks 19–23) | 84 PD | 168 SP

Goal: Full analytics, incentive engine, CSAT, and complete project handover.
Weeks Module PD SP Owner Acceptance Criteria
19–22 End-to-end conversion funnel (Lead → CoD); CPA per UTM source/campaign; source quality analytics dashboard 22 44 BE + FE Funnel chart renders with correct drop-off per stage
19–22 Agent incentive calculation engine: configurable rules + exportable monthly report + projected month-end figure 12 24 BE Incentive report matches manual calculation
19–22 CSAT: WhatsApp survey 2h post Pickup Done; response captured via webhook; aggregated score on Management KPI dashboard 10 20 BE + FE CSAT survey fires 2h post Pickup Done; responses aggregated
19–22 Management KPI dashboard: conversion rate, turnaround, OCR accuracy %, on-time pickup %, CPA by channel, CSAT, uptime 12 24 FE + BE All KPI tiles show live data from production
19–22 SLA adherence trend reporting; QA Phase 3 (regression + UAT re-run) 12 24 QA + BE QA suite green; load test re-run passed
23 Final handover: Git source, API docs (OpenAPI 3.0), data dictionary, AWS infra runbook, role-wise user manuals (6 roles), all credentials transferred 16 32 PM + TL All docs delivered; prod on Mariinox-owned AWS; M4 triggered
TOTAL 84 PD 168 SP
✅ Phase 3 Milestone: Final Project Handover ✅ Week 23 | M4 payment triggered

Grand Total — All Phases: 577 + 60 + 84 = 721 Person-Days | 1,154 + 120 + 168 = 1,442 Story Points

14. Non-Functional Requirements

Requirement Target Status How Achieved
Uptime 99.5% Achievable EC2 ASG + RDS Multi-AZ + ALB health checks
Page load P95 < 2s Achievable Redis cache, DB indexing, CloudFront CDN, load tested Week 9
Lead volume 50,000+/month Achievable Horizontal scaling; separate web + worker + DB tiers
Concurrent users 50+ min Achievable Stateless API + Redis sessions
RPO 4 hours Achievable RDS automated snapshots every 4h
RTO 8 hours Achievable Tested restore runbook delivered at handover
Data residency India only Fully met All AWS resources in ap-south-1; S3 CRR to ap-south-2 only
Encryption at rest AES-256 Fully met S3 SSE-KMS; RDS encryption at rest; Redis TLS
TLS TLS 1.2+ Fully met ACM cert on ALB; HTTP → HTTPS redirect; HSTS
Aadhaar UIDAI Compliant Fully met No raw Aadhaar in logs; field-level masking; legal review before go-live
Mobile responsive Web + tablet Partial React Tailwind responsive; native iOS/Android separate scope
Audit retention 3 years Fully met CloudWatch Logs 3-year policy; immutable DB table

15. Risk Register

Risk Likelihood Impact Mitigation
Business rules not finalised by Week 2 High High Vendor provides rules template in Week 1; every week delay = direct timeline slip
Laravel performance at 50K leads/month Medium High Infra designed for horizontal scale from Week 1; load tested 2× peak in Week 9
VAHAN API unavailability / rate limits Medium Medium Manual entry fallback from day one; retry queue; CRM fully operable without VAHAN
Scope creep — new features mid-sprint High High Formal change request process from contract signing; written approval + revised estimate required
Aadhaar UIDAI compliance gap Medium High AES-256 at rest; no raw Aadhaar in logs; field-level masking; Mariinox legal review mandatory before Phase 1 go-live
OCR accuracy below 92% target Medium Medium Manual correction workflow always available; AWS Textract performs well on Indian IDs; thresholds tuned post go-live
WhatsApp template pre-approval delay Medium Medium Template submission begins Week 5 (Meta 1–7 days); SMS fallback active from Phase 1 go-live
Logistics / Portal team API delays Medium Medium CRM logistics sync built against mock API in Phase 1; Phase 1 go-live not blocked
VAHAN credentials delayed past Week 2 Medium Medium VAHAN deferred to Phase 2; manual vehicle entry only in Phase 1
WhatsApp Business API account not ready by Week 7 Medium Medium Notifications stubbed in Phase 1 dev; live testing and template submission delayed only

Legend: Red = High   Orange = Medium — All risks reviewed at each sprint retrospective.

Mariinox CRM & Scrap Operations Platform — Technical Architecture Document v1.0 | April 2026 | Confidential
Laravel 11 + React 18 + MySQL 8 + AWS ap-south-1 | 721 PD | 1,442 SP