export const NAME_MAX_LENGTH = 100; export const BRAND_MAX_LENGTH = 100; export const OWNER_MAX_LENGTH = 50; export const ADDRESS_DETAIL_MAX_LENGTH = 200; export const MEMO_MAX_LENGTH = 500; export const ACCOUNT_NUMBER_MAX_LENGTH = 30; export interface MerchantValues { id: string; /** 백엔드에 컬럼이 없어 저장되지 않는다 — 기획 시안 유지를 위해 화면에는 남긴다. */ brandName: string; memo: string; name: string; categoryCode: string; businessNumber: string; ownerName: string; phoneNumber: string; address: string; addressDetail: string; postalCode: string; latitude: string; longitude: string; /** 백엔드 등록 요청 VO(`MngrVocInsertReqVo`)의 예금주명·입금은행코드·입금계좌번호·업태·구분. */ bankHolderName: string; bankCode: string; accountNumber: string; businessStatusCode: string; divisionCode: string; isVisible: boolean; } export type MerchantFormErrors = Partial< Record< 'name' | 'categoryCode' | 'phoneNumber' | 'address' | 'postalCode' | 'businessNumber' | 'accountNumber', string > >; export type MerchantFormState = | { status: 'idle' } | { status: 'error'; message?: string; errors?: MerchantFormErrors } | { status: 'success' }; export const INITIAL_MERCHANT_FORM_STATE: MerchantFormState = { status: 'idle' }; export const REQUIRED_MESSAGE = '필수입력 사항을 입력하세요.'; /** 사업자등록번호는 선택이지만, 넣었다면 형태를 지킨다(기획 예시 `000-00-00000`). */ const BUSINESS_NUMBER_PATTERN = /^\d{3}-?\d{2}-?\d{5}$/; type ValidationResult = | { ok: true; values: MerchantValues } | { ok: false; errors: MerchantFormErrors }; export function validateMerchant(values: MerchantValues): ValidationResult { const errors: MerchantFormErrors = {}; const name = values.name.trim(); if (!name) { errors.name = REQUIRED_MESSAGE; } else if (name.length > NAME_MAX_LENGTH) { errors.name = `가맹점명은 ${NAME_MAX_LENGTH}자 이내로 입력해 주세요.`; } if (!values.categoryCode.trim()) { errors.categoryCode = REQUIRED_MESSAGE; } const phoneNumber = values.phoneNumber.trim(); if (!phoneNumber) { errors.phoneNumber = REQUIRED_MESSAGE; } else if (!/^[\d-]+$/.test(phoneNumber)) { errors.phoneNumber = '전화번호는 숫자와 -만 입력할 수 있습니다.'; } if (!values.address.trim()) { errors.address = '주소를 검색하거나 입력해 주세요.'; } const postalCode = values.postalCode.trim(); if (postalCode && !/^\d{5}$/.test(postalCode)) { errors.postalCode = '우편번호는 숫자 5자리로 입력해 주세요.'; } const businessNumber = values.businessNumber.trim(); if (businessNumber && !BUSINESS_NUMBER_PATTERN.test(businessNumber)) { errors.businessNumber = '사업자등록번호는 000-00-00000 형식으로 입력해 주세요.'; } const accountNumber = values.accountNumber.trim(); if (accountNumber && !/^[\d-]+$/.test(accountNumber)) { errors.accountNumber = '계좌번호는 숫자와 -만 입력할 수 있습니다.'; } if (Object.keys(errors).length > 0) { return { ok: false, errors }; } return { ok: true, values: { ...values, name, brandName: values.brandName.trim().slice(0, BRAND_MAX_LENGTH), memo: values.memo.trim().slice(0, MEMO_MAX_LENGTH), ownerName: values.ownerName.trim().slice(0, OWNER_MAX_LENGTH), phoneNumber, businessNumber, address: values.address.trim(), addressDetail: values.addressDetail.trim().slice(0, ADDRESS_DETAIL_MAX_LENGTH), postalCode, bankHolderName: values.bankHolderName.trim().slice(0, OWNER_MAX_LENGTH), bankCode: values.bankCode.trim(), accountNumber: accountNumber.slice(0, ACCOUNT_NUMBER_MAX_LENGTH), businessStatusCode: values.businessStatusCode.trim(), divisionCode: values.divisionCode.trim(), }, }; }