summaryrefslogtreecommitdiff
path: root/db/schema/contract.ts
blob: a04bb44b4a9d2ba6cf8e97ef3c70729362bf48e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
import {
    pgTable,
    text,
    varchar,
    timestamp,
    integer,
    numeric,
    date,
    boolean,
    unique,
    uniqueIndex, pgView
} from "drizzle-orm/pg-core"
import { projects } from "./projects"
import { vendorContacts, vendors } from "./vendors"
import { eq, sql } from "drizzle-orm";
import { items } from "./items";

/**
 * PO 상태 << 정의서에 적어놓고 또 다르게 쓰고 있음. 신뢰가 안감
 * ENG : KO : 의미
 * 1. Contract Transfer : 계약번호생성 : 이 상태는 안 쓰이는 것인가?
 * 2. Request to Review Conditions : 조건검토요청 : 조건검토를 할 수 있는 기능의 요구사항이 없음. 무시
 * 3. Confirm to Review Conditions : 조건검토완료 : 조건검토를 할 수 있는 기능의 요구사항이 없음. 무시
 * 4. Contract Accept Request : 계약승인요청 : PO가 들어오면 계약승인 요청으로 처리
 * 5. Complete the Contract : 계약체결(승인) : 벤더가 PO 승인하면 이 상태로 변경
 * 6. Reject to Accept Contract : 계약승인거절 : 벤더가 PO 거절하면 이 상태로 변경
 * 7. Contract Delete : 계약폐기 : 계약폐기를 할 수 있는 기능의 요구사항이 없음. 중공업측에서 변경하거나 무시
 * 8. PCR Request : PCR요청 : PCR 요청 시 이 상태로 변경
 * 9. VO Request : VO요청 : VO 요청 기능 요구사항 없음. 요구사항이 정의되지 않은 상태임. 무시. (요구사항 정의되면 나중에 VO 요청 시 이 상태로 변경)
 * 10. PCR Accept : PCR승인 : PCR 요청 승인 시 이 상태로 변경
 * 11. PCR Reject : PCR거절 : PCR 요청 거절 시 이 상태로 변경
 */

// 계약 상태 enum (영어 키를 타입 안전하게 사용, i18n으로 다국어 지원)
export enum ContractStatus {
  CONTRACT_TRANSFER = "Contract Transfer",
  REQUEST_TO_REVIEW_CONDITIONS = "Request to Review Conditions",
  CONFIRM_TO_REVIEW_CONDITIONS = "Confirm to Review Conditions",
  CONTRACT_ACCEPT_REQUEST = "Contract Accept Request",
  COMPLETE_THE_CONTRACT = "Complete the Contract",
  REJECT_TO_ACCEPT_CONTRACT = "Reject to Accept Contract",
  CONTRACT_DELETE = "Contract Delete",
  PCR_REQUEST = "PCR Request",
  VO_REQUEST = "VO Request",
  PCR_ACCEPT = "PCR Accept",
  PCR_REJECT = "PCR Reject"
}

