7f59b94dcf
Rebrand repository from "Re:Link" to "Startover" across the codebase. Updates include package names and scopes (@relink/* -> @startover/*), import paths, Next.js transpile settings, vitest name, UI text and docs, Dockerfile and CI/workflow names, deploy scripts and repo paths, and example/production env values. Also add auth-related env vars, an apps/web .env symlink, and small formatting/typing cleanups in several TSX/TS files and tests to accommodate the rename.
877 lines
16 KiB
Markdown
877 lines
16 KiB
Markdown
# Startover `schema.prisma` 설계 초안
|
|
|
|
## 목적
|
|
|
|
이 문서는 Startover MVP의 데이터 모델을 실제 `schema.prisma`로 내리기 전에, 엔티티/필드/관계/인덱스/제약 조건을 고정하기 위한 초안이다.
|
|
|
|
대상 범위는 `assisted marketplace MVP`이며, 아래 원칙을 따른다.
|
|
|
|
- 운영자 개입형 플로우를 우선한다.
|
|
- 정보 공개는 제한 공개가 기본값이다.
|
|
- 결제/정산/분쟁은 append-only 로그와 감사 로그를 남긴다.
|
|
- 마스터 데이터는 enum이 아니라 테이블과 seed로 관리한다.
|
|
- 하드코딩 대신 `PolicyVersion`, `RegionHierarchy`, `IndustryTaxonomy`를 참조한다.
|
|
|
|
## 설계 원칙
|
|
|
|
### 네이밍
|
|
|
|
- Prisma 모델명: `PascalCase`
|
|
- Prisma 필드명: `camelCase`
|
|
- 실제 DB 테이블/컬럼명: `snake_case`
|
|
- 실제 DB명은 `@@map`, `@map`으로 매핑한다.
|
|
|
|
### 공통 타입
|
|
|
|
- 내부 PK: `BigInt @id @default(autoincrement())`
|
|
- 외부 노출용 ID: `publicId String @unique`
|
|
- 금액: `Decimal @db.Decimal(14, 2)`
|
|
- 시각: `DateTime @db.Timestamptz(6)`
|
|
- 상태: 안정적인 값만 enum으로 관리
|
|
- 자주 바뀌는 코드: enum보다 `String` 코드 또는 마스터 테이블 사용
|
|
|
|
### 공통 컬럼
|
|
|
|
대부분의 트랜잭션성 모델은 아래 공통 컬럼을 가진다.
|
|
|
|
- `id`
|
|
- `publicId` 또는 비즈니스 고유 키
|
|
- `createdAt`
|
|
- `updatedAt`
|
|
- 필요한 경우 `deletedAt`
|
|
|
|
append-only 성격의 모델은 `updatedAt`, `deletedAt` 없이 생성 시점만 가진다.
|
|
|
|
## 모델 설계
|
|
|
|
### 1. 사용자/권한
|
|
|
|
#### `User`
|
|
|
|
목적:
|
|
사용자 기본 계정과 로그인 주체를 관리한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `publicId`
|
|
- `email`
|
|
- `emailNormalized`
|
|
- `phone`
|
|
- `phoneNormalized`
|
|
- `name`
|
|
- `primaryRole`
|
|
- `status`
|
|
- `emailVerifiedAt`
|
|
- `lastLoginAt`
|
|
- `createdAt`
|
|
- `updatedAt`
|
|
|
|
관계:
|
|
|
|
- `profiles UserProfile[]`
|
|
- `consents UserConsent[]`
|
|
- `stores Store[]`
|
|
- `vendors Vendor[]`
|
|
|
|
인덱스:
|
|
|
|
- `@unique(emailNormalized)`
|
|
- `@@index([primaryRole, status])`
|
|
|
|
#### `UserProfile`
|
|
|
|
목적:
|
|
역할별 추가 프로필 정보를 확장 가능하게 저장한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `userId`
|
|
- `profileType`
|
|
- `businessRegistrationNumber`
|
|
- `metadataJson`
|
|
- `createdAt`
|
|
- `updatedAt`
|
|
|
|
#### `UserConsent`
|
|
|
|
목적:
|
|
개인정보, 정보 공개, 마케팅, 제3자 제공 동의를 버전 단위로 저장한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `userId`
|
|
- `consentType`
|
|
- `policyVersionId`
|
|
- `isGranted`
|
|
- `grantedAt`
|
|
- `revokedAt`
|
|
|
|
### 2. 매장 공급 도메인
|
|
|
|
#### `Store`
|
|
|
|
목적:
|
|
폐업자가 등록한 매장의 중심 aggregate다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `publicId`
|
|
- `ownerUserId`
|
|
- `listingTitle`
|
|
- `publicSummary`
|
|
- `industryLeafId`
|
|
- `regionClusterId`
|
|
- `regionLeafId`
|
|
- `roadAddress`
|
|
- `detailAddress`
|
|
- `latitude`
|
|
- `longitude`
|
|
- `reviewStatus`
|
|
- `publicationStatus`
|
|
- `dealStatus`
|
|
- `policyVersionId`
|
|
- `publishedAt`
|
|
- `approvedAt`
|
|
- `rejectedAt`
|
|
- `createdAt`
|
|
- `updatedAt`
|
|
|
|
핵심 설계:
|
|
|
|
- `StoreStatus` 단일 컬럼 대신 `reviewStatus`, `publicationStatus`, `dealStatus` 3축으로 분리한다.
|
|
- 검색/필터에 쓰는 값은 JSON이 아니라 정식 컬럼으로 둔다.
|
|
- 주소 공개 정책 때문에 상세 주소는 API 응답 단계에서 권한 필터링한다.
|
|
|
|
인덱스:
|
|
|
|
- `@@index([ownerUserId, createdAt])`
|
|
- `@@index([reviewStatus, createdAt])`
|
|
- `@@index([regionClusterId, industryLeafId, publicationStatus, dealStatus, publishedAt])`
|
|
|
|
#### `StoreLease`
|
|
|
|
목적:
|
|
임대/권리금 관련 정보를 1:1 확장 테이블로 저장한다.
|
|
|
|
주요 필드:
|
|
|
|
- `storeId`
|
|
- `depositAmount`
|
|
- `monthlyRentAmount`
|
|
- `maintenanceFeeAmount`
|
|
- `premiumAmount`
|
|
- `transferable`
|
|
- `leaseExpiresAt`
|
|
- `remainingLeaseMonths`
|
|
- `createdAt`
|
|
- `updatedAt`
|
|
|
|
제약:
|
|
|
|
- `storeId` unique
|
|
- 금액 필드는 모두 `>= 0`
|
|
|
|
#### `StoreFacility`
|
|
|
|
목적:
|
|
F&B 필터링과 인수 판단에 필요한 설비 정보를 저장한다.
|
|
|
|
주요 필드:
|
|
|
|
- `storeId`
|
|
- `exclusiveAreaSqm`
|
|
- `floorLevel`
|
|
- `seatCount`
|
|
- `hasGas`
|
|
- `hasDrainage`
|
|
- `hasDuct`
|
|
- `electricCapacityKw`
|
|
- `kitchenEquipmentSummary`
|
|
- `parkingCount`
|
|
- `restroomType`
|
|
- `createdAt`
|
|
- `updatedAt`
|
|
|
|
제약:
|
|
|
|
- `storeId` unique
|
|
- `exclusiveAreaSqm > 0`
|
|
|
|
#### `StoreLifecycle`
|
|
|
|
목적:
|
|
폐업/인수/철거 일정 정보를 저장한다.
|
|
|
|
주요 필드:
|
|
|
|
- `storeId`
|
|
- `closingPlannedAt`
|
|
- `takeoverAvailableAt`
|
|
- `demolitionPreferredAt`
|
|
- `vacateByAt`
|
|
- `isCurrentlyOperating`
|
|
- `urgencyLevel`
|
|
- `createdAt`
|
|
- `updatedAt`
|
|
|
|
#### `StorePhoto`
|
|
|
|
목적:
|
|
매장 사진과 공개 범위를 저장한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `storeId`
|
|
- `storageKey`
|
|
- `photoCategory`
|
|
- `visibilityScope`
|
|
- `sortOrder`
|
|
- `uploadedByUserId`
|
|
- `checksumSha256`
|
|
- `width`
|
|
- `height`
|
|
- `takenAt`
|
|
- `isRepresentative`
|
|
- `createdAt`
|
|
|
|
인덱스:
|
|
|
|
- `@@index([storeId, sortOrder])`
|
|
|
|
### 3. 매칭 도메인
|
|
|
|
#### `MatchRequest`
|
|
|
|
목적:
|
|
창업자/업체/운영자 추천 기반 매칭 요청을 관리한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `publicId`
|
|
- `storeId`
|
|
- `matchType`
|
|
- `requesterUserId`
|
|
- `requesterVendorId`
|
|
- `sourceType`
|
|
- `status`
|
|
- `operatorAssigneeId`
|
|
- `message`
|
|
- `decisionReasonCode`
|
|
- `acceptedAt`
|
|
- `rejectedAt`
|
|
- `closedAt`
|
|
- `createdAt`
|
|
- `updatedAt`
|
|
|
|
핵심 설계:
|
|
|
|
- `ACQUISITION`, `DEMOLITION`, `INTERIOR`를 한 테이블로 관리한다.
|
|
- `matchType`에 따라 필요한 FK가 달라지므로 raw SQL `CHECK` 제약을 둔다.
|
|
|
|
인덱스:
|
|
|
|
- `@@index([storeId, status, createdAt])`
|
|
- `@@index([requesterUserId, status, createdAt])`
|
|
- `@@index([requesterVendorId, status, createdAt])`
|
|
- partial unique index:
|
|
- 열린 상태에서 동일 사용자/동일 매장/동일 요청 타입 중복 금지
|
|
|
|
### 4. 지원금 도메인
|
|
|
|
#### `SubsidyCase`
|
|
|
|
목적:
|
|
매장 기준 지원금 진행 건을 관리한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `publicId`
|
|
- `storeId`
|
|
- `applicantUserId`
|
|
- `programCode`
|
|
- `status`
|
|
- `eligibilityResult`
|
|
- `policyVersionId`
|
|
- `operatorAssigneeId`
|
|
- `checklistVersionCode`
|
|
- `submissionReadyAt`
|
|
- `submittedAt`
|
|
- `reviewedAt`
|
|
- `rejectionReasonCode`
|
|
- `eligibilitySnapshotJson`
|
|
- `createdAt`
|
|
- `updatedAt`
|
|
|
|
인덱스:
|
|
|
|
- `@@index([storeId, status, createdAt])`
|
|
- `@@index([applicantUserId, status, createdAt])`
|
|
- `@@index([operatorAssigneeId, status, updatedAt])`
|
|
- partial unique index:
|
|
- 같은 `storeId + programCode` 조합의 active case는 1개
|
|
|
|
#### `SubsidyChecklistItem`
|
|
|
|
목적:
|
|
정책 버전에 따라 생성된 체크리스트 항목을 저장한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `subsidyCaseId`
|
|
- `itemCode`
|
|
- `isRequired`
|
|
- `status`
|
|
- `checkedAt`
|
|
- `checkedByUserId`
|
|
|
|
#### `SubsidyDocument`
|
|
|
|
목적:
|
|
지원금 첨부 서류와 검토 상태를 관리한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `subsidyCaseId`
|
|
- `documentTypeCode`
|
|
- `storageKey`
|
|
- `reviewStatus`
|
|
- `uploadedByUserId`
|
|
- `uploadedAt`
|
|
- `reviewedAt`
|
|
- `reviewedByUserId`
|
|
|
|
### 5. 업체 도메인
|
|
|
|
#### `Vendor`
|
|
|
|
목적:
|
|
철거/인테리어 업체의 기본 프로필을 관리한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `publicId`
|
|
- `ownerUserId`
|
|
- `vendorType`
|
|
- `businessName`
|
|
- `contactName`
|
|
- `contactPhoneNormalized`
|
|
- `businessRegistrationNumber`
|
|
- `serviceIntro`
|
|
- `primaryRegionId`
|
|
- `certificationStatus`
|
|
- `latestCertificationId`
|
|
- `deletedAt`
|
|
- `createdAt`
|
|
- `updatedAt`
|
|
|
|
인덱스:
|
|
|
|
- `@@index([ownerUserId])`
|
|
- `@@index([vendorType, certificationStatus, createdAt])`
|
|
|
|
#### `VendorCoverageRegion`
|
|
|
|
목적:
|
|
업체 서비스 가능 지역을 다대다 조인으로 관리한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `vendorId`
|
|
- `regionId`
|
|
- `isPrimary`
|
|
- `createdAt`
|
|
|
|
인덱스:
|
|
|
|
- `@@unique([vendorId, regionId])`
|
|
- `@@index([regionId, vendorId])`
|
|
|
|
#### `VendorCertification`
|
|
|
|
목적:
|
|
업체 인증 이력을 append-only에 가깝게 유지한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `vendorId`
|
|
- `status`
|
|
- `requestedScopeCode`
|
|
- `documentChecklistJson`
|
|
- `appliedAt`
|
|
- `reviewedAt`
|
|
- `reviewedByUserId`
|
|
- `reasonCode`
|
|
- `validUntil`
|
|
- `createdAt`
|
|
- `updatedAt`
|
|
|
|
### 6. 신뢰 인프라 도메인
|
|
|
|
#### `Contract`
|
|
|
|
목적:
|
|
매칭에서 생성된 계약의 현재 상태와 참여자를 관리한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `publicId`
|
|
- `matchRequestId`
|
|
- `storeId`
|
|
- `contractType`
|
|
- `status`
|
|
- `createdByUserId`
|
|
- `storeOwnerUserId`
|
|
- `buyerUserId`
|
|
- `vendorId`
|
|
- `policyVersionId`
|
|
- `templateCode`
|
|
- `currentVersionId`
|
|
- `escrowStatus`
|
|
- `signedAt`
|
|
- `effectiveAt`
|
|
- `completedAt`
|
|
- `cancelledAt`
|
|
- `createdAt`
|
|
- `updatedAt`
|
|
|
|
인덱스:
|
|
|
|
- `@@unique([matchRequestId])`
|
|
- `@@index([storeId, status, createdAt])`
|
|
- `@@index([vendorId, status, createdAt])`
|
|
- `@@index([buyerUserId, status, createdAt])`
|
|
|
|
#### `ContractVersion`
|
|
|
|
목적:
|
|
계약 문서를 불변 버전으로 저장한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `contractId`
|
|
- `versionNo`
|
|
- `templateCode`
|
|
- `templateVersion`
|
|
- `documentStorageKey`
|
|
- `documentSha256`
|
|
- `renderedSnapshotJson`
|
|
- `changeSummary`
|
|
- `createdByUserId`
|
|
- `createdAt`
|
|
|
|
인덱스:
|
|
|
|
- `@@unique([contractId, versionNo])`
|
|
|
|
#### `SignatureEvidence`
|
|
|
|
목적:
|
|
전자서명 또는 동의 증적을 저장한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `contractId`
|
|
- `contractVersionId`
|
|
- `signerRole`
|
|
- `signerUserId`
|
|
- `evidenceType`
|
|
- `providerCode`
|
|
- `providerEventId`
|
|
- `signedAt`
|
|
- `signatureHash`
|
|
- `payloadJson`
|
|
- `ipAddress`
|
|
- `userAgent`
|
|
- `createdAt`
|
|
|
|
인덱스:
|
|
|
|
- `@@index([contractVersionId, signedAt])`
|
|
- 가능한 경우 `@unique(providerEventId)`
|
|
|
|
#### `EscrowTransaction`
|
|
|
|
목적:
|
|
결제/환불/정산/보류 관련 금전 이벤트를 immutable ledger 형태로 저장한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `contractId`
|
|
- `transactionType`
|
|
- `providerCode`
|
|
- `providerTransactionId`
|
|
- `status`
|
|
- `amount`
|
|
- `currencyCode`
|
|
- `requestedByUserId`
|
|
- `approvedByUserId`
|
|
- `idempotencyKey`
|
|
- `rawPayloadJson`
|
|
- `occurredAt`
|
|
- `createdAt`
|
|
|
|
인덱스:
|
|
|
|
- `@unique(providerTransactionId)`
|
|
- `@unique(idempotencyKey)`
|
|
- `@@index([contractId, occurredAt])`
|
|
- `@@index([status, occurredAt])`
|
|
|
|
#### `InspectionRecord`
|
|
|
|
목적:
|
|
검수 요청과 검토 결과를 저장한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `contractId`
|
|
- `inspectionType`
|
|
- `status`
|
|
- `submittedByUserId`
|
|
- `submittedAt`
|
|
- `reviewedByUserId`
|
|
- `reviewedAt`
|
|
- `reviewMemo`
|
|
- `evidencePayloadJson`
|
|
- `createdAt`
|
|
- `updatedAt`
|
|
|
|
#### `DisputeCase`
|
|
|
|
목적:
|
|
분쟁 접수, 조사, 중재, 종료 상태를 관리한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `contractId`
|
|
- `openedByUserId`
|
|
- `assignedOperatorId`
|
|
- `status`
|
|
- `reasonCode`
|
|
- `description`
|
|
- `resolutionCode`
|
|
- `resolutionSummary`
|
|
- `evidencePayloadJson`
|
|
- `openedAt`
|
|
- `resolvedAt`
|
|
- `resolvedByUserId`
|
|
- `createdAt`
|
|
- `updatedAt`
|
|
|
|
인덱스:
|
|
|
|
- `@@index([contractId, status, openedAt])`
|
|
- `@@index([assignedOperatorId, status, openedAt])`
|
|
- partial unique index:
|
|
- 계약당 active dispute는 1건
|
|
|
|
### 7. 운영/분석 도메인
|
|
|
|
#### `PolicyVersion`
|
|
|
|
목적:
|
|
정책 버전을 모델에 귀속시키기 위한 기준 테이블이다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `policyType`
|
|
- `versionCode`
|
|
- `contentHash`
|
|
- `effectiveFrom`
|
|
- `isActive`
|
|
- `createdAt`
|
|
|
|
#### `AuditLog`
|
|
|
|
목적:
|
|
누가 무엇을 왜 바꿨는지를 운영 감사 기준으로 남긴다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `resourceType`
|
|
- `resourceId`
|
|
- `actionType`
|
|
- `actorUserId`
|
|
- `actorRole`
|
|
- `reasonCode`
|
|
- `memo`
|
|
- `beforeJson`
|
|
- `afterJson`
|
|
- `requestId`
|
|
- `correlationId`
|
|
- `ipHash`
|
|
- `userAgent`
|
|
- `createdAt`
|
|
|
|
인덱스:
|
|
|
|
- `@@index([resourceType, resourceId, createdAt])`
|
|
- `@@index([actorUserId, createdAt])`
|
|
- `@@index([actionType, createdAt])`
|
|
|
|
#### `EventLog`
|
|
|
|
목적:
|
|
도메인 이벤트를 append-only로 보관한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `aggregateType`
|
|
- `aggregateId`
|
|
- `eventName`
|
|
- `eventVersion`
|
|
- `eventKey`
|
|
- `payloadJson`
|
|
- `actorUserId`
|
|
- `causationId`
|
|
- `correlationId`
|
|
- `piiLevel`
|
|
- `occurredAt`
|
|
- `recordedAt`
|
|
|
|
인덱스:
|
|
|
|
- `@unique(eventKey)`
|
|
- `@@index([aggregateType, aggregateId, occurredAt])`
|
|
- `@@index([eventName, occurredAt])`
|
|
|
|
#### `OutboxEvent`
|
|
|
|
목적:
|
|
외부 발행/재시도/실패 처리를 EventLog와 분리해 관리한다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `aggregateType`
|
|
- `aggregateId`
|
|
- `eventName`
|
|
- `payloadJson`
|
|
- `publishStatus`
|
|
- `availableAt`
|
|
- `retryCount`
|
|
- `lastError`
|
|
- `createdAt`
|
|
|
|
인덱스:
|
|
|
|
- `@@index([publishStatus, availableAt])`
|
|
|
|
#### `IdempotencyKey`
|
|
|
|
목적:
|
|
웹훅과 중복 요청 방지를 위한 키 저장소다.
|
|
|
|
주요 필드:
|
|
|
|
- `id`
|
|
- `scope`
|
|
- `idempotencyKey`
|
|
- `requestHash`
|
|
- `responseHash`
|
|
- `expiresAt`
|
|
- `createdAt`
|
|
|
|
인덱스:
|
|
|
|
- `@@unique([scope, idempotencyKey])`
|
|
|
|
## Enum 초안
|
|
|
|
### 고정 enum 후보
|
|
|
|
- `UserRole`
|
|
- `CLOSING_OWNER`
|
|
- `FOUNDER`
|
|
- `VENDOR_MANAGER`
|
|
- `OPS_MANAGER`
|
|
- `SUBSIDY_OPERATOR`
|
|
- `TRUST_OPERATOR`
|
|
- `FINANCE_OPERATOR`
|
|
- `SUPER_ADMIN`
|
|
- `UserStatus`
|
|
- `PENDING_VERIFICATION`
|
|
- `ACTIVE`
|
|
- `SUSPENDED`
|
|
- `DEACTIVATED`
|
|
- `StoreReviewStatus`
|
|
- `DRAFT`
|
|
- `SUBMITTED`
|
|
- `REVIEWING`
|
|
- `APPROVED`
|
|
- `REJECTED`
|
|
- `StorePublicationStatus`
|
|
- `PRIVATE`
|
|
- `RESTRICTED`
|
|
- `PUBLISHED`
|
|
- `UNPUBLISHED`
|
|
- `StoreDealStatus`
|
|
- `OPEN`
|
|
- `MATCHING`
|
|
- `RESERVED`
|
|
- `CONTRACTED`
|
|
- `CLOSED`
|
|
- `CANCELLED`
|
|
- `PhotoCategory`
|
|
- `EXTERIOR`
|
|
- `INTERIOR`
|
|
- `KITCHEN`
|
|
- `EQUIPMENT`
|
|
- `FLOOR_PLAN`
|
|
- `DOCUMENT`
|
|
- `VisibilityScope`
|
|
- `INTERNAL`
|
|
- `MATCHED_ONLY`
|
|
- `PUBLIC_SUMMARY`
|
|
- `MatchType`
|
|
- `ACQUISITION`
|
|
- `DEMOLITION`
|
|
- `INTERIOR`
|
|
- `MatchRequestStatus`
|
|
- `OPEN`
|
|
- `REVIEWING`
|
|
- `ACCEPTED`
|
|
- `REJECTED`
|
|
- `CONTRACTING`
|
|
- `EXPIRED`
|
|
- `CANCELLED`
|
|
- `COMPLETED`
|
|
- `SubsidyCaseStatus`
|
|
- `DRAFT`
|
|
- `ELIGIBILITY_CHECKED`
|
|
- `DOCUMENTS_PENDING`
|
|
- `REVIEWING`
|
|
- `READY_TO_SUBMIT`
|
|
- `SUBMITTED`
|
|
- `APPROVED`
|
|
- `REJECTED`
|
|
- `CLOSED`
|
|
- `EligibilityResult`
|
|
- `UNKNOWN`
|
|
- `ELIGIBLE`
|
|
- `CONDITIONALLY_ELIGIBLE`
|
|
- `NOT_ELIGIBLE`
|
|
- `VendorType`
|
|
- `DEMOLITION`
|
|
- `INTERIOR`
|
|
- `BOTH`
|
|
- `VendorCertificationStatus`
|
|
- `APPLIED`
|
|
- `REVIEWING`
|
|
- `APPROVED`
|
|
- `REJECTED`
|
|
- `SUSPENDED`
|
|
- `EXPIRED`
|
|
- `ContractType`
|
|
- `ACQUISITION`
|
|
- `DEMOLITION`
|
|
- `INTERIOR`
|
|
- `ContractStatus`
|
|
- `DRAFT`
|
|
- `GENERATED`
|
|
- `SIGNING`
|
|
- `SIGNED`
|
|
- `ACTIVE`
|
|
- `COMPLETED`
|
|
- `CANCELLED`
|
|
- `TERMINATED`
|
|
- `EscrowStatus`
|
|
- `NOT_STARTED`
|
|
- `DEPOSIT_PENDING`
|
|
- `HOLDING`
|
|
- `RELEASE_REVIEW`
|
|
- `RELEASED`
|
|
- `REFUNDED`
|
|
- `DISPUTED`
|
|
- `EscrowTransactionType`
|
|
- `DEPOSIT`
|
|
- `RELEASE`
|
|
- `REFUND`
|
|
- `ADJUSTMENT`
|
|
- `HOLD`
|
|
- `InspectionStatus`
|
|
- `REQUESTED`
|
|
- `SUBMITTED`
|
|
- `REVIEWING`
|
|
- `APPROVED`
|
|
- `REJECTED`
|
|
- `DisputeStatus`
|
|
- `OPEN`
|
|
- `INVESTIGATING`
|
|
- `MEDIATING`
|
|
- `RESOLVED`
|
|
- `CLOSED`
|
|
- `RegionType`
|
|
- `COUNTRY`
|
|
- `SIDO`
|
|
- `SIGUNGU`
|
|
- `ADMIN_DONG`
|
|
- `COMMERCIAL_AREA`
|
|
- `BETA_CLUSTER`
|
|
|
|
### enum으로 두지 않을 값
|
|
|
|
아래는 자주 바뀌거나 운영 정책에 따라 변할 수 있으므로 enum보다 코드 값 또는 마스터 데이터로 둔다.
|
|
|
|
- `reasonCode`
|
|
- `programCode`
|
|
- `documentTypeCode`
|
|
- `actionType`
|
|
- `eventName`
|
|
- `templateCode`
|
|
|
|
## 마이그레이션 전략
|
|
|
|
### 기본 원칙
|
|
|
|
- 운영 DB 변경은 항상 migration 파일로 관리한다.
|
|
- 스키마 변경과 데이터 backfill은 분리한다.
|
|
- 대용량 테이블 인덱스는 raw SQL로 `CREATE INDEX CONCURRENTLY`를 사용한다.
|
|
- Prisma가 표현하기 어려운 partial unique index, check constraint, PostGIS index는 custom SQL migration으로 보강한다.
|
|
|
|
### 첫 스키마에서 넣을 custom SQL 후보
|
|
|
|
1. 열린 매칭 요청 중복 방지 partial unique index
|
|
2. 지원금 active case 중복 방지 partial unique index
|
|
3. 계약당 active dispute 1건 제한 partial unique index
|
|
4. `amount >= 0`, `exclusive_area_sqm > 0` 같은 check constraint
|
|
5. 향후 `locationPoint`를 도입할 경우 GIST index
|
|
|
|
## MVP에서 나중으로 미룰 모델
|
|
|
|
아래 모델은 개념만 유지하고 실제 첫 `schema.prisma`에서는 보류할 수 있다.
|
|
|
|
- `PriceEstimate`
|
|
- `VendorQuote`
|
|
- `StoreAsset`
|
|
- `AlertSubscription`
|
|
- `NotificationDeliveryLog`
|
|
- `KpiSnapshot`
|
|
|
|
## 바로 다음 단계
|
|
|
|
1. 이 문서를 기준으로 실제 `prisma/schema.prisma` 초안을 생성한다.
|
|
2. `docs/master-data/beta-master-data.md`와 FK 관계를 맞춘다.
|
|
3. `D001`, `D002`, `D003` 정책 문서가 완성되면 nullable/required 필드를 다시 조정한다.
|