# Financial Module TypeORM Migration Guide

## Overview

This guide documents the migration of the Financial module from raw SQL queries to TypeORM. The migration maintains backwards compatibility while introducing modern ORM patterns.

## Completed Tasks

### 1. Entity Creation ✅

All TypeORM entities have been created in `/src/modules/financial/entities/`:

- **account-type.entity.ts** - Chart of account types catalog
- **account.entity.ts** - Accounts with hierarchy support
- **journal.entity.ts** - Accounting journals
- **journal-entry.entity.ts** - Journal entries (header)
- **journal-entry-line.entity.ts** - Journal entry lines (detail)
- **invoice.entity.ts** - Customer and supplier invoices
- **invoice-line.entity.ts** - Invoice line items
- **payment.entity.ts** - Payment transactions
- **tax.entity.ts** - Tax configuration
- **fiscal-year.entity.ts** - Fiscal years
- **fiscal-period.entity.ts** - Fiscal periods (months/quarters)
- **index.ts** - Barrel export file

### 2. Entity Registration ✅

All financial entities have been registered in `/src/config/typeorm.ts`:
- Import statements added
- Entities added to the `entities` array in AppDataSource configuration

### 3. Service Refactoring ✅

#### accounts.service.ts - COMPLETED

The accounts service has been fully migrated to TypeORM with the following features:

**Key Changes:**
- Uses `Repository<Account>` and `Repository<AccountType>`
- Implements QueryBuilder for complex queries with joins
- Supports both snake_case (DB) and camelCase (TS) through decorators
- Maintains all original functionality including:
  - Account hierarchy with cycle detection
  - Soft delete with validation
  - Balance calculations
  - Full CRUD operations

**Pattern to Follow:**
```typescript
import { Repository, IsNull } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { Entity } from './entities/index.js';

class MyService {
  private repository: Repository<Entity>;

  constructor() {
    this.repository = AppDataSource.getRepository(Entity);
  }

  async findAll(tenantId: string, filters = {}) {
    const queryBuilder = this.repository
      .createQueryBuilder('alias')
      .leftJoin('alias.relation', 'relation')
      .addSelect(['relation.field'])
      .where('alias.tenantId = :tenantId', { tenantId });

    // Apply filters
    // Get count and results
    return { data, total };
  }
}
```

## Remaining Tasks

### Services to Migrate

#### 1. journals.service.ts - PRIORITY HIGH

**Current State:** Uses raw SQL queries
**Target Pattern:** Same as accounts.service.ts

**Migration Steps:**
1. Import Journal entity and Repository
2. Replace all `query()` and `queryOne()` calls with Repository methods
3. Use QueryBuilder for complex queries with joins (company, account, currency)
4. Update return types to use entity types instead of interfaces
5. Maintain validation logic for:
   - Unique code per company
   - Journal entry existence check before delete
6. Test endpoints thoroughly

**Key Relationships:**
- Journal → Company (ManyToOne)
- Journal → Account (default account, ManyToOne, optional)

---

#### 2. taxes.service.ts - PRIORITY HIGH

**Current State:** Uses raw SQL queries
**Special Feature:** Tax calculation logic

**Migration Steps:**
1. Import Tax entity and Repository
2. Migrate CRUD operations to Repository
3. **IMPORTANT:** Keep `calculateTaxes()` and `calculateDocumentTaxes()` logic intact
4. These calculation methods can still use raw queries if needed
5. Update filters to use QueryBuilder

**Tax Calculation Logic:**
- Located in lines 224-354 of current service
- Critical for invoice and payment processing
- DO NOT modify calculation algorithms
- Only update data access layer

---

#### 3. journal-entries.service.ts - PRIORITY MEDIUM

**Current State:** Uses raw SQL with transactions
**Complexity:** HIGH - Multi-table operations

**Migration Steps:**
1. Import JournalEntry, JournalEntryLine entities
2. Use TypeORM QueryRunner for transactions:
```typescript
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();

try {
  // Operations
  await queryRunner.commitTransaction();
} catch (error) {
  await queryRunner.rollbackTransaction();
  throw error;
} finally {
  await queryRunner.release();
}
```

3. **Double-Entry Balance Validation:**
   - Keep validation logic lines 172-177
   - Validate debit = credit before saving
4. Use cascade operations for lines:
   - `cascade: true` is already set in entity
   - Can save entry with lines in single operation

**Critical Features:**
- Transaction management (BEGIN/COMMIT/ROLLBACK)
- Balance validation (debits must equal credits)
- Status transitions (draft → posted → cancelled)
- Fiscal period validation

---

#### 4. invoices.service.ts - PRIORITY MEDIUM

**Current State:** Uses raw SQL with complex line management
**Complexity:** HIGH - Invoice lines, tax calculations

**Migration Steps:**
1. Import Invoice, InvoiceLine entities
2. Use transactions for multi-table operations
3. **Tax Integration:**
   - Line 331-340: Uses taxesService.calculateTaxes()
   - Keep this integration intact
   - Only migrate data access
