Repair Requests Backend Documentation
Repair Requests — Backend Documentation
1. Backend Scope and Boundaries
The repair request backend owns:
- customer-facing ticket creation, listing, retrieval, and self-cancellation
- admin-facing ticket management: status progression, collection method update, notes management
- push notification dispatch on every admin status change
- no payment logic (repair is free)
- no technician assignment
- no duplicate-ticket prevention
The backend does not manage repair scheduling, technician routing, or part sourcing.
2. Module Composition
RepairModule composes:
RepairCustomerModule— customer controller + serviceRepairAdminModule— admin controller + service
NotificationsService is injected directly in both leaf services. NotificationsModule is @Global() so no explicit module import is needed.
3. Data Model (Drizzle / PostgreSQL)
repair_ticket
id serial primary keypublic_id uuid not null unique— UUID7, generated at creationticket_number varchar(20) not null unique— format:TR-YYYYMMDD-XXXXX(5 uppercase alphanumeric suffix)user_id uuid not null → customers(id) ON DELETE CASCADEorder_id integer nullable → orders(id) ON DELETE SET NULLstatus repair_status not null default 'request_received'collection_method repair_collection_method not null default 'pickup'source_type repair_source_type not null default 'direct_booking'shared_description text nullableadmin_notes text nullableestimated_delivery_at timestamptz nullable- 8 status timestamp columns:
requested_at,picked_up_at,under_repair_at,repaired_at,packed_at,delivered_at,cancelled_at,created_at,updated_at - inline shipping fields:
shipping_address_line_1,shipping_address_line_2,shipping_city,shipping_state,shipping_postal_code,shipping_district,shipping_landmark,shipping_mobile_num,shipping_latitude,shipping_longitude
repair_ticket_device
id serial primary keyrepair_ticket_id integer not null → repair_ticket(id) ON DELETE CASCADEbrand_id integer nullable → brands(id) ON DELETE SET NULLcustom_brand_name varchar(120) nullablelaptop_model varchar(200) not nullproblem_description text nullableorder_item_id integer nullable → order_items(id) ON DELETE SET NULLCHECK (brand_id IS NOT NULL OR custom_brand_name IS NOT NULL)— enforced at DB level
repair_ticket_status_history
id serial primary keyrepair_ticket_id integer not null → repair_ticket(id) ON DELETE CASCADEstatus repair_status not nullactor_type repair_actor_type not null—customer | admin | systemnotes text nullablecreated_at timestamptz— append-only; NOupdated_atcolumn
Enums
| Enum | Values |
|---|---|
repair_status | request_received, picked_up, under_repair, repaired, packed, delivered, cancelled |
repair_collection_method | pickup, dropoff |
repair_actor_type | customer, admin, system |
repair_source_type | direct_booking, support_ticket, order_linked |
4. Status Machine
request_received → picked_up → under_repair → repaired → packed → delivered (terminal)
└────────────────────────────────────────────────→ cancelled (terminal)All non-terminal statuses may transition to cancelled. Admin drives all transitions except self-cancellation (customer may cancel only from request_received).
Each status transition:
- Updates
repair_ticket.statusand the corresponding timestamp column (in oneUPDATE). - Inserts a row in
repair_ticket_status_historywithactor_type = 'admin'. - Fires a push notification to the customer (fire-and-forget via
void).
Both the UPDATE and the INSERT execute inside a single DB transaction. The notification is dispatched after the transaction completes, not inside it.
Status → Timestamp Column Mapping
| Status | Column |
|---|---|
picked_up | picked_up_at |
under_repair | under_repair_at |
repaired | repaired_at |
packed | packed_at |
delivered | delivered_at |
cancelled | cancelled_at |
5. Notifications
All notifications use NotificationsService.sendToUser() with type: "transactional", priority: "high", inAppTargets: ["mobile_in_app", "web_in_app"], pushTargets: ["mobile_push"].
| Trigger | Title | Body |
|---|---|---|
request_received (creation) | "Repair Request Received" | "Your repair request has been received. Ticket #<number>." |
picked_up | "Laptop Picked Up" | "Your laptop has been picked up for repair." |
under_repair | "Repair In Progress" | "Your laptop is now being repaired by our technicians." |
repaired | "Repair Completed" | "Your laptop repair is complete and is being prepared for delivery." |
packed | "Out for Delivery Soon" | "Your laptop has been packed and will be delivered shortly." |
delivered | "Laptop Delivered" | "Your laptop has been successfully delivered. Thank you!" |
cancelled | "Repair Cancelled" | "Your repair request has been cancelled. Contact support for assistance." |
Customer cancel (SP2 service) does not fire a separate notification — the customer initiated it.
6. Performance Notes
findAll(both customer and admin services) usesinArrayto batch-fetch devices and history for all ticket IDs on a page, avoiding N+1 queries.- Device rows are fetched via
LEFT JOIN brandsto resolve brand name in one query. - Admin list filters build a
SQL[]array and spread intoand()— no unnecessary filter clauses for undefined params. - Paginated responses include a separate
COUNT(*)query whenpagination = true.
7. File Map
packages/db/src/schema/repair/ — schema source of truth
enums.ts
repair-tickets.ts
repair-ticket-devices.ts
repair-ticket-status-history.ts
index.ts
packages/db/src/migrations/
0005_repair_request_schema.sql — generated Drizzle migration (78 lines)
apps/api/src/modules/repair/
repair.module.ts — aggregate module
customer/
repair-customer.module.ts
repair-customer.controller.ts — 4 customer endpoints
repair-customer.service.ts
dto/ — 5 files (create-device, create-request, fetch, response, barrel)
__tests__/unit/services/ — 14 unit tests
admin/
repair-admin.module.ts
repair-admin.controller.ts — 5 admin endpoints
repair-admin.service.ts
dto/ — 6 files (fetch-admin, update-status, update-method, update-notes, admin-response, barrel)
__tests__/unit/services/ — 14 unit tests
__tests__/integration/ — 10 integration tests