Assist_Design/docs/_archive/INVOICE-VALIDATION-CLEANUP-PLAN.md

168 lines
4.9 KiB
Markdown

# 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)