Technology Stack
| Layer | Technology | Version |
|---|---|---|
| Backend Framework | NestJS | 11.0.1 |
| Frontend Framework | Next.js | 16.1.6 |
| Database | PostgreSQL | 16-alpine |
| ORM | Prisma | 5.10.0 |
| Authentication | Passport JWT | 4.0.1 |
| Password Hashing | Argon2 | 0.44.0 |
| Validation | class-validator | 0.14.3 |
| PDF Generation | PDFKit | 0.17.2 |
| UI Components | shadcn/ui | 3.8.1 |
| Styling | Tailwind CSS | 4 |
| State Management | React Hook Form | 7.71.1 |
| Charts | Recharts | 3.7.0 |
Purpose and Philosophy
The RMS Financial Management System is designed as a comprehensive solution for municipal or organizational financial record-keeping. The core philosophy revolves around financial integrity through structured workflows, implementing the four-eyes principle (two-person approval) for all monetary transactions. This ensures that no single individual can independently create and finalize financial records without proper oversight.
The system operates on the principle of UI as Lens, Backend as Brain. The frontend is purely a presentation layer that reads from APIs and sends user intent, while all business logic, validation, and state management happen exclusively on the backend. This architectural decision ensures that the system can scale to support multiple client applications (web, mobile, desktop) without duplicating business logic.
Core Design Principles
The system adheres to several fundamental design principles that guide its development and evolution:
- Transactional Integrity: All financial operations are wrapped in database transactions to ensure ACID compliance. Receipt approval involves atomic operations including sequence generation, journal entry creation, wallet updates, and audit logging.
- Hierarchical Account Structure: Financial data is organized through a three-level hierarchy (Major Head → Minor Head → Detail Head) that provides granular classification while maintaining structural integrity.
- Role-Based Isolation: The system implements strict role hierarchy where Developer (Level 2) cannot access Admin financial operations, preventing technical staff from contaminating financial data.
- Audit Everything: No hard deletions occur. All changes are logged with before/after JSON snapshots, IP addresses, and user agents for forensic analysis.
- Offline-First Ready: The architecture supports future offline capabilities through IndexedDB caching and Yjs CRDT for conflict resolution, while enforcing that approvals always require live connections.
Architecture Deep Dive
Monorepo Structure
The project uses a monorepo structure separating concerns between backend and frontend while sharing types and utilities through the file system:
RMS-Dev/
├── apps/
│ ├── backend/ # NestJS API server
│ │ ├── src/
│ │ │ ├── auth/ # Authentication & JWT
│ │ │ ├── citizens/ # Citizen registry
│ │ │ ├── receipts/ # Financial transactions
│ │ │ ├── account-heads/ # Chart of accounts
│ │ │ ├── audit/ # Compliance logging
│ │ │ ├── developer/ # System configuration
│ │ │ └── prisma/ # Database connection
│ │ └── prisma/ # Database schema
│ └── web/ # Next.js frontend
│ ├── app/ # App router pages
│ ├── components/ # UI components
│ └── lib/ # API utilities
├── docs/ # Technical documentation
└── docker-compose.yml # Infrastructure setupBackend Architecture
The NestJS backend implements a modular architecture with clear separation of concerns:
// apps/backend/src/app.module.ts
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
AuthModule,
PrismaModule,
AccountHeadsModule,
ReceiptsModule,
CitizensModule,
AuditModule,
DeveloperModule,
],
providers: [
AppService,
{ provide: APP_INTERCEPTOR, useClass: AuditInterceptor },
],
})
export class AppModule {}The AuditInterceptor automatically logs all API requests with before/after snapshots, creating a comprehensive audit trail without requiring manual logging in each service.
Database Schema Design
The Prisma schema implements a financial-grade data model:
// apps/backend/prisma/schema.prisma
model Receipt {
id Int @id @default(autoincrement())
receiptNumber String? @unique
financialYear String
date DateTime @default(now())
payerName String?
citizenId Int?
citizen Citizen? @relation(fields: [citizenId], references: [id])
totalAmount Decimal
status ReceiptStatus @default(DRAFT)
items ReceiptItem[]
paymentMode PaymentMode @default(CASH)
paymentDetails Json?
customFieldData Json?
@@map("receipts")
}
model ReceiptSequence {
id Int @id @default(autoincrement())
financialYear String @unique
currentNumber Int @default(0)
@@map("receipt_sequences")
}The ReceiptSequence table is critical for ensuring gapless, sequential receipt numbering within each financial year. It uses SELECT FOR UPDATE locking to prevent race conditions during approval.
Account Head Hierarchy
The hierarchical account structure uses a self-referential relationship:
model AccountHead {
id Int @id @default(autoincrement())
name String
code String @unique
type HeadType // MAJOR, MINOR, DETAIL
parentId Int?
parent AccountHead? @relation("Hierarchy", fields: [parentId], references: [id])
children AccountHead[] @relation("Hierarchy")
customFieldsSchema Json? // Dynamic form fields
isActive Boolean @default(true)
receiptItems ReceiptItem[]
@@map("account_heads")
}Each Detail Head can define custom fields stored as JSONB, allowing the system to adapt to various revenue types (shop rent, license fees, taxes) without schema changes.
Key Features and Functionality
1. Protected Setup Mechanism
The system includes a secure first-time setup that prevents unauthorized access:
- On first boot, the backend checks if any users exist in the User table
- If no users exist, the system enters Setup Mode
- Users must provide a valid SETUP_KEY from environment variables
- The first created account receives DEVELOPER (God Mode) privileges
- A force reset option allows complete system wipe with master password
// Backend checks user existence on startup
const userCount = await prisma.user.count();
if (userCount === 0) {
return { mode: 'SETUP_REQUIRED' };
}2. Role-Based Access Control (RBAC)
The system implements a three-tier role hierarchy:
| Role | Level | Dashboard | Purpose |
|---|---|---|---|
| DEVELOPER | 2 | /developer/dashboard | System configuration, admin management, database tools |
| ADMIN | 1 | /admin/dashboard | Financial data entry, approvals, citizen management |
| MASTER | 0 | N/A | Data deletion, critical migrations |
Critical Isolation Rule: Developers cannot access the Admin RMS dashboard, preventing technical staff from accidentally or intentionally contaminating financial records. This is enforced through route guards and JWT scope validation.
3. Four-Eyes Approval Workflow
Financial transactions follow a strict dual-stage verification:
Stage 1 - Entry (Create Receipt)
- Search citizen by Aadhar/Phone/Name (fuzzy search after 3 characters)
- Register new citizens inline if not found
- Select Major Head → Minor Head → Detail Head via tree dropdown
- Dynamic custom fields load based on Detail Head selection
- Enter payment details (Cash, Cheque, Online, Draft, Wallet)
- Submit with status:
PENDING_APPROVAL
Stage 2 - Verification (Approval Queue)
- Admins review pending entries in a dedicated queue
- Actions: APPROVE (finalizes), REWORK (sends back), REJECT (voids)
- On approval: receipt number generated, journal entry created, wallet updated
Stage 3 - Finalization
- Receipt number:
RCP/YYYY/####format (sequential, no gaps) - Citizen ledger shadowing: DEBIT = Bill, CREDIT = Payment
- Audit snapshot stored with full before/after JSON
4. Citizen Registry and Credit Wallet
Each citizen maintains a wallet balance for overpayment handling:
model Citizen {
id Int @id @default(autoincrement())
name String
aadhar String? @unique // 12-digit unique identifier
phone String?
email String?
address String?
walletBalance Decimal @default(0)
receipts Receipt[]
creditTransactions CreditTransaction[]
}
model CreditTransaction {
id Int @id @default(autoincrement())
citizenId Int
amount Decimal
type String // CREDIT or DEBIT
reason String?
receiptId Int? // Optional link to receipt
}Aadhar numbers are masked in the UI for privacy, but remain searchable for citizen lookup. Overpayments automatically credit the citizen's wallet for future use.
5. Dynamic Custom Fields
Each Detail Head can define form fields specific to its purpose:
{
"fields": [
{ "label": "Shop Number", "slug": "shop_number", "type": "text", "required": true },
{ "label": "Lease Period", "slug": "lease_period", "type": "number", "required": false },
{ "label": "Agreement Copy", "slug": "agreement_file", "type": "file", "required": false }
]
}These schemas are stored as JSONB in the account head and dynamically render forms in the UI based on the selected head.
6. Developer Dashboard
The Developer (Level 2) has access to system-wide controls:
- Overview: System-wide metrics and health
- Account Management: Create/manage Admin accounts
- Database: Health checks, performance monitoring
- Monitoring: Service status and logs
- Security: WAF configuration, rate limiting
- Backup/Restore: Database export/import tools
- Webhooks: Event hook configuration for integrations
- API Manager: Token-based external API access
- Email Config: SMTP settings for notifications
- System Audit Log: Full forensic logs
7. PDF Receipt Generation
Approved receipts generate professional PDF documents including:
- Sequential receipt number
- Unique transaction identifier
- QR code for verification
- Amount in number-to-words format
- Compliant fixed layout for record-keeping
// Using PDFKit for receipt generation
const doc = new PDFDocument();
doc.fontSize(20).text(`Receipt: ${receipt.receiptNumber}`);
doc.fontSize(14).text(`Amount: ₹${receipt.totalAmount}`);8. Audit and Compliance
All system actions create audit logs:
model AuditLog {
id Int @id @default(autoincrement())
userId Int?
entityType String // e.g., "receipt", "citizen", "user"
entityId String
action String // CREATE, UPDATE, DELETE, APPROVE, etc.
before Json? // Before state snapshot
after Json? // After state snapshot
ipAddress String?
userAgent String?
createdAt DateTime @default(now())
}No hard deletions occur. Deletion requests create a DELETE action log with full entity snapshot.
Security Implementation
Authentication Flow
The system uses JWT-based authentication with Passport:
// JWT Strategy extracts and validates token
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET,
});
}
async validate(payload: { sub: number; role: Role }) {
return { id: payload.sub, role: payload.role };
}
}Password Security
Argon2id provides modern password hashing:
// Password hashing with Argon2
const hash = await argon2.hash(password, {
type: argon2id,
memoryCost: 2 ** 16,
timeCost: 3,
parallelism: 4,
});Role Guards
Route-level protection ensures role isolation:
// Developer guard prevents Admin access
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.DEVELOPER)
@Get('dashboard')
developerDashboard() {}Global Security Rules
- Rate limiting on sensitive endpoints (login, payment operations)
- Strong password policies enforced
- Forced password reset on first login or admin reset
- Master password required for critical operations (deletion, migration)
- IP and User-Agent logging for all sensitive actions
Deployment and Infrastructure
Docker Compose Setup
The project includes containerized infrastructure:
# docker-compose.yml
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
POSTGRES_DB: ${DB_NAME:-rms_db}
ports:
- "${DB_PORT:-5432}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
pgadmin:
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: admin
ports:
- "${PGADMIN_PORT:-5050}:80"Environment Configuration
Required environment variables:
DATABASE_URL=postgresql://postgres:postgres@localhost:5434/rms_db
JWT_SECRET=super-secret-jwt-key
SETUP_KEY=secure-setup-key-123
MASTER_PASSWORD=super-secret-master-passwordRunning the Application
# Start database infrastructure
docker-compose up -d
# Backend
cd apps/backend
npm install
npx prisma migrate deploy
npm run start:dev
# Frontend
cd apps/web
npm install
npm run devDevelopment Highlights
Backend Testing
The project includes comprehensive unit tests for all modules:
// Example test for receipts service
describe('ReceiptsService', () => {
it('should create a pending receipt', async () => {
const receipt = await service.create({
payerName: 'Test Citizen',
totalAmount: 1000,
financialYear: '2024-25',
});
expect(receipt.status).toBe(ReceiptStatus.PENDING_APPROVAL);
});
});Documentation Strategy
The project maintains extensive technical documentation:
docs/project-information.md: System specification and user guidedocs/nestjs.md,docs/nextjs.md: Framework-specific documentationdocs/prisma.md: Database design and migrationsdocs/shadcn-ui.md,docs/framer-motion.md: UI component guidesdocs/postgresql.md: Database configuration
Future Expansion Path
The architecture supports horizontal scaling:
- UPI Payment Gateway: Integration ready via webhook listeners
- Mobile Apps: Android/iOS using the same API
- Desktop Apps: Electron wrapper for the Next.js frontend
- External Integrations: Token-based API access for third-party systems
Lessons Learned and Best Practices
What Worked Well
- Prisma Transactions: Using
$transaction()for multi-step operations ensured data consistency during receipt approval.
- Hierarchical Account Heads: The three-level tree structure provided flexibility while maintaining query performance.
- Audit Interceptor: Centralized audit logging through interceptors eliminated duplicate logging code.
- Role Guards: Clean separation between Developer and Admin roles prevented accidental data contamination.
Areas for Improvement
- Real-time Updates: Currently lacks WebSocket-based real-time notifications for approval queues.
- Backup Strategy: Database backup automation could be more robust with scheduled jobs.
- API Versioning: No API versioning strategy in place for future breaking changes.
- Caching Layer: Redis could improve performance for frequently accessed data (account heads, citizen lists).
Conclusion
The RMS Financial Management System demonstrates production-grade architectural decisions for financial applications. With strict RBAC, transactional integrity, comprehensive audit logging, and a modular NestJS backend, it provides a solid foundation for organizational financial management. The Next.js frontend offers a modern, responsive interface while maintaining the "UI as Lens" philosophy that keeps all business logic on the server.
The system is ready for production deployment with proper environment configuration and can scale to support additional client applications as needs evolve.
Architecture Feedback
Spotted a potential optimization or antipattern? Let me know.