/** * Address Domain - Schemas * * Zod validation schemas for address lookup and bilingual address data. * Types are derived from schemas (Schema-First Approach). */ import { z } from "zod"; // ============================================================================ // ZIP Code Schemas // ============================================================================ /** * Japanese ZIP code schema * Accepts: "1000001", "100-0001" -> normalizes to "1000001" */ export const zipCodeSchema = z .string() .regex(/^\d{3}-?\d{4}$/, "ZIP code must be 7 digits (e.g., 100-0001)") .transform(val => val.replace(/-/g, "")); /** * ZIP code lookup request */ export const zipCodeLookupRequestSchema = z.object({ zipCode: zipCodeSchema, }); // ============================================================================ // Japan Post Address Schemas // ============================================================================ /** * Address from Japan Post API lookup * Contains both Japanese and romanized versions */ export const japanPostAddressSchema = z.object({ zipCode: z.string(), // Japanese (for Salesforce) prefecture: z.string(), prefectureKana: z.string().optional(), city: z.string(), cityKana: z.string().optional(), town: z.string(), townKana: z.string().optional(), // Romanized (for WHMCS) prefectureRoma: z.string(), cityRoma: z.string(), townRoma: z.string(), }); /** * Address lookup result containing multiple potential matches */ export const addressLookupResultSchema = z.object({ zipCode: z.string(), addresses: z.array(japanPostAddressSchema), count: z.number(), }); // ============================================================================ // Bilingual Address Schemas (Extended from customer/addressSchema) // ============================================================================ /** * Building information for Japanese addresses */ export const buildingInfoSchema = z.object({ buildingName: z.string().max(200).optional().nullable(), roomNumber: z.string().max(50).optional().nullable(), residenceType: z.enum(["house", "apartment"]), }); /** * Extended address data with bilingual fields * Used when updating address in both WHMCS (English) and Salesforce (Japanese) */ export const bilingualAddressSchema = z.object({ // ZIP code postcode: z.string(), // English/Romanized (for WHMCS) prefecture: z.string(), // romanized city: z.string(), // romanized town: z.string(), // romanized // Japanese (for Salesforce) prefectureJa: z.string(), cityJa: z.string(), townJa: z.string(), // Building info (same for both systems) buildingName: z.string().max(200).optional().nullable(), roomNumber: z.string().max(50).optional().nullable(), residenceType: z.enum(["house", "apartment"]), }); /** * Address update request for profile/signup * Combines bilingual address data for dual-write to WHMCS + Salesforce */ export const addressUpdateRequestSchema = bilingualAddressSchema.extend({ country: z.literal("JP").default("JP"), countryCode: z.literal("JP").default("JP"), }); // ============================================================================ // WHMCS Address Mapping // ============================================================================ /** * Prepare address fields for WHMCS update * Maps bilingual address to WHMCS field format */ export function prepareWhmcsAddressFields(address: BilingualAddress): WhmcsAddressFields { const buildingPart = address.buildingName || ""; const roomPart = address.roomNumber || ""; // address1: "{BuildingName} {RoomNumber}" for apartment, "{BuildingName}" for house const address1 = address.residenceType === "apartment" ? `${buildingPart} ${roomPart}`.trim() : buildingPart; return { address1: address1 || undefined, address2: address.town, // romanized town/street city: address.city, // romanized city state: address.prefecture, // romanized prefecture postcode: address.postcode, country: "JP", countrycode: "JP", }; } // ============================================================================ // Salesforce Contact Address Mapping // ============================================================================ /** * Prepare address fields for Salesforce Contact update * Maps bilingual address to Salesforce field format */ export function prepareSalesforceContactAddressFields( address: BilingualAddress ): SalesforceContactAddressFields { return { MailingStreet: address.townJa, // Japanese town/street MailingCity: address.cityJa, // Japanese city MailingState: address.prefectureJa, // Japanese prefecture MailingPostalCode: address.postcode, MailingCountry: "Japan", BuildingName__c: address.buildingName || null, RoomNumber__c: address.roomNumber || null, }; } // ============================================================================ // Exported Types // ============================================================================ export type ZipCode = z.input; export type ZipCodeLookupRequest = z.infer; export type JapanPostAddress = z.infer; export type AddressLookupResult = z.infer; export type BuildingInfo = z.infer; export type BilingualAddress = z.infer; export type AddressUpdateRequest = z.infer; /** * WHMCS address field structure */ export interface WhmcsAddressFields { address1?: string; address2?: string; city?: string; state?: string; postcode?: string; country?: string; countrycode?: string; } /** * Salesforce Contact address field structure */ export interface SalesforceContactAddressFields { MailingStreet: string; MailingCity: string; MailingState: string; MailingPostalCode: string; MailingCountry: string; BuildingName__c: string | null; RoomNumber__c: string | null; }