4. **Amount Calculations:**
   - updateTotals() method (lines 525-543)
   - Can use QueryBuilder aggregation or raw SQL
5. **Number Generation:**
   - Lines 472-478: Sequential invoice numbering
   - Keep this logic, migrate to Repository

**Relationships:**
- Invoice → Company
- Invoice → Journal (optional)
- Invoice → JournalEntry (optional, for accounting integration)
- Invoice → InvoiceLine[] (one-to-many, cascade)
- InvoiceLine → Account (optional)

---

#### 5. payments.service.ts - PRIORITY MEDIUM

**Current State:** Uses raw SQL with invoice reconciliation
**Complexity:** MEDIUM-HIGH - Payment-Invoice linking

**Migration Steps:**
1. Import Payment entity
2. **Payment-Invoice Junction:**
   - Table: `financial.payment_invoice`
   - Not modeled as entity (junction table)
   - Can use raw SQL for this or create entity
3. Use transactions for reconciliation
4. **Invoice Status Updates:**
   - Lines 373-380: Updates invoice amounts
   - Must coordinate with Invoice entity

**Critical Logic:**
- Reconciliation workflow (lines 314-401)
- Invoice amount updates
- Transaction rollback on errors

---

#### 6. fiscalPeriods.service.ts - PRIORITY LOW

**Current State:** Uses raw SQL + database functions
**Complexity:** MEDIUM - Database function calls

**Migration Steps:**
1. Import FiscalYear, FiscalPeriod entities
2. Basic CRUD can use Repository
3. **Database Functions:**
   - Line 242: `financial.close_fiscal_period()`
   - Line 265: `financial.reopen_fiscal_period()`
   - Keep these as raw SQL calls:
   ```typescript
   await this.repository.query(
     'SELECT * FROM financial.close_fiscal_period($1, $2)',
     [periodId, userId]
   );
   ```
4. **Date Overlap Validation:**
   - Lines 102-107, 207-212
   - Use QueryBuilder with date range checks

---

## Controller Updates

### Accept Both snake_case and camelCase

The controller currently only accepts snake_case. Update to support both:

**Current:**
```typescript
const createAccountSchema = z.object({
  company_id: z.string().uuid(),
  code: z.string(),
  // ...
});
```

**Updated:**
```typescript
const createAccountSchema = z.object({
  companyId: z.string().uuid().optional(),
  company_id: z.string().uuid().optional(),
  code: z.string(),
  // ...
}).refine(
  (data) => data.companyId || data.company_id,
  { message: "Either companyId or company_id is required" }
);

// Then normalize before service call:
const dto = {
  companyId: parseResult.data.companyId || parseResult.data.company_id,
  // ... rest of fields
};
```

**Simpler Approach:**
Transform incoming data before validation:
```typescript
// Add utility function
function toCamelCase(obj: any): any {
  const camelObj: any = {};
  for (const key in obj) {
    const camelKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
    camelObj[camelKey] = obj[key];
  }
  return camelObj;
}

// Use in controller
const normalizedBody = toCamelCase(req.body);
const parseResult = createAccountSchema.safeParse(normalizedBody);
```

---

## Migration Patterns

### 1. Repository Setup

```typescript
import { Repository, IsNull } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { MyEntity } from './entities/index.js';

class MyService {
  private repository: Repository<MyEntity>;

  constructor() {
    this.repository = AppDataSource.getRepository(MyEntity);
  }
}
```

### 2. Simple Find Operations

**Before (Raw SQL):**
```typescript
const result = await queryOne<Entity>(
  `SELECT * FROM schema.table WHERE id = $1 AND tenant_id = $2`,
  [id, tenantId]
);
```

**After (TypeORM):**
```typescript
const result = await this.repository.findOne({
  where: { id, tenantId, deletedAt: IsNull() }
});
```

### 3. Complex Queries with Joins

**Before:**
```typescript
const data = await query<Entity>(
  `SELECT e.*, r.name as relation_name
   FROM schema.entities e
   LEFT JOIN schema.relations r ON e.relation_id = r.id
   WHERE e.tenant_id = $1`,
  [tenantId]
);
```

**After:**
```typescript
const data = await this.repository
  .createQueryBuilder('entity')
  .leftJoin('entity.relation', 'relation')
  .addSelect(['relation.name'])
  .where('entity.tenantId = :tenantId', { tenantId })
  .getMany();
```

### 4. Transactions

**Before:**
```typescript
const client = await getClient();
try {
  await client.query('BEGIN');
  // operations
  await client.query('COMMIT');
} catch (error) {
  await client.query('ROLLBACK');
  throw error;
} finally {
  client.release();
}
```

**After:**
```typescript
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();

try {
  // operations using queryRunner.manager
  await queryRunner.manager.save(entity);
  await queryRunner.commitTransaction();
} catch (error) {
  await queryRunner.rollbackTransaction();
  throw error;
} finally {
  await queryRunner.release();
}
```

