# Allowance Scheduling System (Backend) This document captures the complete allowance scheduling feature as implemented in the backend. It is intended as a long-term reference for how the system works, why it was built this way, and how to operate/extend it safely. ## Goals and Scope - Allow parents/guardians to create a recurring allowance schedule for a child. - Credit the allowance automatically based on the schedule. - Scale safely with multiple workers and avoid duplicate credits. - Keep cron lightweight and offload work to RabbitMQ workers. Non-goals in the current implementation: - UI changes - Analytics/reporting views - Advanced scheduling (e.g., custom weekdays) ## High-Level Flow 1. Parent creates a schedule via API. 2. Cron runs every 5 minutes and enqueues due schedules into RabbitMQ. 3. Workers consume queue messages, credit the allowance, and update the next run. 4. Idempotency is enforced in the database to prevent duplicate credits. ## Data Model ### Table: `allowance_schedules` Purpose: Stores the schedule definition (amount, frequency, status, next run). Key fields: - `guardian_id`, `junior_id`: who funds and who receives. - `amount`: the allowance amount. - `frequency`: DAILY / WEEKLY / MONTHLY. - `status`: ON / OFF. - `next_run_at`, `last_run_at`: scheduling metadata. Constraints: - Unique `(guardian_id, junior_id)` ensures one schedule per child. Entity: `src/allowance/entities/allowance-schedule.entity.ts` ### Table: `allowance_credits` Purpose: Audit log and idempotency guard for each credit run. Key fields: - `schedule_id`: which schedule executed. - `transaction_id`: the resulting transaction (nullable). - `amount`, `run_at`, `credited_at`. Idempotency: - Unique `(schedule_id, run_at)` prevents duplicates even with multiple workers. Entity: `src/allowance/entities/allowance-credit.entity.ts` ### Table: `cron_runs` Purpose: shared audit table for all cron jobs (not just allowance). Key fields: - `job_name`: unique identifier for the cron. - `status`: SUCCESS / FAILED. - `processed_count`: number of schedules processed. - `error_message`: failure reason. - `started_at`, `finished_at`. Entity: `src/cron/entities/cron-run.entity.ts` ## API (Schedule Creation) Endpoint: - `POST /guardians/me/allowances/:juniorId` Input DTO: - `amount` (required, numeric, positive) - `frequency` (required, enum) - `status` (required, enum) DTO: `src/allowance/dtos/request/create-allowance-schedule.request.dto.ts` Response DTO: - `AllowanceScheduleResponseDto` with schedule fields. DTO: `src/allowance/dtos/response/allowance-schedule.response.dto.ts` Business validation: - Child must belong to guardian. - No duplicate schedule for the same guardian+child. Service: `src/allowance/services/allowance.service.ts` ## Cron Producer (Queue Enqueue) Cron job: - Runs every 5 minutes. - Batches schedules (cursor-based) to avoid large load. - Enqueues each schedule to RabbitMQ. Cron: `src/cron/tasks/allowance-schedule.cron.ts` Batch behavior: - Batch size = 100 - Uses a cursor (`nextRunAt`, `id`) for stable pagination. - Prevents re-reading the same rows. Locking: - `BaseCronService` uses cache lock to ensure only one instance runs. - Each cron run is logged to `cron_runs` with status and processed count. Lock: `src/cron/services/base-cron.service.ts` ## Queue Publisher Queue publisher: - Asserts queue + retry/DLQ exchanges. - Enqueues jobs with message id (for traceability). Service: `src/allowance/services/allowance-queue.service.ts` Queue names: - `allowance.schedule` (main) - `allowance.schedule.retry` (retry with TTL) - `allowance.schedule.dlq` (dead-letter queue) Constants: `src/allowance/constants/allowance-queue.constants.ts` ## Worker Consumer (Transfers) Worker: - Consumes `allowance.schedule` queue. - Validates schedule is due and active. - Creates `allowance_credits` record for idempotency. - Transfers money via `cardService.transferToChild`. - Updates `last_run_at` and `next_run_at`. Worker: `src/allowance/services/allowance-worker.service.ts` Transfer: - Uses existing logic in `card.service.ts` for balance updates and transaction creation. Service: `src/card/services/card.service.ts` ### Idempotency details 1. Worker inserts `allowance_credits` row first. 2. Unique constraint blocks duplicates. 3. If transfer fails, the credit row is removed so the job can retry. This makes multiple workers safe. ## Retry + DLQ Strategy Retry delay: - Failed jobs are dead-lettered to retry queue. - Retry queue has `messageTtl = 10 minutes`. - After TTL, job is routed back to main queue. DLQ: - If a job fails `ALLOWANCE_MAX_RETRIES` times (default 5), it is routed to the DLQ. - This prevents endless loops and allows manual inspection. Config: - `ALLOWANCE_MAX_RETRIES` (default 5) ## Redis Usage Redis is used by `BaseCronService` to enforce a **distributed lock** for the cron job. This prevents multiple backend instances from enqueuing the same schedules at the same time. Lock behavior: - If the lock key exists, cron exits early. - If the lock key is absent, cron sets it with a TTL and proceeds. - TTL ensures the lock is released even if a node crashes. Service: `src/cron/services/base-cron.service.ts` ## RabbitMQ Setup and Behavior The allowance system uses RabbitMQ for asynchronous processing. Cron publishes due schedules, and workers consume them. ### Exchanges and Queues Main queue: - `allowance.schedule` Retry setup: - Exchange: `allowance.schedule.retry.exchange` - Queue: `allowance.schedule.retry` - Binding key: `allowance.schedule` - TTL: 10 minutes - Dead-letter route: back to `allowance.schedule` Dead-letter queue (DLQ): - Exchange: `allowance.schedule.dlq.exchange` - Queue: `allowance.schedule.dlq` - Binding key: `allowance.schedule` ### Flow Summary 1. Cron enqueues jobs to `allowance.schedule`. 2. Worker consumes jobs from `allowance.schedule`. 3. On failure, job is **dead-lettered** to `allowance.schedule.retry`. 4. After 10 minutes, it returns to `allowance.schedule`. 5. After max retries, worker publishes the job to `allowance.schedule.dlq`. ### Where it is configured - Publisher setup: `src/allowance/services/allowance-queue.service.ts` - Worker consumer: `src/allowance/services/allowance-worker.service.ts` - Queue constants: `src/allowance/constants/allowance-queue.constants.ts` ## Environment Variables - `RABBITMQ_URL` (required for queue/worker) - `ALLOWANCE_QUEUE_NAME` (optional, defaults to `allowance.schedule`) - `ALLOWANCE_MAX_RETRIES` (optional, defaults to 5) - `ALLOWANCE_RETRY_DELAY_MS` (optional, defaults to 10 minutes) ### Example .env snippet ``` RABBITMQ_URL=amqp://guest:guest@localhost:5672 ALLOWANCE_QUEUE_NAME=allowance.schedule ALLOWANCE_MAX_RETRIES=5 ALLOWANCE_RETRY_DELAY_MS=600000 ``` ## Operational Checklist - Ensure Redis is running (cron locking). - Ensure RabbitMQ is running (queue + workers). - Start at least one worker process. - Monitor DLQ for failures. ## Manual Test Checklist 1. Create schedule: - POST `/guardians/me/allowances/:juniorId` - Valid amount, frequency, status. 2. Duplicate schedule: - Expect `ALLOWANCE.ALREADY_EXISTS`. 3. Cron enqueue: - Wait for cron interval or manually trigger. - Confirm messages appear in RabbitMQ. 4. Worker: - Ensure worker is running. - Verify transfers happen and `allowance_credits` is created. 5. Failure paths: - Simulate transfer failure and verify retry queue behavior. - Confirm DLQ after max retries. ## Operational Notes - For large volumes, scale workers horizontally. - Keep cron lightweight; do not perform transfers in cron. - Monitor queue depth and DLQ entries. ## Known Limitations (Current) - Only one schedule per child (guardian+junior unique). - No custom weekdays or complex schedules. - Retry delay is fixed at 10 minutes (can be configurable). ## File Map (Quick Reference) - API: - `src/allowance/controllers/allowance.controller.ts` - `src/allowance/services/allowance.service.ts` - `src/allowance/dtos/request/create-allowance-schedule.request.dto.ts` - `src/allowance/dtos/response/allowance-schedule.response.dto.ts` - Cron: - `src/cron/tasks/allowance-schedule.cron.ts` - `src/cron/services/base-cron.service.ts` - Queue/Worker: - `src/allowance/services/allowance-queue.service.ts` - `src/allowance/services/allowance-worker.service.ts` - `src/allowance/constants/allowance-queue.constants.ts` - Repositories: - `src/allowance/repositories/allowance-schedule.repository.ts` - `src/allowance/repositories/allowance-credit.repository.ts` - Entities: - `src/allowance/entities/allowance-schedule.entity.ts` - `src/allowance/entities/allowance-credit.entity.ts`