// ============ contracts (계약/PO 정보) ============
export const contracts = pgTable('contracts', {
  // 주 키
  id: integer('id').primaryKey().generatedAlwaysAsIdentity(),

  // 프로젝트와 협력업체 참조
  // .notNull() 제외(0912 구매 프로젝트 id 없는 계약 case 존재-최겸)
  projectId: integer('project_id').references(() => projects.id, {
    onDelete: 'cascade',
  }),

  vendorId: integer('vendor_id')
    .notNull()
    .references(() => vendors.id, { onDelete: 'cascade' }),

  // 계약/PO 번호(유니크)
  contractNo: varchar('contract_no', { length: 100 }).notNull().unique(), // EBELN
  contractName: varchar('contract_name', { length: 255 }).notNull(), // ZTITLE

  // 계약/PO 상태나 기간
  status: varchar('status', { length: 50 })
    .notNull()
    .default(ContractStatus.CONTRACT_ACCEPT_REQUEST), // default value: 'Contract Accept Request'
  startDate: date('start_date'), // 발주일(혹은 유효 시작일)
  endDate: date('end_date'), // 계약 종료일/유효 기간 등

  // 거절사유
  rejectionReason: varchar('rejection_reason', { length: 1000 }),

  // --- SAP ECC 인터페이스 매핑 필드들 ---
  // 기본 PO 정보
  paymentTerms: text('payment_terms'), // 지급 조건 (ZTERM - 지급조건코드)
  deliveryTerms: text('delivery_terms'), // 납품 조건 (INCO1 - 인도조건코드)
  deliveryDate: date('delivery_date'), // 납품 기한 (ZPO_DLV_DT - PO납기일자, 개별 품목별)
  shippmentPlace: varchar('shippment_place', { length: 255 }), // 선적지 (ZSHIPMT_PLC_CD - 선적지코드)
  deliveryLocation: varchar('delivery_location', { length: 255 }), // 하역지 (ZUNLD_PLC_CD - 하역지코드)

  // SAP ECC 추가 필드들
  poVersion: integer('po_version'), // PO 버전 (ZPO_VER - 발주버전)
  purchaseDocType: varchar('purchase_doc_type', { length: 10 }), // 구매문서유형 (BSART)
  purchaseOrg: varchar('purchase_org', { length: 10 }), // 구매조직 (EKORG - 구매조직코드)
  purchaseGroup: varchar('purchase_group', { length: 10 }), // 구매그룹 (EKGRP - 구매그룹코드)
  exchangeRate: numeric('exchange_rate', { precision: 9, scale: 5 }), // 환율 (WKURS)
  poConfirmStatus: varchar('po_confirm_status', { length: 10 }), // PO확인상태 (ZPO_CNFM_STAT)
  ZPO_CNFM_STAT: varchar('ZPO_CNFM_STAT', { length: 255 }), // SAP 구매오더확인상태 원본값

  // 계약/보증 관련
  contractGuaranteeCode: varchar('contract_guarantee_code', { length: 2 }), // 계약보증코드 (ZCNRT_GRNT_CD)
  defectGuaranteeCode: varchar('defect_guarantee_code', { length: 2 }), // 하자보증코드 (ZDFCT_GRNT_CD)
  guaranteePeriodCode: varchar('guarantee_period_code', { length: 2 }), // 보증기간코드 (ZGRNT_PRD_CD)
  advancePaymentYn: varchar('advance_payment_yn', { length: 1 }), // 선급금여부 (ZPAMT_YN)

  // 금액 관련 (KRW 변환)
  budgetAmount: numeric('budget_amount', { precision: 17, scale: 2 }), // 예산금액 (ZBGT_AMT)
  budgetCurrency: varchar('budget_currency', { length: 5 }), // 예산통화 (ZBGT_CURR)
  totalAmountKrw: numeric('total_amount_krw', { precision: 17, scale: 2 }), // 발주금액KRW (ZPO_AMT_KRW)

  // 전자계약/승인 관련
  electronicContractYn: varchar('electronic_contract_yn', { length: 1 }), // 전자계약필요여부 (ZELC_CNRT_ND_YN)
  electronicApprovalDate: date('electronic_approval_date'), // 전자승인일자 (ZELC_AGR_DT)
  electronicApprovalTime: varchar('electronic_approval_time', { length: 6 }), // 전자승인시간 (ZELC_AGR_TM)
  ownerApprovalYn: varchar('owner_approval_yn', { length: 1 }), // 선주승인필요여부 (ZOWN_AGR_IND_YN)

  // 기타
  plannedInOutFlag: varchar('planned_in_out_flag', { length: 1 }), // 계획내외구분 (ZPLN_INO_GB)
  settlementStandard: varchar('settlement_standard', { length: 1 }), // 정산기준 (ZECAL_BSE)
  weightSettlementFlag: varchar('weight_settlement_flag', { length: 1 }), // 중량정산구분 (ZWGT_ECAL_GB)

  // 연동제 관련
  priceIndexYn: varchar('price_index_yn', { length: 1 }), // 납품대금연동제대상여부 (ZDLV_PRICE_T)
  writtenContractNo: varchar('written_contract_no', { length: 20 }), // 서면계약번호 (ZWEBELN)
  contractVersion: integer('contract_version'), // 서면계약차수 (ZVER_NO)

  // 가격/금액 관련
  currency: varchar('currency', { length: 10 }).default('KRW'), // 통화 (KRW, USD 등)                   // ZPO_CURR
  totalAmount: numeric('total_amount', { precision: 12, scale: 2 }), // 총 계약 금액(아이템 합산 등)        // ZPO_AMT
  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 }), // (합계) - (할인) + (세금) + (배송비)     // 인터페이스에 없음 (개별 품목별로는 있음)

  // 부분 납품/부분 결제 가능 여부
  partialShippingAllowed: boolean('partial_shipping_allowed').default(false),
  partialPaymentAllowed: boolean('partial_payment_allowed').default(false),

  // 추가 메모/비고
  remarks: text('remarks'),
  vendorComment: text('vendor_comment'),
  shiComment: text('shi_comment'),

  // PO 계약서 내용
  contractContent: text('contract_content'),

  // 버전 관리 (PO 재발행 등)
  version: integer('version').default(1),

  // 생성/수정 시각
  createdAt: timestamp('created_at').defaultNow().notNull(),
  updatedAt: timestamp('updated_at').defaultNow().notNull(),
});