### 5. Soft Deletes

**Pattern:**
```typescript
await this.repository.update(
  { id, tenantId },
  {
    deletedAt: new Date(),
    deletedBy: userId,
  }
);
```

### 6. Pagination

```typescript
const skip = (page - 1) * limit;

const [data, total] = await this.repository.findAndCount({
  where: { tenantId, deletedAt: IsNull() },
  skip,
  take: limit,
  order: { createdAt: 'DESC' },
});

return { data, total };
```

---

## Testing Strategy

### 1. Unit Tests

For each refactored service:

```typescript
describe('AccountsService', () => {
  let service: AccountsService;
  let repository: Repository<Account>;

  beforeEach(() => {
    repository = AppDataSource.getRepository(Account);
    service = new AccountsService();
  });

  it('should create account with valid data', async () => {
    const dto = { /* ... */ };
    const result = await service.create(dto, tenantId, userId);
    expect(result.id).toBeDefined();
    expect(result.code).toBe(dto.code);
  });
});
```

### 2. Integration Tests

Test with actual database:

```bash
# Run tests
npm test src/modules/financial/__tests__/
```

### 3. API Tests

Test HTTP endpoints:

```bash
# Test accounts endpoints
curl -X GET http://localhost:3000/api/financial/accounts?companyId=xxx
curl -X POST http://localhost:3000/api/financial/accounts -d '{"companyId":"xxx",...}'
```

---

## Rollback Plan

If migration causes issues:

1. **Restore Old Services:**
```bash
cd src/modules/financial
mv accounts.service.ts accounts.service.new.ts
mv accounts.service.old.ts accounts.service.ts
```

2. **Remove Entity Imports:**
Edit `/src/config/typeorm.ts` and remove financial entity imports

3. **Restart Application:**
```bash
npm run dev
```

---

## Database Schema Notes

### Schema: `financial`

All tables use the `financial` schema as specified in entities.

### Important Columns:

- **tenant_id**: Multi-tenancy isolation (UUID, NOT NULL)
- **company_id**: Company isolation (UUID, NOT NULL)
- **deleted_at**: Soft delete timestamp (NULL = active)
- **created_at**: Audit timestamp
- **created_by**: User ID who created (UUID)
- **updated_at**: Audit timestamp
- **updated_by**: User ID who updated (UUID)

### Decimal Precision:

- **Amounts**: DECIMAL(15, 2) - invoices, payments
- **Quantity**: DECIMAL(15, 4) - invoice lines
- **Tax Rate**: DECIMAL(5, 2) - tax percentage

---

## Common Issues and Solutions

### Issue 1: Column Name Mismatch

**Error:** `column "companyId" does not exist`

**Solution:** Entity decorators map camelCase to snake_case:
```typescript
@Column({ name: 'company_id' })
companyId: string;
```

### Issue 2: Soft Deletes Not Working

**Solution:** Always include `deletedAt: IsNull()` in where clauses:
```typescript
where: { id, tenantId, deletedAt: IsNull() }
```

### Issue 3: Transaction Not Rolling Back

**Solution:** Always use try-catch-finally with queryRunner:
```typescript
finally {
  await queryRunner.release(); // MUST release
}
```

### Issue 4: Relations Not Loading

**Solution:** Use leftJoin or relations option:
```typescript
// Option 1: Query Builder
.leftJoin('entity.relation', 'relation')
.addSelect(['relation.field'])

// Option 2: Find options
findOne({
  where: { id },
  relations: ['relation'],
})
```

---

## Performance Considerations

### 1. Query Optimization

- Use `leftJoin` + `addSelect` instead of `relations` option for better control
- Add indexes on frequently queried columns (already in entities)
- Use pagination for large result sets

### 2. Connection Pooling

TypeORM pool configuration (in typeorm.ts):
```typescript
extra: {
  max: 10,  // Conservative to not compete with pg pool
  min: 2,
  idleTimeoutMillis: 30000,
}
```

### 3. Caching

Currently disabled:
```typescript
cache: false
```

Can enable later for read-heavy operations.

---

## Next Steps

1. **Complete service migrations** in this order:
   - taxes.service.ts (High priority, simple)
   - journals.service.ts (High priority, simple)
   - journal-entries.service.ts (Medium, complex transactions)
   - invoices.service.ts (Medium, tax integration)
   - payments.service.ts (Medium, reconciliation)
   - fiscalPeriods.service.ts (Low, DB functions)

2. **Update controller** to accept both snake_case and camelCase

3. **Write tests** for each migrated service

4. **Update API documentation** to reflect camelCase support

5. **Monitor performance** after deployment

---

## Support and Questions

For questions about this migration:
- Check existing patterns in `accounts.service.ts`
- Review TypeORM documentation: https://typeorm.io
- Check entity definitions in `/entities/` folder

---

## Changelog

### 2024-12-14
- Created all TypeORM entities
- Registered entities in AppDataSource
- Completed accounts.service.ts migration
- Created this migration guide
