Initializing
Back to Projects
Year2024
DomainFullstack
AccessOpen Source
Complexity0 / 10
NestJSNext.jsPostgreSQLPrismaFinancial SystemsRBACDocker
FullstackProduction

RMS Financial Management System

A production-grade financial record management system with four-eyes approval workflow, hierarchical account heads, citizen registry with credit wallet, and comprehensive audit logging.

Parsing system architecture diagram...

Technology Stack

LayerTechnologyVersion
Backend FrameworkNestJS11.0.1
Frontend FrameworkNext.js16.1.6
DatabasePostgreSQL16-alpine
ORMPrisma5.10.0
AuthenticationPassport JWT4.0.1
Password HashingArgon20.44.0
Validationclass-validator0.14.3
PDF GenerationPDFKit0.17.2
UI Componentsshadcn/ui3.8.1
StylingTailwind CSS4
State ManagementReact Hook Form7.71.1
ChartsRecharts3.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:

  1. 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.
  1. 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.
  1. 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.
  1. Audit Everything: No hard deletions occur. All changes are logged with before/after JSON snapshots, IP addresses, and user agents for forensic analysis.
  1. 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:

code
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 setup

Backend Architecture

The NestJS backend implements a modular architecture with clear separation of concerns:

typescript
// 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:

prisma
// 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:

prisma
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
typescript
// 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:

RoleLevelDashboardPurpose
DEVELOPER2/developer/dashboardSystem configuration, admin management, database tools
ADMIN1/admin/dashboardFinancial data entry, approvals, citizen management
MASTER0N/AData 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:

prisma
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:

json
{
  "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
typescript
// 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:

prisma
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:

typescript
// 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:

typescript
// 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:

typescript
// 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:

yaml
# 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:

code
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-password

Running the Application

bash
# 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 dev

Development Highlights

Backend Testing

The project includes comprehensive unit tests for all modules:

typescript
// 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 guide
  • docs/nestjs.md, docs/nextjs.md: Framework-specific documentation
  • docs/prisma.md: Database design and migrations
  • docs/shadcn-ui.md, docs/framer-motion.md: UI component guides
  • docs/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

  1. Prisma Transactions: Using $transaction() for multi-step operations ensured data consistency during receipt approval.
  1. Hierarchical Account Heads: The three-level tree structure provided flexibility while maintaining query performance.
  1. Audit Interceptor: Centralized audit logging through interceptors eliminated duplicate logging code.
  1. Role Guards: Clean separation between Developer and Admin roles prevented accidental data contamination.

Areas for Improvement

  1. Real-time Updates: Currently lacks WebSocket-based real-time notifications for approval queues.
  1. Backup Strategy: Database backup automation could be more robust with scheduled jobs.
  1. API Versioning: No API versioning strategy in place for future breaking changes.
  1. 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.

Submit a Technical Suggestion