// 타입 추론
export type Contract = typeof contracts.$inferSelect

// ============ contract_items (1:N 관계) ============
// 한 계약(contracts.id)에 여러 아이템을 연결
export const contractItems = pgTable("contract_items", {
    id: integer("id").primaryKey().generatedAlwaysAsIdentity(),

    contractId: integer("contract_id")
        .notNull()
        .references(() => contracts.id, { onDelete: "cascade" }),

    itemId: integer("item_id")
        .notNull()
    .references(() => items.id, { onDelete: "cascade" })
    ,

    // --- SAP ECC 인터페이스 매핑 필드 ---
    itemNo: varchar("item_no", { length: 10 }), // 품번 (EBELP - 구매오더품목번호)
    prNo: varchar("pr_no", { length: 10 }), // PR번호 (BANFN - 구매요청번호)
    prItemNo: varchar("pr_item_no", { length: 5 }), // PR 품번 (BNFPO - 구매요청품목번호)
    materialGroup: varchar("material_group", { length: 9 }), // 자재그룹 (MATKL)
    weight: numeric("weight", { precision: 13, scale: 3 }), // 순중량 (NTGEW)
    weightUnit: varchar("weight_unit", { length: 3 }), // 중량단위 (GEWEI)
    totalWeight: numeric("total_weight", { precision: 15, scale: 3 }), // 총중량 (BRGEW)

    // --- 품목(아이템) 단위 정보 ---
    description: text("description"), // 품목 설명 (스펙, 모델명 등)
    quantity: integer("quantity").notNull().default(1),
    ZPO_UNIT: varchar("ZPO_UNIT", { length: 10 }), // 구매오더수량단위 (ZPO_UNIT)
    unitPrice: numeric("unit_price", { precision: 10, scale: 2 }), // 구매단가 (NETPR)
    
    // 가격 관련 추가 필드
    PEINH: integer("PEINH"), // 가격단위값 (예: 1, 10, 100)
    BPRME: varchar("BPRME", { length: 3 }), // 구매단가단위 (EA, KG 등)
    ZNETPR: numeric("ZNETPR", { precision: 17, scale: 2 }), // 발주단가
    ZREF_NETPR: numeric("ZREF_NETPR", { precision: 17, scale: 2 }), // 참조단가

    taxType: varchar("tax_type", { length: 10 }), // 세금코드( MWSKZ - 매출부가가치세코드이며, V1, V2 같은 두자리 코드를 가짐, 각 코드별 taxRate가 있음. 코드별 taxRate는 아직 받아오기 전임)
    taxRate: numeric("tax_rate", { precision: 5, scale: 2 }),   // % (예: 10.00)
    taxAmount: numeric("tax_amount", { precision: 10, scale: 2 }), // 계산된 세금
    
    // SAP ECC 금액 필드 (금액 관계: NETWR = BRTWR + ZPDT_EXDS_AMT)
    NETWR: numeric("NETWR", { precision: 17, scale: 2 }), // 오더정가 (최종 정가)
    BRTWR: numeric("BRTWR", { precision: 17, scale: 2 }), // 오더총액 (기본 총액)
    ZPDT_EXDS_AMT: numeric("ZPDT_EXDS_AMT", { precision: 17, scale: 2 }), // 할인/할증금액 (조정금액: 할인은 음수, 할증은 양수)
    
    totalLineAmount: numeric("total_line_amount", { precision: 12, scale: 2 }), // (수량×단가±할인+세금) 등

    // 위치 정보
    WERKS: varchar("WERKS", { length: 4 }), // 플랜트코드
    LGORT: varchar("LGORT", { length: 10 }), // 저장위치
    
    // RFQ 추적
    ANFNR: varchar("ANFNR", { length: 10 }), // RFQ번호
    ANFPS: varchar("ANFPS", { length: 5 }), // RFQ품목번호
    
    // 자재 추적
    ZPO_LOT_NO: varchar("ZPO_LOT_NO", { length: 50 }), // Steel Material Marking No
    
    // 볼륨 정보
    VOLUM: numeric("VOLUM", { precision: 15, scale: 3 }), // 볼륨
    VOLEH: varchar("VOLEH", { length: 3 }), // 볼륨단위
    
    // 날짜 정보
    ZPO_DLV_DT: date("ZPO_DLV_DT"), // PO납기일자
    ZPLN_ST_DT: date("ZPLN_ST_DT"), // 예정시작일자
    ZPLN_ED_DT: date("ZPLN_ED_DT"), // 예정종료일자
    LFDAT: date("LFDAT"), // PR Delivery Date
    ZRCV_DT: date("ZRCV_DT"), // 구매접수일자
    
    // 기타
    ZCON_IND: varchar("ZCON_IND", { length: 1 }), // 시리즈구분 (SS 등)

    // 비고
    remark: text("remark"), // 발주비고 (ZPO_RMK)

    // 생성/수정 시각
    createdAt: timestamp("created_at").defaultNow().notNull(),
    updatedAt: timestamp("updated_at").defaultNow().notNull(),
}, (t) => [
    // 같은 계약 + 같은 itemId에 대한 중복을 막음
    unique().on(t.contractId, t.itemId),
    uniqueIndex("contract_items_contract_item_idx").on(t.contractId, t.itemId),
])

