OnSureLab 기술 레퍼런스
개발자
어드민 API, OpenAPI 스펙, DB Viewer를 통합한 기술 레퍼런스입니다.
최근 변경사항
2026-03-12
- 추가결제 요청 API (POST /executions/:id/request-payment)
- 추가견적서 PDF API (GET /executions/:id/quote/pdf)
- 추가결제 계좌 정보 조회 API (GET /executions/:id/payment-info)
- 추가이메일 인증 재발송 API (POST /auth/resend-verification)
- 추가회원 탈퇴 API (DELETE /auth/me)
- 추가환불 처리 API (POST /admin/executions/:id/refund)
- 추가부분결제 처리 API (POST /admin/executions/:id/partial-payment)
- 수정GET /executions, PATCH /executions/:id — status 필터/검증 10단계 전체 허용
- 수정MEMBER 역할에 PAYMENT_WAIT 전이 권한 추가
2026-03-11
- 추가게첨보고서(execution_report) API — PDF 업로드/조회/삭제
- 추가집행 사진(execution_photo) API — 시점별 증빙 사진 업로드/조회/삭제
- 추가세금계산서 발행정보 CRUD API 5개 (고객용)
- 추가PATCH /auth/me 이메일 변경 기능 (중복 체크 포함)
- 삭제PLAN_REVIEW, PLAN_APPROVED, PLAN_REJECTED 상태 제거 (13 → 10단계)
- 삭제submit-plan, approve-plan, reject-plan API 제거 (고객 + 어드민)
959 lines
1openapi: 3.0.3
2info:
3 title: OnSureLab Admin API
4 description: |
5 OnSureLab 관리자 전용 API 스펙.
6 - 대시보드 (1개): KPI 조회
7 - 집행 관리 (12개): 목록, 상세, 상태 변경, 환불, 부분결제, 액션 API
8 - 집행 사진 (3개): 사진 업로드, 목록 조회, 삭제
9 - 게첨보고서 (3개): PDF 업로드, 목록 조회, 삭제
10 - 유저 관리 (3개): 목록, 상세, 수정 (ADMIN 전용)
11 - 결제 관리 (1개): 결제 목록
12 - DB 관리 (1개): 스키마 조회
13
14 ## 인증
15 모든 API는 X-API-Key 헤더 + onsure_session 쿠키(JWT)가 필요합니다.
16
17 ## 권한
18 - **DEVELOPER**: 모든 어드민 API 접근 가능
19 - **ADMIN**: 모든 어드민 API 접근 가능
20 - **OPERATOR**: 유저 관리 제외 접근 가능
21 - **MEMBER**: 접근 불가
22 version: 2.0.0
23
24servers:
25 - url: https://ad.dukplace.com
26 description: Production
27 - url: http://localhost:4000
28 description: Development
29
30tags:
31 - name: Dashboard
32 description: 대시보드 KPI
33 - name: Execution
34 description: 집행 관리 (ADMIN, OPERATOR)
35 - name: User
36 description: 유저 관리 (ADMIN 전용)
37 - name: Payment
38 description: 결제 관리 (ADMIN, OPERATOR)
39 - name: Database
40 description: DB 스키마 조회
41
42security:
43 - apiKey: []
44 - session: []
45
46# ──────────────────────────────────────────────
47# PATHS
48# ──────────────────────────────────────────────
49paths:
50
51 # ════════════════════════════════════════════
52 # 대시보드
53 # ════════════════════════════════════════════
54
55 /api/onsurelab/admin/dashboard:
56 get:
57 tags: [Dashboard]
58 summary: 대시보드 KPI
59 description: "상태별 집행 건수, 긴급 처리 항목, 최근 히스토리 조회"
60 operationId: adminDashboard
61 responses:
62 '200':
63 description: 대시보드 데이터
64 content:
65 application/json:
66 schema:
67 $ref: '#/components/schemas/SuccessData'
68 example:
69 success: true
70 data:
71 statusCounts:
72 - { status: RUNNING, count: 12 }
73 - { status: PAYMENT_WAIT, count: 3 }
74 urgentItems:
75 - id: exec_xxx
76 title: "지하철 포스터"
77 status: PAYMENT_WAIT
78 updated_at: "2026-03-07T08:00:00.000Z"
79 user_name: "김OO"
80 recentHistory: []
81
82 # ════════════════════════════════════════════
83 # 집행 관리
84 # ════════════════════════════════════════════
85
86 /api/onsurelab/admin/executions:
87 get:
88 tags: [Execution]
89 summary: 집행 목록 (필터/검색/페이징)
90 operationId: adminListExecutions
91 parameters:
92 - name: status
93 in: query
94 schema:
95 $ref: '#/components/schemas/ExecutionStatus'
96 - name: search
97 in: query
98 schema:
99 type: string
100 description: 고객명/제목/ID 검색
101 - name: sort
102 in: query
103 schema:
104 type: string
105 enum: [latest, amount]
106 default: latest
107 - name: page
108 in: query
109 schema:
110 type: integer
111 default: 1
112 - name: size
113 in: query
114 schema:
115 type: integer
116 default: 20
117 maximum: 100
118 responses:
119 '200':
120 description: 집행 목록
121 content:
122 application/json:
123 schema:
124 $ref: '#/components/schemas/PaginatedResponse'
125
126 /api/onsurelab/admin/executions/{id}:
127 parameters:
128 - $ref: '#/components/parameters/ExecutionId'
129 get:
130 tags: [Execution]
131 summary: 집행 상세 (모든 탭 데이터 통합)
132 description: "execution, user, plan, files, messages, history, photos, reports 포함"
133 operationId: adminGetExecution
134 responses:
135 '200':
136 description: 집행 상세 통합 데이터
137 content:
138 application/json:
139 schema:
140 $ref: '#/components/schemas/SuccessData'
141 example:
142 success: true
143 data:
144 execution: { id: "exec_abc123", title: "마케팅 실증 사업", status: "RUNNING" }
145 user: { id: 1, name: "홍길동", email: "[email protected]" }
146 plan: { id: "plan_xxx", executionId: "exec_abc123" }
147 files: []
148 messages: []
149 history: []
150 patch:
151 tags: [Execution]
152 summary: 범용 상태 변경 (HOLD, CANCELLED 등)
153 description: "상태 머신 검증 후 히스토리 자동 기록"
154 operationId: adminUpdateExecution
155 requestBody:
156 required: true
157 content:
158 application/json:
159 schema:
160 type: object
161 required: [status]
162 properties:
163 status:
164 $ref: '#/components/schemas/ExecutionStatus'
165 reason:
166 type: string
167 description: HOLD, CANCELLED 시 사유
168 example:
169 status: HOLD
170 reason: "고객 요청으로 일시 중지"
171 responses:
172 '200':
173 description: 상태 변경 완료
174 content:
175 application/json:
176 schema:
177 $ref: '#/components/schemas/SuccessMessage'
178
179 /api/onsurelab/admin/executions/{id}/confirm-payment:
180 parameters:
181 - $ref: '#/components/parameters/ExecutionId'
182 post:
183 tags: [Execution]
184 summary: 입금 확인
185 description: "전이: PAYMENT_WAIT → PAID"
186 operationId: adminConfirmPayment
187 requestBody:
188 required: true
189 content:
190 application/json:
191 schema:
192 type: object
193 properties:
194 amount:
195 type: integer
196 example: 3850000
197 date:
198 type: string
199 format: date
200 example: "2026-03-07"
201 memo:
202 type: string
203 example: "기업은행 입금 확인"
204 responses:
205 '200':
206 description: 입금 확인 완료
207 content:
208 application/json:
209 schema:
210 $ref: '#/components/schemas/SuccessMessage'
211
212 /api/onsurelab/admin/executions/{id}/approve-material:
213 parameters:
214 - $ref: '#/components/parameters/ExecutionId'
215 post:
216 tags: [Execution]
217 summary: 소재 승인
218 description: "전이: MATERIAL_SENT → MATERIAL_APPROVED"
219 operationId: adminApproveMaterial
220 responses:
221 '200':
222 description: 소재 승인 완료
223 content:
224 application/json:
225 schema:
226 $ref: '#/components/schemas/SuccessMessage'
227
228 /api/onsurelab/admin/executions/{id}/request-revision:
229 parameters:
230 - $ref: '#/components/parameters/ExecutionId'
231 post:
232 tags: [Execution]
233 summary: 소재 수정 요청
234 description: "전이: MATERIAL_SENT → MATERIAL_REVISION"
235 operationId: adminRequestRevision
236 requestBody:
237 required: true
238 content:
239 application/json:
240 schema:
241 type: object
242 required: [reason]
243 properties:
244 reason:
245 type: string
246 example: "이미지 해상도가 낮습니다. 300dpi 이상으로 재업로드해주세요."
247 responses:
248 '200':
249 description: 수정 요청 완료
250 content:
251 application/json:
252 schema:
253 $ref: '#/components/schemas/SuccessMessage'
254
255 /api/onsurelab/admin/executions/{id}/start:
256 parameters:
257 - $ref: '#/components/parameters/ExecutionId'
258 post:
259 tags: [Execution]
260 summary: 집행 시작
261 description: "전이: MATERIAL_APPROVED → RUNNING"
262 operationId: adminStartExecution
263 requestBody:
264 required: true
265 content:
266 application/json:
267 schema:
268 type: object
269 required: [startDate]
270 properties:
271 startDate:
272 type: string
273 format: date
274 example: "2026-03-10"
275 responses:
276 '200':
277 description: 집행 시작 완료
278 content:
279 application/json:
280 schema:
281 $ref: '#/components/schemas/SuccessMessage'
282
283 /api/onsurelab/admin/executions/{id}/complete:
284 parameters:
285 - $ref: '#/components/parameters/ExecutionId'
286 post:
287 tags: [Execution]
288 summary: 집행 완료
289 description: "전이: RUNNING → DONE. endDate 미입력 시 현재 날짜 사용"
290 operationId: adminCompleteExecution
291 requestBody:
292 content:
293 application/json:
294 schema:
295 type: object
296 properties:
297 endDate:
298 type: string
299 format: date
300 example: "2026-04-10"
301 responses:
302 '200':
303 description: 집행 완료 처리
304 content:
305 application/json:
306 schema:
307 $ref: '#/components/schemas/SuccessMessage'
308
309 /api/onsurelab/admin/executions/{id}/refund:
310 parameters:
311 - $ref: '#/components/parameters/ExecutionId'
312 post:
313 tags: [Execution]
314 summary: 환불 처리
315 description: "payment_status가 PAID/PARTIAL → REFUNDED + status CANCELLED"
316 operationId: adminRefund
317 requestBody:
318 required: true
319 content:
320 application/json:
321 schema:
322 type: object
323 required: [reason]
324 properties:
325 reason:
326 type: string
327 example: "고객 요청에 의한 전액 환불"
328 responses:
329 '200':
330 description: 환불 완료
331 content:
332 application/json:
333 schema:
334 $ref: '#/components/schemas/SuccessData'
335
336 /api/onsurelab/admin/executions/{id}/partial-payment:
337 parameters:
338 - $ref: '#/components/parameters/ExecutionId'
339 post:
340 tags: [Execution]
341 summary: 부분결제 처리
342 description: "payment_status가 WAITING/PARTIAL → PARTIAL. status 변경 없음"
343 operationId: adminPartialPayment
344 requestBody:
345 required: true
346 content:
347 application/json:
348 schema:
349 type: object
350 required: [amount]
351 properties:
352 amount:
353 type: integer
354 example: 1000000
355 description: 결제 금액 (0보다 커야 함)
356 memo:
357 type: string
358 example: "1차 분할 입금"
359 responses:
360 '200':
361 description: 부분결제 완료
362 content:
363 application/json:
364 schema:
365 $ref: '#/components/schemas/SuccessData'
366
367 /api/onsurelab/admin/executions/{id}/files:
368 parameters:
369 - $ref: '#/components/parameters/ExecutionId'
370 post:
371 tags: [Execution]
372 summary: 파일 업로드
373 operationId: adminUploadFile
374 requestBody:
375 required: true
376 content:
377 multipart/form-data:
378 schema:
379 type: object
380 required: [file, category]
381 properties:
382 file:
383 type: string
384 format: binary
385 description: 업로드할 파일
386 category:
387 type: string
388 enum: [reference, logo, brand_guide, evidence, deliverable]
389 description: 파일 카테고리
390 step:
391 type: integer
392 description: 스텝 번호
393 responses:
394 '201':
395 description: 업로드 완료
396 content:
397 application/json:
398 schema:
399 $ref: '#/components/schemas/SuccessData'
400
401 # ════════════════════════════════════════════
402 # 집행 사진 관리
403 # ════════════════════════════════════════════
404
405 /api/onsurelab/admin/executions/{id}/photos:
406 parameters:
407 - $ref: '#/components/parameters/ExecutionId'
408 get:
409 tags: [Execution]
410 summary: 집행 사진 목록 조회
411 operationId: adminListPhotos
412 parameters:
413 - name: type
414 in: query
415 schema:
416 type: string
417 enum: [PROGRESS, FINAL]
418 description: 사진 유형 필터
419 - name: milestone
420 in: query
421 schema:
422 type: string
423 description: 시점 필터
424 responses:
425 '200':
426 description: 사진 목록
427 content:
428 application/json:
429 schema:
430 type: object
431 properties:
432 success:
433 type: boolean
434 data:
435 type: object
436 properties:
437 photos:
438 type: array
439 items:
440 $ref: '#/components/schemas/ExecutionPhoto'
441 post:
442 tags: [Execution]
443 summary: 집행 사진 업로드
444 operationId: adminUploadPhoto
445 requestBody:
446 required: true
447 content:
448 multipart/form-data:
449 schema:
450 type: object
451 required: [file, milestone]
452 properties:
453 file:
454 type: string
455 format: binary
456 description: 업로드할 사진 파일
457 type:
458 type: string
459 enum: [PROGRESS, FINAL]
460 default: PROGRESS
461 description: "사진 유형 (PROGRESS: 진행 중, FINAL: 최종)"
462 milestone:
463 type: string
464 description: "시점 (자유 입력: '1개월차', '3개월차', '최종' 등)"
465 example: 1개월차
466 shot_at:
467 type: string
468 format: date
469 description: 촬영/해당 시점 날짜 (선택)
470 example: "2026-03-15"
471 note:
472 type: string
473 description: 메모 (선택)
474 responses:
475 '201':
476 description: 업로드 완료
477 content:
478 application/json:
479 schema:
480 $ref: '#/components/schemas/SuccessData'
481
482 /api/onsurelab/admin/executions/{id}/photos/{photoId}:
483 parameters:
484 - $ref: '#/components/parameters/ExecutionId'
485 - name: photoId
486 in: path
487 required: true
488 schema:
489 type: string
490 description: 사진 ID (photo_xxxx)
491 delete:
492 tags: [Execution]
493 summary: 집행 사진 삭제
494 operationId: adminDeletePhoto
495 responses:
496 '200':
497 description: 삭제 완료
498 content:
499 application/json:
500 schema:
501 $ref: '#/components/schemas/SuccessResponse'
502
503 # ════════════════════════════════════════════
504 # 게첨보고서 관리
505 # ════════════════════════════════════════════
506
507 /api/onsurelab/admin/executions/{id}/reports:
508 parameters:
509 - $ref: '#/components/parameters/ExecutionId'
510 get:
511 tags: [Execution]
512 summary: 게첨보고서 목록 조회
513 operationId: adminListReports
514 responses:
515 '200':
516 description: 보고서 목록
517 content:
518 application/json:
519 schema:
520 type: object
521 properties:
522 success:
523 type: boolean
524 data:
525 type: object
526 properties:
527 reports:
528 type: array
529 items:
530 $ref: '#/components/schemas/ExecutionReport'
531 post:
532 tags: [Execution]
533 summary: 게첨보고서 업로드 (PDF)
534 operationId: adminUploadReport
535 requestBody:
536 required: true
537 content:
538 multipart/form-data:
539 schema:
540 type: object
541 required: [file, title]
542 properties:
543 file:
544 type: string
545 format: binary
546 description: 업로드할 PDF 파일
547 title:
548 type: string
549 description: 보고서 제목
550 example: 1개월차 게첨보고서
551 milestone:
552 type: string
553 description: "시점 (선택, 자유 입력)"
554 example: 1개월차
555 note:
556 type: string
557 description: 메모 (선택)
558 responses:
559 '201':
560 description: 업로드 완료
561 content:
562 application/json:
563 schema:
564 $ref: '#/components/schemas/SuccessData'
565
566 /api/onsurelab/admin/executions/{id}/reports/{reportId}:
567 parameters:
568 - $ref: '#/components/parameters/ExecutionId'
569 - name: reportId
570 in: path
571 required: true
572 schema:
573 type: string
574 description: 보고서 ID (report_xxxx)
575 delete:
576 tags: [Execution]
577 summary: 게첨보고서 삭제
578 operationId: adminDeleteReport
579 responses:
580 '200':
581 description: 삭제 완료
582 content:
583 application/json:
584 schema:
585 $ref: '#/components/schemas/SuccessResponse'
586
587 # ════════════════════════════════════════════
588 # 유저 관리 (ADMIN 전용)
589 # ════════════════════════════════════════════
590
591 /api/onsurelab/admin/users:
592 get:
593 tags: [User]
594 summary: 유저 목록
595 description: "권한: ADMIN 전용"
596 operationId: adminListUsers
597 parameters:
598 - name: search
599 in: query
600 schema:
601 type: string
602 description: 이메일/이름/회사명 검색
603 - name: role
604 in: query
605 schema:
606 $ref: '#/components/schemas/UserRole'
607 - name: isActive
608 in: query
609 schema:
610 type: boolean
611 - name: page
612 in: query
613 schema:
614 type: integer
615 default: 1
616 - name: size
617 in: query
618 schema:
619 type: integer
620 default: 20
621 responses:
622 '200':
623 description: 유저 목록
624 content:
625 application/json:
626 schema:
627 $ref: '#/components/schemas/PaginatedResponse'
628
629 /api/onsurelab/admin/users/{userId}:
630 parameters:
631 - name: userId
632 in: path
633 required: true
634 schema:
635 type: integer
636 get:
637 tags: [User]
638 summary: 유저 상세
639 description: "유저 정보 + 해당 유저의 집행 목록. 권한: ADMIN 전용"
640 operationId: adminGetUser
641 responses:
642 '200':
643 description: 유저 상세
644 content:
645 application/json:
646 schema:
647 $ref: '#/components/schemas/SuccessData'
648 example:
649 success: true
650 data:
651 user: { id: 1, email: "[email protected]", name: "홍길동", role: "MEMBER" }
652 executions: []
653 patch:
654 tags: [User]
655 summary: 유저 정보 수정 (역할, 활성화 등)
656 description: "권한: ADMIN 전용. 부분 업데이트"
657 operationId: adminUpdateUser
658 requestBody:
659 content:
660 application/json:
661 schema:
662 type: object
663 properties:
664 role:
665 $ref: '#/components/schemas/UserRole'
666 isActive:
667 type: boolean
668 name:
669 type: string
670 company:
671 type: string
672 example:
673 role: OPERATOR
674 isActive: true
675 responses:
676 '200':
677 description: 수정 완료
678 content:
679 application/json:
680 schema:
681 $ref: '#/components/schemas/SuccessData'
682
683 # ════════════════════════════════════════════
684 # 결제 관리
685 # ════════════════════════════════════════════
686
687 /api/onsurelab/admin/payments:
688 get:
689 tags: [Payment]
690 summary: 결제 목록
691 operationId: adminListPayments
692 parameters:
693 - name: paymentStatus
694 in: query
695 schema:
696 type: string
697 enum: [ALL, WAITING, PAID, REFUNDED]
698 description: "ALL이면 전체 조회"
699 - name: page
700 in: query
701 schema:
702 type: integer
703 default: 1
704 - name: size
705 in: query
706 schema:
707 type: integer
708 default: 20
709 responses:
710 '200':
711 description: 결제 목록
712 content:
713 application/json:
714 schema:
715 $ref: '#/components/schemas/PaginatedResponse'
716 example:
717 success: true
718 data:
719 items:
720 - id: exec_abc123
721 title: "마케팅 실증 사업"
722 payment_status: PAID
723 payment_method: BANK_TRANSFER
724 final_quote: 5000000
725 user_name: "홍길동"
726 user_company: "(주)온슈어"
727 total: 10
728 page: 1
729 totalPages: 1
730
731 # ════════════════════════════════════════════
732 # DB 관리
733 # ════════════════════════════════════════════
734
735 /api/onsurelab/admin/database:
736 get:
737 tags: [Database]
738 summary: DB 스키마 조회
739 description: "전체 테이블 목록 + 각 테이블 컬럼 구조 (DESCRIBE)"
740 operationId: adminGetDatabaseSchema
741 responses:
742 '200':
743 description: DB 스키마
744 content:
745 application/json:
746 schema:
747 $ref: '#/components/schemas/SuccessData'
748 example:
749 success: true
750 env: dev
751 tableCount: 6
752 tables:
753 - name: user
754 columns:
755 - { Field: id, Type: bigint, "Null": "NO", Key: PRI, Default: null, Extra: auto_increment }
756 - { Field: email, Type: "varchar(255)", "Null": "NO", Key: UNI, Default: null, Extra: "" }
757 - { Field: name, Type: "varchar(100)", "Null": "NO", Key: "", Default: null, Extra: "" }
758 - name: execution
759 columns:
760 - { Field: id, Type: "varchar(20)", "Null": "NO", Key: PRI, Default: null, Extra: "" }
761
762# ──────────────────────────────────────────────
763# COMPONENTS
764# ──────────────────────────────────────────────
765components:
766
767 securitySchemes:
768 apiKey:
769 type: apiKey
770 in: header
771 name: X-API-Key
772 description: |
773 Dev: `duk-med-partner-dev`
774 Prod: `duk-med-partner`
775 session:
776 type: apiKey
777 in: cookie
778 name: onsure_session
779 description: 로그인 시 발급되는 httpOnly JWT 쿠키 (24시간 만료)
780
781 parameters:
782 ExecutionId:
783 name: id
784 in: path
785 required: true
786 schema:
787 type: string
788 description: 집행 ID (예: exec_a1b2c3d4e5f6)
789
790 schemas:
791 SuccessMessage:
792 type: object
793 properties:
794 success:
795 type: boolean
796 example: true
797 data:
798 type: object
799 message:
800 type: string
801
802 SuccessData:
803 type: object
804 properties:
805 success:
806 type: boolean
807 example: true
808 data:
809 type: object
810
811 SuccessResponse:
812 type: object
813 properties:
814 success:
815 type: boolean
816 example: true
817 message:
818 type: string
819
820 ErrorResponse:
821 type: object
822 properties:
823 success:
824 type: boolean
825 example: false
826 error:
827 type: string
828
829 PaginatedResponse:
830 type: object
831 properties:
832 success:
833 type: boolean
834 example: true
835 data:
836 type: object
837 properties:
838 items:
839 type: array
840 items:
841 type: object
842 total:
843 type: integer
844 page:
845 type: integer
846 totalPages:
847 type: integer
848
849 ExecutionReport:
850 type: object
851 properties:
852 reportId:
853 type: string
854 example: report_abc123def456
855 executionId:
856 type: string
857 example: exec_abc123
858 url:
859 type: string
860 example: https://kr.object.ncloudstorage.com/bucket/executions/exec_abc123/reports/report.pdf
861 originalName:
862 type: string
863 example: 1개월차_게첨보고서.pdf
864 size:
865 type: integer
866 example: 2048000
867 mimeType:
868 type: string
869 example: application/pdf
870 title:
871 type: string
872 example: 1개월차 게첨보고서
873 milestone:
874 type: string
875 nullable: true
876 example: 1개월차
877 note:
878 type: string
879 nullable: true
880 uploadedAt:
881 type: string
882 format: date-time
883 uploadedBy:
884 type: integer
885 nullable: true
886 description: 업로드한 어드민 user_id
887
888 ExecutionPhoto:
889 type: object
890 properties:
891 photoId:
892 type: string
893 example: photo_abc123def456
894 executionId:
895 type: string
896 example: exec_abc123
897 url:
898 type: string
899 example: https://kr.object.ncloudstorage.com/bucket/executions/exec_abc123/photos/photo.webp
900 originalName:
901 type: string
902 example: 1개월차_정류장사진.jpg
903 size:
904 type: integer
905 example: 1024000
906 mimeType:
907 type: string
908 example: image/jpeg
909 type:
910 type: string
911 enum: [PROGRESS, FINAL]
912 description: "PROGRESS: 진행 중 시점, FINAL: 최종"
913 milestone:
914 type: string
915 description: "시점 (자유 입력)"
916 example: 1개월차
917 shotAt:
918 type: string
919 format: date
920 nullable: true
921 example: "2026-03-15"
922 note:
923 type: string
924 nullable: true
925 uploadedAt:
926 type: string
927 format: date-time
928 uploadedBy:
929 type: integer
930 nullable: true
931 description: 업로드한 어드민 user_id
932
933 ExecutionStatus:
934 type: string
935 enum:
936 - DRAFT
937 - PAYMENT_WAIT
938 - PAID
939 - MATERIAL_SENT
940 - MATERIAL_APPROVED
941 - MATERIAL_REVISION
942 - RUNNING
943 - DONE
944 - HOLD
945 - CANCELLED
946 description: |
947 10개 집행 상태:
948 DRAFT → PAYMENT_WAIT → PAID → MATERIAL_SENT → MATERIAL_APPROVED → RUNNING → DONE
949 분기: MATERIAL_REVISION, HOLD, CANCELLED
950
951 UserRole:
952 type: string
953 enum: [DEVELOPER, ADMIN, OPERATOR, MEMBER]
954 description: |
955 - DEVELOPER: 개발자 (전체 접근)
956 - ADMIN: 전체 관리 (유저관리 포함)
957 - OPERATOR: 집행/결제/소재 관리
958 - MEMBER: 고객 (기본값)
959