# Invoice Validation Cleanup Plan

## Problem Statement

The `InvoiceValidatorService` contains redundant validation logic that duplicates what Zod schemas already provide.

## Current Redundant Code

### ❌ In BFF: `apps/bff/src/modules/invoices/validators/invoice-validator.service.ts`

```typescript
// ALL OF THESE ARE REDUNDANT:
validateInvoiceId(invoiceId: number): void {
  if (!invoiceId || invoiceId < 1) throw new BadRequestException("Invalid invoice ID");
}

validateUserId(userId: string): void {
  if (!userId || typeof userId !== "string" || userId.trim().length === 0) {
    throw new BadRequestException("Invalid user ID");
  }
}

validatePagination(options: Partial<InvoiceListQuery>): void {
  if (page < 1) throw new BadRequestException("Page must be greater than 0");
  if (limit < min || limit > max) throw new BadRequestException(`Limit must be between...`);
}

validateInvoiceStatus(status: string): InvoiceStatus {
  if (!isValidInvoiceStatus(status)) throw new BadRequestException(`Invalid status...`);
  return status as InvoiceStatus;
}

validateWhmcsClientId(clientId: number | undefined): void {
  if (!clientId || clientId < 1) throw new BadRequestException("Invalid WHMCS client ID");
}

validatePaymentGateway(gatewayName: string): void {
  if (!gatewayName || typeof gatewayName !== "string" || gatewayName.trim().length === 0) {
    throw new BadRequestException("Invalid payment gateway name");
  }
}

validateGetInvoicesOptions(options: InvoiceListQuery): InvoiceValidationResult {
  // Calls the above functions - all redundant!
}
```

### ✅ Already Exists: `packages/domain/billing/schema.ts`

```typescript
// THESE ALREADY HANDLE ALL VALIDATION:
export const invoiceSchema = z.object({
  id: z.number().int().positive("Invoice id must be positive"),
  // ...
});

export const invoiceListQuerySchema = z.object({
  page: z.coerce.number().int().positive().optional(),
  limit: z.coerce.number().int().positive().max(100).optional(),
  status: invoiceListStatusSchema.optional(),
});
```

## Solution: Use Schemas Directly

### Step 1: Add Missing Schemas to Domain

Only ONE new schema needed:

```typescript
// packages/domain/common/validation.ts (add to existing file)
export const urlSchema = z.string().url();

export function validateUrl(url: string): { isValid: boolean; errors: string[] } {
  const result = urlSchema.safeParse(url);
  return {
    isValid: result.success,
    errors: result.success ? [] : result.error.issues.map(i => i.message),
  };
}
```

### Step 2: Use Schemas at Controller/Entry Point

```typescript
// apps/bff/src/modules/invoices/invoices.controller.ts
import { invoiceListQuerySchema } from "@customer-portal/domain/billing";
import { ZodValidationPipe } from "@bff/core/validation";

@Controller("invoices")
export class InvoicesController {
  @Get()
  async getInvoices(
    @Query(new ZodValidationPipe(invoiceListQuerySchema)) query: InvoiceListQuery,
    @Request() req: RequestWithUser
  ) {
    // query is already validated by Zod pipe!
    // No need for validator service
    return this.invoicesService.getInvoices(req.user.id, query);
  }
}
```

### Step 3: Delete InvoiceValidatorService

The entire service can be removed:
- ❌ Delete `apps/bff/src/modules/invoices/validators/invoice-validator.service.ts`
- ❌ Delete `apps/bff/src/modules/invoices/types/invoice-service.types.ts` (InvoiceValidationResult)
- ✅ Use Zod schemas + `ZodValidationPipe` instead

## Migration Steps

### A. Add URL Validation to Domain (Only Useful One)

1. Add `urlSchema` and `validateUrl()` to `packages/domain/common/validation.ts`
2. Export from `packages/domain/common/index.ts`

### B. Update BFF to Use Schemas Directly

3. Update `InvoiceRetrievalService` to use schemas instead of validator
4. Update `InvoicesOrchestratorService` to use schemas
5. Update controller to use `ZodValidationPipe` with domain schemas

### C. Remove Redundant Code

6. Delete `InvoiceValidatorService`
7. Delete `InvoiceValidationResult` type
8. Remove from `InvoicesModule` providers

## Benefits

✅ Single source of truth (Zod schemas in domain)
✅ No duplicate validation logic
✅ Type-safe (schemas generate types)
✅ Consistent error messages
✅ Less code to maintain

## General Pattern

This applies to ALL modules:

```typescript
// ❌ DON'T: Create validator service for field validation
class XyzValidatorService {
  validateField(value) { ... }  // Redundant with schema
}

// ✅ DO: Use Zod schema + validation pipe
const xyzRequestSchema = z.object({
  field: z.string().min(1),
});

@Post()
async create(@Body(new ZodValidationPipe(xyzRequestSchema)) body: XyzRequest) {
  // Already validated!
}
```

**Validator Services should ONLY exist for:**
- Business logic validation (requires multiple fields or data)
- Infrastructure validation (requires DB/API calls)

**NOT for:**
- Field format validation (use Zod schemas)
- Type validation (use Zod schemas)
- Range validation (use Zod schemas)