export type ContractItem = typeof contractItems.$inferSelect

// ============ DocuSign 연동용 (전자서명 이력) ============

// Envelope(전자서명 요청) 테이블
export const contractEnvelopes = pgTable("contract_envelopes", {
    id: integer("id").primaryKey().generatedAlwaysAsIdentity(),

    // 연결된 계약
    contractId: integer("contract_id")
        .notNull()
        .references(() => contracts.id, { onDelete: "cascade" }),

    // DocuSign에서 발급되는 Envelope/Document 식별자
    envelopeId: varchar("envelope_id", { length: 200 }).notNull(),
    documentId: varchar("document_id", { length: 200 }),

    // Envelope 전체 상태 (예: sent, completed, voided ...)
    envelopeStatus: varchar("envelope_status", { length: 50 }),

    fileName: varchar("file_name", { length: 255 }).notNull(),
    filePath: varchar("file_path", { length: 1024 }).notNull(),
    // 생성/수정 시각
    createdAt: timestamp("created_at").defaultNow().notNull(),
    updatedAt: timestamp("updated_at").defaultNow().notNull(),
});

// 하나의 Envelope에 여러 서명자(사인 요청 대상)가 있을 수 있음
export const contractSigners = pgTable("contract_signers", {
    id: integer("id").primaryKey().generatedAlwaysAsIdentity(),

    // Envelope와 1:N 관계
    envelopeId: integer("envelope_id")
        .notNull()
        .references(() => contractEnvelopes.id, { onDelete: "cascade" }),

    // Reference to vendor_contacts table (optional - if signer is from vendor contacts)
    vendorContactId: integer("vendor_contact_id")
        .references(() => vendorContacts.id),

    // Is this signer from the requester (company) side or vendor side
    signerType: varchar("signer_type", {
        length: 20,
        enum: ["REQUESTER", "VENDOR"]
    }).notNull().default("VENDOR"),

    // 서명자 정보 (manual entry or populated from vendor contact)
    signerEmail: varchar("signer_email", { length: 255 }).notNull(),
    signerName: varchar("signer_name", { length: 100 }).notNull(),
    signerPosition: varchar("signer_position", { length: 100 }),

    // 서명자별 상태 (sent, delivered, signed, declined, etc.)
    signerStatus: varchar("signer_status", { length: 50 }).default("PENDING"),
    signedAt: timestamp("signed_at"),

    // 생성/수정 시각
    createdAt: timestamp("created_at").defaultNow().notNull(),
    updatedAt: timestamp("updated_at").defaultNow().notNull(),
});



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
        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 ${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(),
})

// 타입 추론
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))
});

// Type inference for the view
export type POADetail = typeof poaDetailView.$inferSelect;