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
|
import { pgSchema, varchar, timestamp, jsonb, text, index, serial, boolean, uuid, integer } from "drizzle-orm/pg-core";
export const dolceSchema = pgSchema("dolce");
/**
* 돌체 스키마: 동기화 기능을 만들기 위해, 로컬에 임시 저장을 위한 퍼시스턴스 레이어를 구성
*
* 전체적인 설명:
* 외부 시스템인 돌체는 문서관리 시스템임.
* 우리 시스템은 돌체와 API 를 통해, 벤더가 문서를 넣어줄 수 있게 하는 역할을 함.
*
* 특정 프로젝트에 B3, B4 라는 타입으로 문서를 관리함
* B3는 일반 벤더, B4는 GTT 라는 특정 벤더에 대한 타입임.
*
* 전체적인 구조는 B3, B4가 유사함
* - 도면리스트: 외부시스템에서 관리하므로, 우리는 받기만 함
*
* - 상세도면리스트: 특정 도면에 소속된 개별 도면임.
* Revision을 관리하기 위해 생긴 레이어이며, 개별 도면에 연결된, 파일이 첨부된 실제 수발신하는 도면 단위라고 보면 됨.
* 외부시스템(DOLCE)에서 리스트를 API로 응답함. 개별 도면별로 응답함.
* 우리 시스템에서 벤더가 생성한다. (생성 API를 호출함) 단, 외부 시스템 측에서도 작업해 응답 결과를 다르게 줄 수도 있음.
* 우리 시스템에서 벤더가 수정할 수 있다. (수정 API를 호출함) 수정시에도 로컬 측에 저장하고, 동기화시 수정된 내용을 외부시스템에 전송한다.
* 우리 시스템에서 벤더가 상세도면을 생성했을 때, 우선 우리 시스템 DB에만 생성하고, API 응답 리스트에 로컬DB 임시저장값을 추가해서 보여줄 것임.
*
* - 파일리스트: 외부시스템에서 관리하며, 개별 상세도면에 첨부된 파일 리스트임.
* 외부시스템(DOLCE)에서 특정 상세도면의 파일 리스트를 API로 조회할 수 있음. 개별 상세도면에 할당된 uploadId 별로 파일리스트를 응답함.
* 우리 시스템에서 벤더가 파일을 추가하기도 함. 이 경우, 우선 우리 시스템 DB에 추가한 파일 정보를 임시 저장하고, API 응답 리스트에 로컬DB 임시저장값을 추가해서 보여줄 것임.
*
* 동기화 기능
* - 우리 시스템에서 상세도면 추가 및 첨부파일을 추가했던 건들을 외부시스템에 전송해주는 기능임
* - 임시 저장시 dolce에 생성/수정 요청을 할 수 있도록 필요한 정보를 저장해야 함. 이것은 스키마의 작성 기준임
* - 동기화 하기 이전에, 우리 시스템에서 임시로 저장한 건들은 삭제할 수 있도록 함. (soft 삭제가 아니고 hard 삭제)
*
* 동기화 목록 구성 방법
* - isSynced==false 인 건들로 목록을 구성한다.
* - 상세도면 추가 작업 건은, 상세도면+첨부파일을 하나의 동기화 건으로 구성한다.
* - 파일 추가 작업 건은, 개별 파일들을 동기화 건으로 구성한다.
* - B4 Bulk 업로드 건은, 개별 파일들을 동기화 건으로 구성한다.
* - 사용자는 본인이 임시 저장한 건들만을 선택해서 동기화할 수 있도록 한다.
*
* 사용자가 로컬에 파일 업로드하는 부분의 구현
* 1. 환경변수 DOLCE_LOCAL_UPLOAD_ABSOLUTE_DIRECTORY="/evcp/data/dolce" 가 설정되어 있음. (없으면 경로 만들기)
* 2. 로컬에서 저장할 때는 파일 메타데이터를 별도로 저장 (로컬 파일 경로는 API 호출시 사용하지 않으나, 로컬 건들도 조회 및 다운로드가 가능해야 하므로)
* 3. 동기화되지 않아 로컬에만 있는 건들은 파일 다운로드시 로컬에서 다운로드
* 4. 동기화 성공한 경우, 로컬에 저장된 파일은 삭제
*/
export const dolceSyncList = dolceSchema.table("dolce_sync_list", {
id: uuid("id").primaryKey().defaultRandom(),
// [필수] 작업 유형
// "ADD_DETAIL": 상세도면 추가 (메타데이터 + 파일)
// "MOD_DETAIL": 상세도면 수정 (메타데이터)
// "ADD_FILE": 기존 상세도면에 파일 추가 (파일)
// "B4_BULK": B4 일괄 업로드
type: varchar("type", { length: 50 }).notNull(),
// [중요] 조회 성능 및 필터링을 위한 메타데이터 컬럼 (JSONB에서 자주 쓰는 값 추출)
projectNo: varchar("project_no", { length: 50 }).notNull(), // 프로젝트별 조회용
drawingNo: varchar("drawing_no", { length: 100 }), // 특정 도면 조회용
uploadId: varchar("upload_id", { length: 100 }), // 업로드ID 기준 조회용
userId: varchar("user_id", { length: 50 }).notNull(), // 내 작업만 보기용
userName: varchar("user_name", { length: 100 }), // [추가] UI 표시용
vendorCode: varchar("vendor_code", { length: 50 }), // [추가] 벤더별 필터링용
// [데이터]
// 실제 API 호출에 필요한 Body 데이터 + 로컬 파일 경로 정보 포함
// 예: { meta: { ...API Body... }, files: [{ localPath: "...", originalName: "..." }] }
payload: jsonb("payload").notNull(),
// [상태]
isSynced: boolean("is_synced").default(false).notNull(),
syncAttempts: integer("sync_attempts").default(0).notNull(),
lastError: text("last_error"), // 실패 사유
// [응답]
responseCode: varchar("response_code", { length: 50 }),
response: text("response"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
}, (table) => ({
// 인덱스 설정: 프로젝트별 미동기화 건 조회 속도 향상
projectIdx: index("dolce_sync_project_idx").on(table.projectNo, table.isSynced),
userIdx: index("dolce_sync_user_idx").on(table.userId, table.isSynced),
// [추가] 벤더별 조회 인덱스
vendorIdx: index("dolce_sync_vendor_idx").on(table.projectNo, table.vendorCode, table.isSynced),
}));
|