INSURANCE_FEATURES_SPEC §1. The office printed and mailed renewal letters from the legacy CONTROL <ramo> RENEW[2/3] paper log; 91% of policyholders have an email on file, so send the notice instead and keep the paper log as the fallback. A daily cron (06:00 America/Tijuana) sweeps three generations off policyTo — 30 and 15 days before expiry, 7 days after — sends each through SES, and upserts RenewalNotice by [policyId, generation] so a policy is never notified twice for the same milestone. RenewalNotice now records providerMessageId, so a later bounce or complaint webhook can be traced back to the row that sent it. - customers.emailOptOut excludes a customer from every sweep; editable from the customer form - scheduled_job_states holds the sweep's lock and last successful run; the window is widened to cover days the job did not run, so a weekend outage does not silently drop a generation - SES unconfigured is not an error outside production — messages are logged and skipped, so dev and CI never send - /renovaciones (renewal:send, MANAGER+) lists what is pending per generation, runs the sweep by hand, and marks a notice sent by mail for the customers with no email - POST /policies/:id/renewal-notices records that manual mark - the aviso-renovacion report and the emails now share one projection (reports/renewal-letter.ts) instead of two copies of the mapping
44 lines
1.5 KiB
TypeScript
44 lines
1.5 KiB
TypeScript
import {
|
|
IsBoolean,
|
|
IsEmail,
|
|
IsEnum,
|
|
IsNumber,
|
|
IsOptional,
|
|
IsString,
|
|
MinLength,
|
|
} from "class-validator";
|
|
import { Currency } from "@jorgecuadros/database";
|
|
|
|
/**
|
|
* Editable customer fields. Internal/derived columns (nameSource, nameMissing,
|
|
* legacy* provenance, archivedAt) are managed by the service, not the client.
|
|
* `name` is the only required field; everything else is optional.
|
|
*/
|
|
export class CreateCustomerDto {
|
|
@IsString()
|
|
@MinLength(1)
|
|
name!: string;
|
|
|
|
@IsOptional() @IsString() addressLine1?: string;
|
|
@IsOptional() @IsString() addressLine2?: string;
|
|
@IsOptional() @IsString() city?: string;
|
|
@IsOptional() @IsString() state?: string;
|
|
@IsOptional() @IsString() zipCode?: string;
|
|
@IsOptional() @IsString() country?: string;
|
|
@IsOptional() @IsString() phone?: string;
|
|
@IsOptional() @IsString() mobile?: string;
|
|
@IsOptional() @IsString() fax?: string;
|
|
@IsOptional() @IsEmail() email?: string;
|
|
@IsOptional() @IsBoolean() emailOptOut?: boolean;
|
|
@IsOptional() @IsString() notes?: string;
|
|
@IsOptional() @IsString() identificationType?: string;
|
|
@IsOptional() @IsString() identificationNumber?: string;
|
|
/** ISO date string; coerced to Date by the service. */
|
|
@IsOptional() @IsString() identificationExpiration?: string;
|
|
@IsOptional() @IsString() customerSince?: string;
|
|
@IsOptional() @IsBoolean() status?: boolean;
|
|
@IsOptional() @IsNumber() minimumBalance?: number;
|
|
@IsOptional() @IsNumber() feeAmount?: number;
|
|
@IsOptional() @IsEnum(Currency) preferredCurrency?: Currency;
|
|
}
|