File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
/**
* 코드관리 등록/수정 입력 규칙 — 순수 검증 로직만 담는다(외부 의존 없음).
*
* 시안 네 팝업(공통코드 등록/수정, 상세코드 등록/수정)이 이 파일의 규칙을 공유한다.
* **이 파일이 검증의 단일 진실원천이다** — Server Action이 저장 직전에 여기를 거친다.
*
* 시안의 필수(*) 표시는 상세코드 팝업에만 있지만(코드·코드값의미·정렬번호), 공통코드도
* 코드ID·코드명 없이는 저장할 수 없으므로 같은 강도로 검증한다 — 화면 표시와 무관하게
* 백엔드가 PK로 쓰는 값이다.
*
* `CommonCodeFormState`가 이 파일에 있는 이유는 Next.js가 `'use server'` 파일에서 함수가 아닌
* 값을 export하는 것을 런타임에 거부하기 때문이다(꾸미기 아이템 폼과 같은 사정).
*/
const CODE_MAX_LENGTH = 50;
const NAME_MAX_LENGTH = 100;
const DESCRIPTION_MAX_LENGTH = 500;
/** 코드값은 영문 대문자·숫자·언더스코어만 받는다 — 코드테이블 키라 공백·한글이 섞이면 곤란하다. */
const CODE_PATTERN = /^[A-Z0-9_]+$/;
/** 공통코드(그룹) 등록·수정이 실제로 바꿀 수 있는 항목. */
export type CommonCodeGroupValues = {
comCd: string;
name: string;
description: string;
};
/** 상세코드 등록·수정이 실제로 바꿀 수 있는 항목. */
export type CommonCodeDetailValues = {
/** 소속 그룹. 화면에서는 읽기 전용이지만 저장 대상이라 값으로 다룬다. */
comCd: string;
comDtlCd: string;
/** 시안의 "코드값의미" — 백엔드 `cdNm`이다. */
name: string;
description: string;
sortSeq: number;
};
export type CommonCodeFormErrors = Partial<
Record<
keyof CommonCodeGroupValues | keyof CommonCodeDetailValues,
string
>
>;
export type ValidationResult<T> =
| { ok: true; values: T }
| { ok: false; errors: CommonCodeFormErrors };
export type CommonCodeFormState =
| { status: 'idle' }
| { status: 'error'; message?: string; errors?: CommonCodeFormErrors }
| { status: 'success' };
export const INITIAL_COMMON_CODE_FORM_STATE: CommonCodeFormState = {
status: 'idle',
};
/** 코드값 공통 검증 — 그룹의 `comCd`와 상세의 `comDtlCd`가 같은 규칙을 쓴다. */
function validateCode(
raw: string,
label: string
): { value: string; error?: string } {
// 코드는 대문자로 정규화한다 — 소문자로 저장되면 조회 조건과 어긋난다.
const value = raw.trim().toUpperCase();
if (!value) {
return { value, error: `${label}를 입력해 주세요.` };
}
if (value.length > CODE_MAX_LENGTH) {
return {
value,
error: `${label}는 ${CODE_MAX_LENGTH}자 이내로 입력해 주세요.`,
};
}
if (!CODE_PATTERN.test(value)) {
return { value, error: `${label}는 영문 대문자·숫자·_만 사용할 수 있습니다.` };
}
return { value };
}
function validateName(raw: string, label: string): { value: string; error?: string } {
const value = raw.trim();
if (!value) {
return { value, error: `${label}을 입력해 주세요.` };
}
if (value.length > NAME_MAX_LENGTH) {
return {
value,
error: `${label}은 ${NAME_MAX_LENGTH}자 이내로 입력해 주세요.`,
};
}
return { value };
}
function validateDescription(raw: string): { value: string; error?: string } {
const value = raw.trim();
if (value.length > DESCRIPTION_MAX_LENGTH) {
return {
value,
error: `설명은 ${DESCRIPTION_MAX_LENGTH}자 이내로 입력해 주세요.`,
};
}
return { value };
}
/** 시안 공통코드 등록/수정 — 수정은 코드ID가 읽기 전용이라 값이 폼이 아니라 경로에서 온다. */
export function validateCommonCodeGroup(
values: CommonCodeGroupValues
): ValidationResult<CommonCodeGroupValues> {
const errors: CommonCodeFormErrors = {};
const code = validateCode(values.comCd, '코드ID');
if (code.error) {
errors.comCd = code.error;
}
const name = validateName(values.name, '코드ID명');
if (name.error) {
errors.name = name.error;
}
const description = validateDescription(values.description);
if (description.error) {
errors.description = description.error;
}
if (Object.keys(errors).length > 0) {
return { ok: false, errors };
}
return {
ok: true,
values: {
comCd: code.value,
name: name.value,
description: description.value,
},
};
}
/** 시안 상세코드 등록/수정. */
export function validateCommonCodeDetail(
values: CommonCodeDetailValues
): ValidationResult<CommonCodeDetailValues> {
const errors: CommonCodeFormErrors = {};
const group = validateCode(values.comCd, '코드ID');
if (group.error) {
// 상세코드 팝업의 코드ID는 좌측 선택에서 오는 읽기 전용 값이라, 여기가 비었다는 것은
// 공통코드를 고르지 않고 저장이 시도됐다는 뜻이다.
errors.comCd = '공통코드를 먼저 선택해 주세요.';
}
const code = validateCode(values.comDtlCd, '코드');
if (code.error) {
errors.comDtlCd = code.error;
}
const name = validateName(values.name, '코드값의미');
if (name.error) {
errors.name = name.error;
}
const description = validateDescription(values.description);
if (description.error) {
errors.description = description.error;
}
if (!Number.isInteger(values.sortSeq) || values.sortSeq < 1) {
errors.sortSeq = '정렬번호는 1 이상의 숫자로 입력해 주세요.';
}
if (Object.keys(errors).length > 0) {
return { ok: false, errors };
}
return {
ok: true,
values: {
comCd: group.value,
comDtlCd: code.value,
name: name.value,
description: description.value,
sortSeq: values.sortSeq,
},
};
}