16bd2cb92a
- 모노레포 구조 (Turborepo + pnpm): @relink/domain, @relink/shared, @relink/infrastructure, @relink/database, @relink/web - 도메인 레이어: 매장(store), 매칭(matching), 업체(vendor), 보조금(subsidy), 계약/에스크로(contract) TDD 완료 (158 단위 테스트) - 서비스 레이어: 전 도메인 서비스 함수 + 통합 테스트 (58 테스트) - 프론트엔드: Next.js 15 App Router, 13개 페이지 (사용자 6 + 관리자 7) - 인프라: PostgreSQL 16 + PostGIS, Prisma ORM, Docker Compose, AuditLog + OutboxEvent 패턴 - .env 파일 포함 (로컬 개발 기본값만 포함, 실제 시크릿 없음) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
52 lines
1.5 KiB
TypeScript
52 lines
1.5 KiB
TypeScript
import { success, failure, appError, type Result, type AppError } from '@relink/shared';
|
|
|
|
export type ContractType = 'ACQUISITION' | 'DEMOLITION' | 'INTERIOR';
|
|
|
|
export interface CreateContractInput {
|
|
readonly matchRequestStatus: string;
|
|
readonly contractType: ContractType;
|
|
readonly templateCode: string;
|
|
readonly policyVersionId: string;
|
|
}
|
|
|
|
export interface ContractDraft {
|
|
readonly contractType: ContractType;
|
|
readonly status: 'DRAFT';
|
|
readonly templateCode: string;
|
|
readonly policyVersionId: string;
|
|
}
|
|
|
|
export function createContract(
|
|
input: CreateContractInput,
|
|
): Result<ContractDraft, AppError> {
|
|
// U021: 수락된 매칭만 계약 생성 가능
|
|
if (input.matchRequestStatus !== 'ACCEPTED') {
|
|
return failure(
|
|
appError('MATCH_NOT_ACCEPTED', '수락된 매칭 요청만 계약을 생성할 수 있습니다.', {
|
|
matchRequestStatus: input.matchRequestStatus,
|
|
}),
|
|
);
|
|
}
|
|
|
|
// U022: 템플릿 코드 필수
|
|
if (!input.templateCode.trim()) {
|
|
return failure(
|
|
appError('VALIDATION_ERROR', '계약 템플릿 코드는 필수입니다.', { field: 'templateCode' }),
|
|
);
|
|
}
|
|
|
|
// U022: 정책 버전 필수
|
|
if (!input.policyVersionId.trim()) {
|
|
return failure(
|
|
appError('VALIDATION_ERROR', '정책 버전은 필수입니다.', { field: 'policyVersionId' }),
|
|
);
|
|
}
|
|
|
|
return success({
|
|
contractType: input.contractType,
|
|
status: 'DRAFT' as const,
|
|
templateCode: input.templateCode,
|
|
policyVersionId: input.policyVersionId,
|
|
});
|
|
}
|