summaryrefslogtreecommitdiff
path: root/db/schema/contract.ts
diff options
context:
space:
mode:
Diffstat (limited to 'db/schema/contract.ts')
-rw-r--r--db/schema/contract.ts421
1 files changed, 246 insertions, 175 deletions
diff --git a/db/schema/contract.ts b/db/schema/contract.ts
index c14921bb..1d628442 100644
--- a/db/schema/contract.ts
+++ b/db/schema/contract.ts
@@ -169,144 +169,215 @@ export const contractSigners = pgTable("contract_signers", {
export const contractsDetailView = pgView("contracts_detail_view").as((qb) => {
return qb
- .select({
- // Contract primary information
- id: contracts.id,
- contractNo: contracts.contractNo,
- contractName: contracts.contractName,
- status: contracts.status,
- startDate: contracts.startDate,
- endDate: contracts.endDate,
-
- // Project information
- projectId: contracts.projectId,
- projectCode: projects.code,
- projectName: projects.name,
-
- // Vendor information
- vendorId: contracts.vendorId,
- vendorName: vendors.vendorName,
-
- // Payment and delivery details
- paymentTerms: contracts.paymentTerms,
- deliveryTerms: contracts.deliveryTerms,
- deliveryDate: contracts.deliveryDate,
- deliveryLocation: contracts.deliveryLocation,
-
- // Financial information
- currency: contracts.currency,
- totalAmount: contracts.totalAmount,
- discount: contracts.discount,
- tax: contracts.tax,
- shippingFee: contracts.shippingFee,
- netTotal: contracts.netTotal,
-
- // Additional settings
- partialShippingAllowed: contracts.partialShippingAllowed,
- partialPaymentAllowed: contracts.partialPaymentAllowed,
- remarks: contracts.remarks,
- version: contracts.version,
-
- // Timestamps
- createdAt: contracts.createdAt,
- updatedAt: contracts.updatedAt,
-
- // Electronic signature status - ADDED .as('has_signature') here
- hasSignature: sql<boolean>`EXISTS (
- SELECT 1
- FROM ${contractEnvelopes}
- WHERE ${contractEnvelopes.contractId} = ${contracts.id}
- )`.as('has_signature'),
- // --- 전자서명 이력 (Envelope) + 서명자(Signer)를 JSON 으로 중첩한 배열 ---
- envelopes: sql<string>`COALESCE((
- SELECT json_agg(
- json_build_object(
- 'id', ce.id,
- 'envelopeId', ce.envelope_id,
- 'documentId', ce.document_id,
- 'envelopeStatus', ce.envelope_status,
- 'fileName', ce.file_name,
- 'filePath', ce.file_path,
- 'createdAt', ce.created_at,
- 'updatedAt', ce.updated_at,
- 'signers', (
- SELECT json_agg(
- json_build_object(
- 'id', cs.id,
- 'vendorContactId', cs.vendor_contact_id,
- 'signerType', cs.signer_type,
- 'signerEmail', cs.signer_email,
- 'signerName', cs.signer_name,
- 'signerPosition', cs.signer_position,
- 'signerStatus', cs.signer_status,
- 'signedAt', cs.signed_at
+ .select({
+ // Contract primary information
+ id: contracts.id,
+ contractNo: contracts.contractNo,
+ contractName: contracts.contractName,
+ status: contracts.status,
+ startDate: contracts.startDate,
+ endDate: contracts.endDate,
+
+ // Project information
+ projectId: contracts.projectId,
+ projectCode: projects.code,
+ projectName: projects.name,
+
+ // Vendor information
+ vendorId: contracts.vendorId,
+ vendorName: vendors.vendorName,
+
+ // Payment and delivery details
+ paymentTerms: contracts.paymentTerms,
+ deliveryTerms: contracts.deliveryTerms,
+ deliveryDate: contracts.deliveryDate,
+ deliveryLocation: contracts.deliveryLocation,
+
+ // Financial information
+ currency: contracts.currency,
+ totalAmount: contracts.totalAmount,
+ discount: contracts.discount,
+ tax: contracts.tax,
+ shippingFee: contracts.shippingFee,
+ netTotal: contracts.netTotal,
+
+ // Additional settings
+ partialShippingAllowed: contracts.partialShippingAllowed,
+ partialPaymentAllowed: contracts.partialPaymentAllowed,
+ remarks: contracts.remarks,
+ version: contracts.version,
+
+ // Timestamps
+ createdAt: contracts.createdAt,
+ updatedAt: contracts.updatedAt,
+
+ // Electronic signature status
+ hasSignature: sql<boolean>`EXISTS (
+ SELECT 1
+ FROM ${contractEnvelopes}
+ WHERE ${contractEnvelopes.contractId} = ${contracts.id}
+ )`.as("has_signature"),
+ // hasItme: sql<boolean>`EXISTS (
+ // SELECT 1
+ // FROM ${contractItems}
+ // WHERE ${contractItems.contractId} = ${contracts.id}
+ // )`.as("has_signature"),
+
+ // =========================
+ // 1) contract_items -> JSON
+ // =========================
+ // 'items' (or "contractItems")라는 필드를 JSON 배열로 가져오기
+ items: sql<string>`COALESCE((
+ SELECT json_agg(
+ json_build_object(
+ 'id', ci.id,
+ 'itemId', ci.item_id,
+ 'description', ci.description,
+ 'quantity', ci.quantity,
+ 'unitPrice', ci.unit_price,
+ 'taxRate', ci.tax_rate,
+ 'taxAmount', ci.tax_amount,
+ 'totalLineAmount', ci.total_line_amount,
+ 'remark', ci.remark,
+ 'createdAt', ci.created_at,
+ 'updatedAt', ci.updated_at
+ )
+ )
+ FROM ${contractItems} AS ci
+ WHERE ci.contract_id = ${contracts.id}
+ ), '[]')`.as("items"),
+
+ // =========================
+ // 2) contract_envelopes -> JSON
+ // =========================
+ envelopes: sql<string>`COALESCE((
+ SELECT json_agg(
+ json_build_object(
+ 'id', ce.id,
+ 'envelopeId', ce.envelope_id,
+ 'documentId', ce.document_id,
+ 'envelopeStatus', ce.envelope_status,
+ 'fileName', ce.file_name,
+ 'filePath', ce.file_path,
+ 'createdAt', ce.created_at,
+ 'updatedAt', ce.updated_at,
+ 'signers', (
+ SELECT json_agg(
+ json_build_object(
+ 'id', cs.id,
+ 'vendorContactId', cs.vendor_contact_id,
+ 'signerType', cs.signer_type,
+ 'signerEmail', cs.signer_email,
+ 'signerName', cs.signer_name,
+ 'signerPosition', cs.signer_position,
+ 'signerStatus', cs.signer_status,
+ 'signedAt', cs.signed_at
+ )
)
+ FROM ${contractSigners} AS cs
+ WHERE cs.envelope_id = ce.id
)
- FROM ${contractSigners} AS cs
- WHERE cs.envelope_id = ce.id
)
)
- )
- FROM ${contractEnvelopes} AS ce
- WHERE ce.contract_id = ${contracts.id}
- ), '[]')`.as("envelopes")
- })
- .from(contracts)
- .leftJoin(projects, eq(contracts.projectId, projects.id))
- .leftJoin(vendors, eq(contracts.vendorId, vendors.id))
-});
-
-// Type inference for the view
-export type ContractDetail = typeof contractsDetailView.$inferSelect;
-
-
-
-
-// ============ poa (Purchase Order Amendment) ============
+ FROM ${contractEnvelopes} AS ce
+ WHERE ce.contract_id = ${contracts.id}
+ ), '[]')`.as("envelopes"),
+ })
+ .from(contracts)
+ .leftJoin(projects, eq(contracts.projectId, projects.id))
+ .leftJoin(vendors, eq(contracts.vendorId, vendors.id))
+ });
+
+ export interface ContractItemParsed {
+ id: number
+ itemId: number
+ description: string | null
+ quantity: number
+ unitPrice: number | null
+ taxRate: number | null
+ taxAmount: number | null
+ totalLineAmount: number | null
+ remark: string | null
+ createdAt: string
+ updatedAt: string
+ }
+
+ // 기존 Envelope + Signers
+ export interface Envelope {
+ id: number
+ envelopeId: string
+ documentId: string | null
+ envelopeStatus: string | null
+ fileName: string
+ filePath: string
+ createdAt: string
+ updatedAt: string
+ signers?: {
+ id: number
+ vendorContactId: number | null
+ signerType: string
+ signerEmail: string
+ signerName: string
+ signerPosition: string | null
+ signerStatus: string
+ signedAt: string | null
+ }[]
+ }
+
+ // Drizzle가 만들어준 raw type
+ export type ContractDetail = typeof contractsDetailView.$inferSelect;
+
+ // 우리가 UI에서 쓰고 싶은 파싱된 타입
+ export type ContractDetailParsed = Omit<ContractDetail, "envelopes" | "items"> & {
+ envelopes: Envelope[]
+ items: ContractItemParsed[]
+ }
+
+ // ============ poa (Purchase Order Amendment) ============
export const poa = pgTable("poa", {
- // 주 키
- id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
-
- // Form code는 원본과 동일하게 유지
- contractNo: varchar("contract_no", { length: 100 }).notNull(),
-
- // 원본 PO 참조
- originalContractNo: varchar("original_contract_no", { length: 100 })
- .notNull()
- .references(() => contracts.contractNo, { onDelete: "cascade" }),
-
- // 원본 계약 정보
- projectId: integer("project_id")
- .notNull()
- .references(() => projects.id, { onDelete: "cascade" }),
- vendorId: integer("vendor_id")
- .notNull()
- .references(() => vendors.id, { onDelete: "cascade" }),
- originalContractName: varchar("original_contract_name", { length: 255 }).notNull(),
- originalStatus: varchar("original_status", { length: 50 }).notNull(),
-
- // 변경된 납품 조건
- deliveryTerms: text("delivery_terms"), // 변경된 납품 조건
- deliveryDate: date("delivery_date"), // 변경된 납품 기한
- deliveryLocation: varchar("delivery_location", { length: 255 }), // 변경된 납품 장소
-
- // 변경된 가격/금액 관련
- currency: varchar("currency", { length: 10 }), // 변경된 통화
- totalAmount: numeric("total_amount", { precision: 12, scale: 2 }), // 변경된 총 금액
- discount: numeric("discount", { precision: 12, scale: 2 }), // 변경된 할인
- tax: numeric("tax", { precision: 12, scale: 2 }), // 변경된 세금
- shippingFee: numeric("shipping_fee", { precision: 12, scale: 2 }), // 변경된 배송비
- netTotal: numeric("net_total", { precision: 12, scale: 2 }), // 변경된 순 총액
-
- // 변경 사유
- changeReason: text("change_reason"),
-
- // 승인 상태
- approvalStatus: varchar("approval_status", { length: 50 }).default("PENDING"),
-
- // 생성/수정 시각
- createdAt: timestamp("created_at").defaultNow().notNull(),
- updatedAt: timestamp("updated_at").defaultNow().notNull(),
+ // 주 키
+ id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
+
+ // Form code는 원본과 동일하게 유지
+ contractNo: varchar("contract_no", { length: 100 }).notNull(),
+
+ // 원본 PO 참조
+ originalContractNo: varchar("original_contract_no", { length: 100 })
+ .notNull()
+ .references(() => contracts.contractNo, { onDelete: "cascade" }),
+
+ // 원본 계약 정보
+ projectId: integer("project_id")
+ .notNull()
+ .references(() => projects.id, { onDelete: "cascade" }),
+ vendorId: integer("vendor_id")
+ .notNull()
+ .references(() => vendors.id, { onDelete: "cascade" }),
+ originalContractName: varchar("original_contract_name", { length: 255 }).notNull(),
+ originalStatus: varchar("original_status", { length: 50 }).notNull(),
+
+ // 변경된 납품 조건
+ deliveryTerms: text("delivery_terms"), // 변경된 납품 조건
+ deliveryDate: date("delivery_date"), // 변경된 납품 기한
+ deliveryLocation: varchar("delivery_location", { length: 255 }), // 변경된 납품 장소
+
+ // 변경된 가격/금액 관련
+ currency: varchar("currency", { length: 10 }), // 변경된 통화
+ totalAmount: numeric("total_amount", { precision: 12, scale: 2 }), // 변경된 총 금액
+ discount: numeric("discount", { precision: 12, scale: 2 }), // 변경된 할인
+ tax: numeric("tax", { precision: 12, scale: 2 }), // 변경된 세금
+ shippingFee: numeric("shipping_fee", { precision: 12, scale: 2 }), // 변경된 배송비
+ netTotal: numeric("net_total", { precision: 12, scale: 2 }), // 변경된 순 총액
+
+ // 변경 사유
+ changeReason: text("change_reason"),
+
+ // 승인 상태
+ approvalStatus: varchar("approval_status", { length: 50 }).default("PENDING"),
+
+ // 생성/수정 시각
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
})
// 타입 추론
@@ -314,48 +385,48 @@ export type POA = typeof poa.$inferSelect
// ============ poa_detail_view ============
export const poaDetailView = pgView("poa_detail_view").as((qb) => {
- return qb
- .select({
- // POA primary information
- id: poa.id,
- contractNo: poa.contractNo,
- projectId: contracts.projectId,
- vendorId: contracts.vendorId,
- changeReason: poa.changeReason,
- approvalStatus: poa.approvalStatus,
-
- // Original PO information
- originalContractName: sql<string>`${contracts.contractName}`.as('original_contract_name'),
- originalStatus: sql<string>`${contracts.status}`.as('original_status'),
- originalStartDate: sql<Date>`${contracts.startDate}`.as('original_start_date'),
- originalEndDate: sql<Date>`${contracts.endDate}`.as('original_end_date'),
-
- // Changed delivery details
- deliveryTerms: poa.deliveryTerms,
- deliveryDate: poa.deliveryDate,
- deliveryLocation: poa.deliveryLocation,
-
- // Changed financial information
- currency: poa.currency,
- totalAmount: poa.totalAmount,
- discount: poa.discount,
- tax: poa.tax,
- shippingFee: poa.shippingFee,
- netTotal: poa.netTotal,
-
- // Timestamps
- createdAt: poa.createdAt,
- updatedAt: poa.updatedAt,
-
- // Electronic signature status
- hasSignature: sql<boolean>`EXISTS (
- SELECT 1
- FROM ${contractEnvelopes}
- WHERE ${contractEnvelopes.contractId} = ${poa.id}
- )`.as('has_signature'),
- })
- .from(poa)
- .leftJoin(contracts, eq(poa.contractNo, contracts.contractNo))
+ return qb
+ .select({
+ // POA primary information
+ id: poa.id,
+ contractNo: poa.contractNo,
+ projectId: contracts.projectId,
+ vendorId: contracts.vendorId,
+ changeReason: poa.changeReason,
+ approvalStatus: poa.approvalStatus,
+
+ // Original PO information
+ originalContractName: sql<string>`${contracts.contractName}`.as('original_contract_name'),
+ originalStatus: sql<string>`${contracts.status}`.as('original_status'),
+ originalStartDate: sql<Date>`${contracts.startDate}`.as('original_start_date'),
+ originalEndDate: sql<Date>`${contracts.endDate}`.as('original_end_date'),
+
+ // Changed delivery details
+ deliveryTerms: poa.deliveryTerms,
+ deliveryDate: poa.deliveryDate,
+ deliveryLocation: poa.deliveryLocation,
+
+ // Changed financial information
+ currency: poa.currency,
+ totalAmount: poa.totalAmount,
+ discount: poa.discount,
+ tax: poa.tax,
+ shippingFee: poa.shippingFee,
+ netTotal: poa.netTotal,
+
+ // Timestamps
+ createdAt: poa.createdAt,
+ updatedAt: poa.updatedAt,
+
+ // Electronic signature status
+ hasSignature: sql<boolean>`EXISTS (
+ SELECT 1
+ FROM ${contractEnvelopes}
+ WHERE ${contractEnvelopes.contractId} = ${poa.id}
+ )`.as('has_signature'),
+ })
+ .from(poa)
+ .leftJoin(contracts, eq(poa.contractNo, contracts.contractNo))
});
// Type inference for the view