Compare commits
125 Commits
db63fcff85
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee1aaf183d | ||
|
|
c143c7e9f8 | ||
| 7a969b9d57 | |||
| cc38b00c11 | |||
| bfcd6178ea | |||
| 04e877dea3 | |||
| 85dc30bfcd | |||
|
|
e94123ad49 | ||
|
|
1f7bd13816 | ||
|
|
03ccd7ba93 | ||
|
|
ec3abc1a85 | ||
|
|
5000c67ec1 | ||
| 925ed82ae1 | |||
|
|
8f939d3609 | ||
|
|
2efe56df70 | ||
|
|
129332d4b1 | ||
|
|
4d13301ce0 | ||
|
|
7c9f7afb52 | ||
|
|
8e700fcd64 | ||
|
|
ba68e138e6 | ||
|
|
95b9efbcc5 | ||
|
|
2dc20952b2 | ||
|
|
428e77aa9b | ||
| 03850fefdd | |||
| cf189fd453 | |||
| b1472f3c35 | |||
| 5bfc89afa7 | |||
| f1683f753e | |||
|
|
5798058125 | ||
| 614e90066f | |||
| 8efe0ac477 | |||
| 303de36e1c | |||
| 334c8f3918 | |||
|
|
6d042f5bfd | ||
|
|
51446080db | ||
|
|
37bbab7cd4 | ||
|
|
29117d65d4 | ||
|
|
78cfc292a9 | ||
|
|
4f90c0e869 | ||
|
|
09793e629b | ||
| 0223c33fd9 | |||
|
|
ee42b12c2b | ||
|
|
4c581ad7f5 | ||
|
|
52417acad6 | ||
|
|
8cb15cf3c4 | ||
|
|
a3c910d91b | ||
|
|
f8c4536331 | ||
|
|
23570d3ee9 | ||
|
|
b8fa244271 | ||
|
|
4c9fd233cc | ||
|
|
8769b68ef0 | ||
|
|
132c573ab9 | ||
|
|
415c55b7c0 | ||
|
|
66db1832da | ||
|
|
d5e6172c22 | ||
|
|
08577b5af9 | ||
|
|
7b45b4c635 | ||
|
|
1f06c1a607 | ||
|
|
00d7a583cb | ||
|
|
8be729c698 | ||
|
|
9c5443aec1 | ||
|
|
5d76705f4f | ||
|
|
b52c31a700 | ||
|
|
490477421d | ||
|
|
359dc5d029 | ||
|
|
0123c3d780 | ||
|
|
fc97dfe454 | ||
|
|
0ae6eec973 | ||
|
|
d24a19a3f1 | ||
|
|
ac35c5c0f9 | ||
|
|
fd60e51ac9 | ||
|
|
dd3b045c46 | ||
|
|
62fdc6869b | ||
|
|
d44b99d5e4 | ||
|
|
007277d401 | ||
|
|
9c00447e18 | ||
|
|
bbe410150d | ||
|
|
b8a8ca5442 | ||
|
|
8d6fd5aee6 | ||
|
|
41b1e01ce4 | ||
|
|
b8e249c6b3 | ||
|
|
5acac8f558 | ||
|
|
f98d287958 | ||
|
|
e9c7cd21cc | ||
|
|
0c923401bf | ||
|
|
73d25b99ec | ||
|
|
b7d1fb97b4 | ||
|
|
5957261ffa | ||
|
|
9660c58bf4 | ||
|
|
8601d1738e | ||
|
|
f0f4a8627d | ||
|
|
a0de29d5ec | ||
|
|
27e17bad48 | ||
|
|
98b01bf633 | ||
|
|
cd3b155cdc | ||
|
|
e4b875a69f | ||
|
|
92c5b3575d | ||
|
|
52e3f8e375 | ||
|
|
3013406100 | ||
|
|
96f25fc0eb | ||
|
|
d57b84c8f2 | ||
|
|
01eee88e40 | ||
|
|
bf6bbf92f7 | ||
|
|
24a542cb95 | ||
|
|
6fb6e4fdbe | ||
|
|
833a957d9e | ||
|
|
1a1ef04798 | ||
|
|
ccb93e3aca | ||
|
|
06e8d5f328 | ||
|
|
71654f5f63 | ||
|
|
03e12d8fe2 | ||
|
|
56fdf76f49 | ||
|
|
8278284e97 | ||
|
|
665e6b52a4 | ||
|
|
61f226be7f | ||
|
|
b1b6a83aef | ||
|
|
768ab68f13 | ||
|
|
017f492d70 | ||
|
|
24271cfef3 | ||
|
|
6ba8738b71 | ||
|
|
a8e5e2fba0 | ||
|
|
9a2948da6c | ||
|
|
89226629eb | ||
|
|
a28a4ef2f2 | ||
|
|
57b9a189a4 |
41
.gitignore
vendored
41
.gitignore
vendored
@@ -1,2 +1,43 @@
|
||||
# 모든 파일 무시
|
||||
*
|
||||
|
||||
# 추적할 파일만 허용
|
||||
!.gitignore
|
||||
!INDEX.md
|
||||
!README.md
|
||||
!resources.md
|
||||
|
||||
# 문서 폴더 (루트 기준)
|
||||
!assets/
|
||||
!assets/**
|
||||
!brochure/
|
||||
!brochure/**
|
||||
!changes/
|
||||
!changes/**
|
||||
!contracts/
|
||||
!contracts/**
|
||||
contracts/docx/backup/
|
||||
!data/
|
||||
!data/**
|
||||
!dev/
|
||||
!dev/**
|
||||
!features/
|
||||
!features/**
|
||||
!frontend/
|
||||
!frontend/**
|
||||
!guides/
|
||||
!guides/**
|
||||
!plans/
|
||||
!plans/**
|
||||
!projects/
|
||||
!projects/**
|
||||
!requests/
|
||||
!requests/**
|
||||
!rules/
|
||||
!rules/**
|
||||
!system/
|
||||
!system/**
|
||||
|
||||
# 기타
|
||||
.DS_Store
|
||||
_to_notion/
|
||||
|
||||
5
INDEX.md
5
INDEX.md
@@ -1,7 +1,7 @@
|
||||
# SAM 문서 인덱스 (Claude Code용)
|
||||
|
||||
> 작업 유형에 맞는 문서를 먼저 읽고 시작하세요.
|
||||
> 최종 갱신: 2026-03-05
|
||||
> 최종 갱신: 2026-03-07
|
||||
|
||||
---
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
| 견적관리 | `features/quotes/README.md` | 견적 시스템, BOM 계산 |
|
||||
| 운영 배포 | `dev/dev_plans/production-deployment-plan.md` | 배포 계획 |
|
||||
| 서버 운영 | `dev/deploys/ops-manual/README.md` | 서버 운영 매뉴얼 |
|
||||
| 서버 접근/백업 | `system/server-access-management.md` | 계정, 권한, 백업, 리플리케이션 |
|
||||
| MES | `projects/mes/README.md` | MES 프로젝트 |
|
||||
|
||||
---
|
||||
@@ -72,6 +73,7 @@ docs/
|
||||
| [docker-setup.md](system/docker-setup.md) | Docker 환경 + CI/CD |
|
||||
| [database/README.md](system/database/README.md) | DB 스키마 인덱스 |
|
||||
| [security-policy.md](system/security-policy.md) | 보안 정책 |
|
||||
| [server-access-management.md](system/server-access-management.md) | 서버 접근 권한, 백업, 리플리케이션 |
|
||||
| [scaling-roadmap.md](system/scaling-roadmap.md) | 스케일링 로드맵 |
|
||||
| [board-system-spec.md](system/board-system-spec.md) | 게시판 시스템 설계 |
|
||||
| [item-master-integration.md](system/item-master-integration.md) | 품목 마스터 통합 설계 |
|
||||
@@ -139,6 +141,7 @@ DB 도메인별:
|
||||
| [card-vehicle/README.md](features/card-vehicle/README.md) | 법인카드·차량 |
|
||||
| [settlement/README.md](features/settlement/README.md) | 정산 |
|
||||
| [barobill-kakaotalk/README.md](features/barobill-kakaotalk/README.md) | 바로빌 카카오톡 |
|
||||
| [quality-management/README.md](features/quality-management/README.md) | 품질관리 (제품검사, 실적신고) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SAM 프로젝트 문서
|
||||
# SAM 프로젝트 문서
|
||||
|
||||
SAM ERP 시스템의 기술 문서, 비즈니스 규칙, 기능 명세를 관리하는 저장소입니다.
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -197,10 +197,12 @@
|
||||
<div style="margin-top: auto; border-top: 1pt solid rgba(255,255,255,0.08); padding-top: 10pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 8pt; font-weight: 700; color: rgba(255,255,255,0.7);">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; color: rgba(255,255,255,0.3); margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 8pt; font-weight: 700; color: rgba(255,255,255,0.7);">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; color: rgba(255,255,255,0.3); margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 7pt; color: rgba(255,255,255,0.3);">무료 데모 및 상담</p>
|
||||
<p style="white-space: nowrap; font-size: 8pt; font-weight: 600; color: #10B981;">contact@codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -209,17 +209,19 @@
|
||||
<div style="margin-top: auto; background: rgba(16,185,129,0.08); border: 1pt solid rgba(16,185,129,0.2); border-radius: 8pt; padding: 12pt 14pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 10pt; font-weight: 800; color: #ffffff;">무료 데모를 신청하세요</p>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; color: rgba(255,255,255,0.45); margin-top: 3pt;">귀사에 최적화된 맞춤 데모를 제공합니다</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 7pt; color: rgba(255,255,255,0.4); margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 700; color: #10B981;">contact@codebridge-x.com</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; color: rgba(255,255,255,0.4); margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 회사명 -->
|
||||
<div style="margin-top: 6pt; text-align: center;">
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.2);">SAM — Smart Automation Management</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.2);">(주)코드브릿지엑스 | SAM - Smart Automation Management</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -110,8 +110,8 @@
|
||||
<div style="margin-top: auto; border-top: 1pt solid rgba(255,255,255,0.08); padding-top: 12pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 8pt; font-weight: 700; color: rgba(255,255,255,0.7);">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; color: rgba(255,255,255,0.3); margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 8pt; font-weight: 700; color: rgba(255,255,255,0.7);">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; color: rgba(255,255,255,0.3); margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 7pt; color: rgba(255,255,255,0.35);">뒷면에서 상세 기능과 가격을 확인하세요</p>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -248,10 +248,12 @@
|
||||
<div style="margin-top: auto; border-top: 1pt solid rgba(255,255,255,0.06); padding-top: 8pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 700; color: rgba(255,255,255,0.65);">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.25); margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 700; color: rgba(255,255,255,0.65);">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.25); margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.3);">무료 데모 신청</p>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 600; color: #0EA5E9;">contact@codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -224,17 +224,19 @@
|
||||
<div style="margin-top: auto; background: rgba(14,165,233,0.08); border: 1.5pt solid rgba(14,165,233,0.2); border-radius: 8pt; padding: 10pt 14pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 10pt; font-weight: 800; color: #ffffff;">무료 데모를 신청하세요</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; color: rgba(255,255,255,0.4); margin-top: 2pt;">대표님 전용 대시보드를 직접 체험해 보세요</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.35); margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 700; color: #0EA5E9;">contact@codebridge-x.com</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.35); margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 회사명 -->
|
||||
<div style="margin-top: 5pt; text-align: center;">
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.18);">SAM — Smart Automation Management</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.18);">(주)코드브릿지엑스 | SAM - Smart Automation Management</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -160,8 +160,8 @@
|
||||
<div style="margin-top: auto; border-top: 1pt solid rgba(255,255,255,0.06); padding-top: 10pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 8pt; font-weight: 700; color: rgba(255,255,255,0.65);">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; color: rgba(255,255,255,0.25); margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 8pt; font-weight: 700; color: rgba(255,255,255,0.65);">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; color: rgba(255,255,255,0.25); margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 7pt; color: rgba(255,255,255,0.3);">뒷면에서 상세 기능을 확인하세요</p>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -392,10 +392,12 @@
|
||||
<div style="margin-top: auto; border-top: 1pt solid rgba(255,255,255,0.06); padding-top: 7pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 700; color: rgba(255,255,255,0.6);">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.2); margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 700; color: rgba(255,255,255,0.6);">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.2); margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.25);">무료 데모 신청</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 600; color: #0EA5E9;">contact@codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -354,18 +354,20 @@
|
||||
<path d="M11 6 L11 11 L15 13" fill="none" stroke="#0EA5E9" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 9pt; font-weight: 800; color: #ffffff;">무료 데모를 신청하세요</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.35); margin-top: 1pt;">대표님 전용 대시보드를 직접 체험</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.3); margin-top: 1pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; font-weight: 700; color: #0EA5E9;">contact@codebridge-x.com</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.3); margin-top: 1pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 회사명 -->
|
||||
<div style="margin-top: 4pt; text-align: center;">
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: rgba(255,255,255,0.15);">SAM — Smart Automation Management</p>
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: rgba(255,255,255,0.15);">(주)코드브릿지엑스 | SAM - Smart Automation Management</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -250,8 +250,8 @@
|
||||
<div style="margin-top: auto; border-top: 1pt solid rgba(255,255,255,0.06); padding-top: 8pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 700; color: rgba(255,255,255,0.6);">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.2); margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 700; color: rgba(255,255,255,0.6);">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.2); margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.25);">뒷면에서 상세 기능을 확인하세요 ▶</p>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -392,10 +392,12 @@
|
||||
<div style="margin-top: auto; border-top: 1pt solid #E2E8F0; padding-top: 7pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 700; color: #475569;">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #CBD5E1; margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 700; color: #475569;">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #CBD5E1; margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #94A3B8;">무료 데모 신청</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 600; color: #0EA5E9;">contact@codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -354,18 +354,20 @@
|
||||
<path d="M11 6 L11 11 L15 13" fill="none" stroke="#0EA5E9" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 9pt; font-weight: 800; color: #0F172A;">무료 데모를 신청하세요</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: #94A3B8; margin-top: 1pt;">대표님 전용 대시보드를 직접 체험</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #94A3B8; margin-top: 1pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; font-weight: 700; color: #0EA5E9;">contact@codebridge-x.com</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #94A3B8; margin-top: 1pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 회사명 -->
|
||||
<div style="margin-top: 4pt; text-align: center;">
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: #CBD5E1;">SAM — Smart Automation Management</p>
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: #CBD5E1;">(주)코드브릿지엑스 | SAM - Smart Automation Management</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -248,8 +248,8 @@
|
||||
<div style="margin-top: auto; border-top: 1pt solid #E2E8F0; padding-top: 8pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 700; color: #475569;">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: #CBD5E1; margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 700; color: #475569;">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: #CBD5E1; margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: #94A3B8;">뒷면에서 상세 기능을 확인하세요 ▶</p>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -306,10 +306,12 @@
|
||||
<div style="margin-top: auto; border-top: 1pt solid rgba(251,191,36,0.1); padding-top: 7pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 700; color: rgba(255,255,255,0.6);">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.2); margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 700; color: rgba(255,255,255,0.6);">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.2); margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.25);">무료 데모 신청</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 600; color: #FBBF24;">contact@codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -292,18 +292,20 @@
|
||||
<path d="M11 6 L11 11 L15 13" fill="none" stroke="#FBBF24" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 9pt; font-weight: 800; color: #ffffff;">무료 데모를 신청하세요</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.3); margin-top: 1pt;">대표님 전용 대시보드를 직접 체험</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.25); margin-top: 1pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; font-weight: 700; color: #FBBF24;">contact@codebridge-x.com</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.25); margin-top: 1pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 회사명 -->
|
||||
<div style="margin-top: 4pt; text-align: center;">
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: rgba(255,255,255,0.15);">SAM — Smart Automation Management</p>
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: rgba(255,255,255,0.15);">(주)코드브릿지엑스 | SAM - Smart Automation Management</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -204,8 +204,8 @@
|
||||
<div style="margin-top: auto; border-top: 1pt solid rgba(251,191,36,0.1); padding-top: 8pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 700; color: rgba(255,255,255,0.6);">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.2); margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 700; color: rgba(255,255,255,0.6);">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.2); margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.25);">뒷면에서 상세 기능을 확인하세요 ▶</p>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -359,10 +359,12 @@
|
||||
<div style="margin-top: auto; background: #EFF6FF; border-radius: 5pt; padding: 7pt 10pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 700; color: #1E293B;">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #94A3B8; margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 700; color: #1E293B;">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #94A3B8; margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #94A3B8;">무료 데모 신청</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 600; color: #2563EB;">contact@codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -316,18 +316,20 @@
|
||||
<path d="M11 6 L11 11 L15 13" fill="none" stroke="#FFFFFF" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 9pt; font-weight: 800; color: #FFFFFF;">무료 데모를 신청하세요</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.6); margin-top: 1pt;">대표님 전용 대시보드를 직접 체험</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.5); margin-top: 1pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; font-weight: 700; color: #FFFFFF;">contact@codebridge-x.com</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.5); margin-top: 1pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 회사명 -->
|
||||
<div style="margin-top: 4pt; text-align: center;">
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: #94A3B8;">SAM — Smart Automation Management</p>
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: #94A3B8;">(주)코드브릿지엑스 | SAM - Smart Automation Management</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -217,8 +217,8 @@
|
||||
<div style="margin-top: auto; background: #EFF6FF; border-radius: 5pt; padding: 8pt 12pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 700; color: #1E293B;">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: #94A3B8; margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 700; color: #1E293B;">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: #94A3B8; margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: #94A3B8;">뒷면에서 상세 기능을 확인하세요 ▶</p>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -363,10 +363,12 @@
|
||||
<div style="margin-top: auto; border-top: 1pt solid #D6D3D1; padding-top: 7pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 700; color: #57534E;">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #A8A29E; margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 700; color: #57534E;">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #A8A29E; margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #A8A29E;">무료 데모 신청</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 600; color: #0D9488;">contact@codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -354,18 +354,20 @@
|
||||
<path d="M11 6 L11 11 L15 13" fill="none" stroke="#0D9488" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 9pt; font-weight: 800; color: #292524;">무료 데모를 신청하세요</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: #A8A29E; margin-top: 1pt;">대표님 전용 대시보드를 직접 체험</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #A8A29E; margin-top: 1pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; font-weight: 700; color: #0D9488;">contact@codebridge-x.com</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #A8A29E; margin-top: 1pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 회사명 -->
|
||||
<div style="margin-top: 4pt; text-align: center;">
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: #A8A29E;">SAM — Smart Automation Management</p>
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: #A8A29E;">(주)코드브릿지엑스 | SAM - Smart Automation Management</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -266,8 +266,8 @@
|
||||
<div style="margin-top: auto; border-top: 1pt solid #D6D3D1; padding-top: 8pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 700; color: #57534E;">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: #A8A29E; margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 700; color: #57534E;">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: #A8A29E; margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: #A8A29E;">뒷면에서 상세 기능을 확인하세요 ▶</p>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -218,18 +218,20 @@
|
||||
<path d="M11 6 L11 11 L15 13" fill="none" stroke="#F97316" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 8pt; font-weight: 800; color: #FFFFFF;">무료 데모를 신청하세요</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.5); margin-top: 1pt;">대표님 전용 대시보드를 직접 체험</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: rgba(255,255,255,0.3); margin-top: 1pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; font-weight: 700; color: #F97316;">contact@codebridge-x.com</p>
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: rgba(255,255,255,0.3); margin-top: 1pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 회사명 -->
|
||||
<div style="margin-top: 5pt; text-align: center;">
|
||||
<p style="white-space: nowrap; font-size: 5pt; color: #94A3B8;">SAM — Smart Automation Management</p>
|
||||
<p style="white-space: nowrap; font-size: 5pt; color: #94A3B8;">(주)코드브릿지엑스 | SAM - Smart Automation Management</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -301,18 +301,20 @@
|
||||
<path d="M11 6 L11 11 L15 13" fill="none" stroke="#F97316" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 9pt; font-weight: 800; color: #FFFFFF;">무료 데모를 신청하세요</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: rgba(255,255,255,0.5); margin-top: 1pt;">대표님 전용 대시보드를 직접 체험</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.3); margin-top: 1pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; font-weight: 700; color: #F97316;">contact@codebridge-x.com</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: rgba(255,255,255,0.3); margin-top: 1pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 회사명 -->
|
||||
<div style="padding: 5pt 0; text-align: center;">
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: #94A3B8;">SAM — Smart Automation Management</p>
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: #94A3B8;">(주)코드브릿지엑스 | SAM - Smart Automation Management</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -268,8 +268,8 @@
|
||||
<div style="margin-top: auto; border-top: 1pt solid #E2E8F0; padding-top: 8pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 700; color: #475569;">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: #94A3B8; margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7.5pt; font-weight: 700; color: #475569;">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: #94A3B8; margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; color: #94A3B8;">뒷면에서 상세 기능을 확인하세요 ▶</p>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -250,15 +250,17 @@
|
||||
<div style="margin-top: auto; background: #F8FAFC; border: 1pt solid #F1F5F9; border-radius: 5pt; padding: 8pt 10pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 8pt; font-weight: 800; color: #0F172A;">무료 데모를 신청하세요</p>
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: #64748B; margin-top: 1pt;">대표님 전용 대시보드를 직접 체험</p>
|
||||
</div>
|
||||
<div style="text-align: right; border-left: 2pt solid #6366F1; padding-left: 8pt;">
|
||||
<p style="white-space: nowrap; font-size: 5pt; color: #CBD5E1; margin-top: 1pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; font-weight: 700; color: #6366F1;">contact@codebridge-x.com</p>
|
||||
<p style="white-space: nowrap; font-size: 5pt; color: #CBD5E1; margin-top: 1pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 4pt; text-align: center;">
|
||||
<p style="white-space: nowrap; font-size: 4.5pt; color: #CBD5E1;">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 4.5pt; color: #CBD5E1;">(주)코드브릿지엑스</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -211,17 +211,19 @@
|
||||
<div style="margin-top: auto; background: #F8FAFC; border: 1pt solid #F1F5F9; border-radius: 6pt; padding: 12pt 14pt;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 10pt; font-weight: 800; color: #0F172A;">무료 데모를 신청하세요</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #64748B; margin-top: 2pt;">대표님 전용 대시보드를 직접 체험</p>
|
||||
</div>
|
||||
<div style="text-align: right; border-left: 2pt solid #6366F1; padding-left: 10pt;">
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: #CBD5E1; margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 6.5pt; font-weight: 700; color: #6366F1;">contact@codebridge-x.com</p>
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: #CBD5E1; margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div style="margin-top: 6pt; text-align: center;">
|
||||
<p style="white-space: nowrap; font-size: 5pt; color: #CBD5E1;">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 5pt; color: #CBD5E1;">(주)코드브릿지엑스</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -172,8 +172,8 @@
|
||||
<!-- Footer -->
|
||||
<div style="margin-top: auto; display: flex; justify-content: space-between; align-items: flex-end;">
|
||||
<div>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 600; color: #0F172A;">SAM</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #CBD5E1; margin-top: 2pt;">www.sam.it.kr</p>
|
||||
<p style="white-space: nowrap; font-size: 7pt; font-weight: 600; color: #0F172A;">(주)코드브릿지엑스</p>
|
||||
<p style="white-space: nowrap; font-size: 6pt; color: #CBD5E1; margin-top: 2pt;">www.codebridge-x.com</p>
|
||||
</div>
|
||||
<p style="white-space: nowrap; font-size: 5.5pt; color: #CBD5E1;">뒷면에서 상세 기능을 확인하세요</p>
|
||||
</div>
|
||||
|
||||
119
changes/20260303_gemini_model_upgrade.md
Normal file
119
changes/20260303_gemini_model_upgrade.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Gemini 모델 업그레이드: 2.0-flash → 2.5-flash
|
||||
|
||||
**날짜:** 2026-03-03
|
||||
**작업자:** Claude Code
|
||||
|
||||
---
|
||||
|
||||
## 변경 개요
|
||||
|
||||
Google이 2026년 6월 1일부로 Gemini 2.0 Flash 모델 서비스를 종료한다는 통보를 받아, SAM 시스템 전체의 Gemini 모델을 `gemini-2.0-flash` → `gemini-2.5-flash`로 마이그레이션했다.
|
||||
|
||||
---
|
||||
|
||||
## 변경 사유
|
||||
|
||||
- Google의 공식 메일 통보: Gemini 2.0 Flash / 2.0 Flash-Lite → 2026-06-01 강제 종료
|
||||
- 마이그레이션 경로: `gemini-2.0-flash` → `gemini-2.5-flash`
|
||||
- API 키, Base URL 변경 없음 (모델명만 변경)
|
||||
|
||||
---
|
||||
|
||||
## 수정된 파일
|
||||
|
||||
### API 프로젝트 (`/home/aweso/sam/api`)
|
||||
|
||||
| 파일 | 변경 내용 |
|
||||
|------|----------|
|
||||
| `.env` | `GEMINI_MODEL=gemini-2.0-flash` → `gemini-2.5-flash` |
|
||||
| `config/services.php` | fallback 기본값 `gemini-2.0-flash` → `gemini-2.5-flash` |
|
||||
| `app/Services/AiReportService.php` | fallback 기본값 변경 |
|
||||
|
||||
### MNG 프로젝트 (`/home/aweso/sam/mng`)
|
||||
|
||||
| 파일 | 변경 내용 |
|
||||
|------|----------|
|
||||
| `.env` | `GEMINI_MODEL=gemini-2.0-flash` → `gemini-2.5-flash` |
|
||||
| `config/services.php` | fallback 기본값 변경 |
|
||||
| `app/Models/System/AiConfig.php` | `DEFAULT_MODELS['gemini']` 상수 + `getActiveGemini()` fallback 변경 |
|
||||
| `app/Services/NotionService.php` | fallback 기본값 변경 |
|
||||
| `resources/views/system/ai-config/index.blade.php` | UI placeholder, 기본값, JS defaultModels 변경 |
|
||||
| `resources/views/google-cloud/ai-guide/index.blade.php` | 서비스 현황 테이블 모델명 7곳 변경 |
|
||||
| `resources/views/academy/env-management.blade.php` | 환경변수 예시 테이블 변경 |
|
||||
|
||||
### 문서 (`/home/aweso/sam/docs`)
|
||||
|
||||
| 파일 | 변경 내용 |
|
||||
|------|----------|
|
||||
| `guides/ai-config-settings.md` | 기본 모델명 업데이트, 최종 업데이트 날짜 변경 |
|
||||
| `guides/ai-management.md` | **신규** — AI 관리 종합 가이드 (아키텍처, 버전 이력, 온보딩) |
|
||||
| `guides/ai-model-update-workflow.md` | **신규** — 모델 업데이트 표준 절차 (7단계 워크플로우) |
|
||||
| `changes/20260303_gemini_model_upgrade.md` | **신규** — 이 변경 이력 문서 |
|
||||
|
||||
### 수정하지 않은 파일 (의도적)
|
||||
|
||||
| 파일 | 이유 |
|
||||
|------|------|
|
||||
| `api/database/migrations/2026_01_27_*.php` | 이미 실행된 마이그레이션 — 변경 시 DB 무결성 문제 |
|
||||
| `api/database/migrations/2026_02_07_*.php` | 동일 |
|
||||
| `api/database/migrations/2026_02_09_*.php` | 동일 |
|
||||
| `mng/views/google-cloud/cloud-api-pricing/index.blade.php` | `2.0 → 2.5` 마이그레이션 안내 UI — 이전 모델명이 의도적 잔존 |
|
||||
|
||||
---
|
||||
|
||||
## 서버 .env 수정 필요 (배포 후)
|
||||
|
||||
| 환경 | 파일 | 변수 | 담당 |
|
||||
|------|------|------|------|
|
||||
| 개발서버 | `/home/webservice/api/.env` | `GEMINI_MODEL=gemini-2.5-flash` | SSH 접속 수정 |
|
||||
| 개발서버 | `/home/webservice/mng/.env` | `GEMINI_MODEL=gemini-2.5-flash` | SSH 접속 수정 |
|
||||
| 운영서버 | `/home/webservice/api/.env` | `GEMINI_MODEL=gemini-2.5-flash` | 개발팀장 직접 |
|
||||
| 운영서버 | `/home/webservice/mng/.env` | `GEMINI_MODEL=gemini-2.5-flash` | 개발팀장 직접 |
|
||||
|
||||
수정 후 반드시 실행:
|
||||
```bash
|
||||
php artisan config:clear
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DB 단가 설정 필요
|
||||
|
||||
MNG `/system/ai-token-usage` → 단가 설정에서:
|
||||
- 기존 `gemini-2.0-flash` 단가 → 비활성화
|
||||
- 신규 `gemini-2.5-flash` 단가 추가:
|
||||
- `input_price_per_million`: 0.15
|
||||
- `output_price_per_million`: 0.60
|
||||
- `exchange_rate`: 현재 환율
|
||||
|
||||
---
|
||||
|
||||
## 테스트 체크리스트
|
||||
|
||||
- [x] 로컬 .env 수정 완료
|
||||
- [x] 코드 fallback 전체 변경 완료
|
||||
- [ ] 로컬 연결 테스트 (MNG `/system/ai-config`)
|
||||
- [ ] 개발서버 .env 수정 + config:clear
|
||||
- [ ] 개발서버 연결 테스트
|
||||
- [ ] 운영서버 .env 수정 + config:clear
|
||||
- [ ] DB 단가 설정 (gemini-2.5-flash)
|
||||
- [ ] 토큰 사용량 로그 확인 (새 모델명)
|
||||
|
||||
---
|
||||
|
||||
## 롤백 절차
|
||||
|
||||
문제 발생 시 `.env`만 되돌리면 즉시 복구:
|
||||
```bash
|
||||
# 모든 환경의 .env에서
|
||||
GEMINI_MODEL=gemini-2.0-flash
|
||||
php artisan config:clear
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [AI 관리 종합 가이드](../guides/ai-management.md)
|
||||
- [모델 업데이트 워크플로우](../guides/ai-model-update-workflow.md)
|
||||
- [AI 설정 기술문서](../guides/ai-config-settings.md)
|
||||
165
changes/20260304_eaccount_infinite_loop_fix.md
Normal file
165
changes/20260304_eaccount_infinite_loop_fix.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# 계좌 입출금내역 부분 월 조회 시 무한루프 크래시 수정
|
||||
|
||||
**날짜:** 2026-03-04
|
||||
**작업자:** Claude Code
|
||||
|
||||
---
|
||||
|
||||
## 변경 개요
|
||||
|
||||
계좌 입출금내역 페이지에서 **날짜를 수동 입력**하여 조회 시 500 에러가 발생하는 문제를 수정했다.
|
||||
편의 버튼(이번달, 지난달 등)은 항상 전체 월(1일~말일)을 사용하여 문제가 없었으나,
|
||||
수동으로 날짜를 입력하면 **부분 월**(예: 12/01~12/18)이 되어 무한루프가 발생했다.
|
||||
|
||||
---
|
||||
|
||||
## 근본 원인
|
||||
|
||||
### `splitDateRangeMonthly()` 함수의 cursor 이동 버그
|
||||
|
||||
긴 기간 조회 시 바로빌 SOAP API의 한계로 인해 기간을 **월별 청크**로 분할하는 함수에서,
|
||||
endDate가 **월 중간**일 때 cursor가 **같은 달 1일로 되돌아가** 무한루프가 발생했다.
|
||||
|
||||
```php
|
||||
// ❌ 버그 코드 — endDate가 월 중간이면 무한루프
|
||||
$cursor = $chunkEnd->copy()->addDay()->startOfMonth();
|
||||
|
||||
// 예시: endDate = 20251218
|
||||
// chunkEnd = 20251218
|
||||
// → addDay() = 20251219
|
||||
// → startOfMonth() = 20251201 ← 같은 달 1일로 되돌아감!
|
||||
// → while($cursor <= $end) 조건 여전히 true → 무한 반복
|
||||
```
|
||||
|
||||
```php
|
||||
// ✅ 수정 코드 — chunkStart 기준으로 다음 월로 이동
|
||||
$cursor = $chunkStart->copy()->addMonth()->startOfMonth();
|
||||
|
||||
// 예시: startDate = 20251201
|
||||
// chunkStart = 20251201
|
||||
// → addMonth() = 20260101
|
||||
// → startOfMonth() = 20260101 ← 다음 달로 정상 이동
|
||||
// → while($cursor <= $end) 조건 false → 루프 종료
|
||||
```
|
||||
|
||||
### 재현 조건
|
||||
|
||||
| 조건 | 결과 |
|
||||
|------|------|
|
||||
| 전체 월 (12/01~12/31) | 정상 — `addDay()` = 01/01 → `startOfMonth()` = 01/01 |
|
||||
| 부분 월 (12/01~12/18) | **무한루프** — `addDay()` = 12/19 → `startOfMonth()` = 12/01 |
|
||||
| 다중 월 (12/01~02/18) | **무한루프** — 마지막 월이 부분 월이면 동일 증상 |
|
||||
|
||||
### 증상
|
||||
|
||||
- PHP 프로세스가 메모리 한도(256M/512M)에 도달하여 **Fatal Error로 크래시**
|
||||
- Laravel 로그에 에러 기록 없음 (try-catch 밖에서 프로세스가 종료)
|
||||
- 프론트엔드에 `서버 응답 오류 (500):` (빈 응답 본문)
|
||||
|
||||
---
|
||||
|
||||
## 수정된 파일
|
||||
|
||||
| 파일 | 변경 내용 |
|
||||
|------|----------|
|
||||
| `app/Http/Controllers/Barobill/EaccountController.php` | `splitDateRangeMonthly()` cursor 이동 로직 수정 |
|
||||
|
||||
---
|
||||
|
||||
## 검증 결과
|
||||
|
||||
tinker에서 수정 전후 비교 테스트:
|
||||
|
||||
```
|
||||
=== 수정 전 (버그): 20251201~20251218 ===
|
||||
→ 같은 청크 무한 반복 (10회 제한으로 강제 중단)
|
||||
|
||||
=== 수정 후: 20251201~20251218 ===
|
||||
→ [{start: 20251201, end: 20251218}] ← 1개 청크, 정상
|
||||
|
||||
=== 수정 후: 20251201~20260218 (다중 월) ===
|
||||
→ [{20251201~20251231}, {20260101~20260131}, {20260201~20260218}] ← 3개 청크, 정상
|
||||
|
||||
=== 수정 후: 20251215~20251231 ===
|
||||
→ [{start: 20251215, end: 20251231}] ← 1개 청크, 정상
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 동일 패턴 코드베이스 점검 결과
|
||||
|
||||
`sam/mng` 전체를 검색하여 유사 패턴을 점검했다:
|
||||
|
||||
| 파일 | 함수 | 패턴 | 위험도 |
|
||||
|------|------|------|--------|
|
||||
| `EaccountController.php` | `splitDateRangeMonthly()` | 월별 청크 분할 | ✅ 수정 완료 |
|
||||
| `DashboardStatService.php` | `generateDateRange()` | `addDay()` 단순 증가 | 안전 |
|
||||
| `InspectionCycle.php` | `getHolidayDates()` | `addDay()` 단순 증가 | 안전 |
|
||||
| `CorporateCardController.php` | `getNextBusinessDay()` | `addDay()` 단순 증가 | 안전 |
|
||||
| `PartitionManagementService.php` | `addPartitions()` | `for` 루프 (고정 횟수) | 안전 |
|
||||
|
||||
> **결론**: `EaccountController` 외에 동일 버그 패턴 없음.
|
||||
> 다른 코드들은 모두 `addDay()` 단순 증가 패턴을 사용하여 무한루프 위험 없음.
|
||||
|
||||
---
|
||||
|
||||
## 교훈 및 방지 규칙
|
||||
|
||||
### R1. 날짜 cursor 이동 시 `chunkEnd` 기반 이동 금지
|
||||
|
||||
```php
|
||||
// ❌ 위험: chunkEnd가 월 중간이면 startOfMonth()가 같은 달로 되돌림
|
||||
$cursor = $chunkEnd->copy()->addDay()->startOfMonth();
|
||||
|
||||
// ✅ 안전: chunkStart 기준으로 항상 다음 월로 이동
|
||||
$cursor = $chunkStart->copy()->addMonth()->startOfMonth();
|
||||
```
|
||||
|
||||
### R2. 날짜 루프에 안전장치(max iterations) 추가 권장
|
||||
|
||||
```php
|
||||
$maxIterations = 120; // 10년 = 120개월
|
||||
$iterations = 0;
|
||||
|
||||
while ($cursor->lte($end) && $iterations < $maxIterations) {
|
||||
// ... 청크 처리 ...
|
||||
$iterations++;
|
||||
}
|
||||
|
||||
if ($iterations >= $maxIterations) {
|
||||
Log::error('날짜 분할 루프 안전장치 작동', compact('startDate', 'endDate'));
|
||||
}
|
||||
```
|
||||
|
||||
### R3. 부분 월 테스트 필수
|
||||
|
||||
날짜 범위를 분할하는 코드 작성/수정 시 반드시 다음 케이스를 테스트:
|
||||
|
||||
- [ ] 전체 월 (01일~말일)
|
||||
- [ ] 부분 월 — 시작 (01일~중간)
|
||||
- [ ] 부분 월 — 끝 (중간~말일)
|
||||
- [ ] 다중 월 (마지막 월이 부분 월)
|
||||
- [ ] 같은 날 (시작일 = 종료일)
|
||||
|
||||
---
|
||||
|
||||
## 부수 개선 사항
|
||||
|
||||
이 문제 조사 과정에서 추가로 발견/수정된 항목:
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| WSDL 캐싱 | `WSDL_CACHE_NONE` → `WSDL_CACHE_BOTH` (4개 바로빌 컨트롤러 전체) |
|
||||
| 소켓 타임아웃 | `default_socket_timeout` 60→120초 연장 |
|
||||
| Shutdown handler | PHP Fatal Error 감지 시 Laravel 로그에 기록 |
|
||||
| SOAP 호출 로깅 | 호출 시작/완료 시간 + 소요시간(ms) 기록 |
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- `app/Http/Controllers/Barobill/EaccountController.php` — 바로빌 계좌 입출금내역
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-04
|
||||
69
changes/20260306_purchase_request_payment_method.md
Normal file
69
changes/20260306_purchase_request_payment_method.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# 품의서 지급방법 UI 개선
|
||||
|
||||
**날짜:** 2026-03-06
|
||||
**작업자:** Claude Code
|
||||
|
||||
## 변경 개요
|
||||
|
||||
품의서 2종(구매품의서, 비용정산품의서)에 지급방법 선택 기능을 추가/개선하였다.
|
||||
|
||||
## 변경 내용
|
||||
|
||||
### 1. 구매품의서 (pr_purchase) - 지급방법 추가
|
||||
|
||||
- 납품 정보(납품업체/납품예정일/납품장소) 아래에 **지급방법 radio** 추가
|
||||
- 옵션: `법인카드` / `계좌이체`
|
||||
- **일괄 선택** 방식 (전체 구매건에 하나의 지급방법)
|
||||
|
||||
### 2. 비용정산품의서 (pr_settlement) - 지급방법 행별 변경
|
||||
|
||||
- 기존: 테이블 아래에 일괄 radio (법인카드/개인선지출)
|
||||
- 변경: 각 내역행에 **지급방법 select** 컬럼 추가
|
||||
- 테이블 하단에 **지급방법별 합계표** 추가 (법인카드 합계 / 개인선지출 합계)
|
||||
- 이유: 하나의 정산서에 법인카드/개인선지출 내역이 혼재할 수 있음
|
||||
|
||||
### 3. 지출품의서 (pr_expense) - 라벨 변경
|
||||
|
||||
- `사용일자` -> `지출일자` 라벨 변경 (폼 + 조회 화면)
|
||||
|
||||
## 수정된 파일
|
||||
|
||||
| 파일 | 변경 내용 |
|
||||
|------|----------|
|
||||
| `mng/resources/views/approvals/partials/_purchase-request-form.blade.php` | 구매품의서 지급방법 radio 추가, 비용정산품의서 행별 select + 합계표, 지출일자 라벨 변경 |
|
||||
| `mng/resources/views/approvals/partials/_purchase-request-show.blade.php` | 구매품의서/비용정산품의서 조회 화면 동기화 |
|
||||
|
||||
## Alpine.js 데이터 변경
|
||||
|
||||
### 구매품의서
|
||||
|
||||
```javascript
|
||||
// formData에 추가
|
||||
payment_method: initialData?.payment_method || '',
|
||||
|
||||
// getFormData()에 포함
|
||||
{ ...base, ..., payment_method: this.formData.payment_method }
|
||||
```
|
||||
|
||||
### 비용정산품의서
|
||||
|
||||
```javascript
|
||||
// makeItem()에 추가
|
||||
payment_method: data?.payment_method || '',
|
||||
|
||||
// computed 속성 추가
|
||||
get corporateCardTotal() { /* corporate_card 행만 합산 */ },
|
||||
get personalAdvanceTotal() { /* personal_advance 행만 합산 */ },
|
||||
|
||||
// getFormData() 변경
|
||||
// 기존: payment_method: this.formData.payment_method (일괄)
|
||||
// 변경: 각 item.payment_method (행별) + corporate_card_total, personal_advance_total
|
||||
```
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [결재 양식 기술 명세](../features/approvals/form-types.md) - 섹션 12, 14 업데이트
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-06
|
||||
42
contracts/CHANGELOG.md
Normal file
42
contracts/CHANGELOG.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 계약서 개정이력
|
||||
|
||||
> **작성일**: 2026-02-22
|
||||
> **관리 대상**: 전자계약 DOCX 4종
|
||||
|
||||
---
|
||||
|
||||
## v4.1 (2026-02-22)
|
||||
|
||||
**작성자**: 개발팀
|
||||
**대상**: 고객사 서비스 이용계약서
|
||||
|
||||
- 제4조에 사용량 기반 추가 과금 조항(4.5) 추가
|
||||
- 파일 저장 공간: 기본 100GB 초과 시 100GB당 100,000원/월
|
||||
- AI 토큰: 월 100만 토큰 기본, 초과 시 1,000토큰 단위 실비 과금
|
||||
- 제4조에 바로빌 부가 서비스 요금 조항(4.6) 추가
|
||||
- 계좌조회, 카드내역, 세금계산서 발행 요금 명시
|
||||
- 홈택스 매입/매출 조회는 회사 부담 명시
|
||||
|
||||
---
|
||||
|
||||
## v4.0 (2026-02-22)
|
||||
|
||||
**작성자**: 개발팀
|
||||
|
||||
- 계약서 버전 관리 시스템 도입
|
||||
- DOCX → Markdown 미러링 체계 구축
|
||||
- 4개 전자계약 문서에 개정이력 테이블 삽입
|
||||
- 동기화 검증 스크립트 구축
|
||||
|
||||
### 대상 문서
|
||||
|
||||
| 파일 | 문서명 |
|
||||
|------|--------|
|
||||
| `01_고객_서비스이용계약서_v4_0_전자서명용.docx` | 고객사 서비스 이용계약서 |
|
||||
| `비밀유지서약서.docx` | 비밀유지서약서 (NDA) |
|
||||
| `영업파트너 위촉계약서.docx` | 영업파트너 위촉계약서 |
|
||||
| `영업파트너 위촉계약서(단체용).docx` | 영업파트너 위촉계약서 (단체용) |
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-02-22 (v4.1)
|
||||
BIN
contracts/docx/01_고객_서비스이용계약서_v4_0_전자서명용.docx
Normal file
BIN
contracts/docx/01_고객_서비스이용계약서_v4_0_전자서명용.docx
Normal file
Binary file not shown.
BIN
contracts/docx/비밀유지서약서.docx
Executable file
BIN
contracts/docx/비밀유지서약서.docx
Executable file
Binary file not shown.
BIN
contracts/docx/영업파트너 위촉계약서(단체용).docx
Executable file
BIN
contracts/docx/영업파트너 위촉계약서(단체용).docx
Executable file
Binary file not shown.
BIN
contracts/docx/영업파트너 위촉계약서.docx
Executable file
BIN
contracts/docx/영업파트너 위촉계약서.docx
Executable file
Binary file not shown.
458
contracts/markdown/01-service-agreement.md
Normal file
458
contracts/markdown/01-service-agreement.md
Normal file
@@ -0,0 +1,458 @@
|
||||
---
|
||||
title: "고객사 서비스 이용계약서"
|
||||
version: "v4.2"
|
||||
date: "2026-02-24"
|
||||
docx_file: "01_고객_서비스이용계약서_v4_0_전자서명용.docx"
|
||||
---
|
||||
|
||||
# 고객사 서비스 이용계약서
|
||||
|
||||
Customer Service Agreement
|
||||
|
||||
계약번호:
|
||||
계약일:
|
||||
|
||||
본 계약은 주식회사 코드브릿지엑스(이하 “회사”)와 간에 SAM 서비스 제공과 관련하여 다음과 같이 계약을 체결합니다.
|
||||
|
||||
## 제1조 (계약의 목적)
|
||||
|
||||
본 계약은 회사가 고객에게 SAM(Smart MES/ERP Solution) 서비스를 제공함에 있어 필요한 사항을 규정하고, 양측의 권리와 의무를 명확히 함을 목적으로 합니다.
|
||||
|
||||
## 제2조 (용어의 정의)
|
||||
|
||||
- **서비스**: 회사가 제공하는 SAM 클라우드 기반 MES/ERP 솔루션
|
||||
- **SaaS**: Software as a Service (서비스형 소프트웨어)
|
||||
- **서비스 게시**: 개발 완료 후 고객이 서비스에 접근 가능하도록 제공하는 것
|
||||
- **액세스 제공**: 고객에게 서비스 사용 권한을 부여하는 것
|
||||
- **검수 기간**: 서비스 게시 전 고객이 완성도를 확인하는 기간 (최대 1개월)
|
||||
- **하자**: 계약서에 명시된 기능의 오류, 미구현, 성능 미달 등
|
||||
- **하자담보 책임**: 서비스 게시 후 1년간 하자를 무상으로 수정하는 의무
|
||||
|
||||
## 제3조 (서비스 내용)
|
||||
|
||||
### 3.1 서비스 범위
|
||||
|
||||
회사는 다음의 서비스를 제공합니다:
|
||||
- **맞춤형 개발**:
|
||||
- 고객 요구사항에 맞춘 SAM 시스템 개발
|
||||
- 개발 범위: [별첨 기획서 참조]
|
||||
- 개발 기간: 계약일로부터 [ 3 ]개월
|
||||
- **클라우드 제공** (SaaS):
|
||||
- 연중무휴 24시간 접근 가능
|
||||
- 자동 백업 및 보안
|
||||
- **기술 지원**:
|
||||
- 고객센터 운영 (평일 09:00~18:00)
|
||||
- 이메일 지원 (24시간)
|
||||
- 긴급 장애 대응
|
||||
- **하자담보 책임** (1년):
|
||||
- 서비스 게시일로부터 1년간 무상 수정
|
||||
- 버그, 미구현 기능, 성능 개선 등
|
||||
|
||||
### 3.2 제공 방식
|
||||
|
||||
- 회사는 서비스를 **SaaS 방식**으로 제공합니다.
|
||||
- 고객은 서비스에 대한 **사용 권한**만을 부여받으며, 소유권은 회사에 귀속됩니다.
|
||||
- 소스코드는 제공되지 않습니다.
|
||||
|
||||
## 제4조 (비용 및 납부)
|
||||
|
||||
### 4.1 개발비
|
||||
|
||||
| 구분 | 금액 (부가세 별도) | 지급 시기 | 비고 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1차 개발비 | 총 개발비의 50% | 계약 체결 시 | 착수금 |
|
||||
| 2차 개발비 | 총 개발비의 50% | 서비스 게시일로부터 3일 이내 | 잔금 |
|
||||
| 총 개발비 | [ ]원 | | |
|
||||
|
||||
### 4.2 월 구독료
|
||||
|
||||
| 구분 | 금액 (부가세 별도) | 지급 시기 | 비고 |
|
||||
| --- | --- | --- | --- |
|
||||
| 월 구독료 | 원 ~ | 매월 말일 | 후불제, 사용량 기준 청구 |
|
||||
|
||||
> ⚠️ 중요: - 월 구독료는 원이며, 영업 협상 및 개발 범위에 따라 증액될 수 있습니다.
|
||||
|
||||
- 계약 시 확정된 구독료: [ ]원/월
|
||||
|
||||
### 4.3 납부 방법
|
||||
|
||||
- **개발비**:
|
||||
- 계좌이체 (세금계산서 발행)
|
||||
- 입금 계좌: 기업은행 170-175519-04-011 (주)코드브릿지엑스
|
||||
- **구독료**:
|
||||
- CMS 자동이체 (권장)
|
||||
- 또는 세금계산서 발행 후 계좌이체
|
||||
|
||||
### 4.4 잔금 지급 기한 [법률 검토 반영]
|
||||
|
||||
- **지급 기한**: 서비스 게시일로부터 **3일 이내**
|
||||
- **사전 준비**: 회사는 영업 단계부터 납품 일정을 공유하여 고객이 미리 준비할 수 있도록 합니다.
|
||||
- **미납 시 조치**: 제13조 참조
|
||||
|
||||
### 4.5 사용량 기반 추가 과금
|
||||
|
||||
기본 제공 한도 초과 시 다음과 같이 실비 과금됩니다.
|
||||
|
||||
| 항목 | 기본 제공 | 추가 과금 기준 |
|
||||
| --- | --- | --- |
|
||||
| 파일 저장 공간 | 100GB | 100GB당 100,000원/월 (부가세 별도) |
|
||||
| AI 토큰 | 월 100만 토큰 | 1,000토큰 단위 실비 과금 |
|
||||
|
||||
- **파일 저장 공간: **기본 100GB를 초과하는 경우 100GB 단위로 월 100,000원(부가세 별도)이 추가 과금됩니다.
|
||||
- **AI 토큰: **월 100만 토큰 기본 제공되며, 초과 사용 시 1,000토큰 단위로 실비 과금됩니다.
|
||||
- 미사용 잔여 토큰은 이월되지 않습니다. (매월 1일 갱신)
|
||||
- 기본 제공량 80%, 100% 소진 시 자동 알림이 발송됩니다.
|
||||
|
||||
### 4.6 바로빌 부가 서비스 요금
|
||||
|
||||
고객이 선택적으로 이용하는 바로빌 연동 서비스의 요금은 다음과 같습니다.
|
||||
|
||||
| 서비스 | 과금 방식 | 기본 제공 | 추가 과금 |
|
||||
| --- | --- | --- | --- |
|
||||
| 계좌조회 | 월정액 10,000원 | 1계좌 | 추가 1계좌당 10,000원 |
|
||||
| 카드내역 | 월정액 10,000원 | 5장 | 추가 1장당 5,000원 |
|
||||
| 세금계산서 발행 | 건별 | 100건 | 추가 50건당 5,000원 |
|
||||
|
||||
- **바로빌 서비스 요금은 고객이 부담하며, 월 구독료와 별도로 청구됩니다.**
|
||||
- 홈택스 매입/매출 조회 서비스(월 30,000원)는 회사가 부담합니다.
|
||||
- 상기 금액은 부가세 별도입니다.
|
||||
|
||||
## 제5조 (마일스톤 및 진행 일정)
|
||||
|
||||
### 5.1 개발 단계 (5단계 통일)
|
||||
|
||||
| 단계 | 주요 활동 | 진행률 | 기간 | 납부 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| M1 | 요구사항 분석 및 기획 | 20% | [ 2 ]주 | 1차 개발비 (착수금 50%) |
|
||||
| M2 | 설계 및 개발 착수 | 50% | [ 2 ]주 | - |
|
||||
| M3 | 개발 진행 (50% 완료) | 60% | [ 2 ]주 | - |
|
||||
| M4 | 개발 완료 및 테스트 | 80% | [ 2 ]주 | - |
|
||||
| M5 | 검수 및 서비스 게시 | 100% | 최대 2주 | 2차 개발비 (잔금 50%) |
|
||||
|
||||
> ⚠️ 중요: - 5단계 마일스톤으로 통일 관리 - M5 검수 완료 후 서비스 게시 - 서비스 게시일로부터 3일 이내 잔금 납부
|
||||
|
||||
### 5.2 일정 조정
|
||||
|
||||
- 개발 일정은 고객의 협조에 따라 변동될 수 있습니다.
|
||||
- 고객 귀책 사유로 인한 지연은 회사의 책임이 아닙니다.
|
||||
- 불가항력으로 인한 지연 시 양측 협의하여 일정을 조정합니다.
|
||||
|
||||
## 제6조 (서비스 게시 및 검수)
|
||||
|
||||
### 6.1 서비스 게시
|
||||
|
||||
- 회사는 개발 완료 후 고객에게 **서비스 게시**를 통지합니다.
|
||||
- **서비스 게시일**은 고객이 서비스에 접근 가능한 날짜를 의미합니다.
|
||||
- 서비스 게시일부터 구독료가 발생합니다.
|
||||
|
||||
### 6.2 검수 기간
|
||||
|
||||
- 고객은 개발 완료 후 **최대 2주간 검수 기간**을 가집니다.
|
||||
- 검수 기간은 서비스 게시 **전**에 이루어집니다.
|
||||
- 검수 기간 중 발견된 하자는 회사가 무상으로 수정합니다.
|
||||
|
||||
### 6.3 검수 완료
|
||||
|
||||
- 고객이 서면으로 검수 완료를 통지하거나,
|
||||
- 검수 기간 2주 종료 시점에 특별한 이의가 없으면 자동 승인으로 간주합니다.
|
||||
- 검수 완료 후 서비스 게시일이 확정되고, 하자담보 책임 정책이 적용됩니다.
|
||||
|
||||
## 제7조 (하자담보 책임)
|
||||
|
||||
### 7.1 책임 기간
|
||||
|
||||
서비스 게시일로부터 1년 (소프트웨어산업진흥법 제16조, 민법 제667조)
|
||||
|
||||
### 7.2 하자담보 범위 (무상 처리)
|
||||
|
||||
| 항목 | 내용 | 예시 |
|
||||
| --- | --- | --- |
|
||||
| 버그 수정 | 소프트웨어 오류 | 계산 오류, 기능 미작동 |
|
||||
| 미구현 기능 | 계약서에 명시된 기능 누락 | 약속된 기능 미구현 |
|
||||
| 성능 개선 | 명시된 성능 기준 미달 | 속도 저하, 응답 지연 |
|
||||
| UI/UX 수정 | 사용성 문제 | 버튼 미작동, 화면 깨짐 |
|
||||
| 데이터 오류 | 데이터 손실 또는 오류 | 데이터 삭제, 중복 생성 |
|
||||
| 보안 패치 | 보안 취약점 수정 | 해킹 방지, 암호화 |
|
||||
|
||||
### 7.3 제외 사항 (별도 비용)
|
||||
|
||||
| 항목 | 내용 | 예시 |
|
||||
| --- | --- | --- |
|
||||
| 신규 기능 개발 | 계약서에 없던 새 기능 | 새로운 모듈, 기능 확장 |
|
||||
| 구조 변경 | 시스템 아키텍처 변경 | DB 구조, 프레임워크 교체 |
|
||||
| 추가 모듈 | 새로운 모듈 개발 | 회계 모듈, 재고 모듈 |
|
||||
| 기획 변경 | 초기 기획과 다른 요구사항 | 화면 구성, 프로세스 변경 |
|
||||
| 교육/컨설팅 | 사용자 교육, 업무 컨설팅 | 직원 교육, 프로세스 개선 |
|
||||
|
||||
### 7.4 하자 처리 절차
|
||||
|
||||
| 단계 | 내용 | 기간 |
|
||||
| --- | --- | --- |
|
||||
| 1. 하자 신고 | 고객이 이메일로 하자 신고 | - |
|
||||
| 2. 하자 확인 | 회사가 하자 여부 판정 | 3영업일 |
|
||||
| 3. 수정 작업 | 하자 인정 시 무상 수정 | 7영업일 |
|
||||
| 4. 검수 완료 | 고객이 수정 사항 확인 | - |
|
||||
|
||||
> ⚠️ 긴급 하자 (서비스 중단)는 24시간 이내 조치합니다.
|
||||
|
||||
### 7.5 책임 면제 사유
|
||||
|
||||
다음의 경우 하자담보 책임이 면제됩니다:
|
||||
- **고객 귀책 사유**:
|
||||
- 고객의 임의 수정 또는 변경
|
||||
- 승인되지 않은 제3자 개입
|
||||
- 사용 환경 미준수
|
||||
- **불가항력**:
|
||||
- 천재지변 (지진, 태풍 등)
|
||||
- 전쟁, 테러, 전염병
|
||||
- 정부 규제 또는 법령 변경
|
||||
- **기간 만료**:
|
||||
- 서비스 게시일로부터 1년 경과
|
||||
|
||||
## 제8조 (계약 해제 및 환불)
|
||||
|
||||
### 8.1 환불 정책 개요
|
||||
|
||||
고객의 임의 해제 권리와 회사의 투입 비용 보전의 균형을 고려하여 수립되었습니다.
|
||||
|
||||
### 8.2 단계별 환불
|
||||
|
||||
### Phase 1: 상담(인터뷰) 시작 전
|
||||
|
||||
- **환불율**: 100% (전액 환불)
|
||||
- **조건**: 계약 후 상담(인터뷰) 배정 전
|
||||
- **위약금**: 없음
|
||||
- **임의 해제 가능**
|
||||
|
||||
### Phase 2: 상담(인터뷰) 시작 후, 개발 착수 전
|
||||
|
||||
| 진행 상황 | 환불율 | 공제 내역 |
|
||||
| --- | --- | --- |
|
||||
| M1: 기획안 작성 중 (50% 미만) | 80% | 상담매니저 및 기획/개발자 투입 비용 20% 공제 |
|
||||
| M2: 기획안 완료 (50% 이상) | 50% | 상담매니저 및 기획/개발자 투입 비용 50% 공제 |
|
||||
|
||||
### Phase 3: 개발 진행 중 (5단계 마일스톤 기준)
|
||||
|
||||
| 마일스톤 | 진행률 | 청구 금액(개발비 대비) | 비고 |
|
||||
| --- | --- | --- | --- |
|
||||
| M3: 개발 진행 중 (50%) | 70% | 70% | 30% 환불 |
|
||||
| M4: 개발 완료 및 테스트 | 90% | 90% | 10% 환불 |
|
||||
| M5: 서비스 개시 완료 | 100% | 100% | 환불 불가 |
|
||||
|
||||
> ⚠️ 중요: 5단계 마일스톤으로 통일 관리
|
||||
|
||||
### Phase 4: 서비스 게시 후
|
||||
|
||||
- **환불율**: 0% (환불 불가)
|
||||
- **개발비**: 전액 확정, 환불 불가
|
||||
- **구독료**: 매월 말일 후불제이므로 사용한 만큼만 청구 (환불 개념 없음)
|
||||
- **대신 제공**: 하자담보 책임 (1년) + 유지보수 (구독 기간 전체)
|
||||
|
||||
### 8.3 환불 불가 사유
|
||||
|
||||
- **고객 귀책 사유**:
|
||||
- 협조 지연으로 인한 개발 지연
|
||||
- 요구사항 변경으로 인한 추가 개발
|
||||
- 승인 거부 또는 회피
|
||||
- **약관 위반**:
|
||||
- 허위 정보 제공
|
||||
- 부정 사용 또는 재판매
|
||||
- 회사 명예 훼손
|
||||
|
||||
### 8.4 할인 계약 해지 시 추가 조건
|
||||
|
||||
본 계약이 정상가 대비 할인 조건으로 체결된 경우, 다음 조건이 추가 적용된다.
|
||||
|
||||
- 발주자 귀책 해지 시 정상가(할인 전 금액) 기준으로 정산한다.
|
||||
|
||||
## 제9조 (구독 및 해지)
|
||||
|
||||
### 9.1 구독 시작
|
||||
|
||||
- **시작일**: 서비스 게시일 (검수 완료 후)
|
||||
- **결제일**: 매월 말일
|
||||
- **청구 방식**: 후불제 (해당 월 사용량 기준)
|
||||
- **일할 계산**: (사용 일수 / 해당 월 일수) × 구독료
|
||||
|
||||
> ⚠️ 중요: - 계약 시 확정된 구독료 금액은 [ ]원/월입니다.
|
||||
|
||||
- 매월 말일에 해당 월 사용일수만큼만 후불 청구됩니다.
|
||||
|
||||
### 9.2 구독 해지
|
||||
|
||||
- 고객은 언제든지 구독을 해지할 수 있습니다. (위약금 없음)
|
||||
- 해지 신청 후 30일간 데이터 백업 기간 제공
|
||||
- 해지일로부터 30일 후 모든 데이터 완전 삭제
|
||||
|
||||
## 제10조 (유지보수 정책)
|
||||
|
||||
### 10.1 유지보수 개요
|
||||
|
||||
- **적용 대상**: 구독료를 정상 납부하는 고객
|
||||
- **적용 기간**: 구독 기간 전체 (하자담보 책임 1년 이후에도 구독 중이면 계속 제공)
|
||||
- **비용**: 월 구독료(500,000원)에 포함
|
||||
|
||||
### 10.2 하자담보 책임과의 차이
|
||||
|
||||
| 구분 | 하자담보 책임 (제7조) | 유지보수 (제9조의2) |
|
||||
| --- | --- | --- |
|
||||
| 기간 | 서비스 게시일로부터 1년 | 구독 기간 전체 |
|
||||
| 근거 | 법적 의무 (소프트웨어산업진흥법) | 계약 조건 |
|
||||
| 비용 | 무상 | 구독료에 포함 |
|
||||
| 범위 | 하자(버그, 미구현 등) | 하자 + 일반 유지보수 |
|
||||
|
||||
### 10.3 유지보수 범위 (구독료에 포함)
|
||||
|
||||
> ✅ 무상 제공: - 모든 버그 수정 및 오류 처리 - 보안 패치 및 업데이트 - 성능 최적화 - 긴급 장애 대응 (24시간 이내) - 데이터 백업 및 복구 - 기술 지원 (고객센터, 이메일) - 플랫폼 업데이트 (프레임워크, 브라우저 호환성)
|
||||
|
||||
> ❌ 별도 비용: - 신규 기능 개발 - 커스터마이징 및 추가 개발 - 기획 변경 (화면 구성, 프로세스 변경) - 외부 시스템 연동 - 추가 교육 및 컨설팅
|
||||
|
||||
### 10.4 서비스 레벨 (SLA)
|
||||
|
||||
| 심각도 | 상황 | 응답 시간 | 해결 목표 |
|
||||
| --- | --- | --- | --- |
|
||||
| 긴급 (P0) | 서비스 완전 중단 | 1시간 | 24시간 |
|
||||
| 높음 (P1) | 주요 기능 장애 | 4시간 | 3영업일 |
|
||||
| 보통 (P2) | 일반 버그 | 1영업일 | 7영업일 |
|
||||
| 낮음 (P3) | 문의/안내 | 1영업일 | 3영업일 |
|
||||
|
||||
### 10.5 정기 유지보수
|
||||
|
||||
- **월간**: 보안 패치, 백업 점검 (매월 첫째 주 일요일 새벽)
|
||||
- **분기**: 성능 최적화 (분기 말 일요일 새벽)
|
||||
- **반기**: 시스템 점검 (6월/12월 일요일 새벽)
|
||||
|
||||
> ⚠️ 모든 정기 점검은 최소 7일 전 사전 공지됩니다.
|
||||
|
||||
### 10.6 유지보수 신청
|
||||
|
||||
- **고객센터**: 02-6347-0005 (평일 09:00~18:00 )
|
||||
- **이메일**: support@codebridge-x.com (24시간)
|
||||
- **시스템 내**: SAM 시스템 내 고객지원 메뉴
|
||||
|
||||
### 10.7 유지보수 종료
|
||||
|
||||
다음의 경우 유지보수 서비스가 종료됩니다: 1. 구독 해지 시 2. 구독료 3개월 연속 미납 시 3. 중대한 약관 위반 시
|
||||
|
||||
## 제11조 (고객의 의무)
|
||||
|
||||
고객은 다음 사항을 준수해야 합니다:
|
||||
- **정확한 정보 제공**: 허위 정보 제공 금지
|
||||
- **협조 의무**: 개발에 필요한 자료 및 정보 제공
|
||||
- **부정 사용 금지**: 서비스의 재판매, 재배포 금지
|
||||
- **지적재산권 존중**: 무단 복제, 역설계 금지
|
||||
|
||||
## 제12조 (회사의 의무)
|
||||
|
||||
회사는 다음 사항을 준수합니다:
|
||||
- **서비스 제공**: 계약서에 명시된 서비스 제공
|
||||
- **하자담보 책임**: 1년간 하자 무상 수정
|
||||
- **개인정보 보호**: 개인정보보호법 준수
|
||||
- **기술 지원**: 고객센터 운영 및 기술 지원
|
||||
|
||||
## 제13조 (미입금 시 법적 조치)
|
||||
|
||||
### 13.1 개발비 미입금 절차
|
||||
|
||||
| 단계 | 시점 | 조치 내용 |
|
||||
| --- | --- | --- |
|
||||
| 1차 독촉 | 기한 경과 후 3일 | 이메일 및 SMS 발송 |
|
||||
| 내용증명 | 기한 경과 후 7일 | 우편 발송, 7일 내 입금 요청 |
|
||||
| 추심등 | 기한 경과 후 14일 | 신용정보사 연체 등록, 법률대리인 위임 |
|
||||
| 법적 조치 | 기한 경과 후 30일 | 지급명령 신청 또는 소송 제기 |
|
||||
|
||||
### 13.2 구독료 미입금 절차
|
||||
|
||||
| 단계 | 시점 | 조치 내용 |
|
||||
| --- | --- | --- |
|
||||
| 1차 실패 | 익일 | 재출금 |
|
||||
| 2차 실패 | 기한 경과 후 3일 | 재출금 |
|
||||
| 3차 실패 | 미수금 처리 | 서비스 접근 제한, 1차 독촉 |
|
||||
| 내용증명 | 기한 경과 후 7일 | 우편 발송, 7일 내 입금 요청 |
|
||||
| 서비스 중단 | 기한 경과 후 14일 | 로그인 불가, 데이터 격리 |
|
||||
| 강제 해지 | 기한 경과 후 30일 | 계약 해지, 법적 조치 검토 |
|
||||
|
||||
## 제14조 (개인정보 보호)
|
||||
|
||||
- 회사는 「개인정보 보호법」을 준수합니다.
|
||||
- 고객의 개인정보는 서비스 제공 목적으로만 사용됩니다.
|
||||
- 제3자에게 제공하지 않습니다. (법령 제외)
|
||||
- 계약 종료 시 개인정보는 즉시 삭제됩니다. (법정 보관 의무 제외)
|
||||
|
||||
## 제15조 (지적재산권)
|
||||
|
||||
- **소유권**: 서비스에 대한 모든 지적재산권은 회사에 귀속됩니다.
|
||||
- **사용 권한**: 고객은 서비스 사용 권한만을 부여받습니다.
|
||||
- **금지 사항**: 복제, 배포, 역설계, 재판매 금지
|
||||
|
||||
## 제16조 (손해배상)
|
||||
|
||||
- 회사 또는 고객이 본 계약을 위반하여 상대방에게 손해를 입힌 경우 배상 책임이 있습니다.
|
||||
- 다만, 불가항력으로 인한 손해는 배상 책임에서 제외됩니다.
|
||||
|
||||
## 제17조 (불가항력)
|
||||
|
||||
다음의 사유로 서비스 제공이 불가능한 경우 회사는 책임을 지지 않습니다:
|
||||
- 천재지변 (지진, 태풍, 홍수 등)
|
||||
- 전쟁, 테러, 전염병
|
||||
- 정부 규제 또는 법령 변경
|
||||
- 제3자의 불법 행위 (해킹 등)
|
||||
|
||||
## 제18조 (분쟁 해결)
|
||||
|
||||
- 본 계약과 관련한 분쟁은 상호 협의하여 해결합니다.
|
||||
- 협의가 이루어지지 않을 경우, **서울중앙지방법원**을 관할 법원으로 합니다.
|
||||
|
||||
## 제19조 (계약의 효력)
|
||||
|
||||
- 본 계약은 계약일로부터 효력이 발생합니다.
|
||||
- 본 계약은 구독 해지 시까지 유효합니다.
|
||||
|
||||
## 제20조 (기타)
|
||||
|
||||
- 본 계약서는 2부 작성하여 회사와 고객이 각 1부씩 보관합니다.
|
||||
- 본 계약의 해석 및 적용은 대한민국 법률을 준거법으로 합니다.
|
||||
|
||||
## 계약 당사자
|
||||
|
||||
### [회사]
|
||||
|
||||
상호: 주식회사 코드브릿지엑스
|
||||
대표자: 이의찬
|
||||
사업자등록번호: 664-86-03713
|
||||
주소: 서울특별시 강서구 양천로 583, 우림블루나인 B동 1602호
|
||||
이메일: contact@codebridge-x.com
|
||||
전화: 02-6347-0005
|
||||
서명:
|
||||
날짜:
|
||||
|
||||
### [고객]
|
||||
|
||||
상호:
|
||||
대표자:
|
||||
사업자등록번호:
|
||||
주소:
|
||||
이메일:
|
||||
전화:
|
||||
서명:
|
||||
날짜:
|
||||
|
||||
## 별첨
|
||||
|
||||
### 별첨 1: 기획서
|
||||
|
||||
[별도 첨부]
|
||||
|
||||
### 별첨 2: 개발 일정표
|
||||
|
||||
[별도 첨부]
|
||||
|
||||
### 별첨 3: 기능 명세서
|
||||
|
||||
[별도 첨부]
|
||||
|
||||
주식회사 코드브릿지엑스
|
||||
이메일: contact@codebridge-x.com
|
||||
전화: 02-6347-0005
|
||||
주소: 서울특별시 강서구 양천로 583, 우림블루나인 B동 1602호
|
||||
|
||||
199
contracts/markdown/02-nda.md
Normal file
199
contracts/markdown/02-nda.md
Normal file
@@ -0,0 +1,199 @@
|
||||
---
|
||||
title: "비밀유지서약서 (NDA)"
|
||||
version: "v4.0"
|
||||
date: "2026-02-22"
|
||||
docx_file: "비밀유지서약서.docx"
|
||||
---
|
||||
|
||||
# 비밀유지서약서 (NDA)
|
||||
|
||||
- **작성일**:
|
||||
|
||||
- **서약인 정보**
|
||||
- **구분**:
|
||||
|
||||
- **인적 사항:**
|
||||
상호(성명): _______________
|
||||
대표자(본인): _______________
|
||||
사업자등록번호(주민등록번호): ____________________
|
||||
주소: ______________________________________________________________________
|
||||
연락처: _______________
|
||||
이메일: _______________
|
||||
|
||||
## 제1조 (목적)
|
||||
|
||||
- 본 서약서는 주식회사 코드브릿지(이하 “회사”)와의 업무 관계에서 알게 된 기밀 정보를
|
||||
- 보호하기 위해 작성되었습니다.
|
||||
|
||||
## 제2조 (기밀 정보의 정의)
|
||||
|
||||
- 다음 각 호의 정보는 회사의 기밀 정보로 간주됩니다:
|
||||
|
||||
### 2.1 고객 정보
|
||||
|
||||
- 고객사 명단 (법인명, 대표자명, 연락처)
|
||||
- 고객사 담당자 정보 (성명, 부서, 연락처, 이메일)
|
||||
- 계약 내역 (가입비, 할인율, 구독료, 특약 사항)
|
||||
- 고객사의 사업 정보 (매출, 직원 수, 거래처 등)
|
||||
- 고객사가 회사에 요구한 개발 내역 및 기획 문서
|
||||
|
||||
### 2.2 영업 정보
|
||||
|
||||
- 가격 정책 (정가, 할인 정책, 최소 가입비)
|
||||
- 수수료 정책 (비율, 지급 기준, 상계 방식)
|
||||
- 영업 전략 및 마케팅 계획
|
||||
- 잠재 고객 리스트
|
||||
- 계약 체결 노하우 및 제안서 템플릿
|
||||
|
||||
### 2.3 기술 정보
|
||||
|
||||
- SAM 시스템의 소스코드
|
||||
- 데이터베이스 구조 및 설계 문서
|
||||
- 개발 프로세스 및 방법론
|
||||
- 서버 인프라 구성 및 보안 정책
|
||||
- API 키, 접속 정보, 관리자 권한
|
||||
|
||||
### 2.4 경영 정보
|
||||
|
||||
- 회사의 재무 정보 (매출, 이익, 원가)
|
||||
- 조직도 및 인사 정보
|
||||
- 사업 계획 및 전략
|
||||
- 투자 유치 및 M&A 관련 정보
|
||||
|
||||
### 2.5 기타
|
||||
|
||||
- 회사가 **“기밀(Confidential)”** 또는 **“대외비”**로 표시한 모든 문서 및 정보
|
||||
|
||||
## 제3조 (기밀 유지 의무)
|
||||
|
||||
### 3.1 기본 의무
|
||||
|
||||
- 본인은 업무 수행 중 알게 된 모든 기밀 정보를:
|
||||
- **외부에 누설하지 않습니다**
|
||||
- **업무 목적 외에 사용하지 않습니다**
|
||||
- **무단으로 복사, 복제, 전송하지 않습니다**
|
||||
- **제3자에게 제공하거나 공개하지 않습니다**
|
||||
|
||||
### 3.2 정보 관리
|
||||
|
||||
- 기밀 문서는 안전한 장소에 보관
|
||||
- 이메일, 메신저 등 전송 시 암호화
|
||||
- 업무 종료 시 모든 기밀 자료 반환 또는 파기
|
||||
- 개인 디바이스에 기밀 정보 저장 금지
|
||||
|
||||
### 3.3 제3자 접근 차단
|
||||
|
||||
- 가족, 친구 등 타인이 기밀 정보에 접근하지 못하도록 조치
|
||||
- 공공장소(카페, 도서관 등)에서 기밀 정보 취급 금지
|
||||
- 비밀번호 및 접속 정보 타인 공유 금지
|
||||
|
||||
## 제4조 (예외 사항)
|
||||
|
||||
- 다음의 정보는 기밀 정보에서 제외됩니다:
|
||||
- 본인이 알기 전에 이미 공개된 정보
|
||||
- 본인의 귀책사유 없이 공개된 정보
|
||||
- 제3자로부터 적법하게 취득한 정보
|
||||
- 본인이 독자적으로 개발한 정보
|
||||
- 법원, 정부기관의 법적 요구에 따라 공개해야 하는 정보 (단, 회사에 사전 통지 필수)
|
||||
|
||||
## 제5조 (의무 기간)
|
||||
|
||||
### 5.1 기간
|
||||
|
||||
- 본 서약서의 기밀 유지 의무는:
|
||||
- **계약 체결일로부터 효력 발생**
|
||||
- **계약 종료 후 2년간 유지**
|
||||
|
||||
### 5.2 영구 보호
|
||||
|
||||
- 단, 다음 정보는 **영구적으로** 보호됩니다:
|
||||
- 고객사 개인정보
|
||||
- 회사의 영업 비밀 (부정경쟁방지법상 영업 비밀)
|
||||
- 기술 정보 (특허, 저작권 대상)
|
||||
|
||||
## 제6조 (위반 시 책임)
|
||||
|
||||
### 6.1 민사 책임
|
||||
|
||||
- 본인이 본 서약을 위반하여 회사 또는 고객에게 손해를 입힌 경우:
|
||||
- **실손해**** 배상**: 실제 발생한 손해 전액
|
||||
- **징벌적 손해배상**: 실손해의 최대 3배 (악의적 유출 시)
|
||||
- **법률 비용**: 소송 비용, 변호사 비용 등
|
||||
|
||||
### 6.2 형사 책임
|
||||
|
||||
- 다음의 경우 형사 고발 대상이 됩니다:
|
||||
- **부정경쟁방지법** 위반 (영업 비밀 침해)
|
||||
- **개인정보보호법** 위반 (고객 정보 유출)
|
||||
- **정보통신망법** 위반 (기술 정보 침해)
|
||||
- **형법** 위반 (업무상 배임)
|
||||
- **※ 형사 처벌: 5년 이하 징역 또는 5천만원 이하 벌금**
|
||||
|
||||
### 6.3 계약 해지
|
||||
|
||||
- 회사는 본 서약 위반 시 즉시 계약을 해지할 수 있으며, 이미 지급한 수수료 또는
|
||||
- 대금을 환수할 수 있습니다.
|
||||
|
||||
## 제7조 (자료 반환)
|
||||
|
||||
### 7.1 반환 대상
|
||||
|
||||
- 계약 종료 또는 요청 시 다음을 즉시 반환해야 합니다:
|
||||
- 회사로부터 제공받은 모든 문서 (종이, 파일)
|
||||
- 고객사 계약서 및 개인정보
|
||||
- 가격표, 제안서, 템플릿 등 영업 자료
|
||||
- USB, 하드디스크 등 저장 매체
|
||||
|
||||
### 7.2 파기 확인
|
||||
|
||||
- 반환 불가능한 파일(이메일, 클라우드 등)은 즉시 삭제하고, **삭제 확인서**를 회사에
|
||||
- 제출해야 합니다.
|
||||
|
||||
## 제8조 (경업 금지)
|
||||
|
||||
### 8.1 경업 금지 기간
|
||||
|
||||
- 계약 종료 후 **6개월간** 다음 행위를 금지합니다:
|
||||
- 회사의 고객에게 경쟁 제품 판매
|
||||
- 회사의 기밀 정보를 이용한 유사 사업
|
||||
- 회사 직원 또는 영업파트너를 스카우트
|
||||
|
||||
### 8.2 예외
|
||||
|
||||
- 단순히 경쟁사 제품을 판매하는 것은 허용되나, 회사의 기밀 정보를 활용해서는
|
||||
- 안 됩니다.
|
||||
|
||||
## 제9조 (분쟁 해결)
|
||||
|
||||
### 9.1 관할 법원
|
||||
|
||||
- 본 서약과 관련된 분쟁은 회사 본사 소재지 관할 법원으로 합니다.
|
||||
|
||||
### 9.2 준거법
|
||||
|
||||
- 본 서약은 대한민국 법률에 따라 해석됩니다.
|
||||
|
||||
- **서약 확인**
|
||||
- 본인은 위 내용을 충분히 이해하였으며, 이를 성실히 준수할 것을 서약합니다.
|
||||
- **서약일**: ___________________
|
||||
- **서약인**
|
||||
상호(성명): _______________
|
||||
대표자(본인): _______________
|
||||
주민등록번호(또는 사업자등록번호): _______________
|
||||
- **서명 또는 인**: _______________
|
||||
|
||||
- **수령인 (주식회사 ****코드브릿지엑스****)**
|
||||
- 대표이사: 이의찬
|
||||
- **확인****일**: ___________________
|
||||
- **서명 또는 인**: _______________
|
||||
|
||||
- **참고: 관련 법률**
|
||||
- **부정경쟁방지법 제2조 (영업비밀)**
|
||||
- “영업비밀”이란 공공연히 알려져 있지 아니하고 독립된 경제적 가치를 가지는 것으로서,
|
||||
- 비밀로 관리된 생산방법, 판매방법, 그 밖에 영업활동에 유용한 기술상 또는 경영상의
|
||||
- 정보를 말한다.
|
||||
- **부정경쟁방지법 제18조 (벌칙)**
|
||||
- 영업비밀을 외국에서 사용하거나 외국에서 사용되게 할 목적으로 취득·사용 또는 제3자에게 누설한 자는 **15년 이하의 징역** 또는 **15억원 이하의 벌금**에 처한다.
|
||||
|
||||
- **※ 본 서약서는 2부를 작성하여 회사와 서약인이 각 1부씩 보관합니다.**
|
||||
- **※ 서약 위반 시 민·형사상 책임을 질 수 있습니다.**
|
||||
276
contracts/markdown/03-partner-agreement.md
Normal file
276
contracts/markdown/03-partner-agreement.md
Normal file
@@ -0,0 +1,276 @@
|
||||
---
|
||||
title: "영업파트너 위촉계약서"
|
||||
version: "v4.0"
|
||||
date: "2026-02-22"
|
||||
docx_file: "영업파트너 위촉계약서.docx"
|
||||
---
|
||||
|
||||
# < 영업파트너 위촉계약서 >
|
||||
|
||||
# Sales Partner Engagement Agreement
|
||||
|
||||
- 본 계약은 주식회사 코드브릿지엑스(이하 “회사”)와 (이하 “파트너)간에 SAM 서비스 영업 활동과 관련하여 다음과 같이 위촉계약을 체결합니다.
|
||||
|
||||
## 제1조 (계약의 목적)
|
||||
|
||||
- 본 계약은 회사와 파트너 간의 영업파트너 위촉 관계를 규정하고, 상호 권리와 의무를
|
||||
- 명확히 함을 목적으로 합니다.
|
||||
|
||||
## 제2조 (용어의 정의)
|
||||
|
||||
- **판매자**: 고객을 발굴하고 계약 체결을 주도하는 영업파트너
|
||||
- **관리자**: 판매자를 관리하고 지원하는 상급 영업파트너
|
||||
- **개발비**: 고객이 SAM 서비스 개발을 위해 지급하는 비용
|
||||
- **수수료**: 파트너가 영업 활동의 대가로 받는 보상
|
||||
- **서비스 게시**: 개발 완료 후 고객이 서비스에 접근 가능하도록 제공하는 것
|
||||
|
||||
## 제3조 (파트너의 역할 및 업무)
|
||||
|
||||
### 3.1 판매자의 역할
|
||||
|
||||
- 잠재 고객 발굴 및 초기 접촉
|
||||
- SAM 서비스 소개 및 제안
|
||||
- 고객과의 계약 체결 지원
|
||||
- 계약 후 고객 관리 및 사후 지원
|
||||
|
||||
### 3.2 관리자의 역할
|
||||
|
||||
- 판매자 모집 및 관리
|
||||
- 판매자 교육 및 지원
|
||||
- 영업 전략 수립 및 실행
|
||||
- 회사와 판매자 간 소통 중재
|
||||
|
||||
### 3.3 공통 의무
|
||||
|
||||
- 회사의 브랜드 이미지 유지
|
||||
- 정확한 정보 제공
|
||||
- 윤리적 영업 활동 준수
|
||||
- 비밀 유지 의무
|
||||
|
||||
## 제4조 (수수료 정책)
|
||||
|
||||
### 4.1 수수료 비율
|
||||
|
||||
| 역할 | 수수료 비율 | 산정 기준 |
|
||||
| --- | --- | --- |
|
||||
| 판매자 | 개발비의 20% | 1차,2차 입금액 기준 |
|
||||
| 관리자 | 개발비의 5% | 1차,2차 입금액 기준 |
|
||||
|
||||
### 4.2 수수료 산정 예시
|
||||
|
||||
- **총 개발비 80,000,000원 계약 시**
|
||||
|
||||
| 단계 | 고객 입금 | 판매자 수수료 (20%) | 관리자 수수료 (5%) |
|
||||
| --- | --- | --- | --- |
|
||||
| 1차 착수금 (50%) | 40,000,000원 | 8,000,000원 | 2,000,000원 |
|
||||
| 2차 잔금 (50%) | 40,000,000원 | 8,000,000원 | 2,000,000원 |
|
||||
| 총계 | 80,000,000원 | 16,000,000원 | 4,000,000원 |
|
||||
|
||||
- **⚠️ 중요**: 개발비만 수수료 산정 대상이며, 구독료는 수수료 대상이 아닙니다.
|
||||
|
||||
### 4.3 지급 시기
|
||||
|
||||
- **지급일**: 고객 입금일 **익월 10일**
|
||||
- **지급 방식**: 계좌 이체
|
||||
- **세금**: 3.3% 원천징수 (사업소득)
|
||||
|
||||
### 4.4 수수료 지급 조건
|
||||
|
||||
- 고객이 개발비를 실제로 입금한 경우에만 지급
|
||||
- 계약 해지 또는 환불 시 수수료 미지급 또는 환수
|
||||
- 파트너가 계약 위반 시 수수료 지급 보류
|
||||
|
||||
## 제5조 (수수료 정책 변경)
|
||||
|
||||
### 5.1 사전 고지 의무
|
||||
|
||||
- 회사는 수수료 정책을 변경할 경우 **최소 1개월 전** 서면 또는 이메일로 파트너에게 고지합니다.
|
||||
- 수수료 정책을 완전히 폐지하는 경우에도 동일하게 1개월 전 고지합니다.
|
||||
- 고지 기간 중 체결된 계약은 기존 수수료 정책을 적용합니다.
|
||||
|
||||
### 5.2 변경 효력
|
||||
|
||||
- 변경된 수수료 정책은 고지일로부터 **1개월 후** 새로 체결되는 계약부터 적용됩니다.
|
||||
- 고지 기간 만료 전에 체결된 계약은 기존 정책을 따릅니다.
|
||||
- 진행 중인 계약은 최초 약정 조건을 유지합니다.
|
||||
|
||||
### 5.3 변경 예시
|
||||
|
||||
#### 예시 1: 수수료율 변경
|
||||
|
||||
- 고지일: 2026년 2월 1일
|
||||
- 변경 내용: 판매자 수수료 20% → 18%
|
||||
- 적용일: 2026년 3월 1일 이후 체결 계약부터
|
||||
|
||||
#### 예시 2: 수수료 정책 폐지
|
||||
|
||||
- 고지일: 2026년 2월 1일
|
||||
- 변경 내용: 수수료 정책 완전 폐지
|
||||
- 적용일: 2026년 3월 1일 이후 체결 계약부터
|
||||
|
||||
## 제6조 (계약 기간)
|
||||
|
||||
- 본 계약은 계약일로부터 **1년간** 유효합니다.
|
||||
- 양측이 계약 만료 **30일 전**까지 서면으로 해지 의사를 통지하지 않으면 자동으로 **1년 연장**됩니다.
|
||||
- 자동 연장은 동일한 조건으로 반복됩니다.
|
||||
|
||||
## 제7조 (계약 해지)
|
||||
|
||||
### 7.1 일반 해지 (양방향)
|
||||
|
||||
- **통지 기간**: 양측은 **30일 전** 서면 통지로 계약을 해지할 수 있습니다.
|
||||
- **통지 방법**: 이메일 또는 등기우편
|
||||
- **효력 발생**: 통지일로부터 30일 후
|
||||
- **미지급 수수료**: 해지일 이전에 발생한 수수료는 정산하여 지급
|
||||
- **예시**:
|
||||
- 통지일: 2026년 2월 1일
|
||||
- 해지일: 2026년 3월 1일
|
||||
- 2월 중 발생한 수수료는 3월 10일 정상 지급
|
||||
|
||||
### 7.2 즉시 해지 사유
|
||||
|
||||
- 회사는 다음의 경우 **즉시 계약을 해지**할 수 있습니다:
|
||||
- **(1) 품위 유지 결격사유 발생 [신설]**
|
||||
- 음주운전으로 적발된 경우
|
||||
- 형사 범죄로 기소 또는 구속된 경우
|
||||
- 사회적 물의를 일으킨 경우
|
||||
- 기타 파트너로서의 품위를 훼손한 경우
|
||||
- **(2) 계약 위반**
|
||||
- 허위 정보 제공 또는 사기 행위
|
||||
- 회사 명예 훼손 또는 영업 방해
|
||||
- 비밀 유지 의무 위반
|
||||
- 중대한 업무 태만
|
||||
- **(3) 부정 행위**
|
||||
- 고객으로부터 금품 수수
|
||||
- 계약서 위조 또는 변조
|
||||
- 회사 자산 횡령 또는 유용
|
||||
|
||||
### 7.3 즉시 해지 시 조치
|
||||
|
||||
- 미지급 수수료는 지급하지 않습니다.
|
||||
- 이미 지급한 수수료는 환수하지 않습니다. (단, 사기 행위는 예외)
|
||||
- 진행 중인 계약은 회사가 직접 관리합니다.
|
||||
|
||||
## 제8조 (비밀 유지)
|
||||
|
||||
### 8.1 비밀 정보
|
||||
|
||||
- 다음 정보는 비밀로 유지되어야 합니다:
|
||||
- 회사의 영업 전략 및 계획
|
||||
- 고객 정보 (회사명, 담당자, 연락처 등)
|
||||
- 수수료 정책 및 계약 조건
|
||||
- 기술 정보 및 노하우
|
||||
- 회사 내부 자료
|
||||
|
||||
### 8.2 비밀 유지 의무
|
||||
|
||||
- 파트너는 업무 중 알게 된 비밀 정보를 외부에 누설하지 않습니다.
|
||||
- 비밀 유지 의무는 계약 종료 후에도 **3년간** 유효합니다.
|
||||
- 위반 시 손해배상 책임이 있습니다.
|
||||
|
||||
## 제9조 (지적재산권)
|
||||
|
||||
- SAM 서비스에 대한 모든 지적재산권은 회사에 귀속됩니다.
|
||||
- 파트너는 회사의 사전 서면 동의 없이 회사의 상표, 로고, 브랜드를 무단으로 사용할 수 없습니다.
|
||||
- 영업 활동에 필요한 자료는 회사가 제공합니다.
|
||||
|
||||
## 제10조 (세금 및 원천징수)
|
||||
|
||||
### 10.1 사업소득
|
||||
|
||||
- 파트너 수수료는 **사업소득**으로 간주됩니다.
|
||||
|
||||
### 10.2 원천징수
|
||||
|
||||
| 항목 | 비율 | 비고 |
|
||||
| --- | --- | --- |
|
||||
| 소득세 | 3.0% | |
|
||||
| 지방소득세 | 0.3% | 소득세의 10% |
|
||||
| 합계 | 3.3% | |
|
||||
|
||||
### 10.3 지급명세서
|
||||
|
||||
- 회사는 매월 수수료를 지급한 후에 파트너에게 **지급명세서**를 발급합니다.
|
||||
|
||||
## 제11조 (손해배상)
|
||||
|
||||
### 11.1 파트너의 귀책 사유
|
||||
|
||||
- 파트너가 다음의 행위로 회사에 손해를 입힌 경우 배상 책임이 있습니다:
|
||||
- 허위 정보 제공으로 계약 취소
|
||||
- 고객과의 분쟁으로 회사 명예 훼손
|
||||
- 비밀 유지 의무 위반
|
||||
- 부정 행위
|
||||
|
||||
### 11.2 회사의 귀책 사유
|
||||
|
||||
- 회사가 정당한 사유 없이 수수료를 지급하지 않을 경우, 연체 이자를 더하여 지급합니다.
|
||||
|
||||
## 제12조 (분쟁 해결)
|
||||
|
||||
- 본 계약과 관련한 분쟁은 상호 협의하여 해결합니다.
|
||||
- 협의가 이루어지지 않을 경우, **서울중앙지방법원**을 관할 법원으로 합니다.
|
||||
|
||||
## 제13조 (기타 사항)
|
||||
|
||||
### 13.1 계약서 교부
|
||||
|
||||
- 본 계약서는 2부 작성하여 회사와 파트너가 각 1부씩 보관합니다.
|
||||
|
||||
### 13.2 통지
|
||||
|
||||
- 모든 통지는 다음의 연락처로 발송됩니다:
|
||||
- **회사**:
|
||||
- 이메일: admin@codebridge-x.com
|
||||
- 전화: 02-6347-0005
|
||||
- **파트너**:
|
||||
- 이메일:
|
||||
- 전화:
|
||||
|
||||
### 13.3 준거법
|
||||
|
||||
- 본 계약은 대한민국 법률에 따라 해석되고 적용됩니다.
|
||||
|
||||
- **계약 당사자**
|
||||
- **[회사]**
|
||||
- **상호**: 주식회사 코드브릿지엑스
|
||||
- **대표자**: 이의찬 (인)
|
||||
- **사업자등록번호**: 664-86-03713
|
||||
- **주소**: 서울특별시 강서구 양천로 583, 우림블루나인 B동 1602호
|
||||
- **이메일**: admin@codebridge-x.com
|
||||
- **전화**: 02-6347-0005
|
||||
- **날짜**:
|
||||
|
||||
- **[파트너]**
|
||||
- **상호/성명**:
|
||||
- **대표자/본인**: (서명)
|
||||
- **사업자등록번호**:
|
||||
- **주소**:
|
||||
- **이메일**:
|
||||
- **전화**:
|
||||
- **날짜**:
|
||||
|
||||
- **별첨**
|
||||
|
||||
#### 별첨 1: 수수료 정산표
|
||||
|
||||
| 계약번호 | 고객사 | 입금일 | 입금액 | 수수료율 | 수수료 | 지급일 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| | | | | | | |
|
||||
|
||||
#### 별첨 2: 영업 활동 보고서
|
||||
|
||||
| 날짜 | 활동내용 | 고객사 | 진행 상황 |
|
||||
| --- | --- | --- | --- |
|
||||
| | | | |
|
||||
|
||||
- 첨부 서류
|
||||
- 사업자등록증 사본 (사업자인 경우)
|
||||
- 주민등록등본 사본 (개인인 경우)
|
||||
- 통장 사본 (수수료 입금용)
|
||||
- 비밀유지서약서
|
||||
|
||||
- **주식회사 코드브릿지엑스**
|
||||
- 이메일: admin@codebridge-x.com
|
||||
- 전화: 02-6347-0005
|
||||
- 주소: 서울특별시 강서구 양천로 583, 우림블루나인 B동 1602호
|
||||
267
contracts/markdown/04-partner-agreement-group.md
Normal file
267
contracts/markdown/04-partner-agreement-group.md
Normal file
@@ -0,0 +1,267 @@
|
||||
---
|
||||
title: "영업파트너 위촉계약서 (단체용)"
|
||||
version: "v4.0"
|
||||
date: "2026-02-22"
|
||||
docx_file: "영업파트너 위촉계약서(단체용).docx"
|
||||
---
|
||||
|
||||
# < 영업파트너 위촉계약서 >
|
||||
|
||||
# Sales Partner Engagement Agreement
|
||||
|
||||
- 본 계약은 주식회사 코드브릿지엑스(이하 “회사”)와 (이하 “파트너)간에 SAM 서비스 영업 활동과 관련하여 다음과 같이 위촉계약을 체결합니다.
|
||||
|
||||
## 제1조 (계약의 목적)
|
||||
|
||||
- 본 계약은 회사와 파트너 간의 영업파트너 위촉 관계를 규정하고, 상호 권리와 의무를
|
||||
- 명확히 함을 목적으로 합니다.
|
||||
|
||||
## 제2조 (용어의 정의)
|
||||
|
||||
- **판매자**: 고객을 발굴하고 계약 체결을 주도하는 영업파트너
|
||||
- **개발비**: 고객이 SAM 서비스 개발을 위해 지급하는 비용
|
||||
- **수수료**: 파트너가 영업 활동의 대가로 받는 보상
|
||||
- **서비스 게시**: 개발 완료 후 고객이 서비스에 접근 가능하도록 제공하는 것
|
||||
|
||||
## 제3조 (파트너의 역할 및 업무)
|
||||
|
||||
### 3.1 판매자의 역할
|
||||
|
||||
- 잠재 고객 발굴 및 초기 접촉
|
||||
- SAM 서비스 소개 및 제안
|
||||
- 고객과의 계약 체결 지원
|
||||
- 계약 후 고객 관리 및 사후 지원
|
||||
|
||||
### 3.2 공통 의무
|
||||
|
||||
- 회사의 브랜드 이미지 유지
|
||||
- 정확한 정보 제공
|
||||
- 윤리적 영업 활동 준수
|
||||
- 비밀 유지 의무
|
||||
|
||||
## 제4조 (수수료 정책)
|
||||
|
||||
### 4.1 수수료 비율
|
||||
|
||||
| 역할 | 수수료 비율 | 산정 기준 |
|
||||
| --- | --- | --- |
|
||||
| 판매자 | 개발비의 30% | 1차,2차 입금액 기준 |
|
||||
|
||||
### 4.2 수수료 산정 예시
|
||||
|
||||
- **총 개발비 80,000,000원 계약 시**
|
||||
|
||||
| 단계 | 고객 입금 | 판매자 수수료 (30%) |
|
||||
| --- | --- | --- |
|
||||
| 1차 착수금 (50%) | 40,000,000원 | 12,000,000원 |
|
||||
| 2차 잔금 (50%) | 40,000,000원 | 12,000,000원 |
|
||||
| 총계 | 80,000,000원 | 24,000,000원 |
|
||||
|
||||
- **⚠️ 중요**: 개발비만 수수료 산정 대상이며, 구독료는 수수료 대상이 아닙니다.
|
||||
|
||||
### 4.3 지급 시기
|
||||
|
||||
- **지급일**: 고객 입금일 **익월 10일**
|
||||
- **지급 방식**: 계좌 이체
|
||||
- **세금**: 사업소득일 경우 3.3% 원천징수
|
||||
|
||||
### 4.4 수수료 지급 조건
|
||||
|
||||
- 고객이 개발비를 실제로 입금한 경우에만 지급
|
||||
- 계약 해지 또는 환불 시 수수료 미지급 또는 환수
|
||||
- 파트너가 계약 위반 시 수수료 지급 보류
|
||||
|
||||
## 제5조 (수수료 정책 변경)
|
||||
|
||||
### 5.1 사전 고지 의무
|
||||
|
||||
- 회사는 수수료 정책을 변경할 경우 **최소 1개월 전** 서면 또는 이메일로 파트너에게 고지합니다.
|
||||
- 수수료 정책을 완전히 폐지하는 경우에도 동일하게 1개월 전 고지합니다.
|
||||
- 고지 기간 중 체결된 계약은 기존 수수료 정책을 적용합니다.
|
||||
|
||||
### 5.2 변경 효력
|
||||
|
||||
- 변경된 수수료 정책은 고지일로부터 **1개월 후** 새로 체결되는 계약부터 적용됩니다.
|
||||
- 고지 기간 만료 전에 체결된 계약은 기존 정책을 따릅니다.
|
||||
- 진행 중인 계약은 최초 약정 조건을 유지합니다.
|
||||
|
||||
### 5.3 변경 예시
|
||||
|
||||
#### 예시 1: 수수료율 변경
|
||||
|
||||
- 고지일: 2026년 2월 1일
|
||||
- 변경 내용: 판매자 수수료 20% → 18%
|
||||
- 적용일: 2026년 3월 1일 이후 체결 계약부터
|
||||
|
||||
#### 예시 2: 수수료 정책 폐지
|
||||
|
||||
- 고지일: 2026년 2월 1일
|
||||
- 변경 내용: 수수료 정책 완전 폐지
|
||||
- 적용일: 2026년 3월 1일 이후 체결 계약부터
|
||||
|
||||
## 제6조 (계약 기간)
|
||||
|
||||
- 본 계약은 계약일로부터 **1년간** 유효합니다.
|
||||
- 양측이 계약 만료 **30일 전**까지 서면으로 해지 의사를 통지하지 않으면 자동으로 **1년 연장**됩니다.
|
||||
- 자동 연장은 동일한 조건으로 반복됩니다.
|
||||
|
||||
## 제7조 (계약 해지)
|
||||
|
||||
### 7.1 일반 해지 (양방향)
|
||||
|
||||
- **통지 기간**: 양측은 **30일 전** 서면 통지로 계약을 해지할 수 있습니다.
|
||||
- **통지 방법**: 이메일 또는 등기우편
|
||||
- **효력 발생**: 통지일로부터 30일 후
|
||||
- **미지급 수수료**: 해지일 이전에 발생한 수수료는 정산하여 지급
|
||||
- **예시**:
|
||||
- 통지일: 2026년 2월 1일
|
||||
- 해지일: 2026년 3월 1일
|
||||
- 2월 중 발생한 수수료는 3월 10일 정상 지급
|
||||
|
||||
### 7.2 즉시 해지 사유
|
||||
|
||||
- 회사는 다음의 경우 **즉시 계약을 해지**할 수 있습니다:
|
||||
- **(1) 품위 유지 결격사유 발생 [신설]**
|
||||
- 음주운전으로 적발된 경우
|
||||
- 형사 범죄로 기소 또는 구속된 경우
|
||||
- 사회적 물의를 일으킨 경우
|
||||
- 기타 파트너로서의 품위를 훼손한 경우
|
||||
- **(2) 계약 위반**
|
||||
- 허위 정보 제공 또는 사기 행위
|
||||
- 회사 명예 훼손 또는 영업 방해
|
||||
- 비밀 유지 의무 위반
|
||||
- 중대한 업무 태만
|
||||
- **(3) 부정 행위**
|
||||
- 고객으로부터 금품 수수
|
||||
- 계약서 위조 또는 변조
|
||||
- 회사 자산 횡령 또는 유용
|
||||
|
||||
### 7.3 즉시 해지 시 조치
|
||||
|
||||
- 미지급 수수료는 지급하지 않습니다.
|
||||
- 이미 지급한 수수료는 환수하지 않습니다. (단, 사기 행위는 예외)
|
||||
- 진행 중인 계약은 회사가 직접 관리합니다.
|
||||
|
||||
## 제8조 (비밀 유지)
|
||||
|
||||
### 8.1 비밀 정보
|
||||
|
||||
- 다음 정보는 비밀로 유지되어야 합니다:
|
||||
- 회사의 영업 전략 및 계획
|
||||
- 고객 정보 (회사명, 담당자, 연락처 등)
|
||||
- 수수료 정책 및 계약 조건
|
||||
- 기술 정보 및 노하우
|
||||
- 회사 내부 자료
|
||||
|
||||
### 8.2 비밀 유지 의무
|
||||
|
||||
- 파트너는 업무 중 알게 된 비밀 정보를 외부에 누설하지 않습니다.
|
||||
- 비밀 유지 의무는 계약 종료 후에도 **3년간** 유효합니다.
|
||||
- 위반 시 손해배상 책임이 있습니다.
|
||||
|
||||
## 제9조 (지적재산권)
|
||||
|
||||
- SAM 서비스에 대한 모든 지적재산권은 회사에 귀속됩니다.
|
||||
- 파트너는 회사의 사전 서면 동의 없이 회사의 상표, 로고, 브랜드를 무단으로 사용할 수 없습니다.
|
||||
- 영업 활동에 필요한 자료는 회사가 제공합니다.
|
||||
|
||||
## 제10조 (세금 및 원천징수)
|
||||
|
||||
### 10.1 사업소득
|
||||
|
||||
- 파트너 수수료는 **사업소득**으로 간주됩니다.
|
||||
|
||||
### 10.2 원천징수
|
||||
|
||||
| 항목 | 비율 | 비고 |
|
||||
| --- | --- | --- |
|
||||
| 소득세 | 3.0% | |
|
||||
| 지방소득세 | 0.3% | 소득세의 10% |
|
||||
| 합계 | 3.3% | |
|
||||
|
||||
### 10.3 지급명세서
|
||||
|
||||
- 회사는 매월 수수료를 지급한 후에 파트너에게 **지급명세서**를 발급합니다.
|
||||
|
||||
## 제11조 (손해배상)
|
||||
|
||||
### 11.1 파트너의 귀책 사유
|
||||
|
||||
- 파트너가 다음의 행위로 회사에 손해를 입힌 경우 배상 책임이 있습니다:
|
||||
- 허위 정보 제공으로 계약 취소
|
||||
- 고객과의 분쟁으로 회사 명예 훼손
|
||||
- 비밀 유지 의무 위반
|
||||
- 부정 행위
|
||||
|
||||
### 11.2 회사의 귀책 사유
|
||||
|
||||
- 회사가 정당한 사유 없이 수수료를 지급하지 않을 경우, 연체 이자를 더하여 지급합니다.
|
||||
|
||||
## 제12조 (분쟁 해결)
|
||||
|
||||
- 본 계약과 관련한 분쟁은 상호 협의하여 해결합니다.
|
||||
- 협의가 이루어지지 않을 경우, **서울중앙지방법원**을 관할 법원으로 합니다.
|
||||
|
||||
## 제13조 (기타 사항)
|
||||
|
||||
### 13.1 계약서 교부
|
||||
|
||||
- 본 계약서는 2부 작성하여 회사와 파트너가 각 1부씩 보관합니다.
|
||||
|
||||
### 13.2 통지
|
||||
|
||||
- 모든 통지는 다음의 연락처로 발송됩니다:
|
||||
- **회사**:
|
||||
- 이메일: admin@codebridge-x.com
|
||||
- 전화: 02-6347-0005
|
||||
- **파트너**:
|
||||
- 이메일:
|
||||
- 전화:
|
||||
|
||||
### 13.3 준거법
|
||||
|
||||
- 본 계약은 대한민국 법률에 따라 해석되고 적용됩니다.
|
||||
|
||||
- **계약 당사자**
|
||||
- **[회사]**
|
||||
- **상호**: 주식회사 코드브릿지엑스
|
||||
- **대표자**: 이의찬 (인)
|
||||
- **사업자등록번호**: 664-86-03713
|
||||
- **주소**: 서울특별시 강서구 양천로 583, 우림블루나인 B동 1602호
|
||||
- **이메일**: admin@codebridge-x.com
|
||||
- **전화**: 02-6347-0005
|
||||
- **날짜**:
|
||||
|
||||
- **[파트너]**
|
||||
- **상호/성명**:
|
||||
- **대표자/본인**: (서명)
|
||||
- **사업자등록번호**:
|
||||
- **주소**:
|
||||
- **이메일**:
|
||||
- **전화**:
|
||||
- **날짜**:
|
||||
|
||||
- **별첨**
|
||||
|
||||
#### 별첨 1: 수수료 정산표
|
||||
|
||||
| 계약번호 | 고객사 | 입금일 | 입금액 | 수수료율 | 수수료 | 지급일 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| | | | | | | |
|
||||
|
||||
#### 별첨 2: 영업 활동 보고서
|
||||
|
||||
| 날짜 | 활동내용 | 고객사 | 진행 상황 |
|
||||
| --- | --- | --- | --- |
|
||||
| | | | |
|
||||
|
||||
- 첨부 서류
|
||||
- 사업자등록증 사본 (사업자인 경우)
|
||||
- 주민등록등본 사본 (개인인 경우)
|
||||
- 통장 사본 (수수료 입금용)
|
||||
- 비밀유지서약서
|
||||
|
||||
- **주식회사 코드브릿지엑스**
|
||||
- 이메일: admin@codebridge-x.com
|
||||
- 전화: 02-6347-0005
|
||||
- 주소: 서울특별시 강서구 양천로 583, 우림블루나인 B동 1602호
|
||||
58
contracts/revisions.json
Normal file
58
contracts/revisions.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"documents": {
|
||||
"01-service-agreement": {
|
||||
"title": "고객사 서비스 이용계약서",
|
||||
"docx_file": "01_고객_서비스이용계약서_v4_0_전자서명용.docx",
|
||||
"revisions": [
|
||||
{
|
||||
"version": "v4.0",
|
||||
"date": "2026-02-22",
|
||||
"author": "개발팀",
|
||||
"description": "버전 관리 시스템 도입, 개정이력 추적 시작"
|
||||
},
|
||||
{
|
||||
"version": "v4.1",
|
||||
"date": "2026-02-22",
|
||||
"author": "개발팀",
|
||||
"description": "제4조에 사용량 기반 추가 과금(4.5) 및 바로빌 부가 서비스 요금(4.6) 조항 추가"
|
||||
}
|
||||
]
|
||||
},
|
||||
"02-nda": {
|
||||
"title": "비밀유지서약서 (NDA)",
|
||||
"docx_file": "비밀유지서약서.docx",
|
||||
"revisions": [
|
||||
{
|
||||
"version": "v4.0",
|
||||
"date": "2026-02-22",
|
||||
"author": "개발팀",
|
||||
"description": "버전 관리 시스템 도입, 개정이력 추적 시작"
|
||||
}
|
||||
]
|
||||
},
|
||||
"03-partner-agreement": {
|
||||
"title": "영업파트너 위촉계약서",
|
||||
"docx_file": "영업파트너 위촉계약서.docx",
|
||||
"revisions": [
|
||||
{
|
||||
"version": "v4.0",
|
||||
"date": "2026-02-22",
|
||||
"author": "개발팀",
|
||||
"description": "버전 관리 시스템 도입, 개정이력 추적 시작"
|
||||
}
|
||||
]
|
||||
},
|
||||
"04-partner-agreement-group": {
|
||||
"title": "영업파트너 위촉계약서 (단체용)",
|
||||
"docx_file": "영업파트너 위촉계약서(단체용).docx",
|
||||
"revisions": [
|
||||
{
|
||||
"version": "v4.0",
|
||||
"date": "2026-02-22",
|
||||
"author": "개발팀",
|
||||
"description": "버전 관리 시스템 도입, 개정이력 추적 시작"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
334
contracts/scripts/extract_to_markdown.py
Normal file
334
contracts/scripts/extract_to_markdown.py
Normal file
@@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
DOCX → Markdown 추출 스크립트
|
||||
|
||||
4개 전자계약 DOCX 파일을 Markdown으로 변환한다.
|
||||
- 서비스이용계약서: Heading 스타일 기반 매핑
|
||||
- 나머지 3개: Bold 런 + 패턴 매칭으로 구조 유추
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
from docx import Document
|
||||
|
||||
# 경로 설정
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
DOCX_DIR = BASE_DIR / "docx"
|
||||
MD_DIR = BASE_DIR / "markdown"
|
||||
|
||||
# DOCX → Markdown 매핑
|
||||
FILE_MAP = {
|
||||
"01_고객_서비스이용계약서_v4_0_전자서명용.docx": {
|
||||
"output": "01-service-agreement.md",
|
||||
"title": "고객사 서비스 이용계약서",
|
||||
"type": "styled",
|
||||
},
|
||||
"비밀유지서약서.docx": {
|
||||
"output": "02-nda.md",
|
||||
"title": "비밀유지서약서 (NDA)",
|
||||
"type": "pattern",
|
||||
},
|
||||
"영업파트너 위촉계약서.docx": {
|
||||
"output": "03-partner-agreement.md",
|
||||
"title": "영업파트너 위촉계약서",
|
||||
"type": "pattern",
|
||||
},
|
||||
"영업파트너 위촉계약서(단체용).docx": {
|
||||
"output": "04-partner-agreement-group.md",
|
||||
"title": "영업파트너 위촉계약서 (단체용)",
|
||||
"type": "pattern",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def table_to_markdown(table):
|
||||
"""DOCX 테이블을 Markdown 테이블로 변환"""
|
||||
rows = []
|
||||
for row in table.rows:
|
||||
cells = [cell.text.strip().replace("\n", " ") for cell in row.cells]
|
||||
rows.append(cells)
|
||||
|
||||
if not rows:
|
||||
return ""
|
||||
|
||||
lines = []
|
||||
# 헤더
|
||||
lines.append("| " + " | ".join(rows[0]) + " |")
|
||||
lines.append("| " + " | ".join(["---"] * len(rows[0])) + " |")
|
||||
# 본문
|
||||
for row in rows[1:]:
|
||||
# 셀 개수 맞추기
|
||||
while len(row) < len(rows[0]):
|
||||
row.append("")
|
||||
lines.append("| " + " | ".join(row[: len(rows[0])]) + " |")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def get_paragraph_heading_level_styled(para):
|
||||
"""스타일 기반 문서의 헤딩 레벨 판별 (서비스이용계약서)"""
|
||||
style = para.style.name if para.style else ""
|
||||
|
||||
if style == "Heading 1":
|
||||
return 1
|
||||
elif style == "Heading 2":
|
||||
return 2
|
||||
elif style == "Heading 3":
|
||||
return 3
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def get_paragraph_heading_level_pattern(para):
|
||||
"""패턴 매칭 기반 문서의 헤딩 레벨 판별 (비밀유지서약서, 영업파트너 위촉계약서)"""
|
||||
text = para.text.strip()
|
||||
has_bold = any(r.bold for r in para.runs if r.bold)
|
||||
|
||||
if not text or not has_bold:
|
||||
return 0
|
||||
|
||||
# "제X조" 패턴 → ## (h2)
|
||||
if re.match(r"^<?[ ]*제\d+조", text):
|
||||
return 2
|
||||
|
||||
# "X.X " 패턴 (소제목) → ### (h3)
|
||||
if re.match(r"^\d+\.\d+\s", text):
|
||||
return 3
|
||||
|
||||
# 문서 제목 (첫 번째 bold 텍스트)
|
||||
if re.match(r"^<?\s*(영업파트너|비밀유지서약서|Sales Partner)", text):
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def is_list_item(para, doc_type):
|
||||
"""리스트 아이템인지 판별"""
|
||||
text = para.text.strip()
|
||||
if not text:
|
||||
return False
|
||||
|
||||
if doc_type == "styled":
|
||||
style = para.style.name if para.style else ""
|
||||
return style == "Compact"
|
||||
|
||||
# pattern 기반: bold가 아닌 일반 텍스트이면서 제X조나 X.X 패턴이 아닌 것
|
||||
has_bold = any(r.bold for r in para.runs if r.bold)
|
||||
if not has_bold and not re.match(r"^(제\d+조|<?|계약 당사자|\[)", text):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def extract_styled_doc(doc, file_info):
|
||||
"""스타일 기반 문서 추출 (서비스이용계약서)"""
|
||||
lines = []
|
||||
table_positions = {}
|
||||
|
||||
# 테이블 위치 매핑: 문단 인덱스 기준으로 테이블이 어디에 삽입되는지 추적
|
||||
body = doc.element.body
|
||||
table_idx = 0
|
||||
para_idx = 0
|
||||
for child in body:
|
||||
tag = child.tag.split("}")[-1] if "}" in child.tag else child.tag
|
||||
if tag == "p":
|
||||
para_idx += 1
|
||||
elif tag == "tbl":
|
||||
table_positions[para_idx] = table_idx
|
||||
table_idx += 1
|
||||
|
||||
para_idx = 0
|
||||
for child in body:
|
||||
tag = child.tag.split("}")[-1] if "}" in child.tag else child.tag
|
||||
|
||||
if tag == "p":
|
||||
para = doc.paragraphs[para_idx]
|
||||
para_idx += 1
|
||||
text = para.text.strip()
|
||||
|
||||
if not text:
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
style = para.style.name if para.style else ""
|
||||
level = get_paragraph_heading_level_styled(para)
|
||||
|
||||
if level > 0:
|
||||
lines.append("")
|
||||
lines.append(f"{'#' * level} {text}")
|
||||
lines.append("")
|
||||
elif style == "Compact":
|
||||
# Bold 런이 있으면 강조 리스트
|
||||
has_bold = any(r.bold for r in para.runs if r.bold)
|
||||
if has_bold:
|
||||
# Bold 부분과 일반 부분 분리
|
||||
parts = []
|
||||
for run in para.runs:
|
||||
if run.bold:
|
||||
parts.append(f"**{run.text}**")
|
||||
else:
|
||||
parts.append(run.text)
|
||||
combined = "".join(parts)
|
||||
lines.append(f"- {combined}")
|
||||
else:
|
||||
# 들여쓰기된 하위 항목
|
||||
lines.append(f" - {text}")
|
||||
elif style in ("Body Text", "First Paragraph"):
|
||||
# 본문 텍스트
|
||||
if text.startswith("⚠️") or text.startswith("✅") or text.startswith("❌"):
|
||||
lines.append("")
|
||||
lines.append(f"> {text}")
|
||||
lines.append("")
|
||||
else:
|
||||
lines.append(text)
|
||||
else:
|
||||
lines.append(text)
|
||||
|
||||
elif tag == "tbl":
|
||||
if table_idx <= len(doc.tables):
|
||||
current_table_idx = sum(
|
||||
1
|
||||
for c in list(body)[: list(body).index(child)]
|
||||
if (c.tag.split("}")[-1] if "}" in c.tag else c.tag) == "tbl"
|
||||
)
|
||||
if current_table_idx < len(doc.tables):
|
||||
lines.append("")
|
||||
lines.append(table_to_markdown(doc.tables[current_table_idx]))
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def extract_pattern_doc(doc, file_info):
|
||||
"""패턴 매칭 기반 문서 추출 (비밀유지서약서, 영업파트너 위촉계약서)"""
|
||||
lines = []
|
||||
|
||||
body = doc.element.body
|
||||
para_idx = 0
|
||||
|
||||
for child in body:
|
||||
tag = child.tag.split("}")[-1] if "}" in child.tag else child.tag
|
||||
|
||||
if tag == "p":
|
||||
para = doc.paragraphs[para_idx]
|
||||
para_idx += 1
|
||||
text = para.text.strip()
|
||||
|
||||
if not text:
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
level = get_paragraph_heading_level_pattern(para)
|
||||
has_bold = any(r.bold for r in para.runs if r.bold)
|
||||
|
||||
if level > 0:
|
||||
lines.append("")
|
||||
# 제목에서 < > 제거
|
||||
clean_text = re.sub(r"^<\s*|\s*>$", "", text).strip()
|
||||
lines.append(f"{'#' * level} {clean_text}")
|
||||
lines.append("")
|
||||
elif has_bold:
|
||||
# Bold 텍스트는 강조 처리
|
||||
parts = []
|
||||
for run in para.runs:
|
||||
if run.bold:
|
||||
parts.append(f"**{run.text}**")
|
||||
else:
|
||||
parts.append(run.text)
|
||||
combined = "".join(parts)
|
||||
|
||||
# (1), (2) 같은 번호 패턴
|
||||
if re.match(r"^\*\*\(\d+\)", combined):
|
||||
lines.append(f"- {combined}")
|
||||
# "예시 N:", "Phase N:" 같은 패턴
|
||||
elif re.match(r"^\*\*(예시|Phase|별첨)\s", combined):
|
||||
lines.append("")
|
||||
lines.append(f"#### {text}")
|
||||
lines.append("")
|
||||
else:
|
||||
lines.append(f"- {combined}")
|
||||
else:
|
||||
# 일반 텍스트
|
||||
# 빈칸 양식 (___) 유지
|
||||
if "___" in text:
|
||||
lines.append(text)
|
||||
elif re.match(r"^(이메일|전화|주소|상호|대표|사업자|주민|연락처|날짜):", text):
|
||||
lines.append(f"- {text}")
|
||||
else:
|
||||
lines.append(f" - {text}")
|
||||
|
||||
elif tag == "tbl":
|
||||
current_table_idx = sum(
|
||||
1
|
||||
for c in list(body)[: list(body).index(child)]
|
||||
if (c.tag.split("}")[-1] if "}" in c.tag else c.tag) == "tbl"
|
||||
)
|
||||
if current_table_idx < len(doc.tables):
|
||||
lines.append("")
|
||||
lines.append(table_to_markdown(doc.tables[current_table_idx]))
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def add_frontmatter(content, file_info, docx_name):
|
||||
"""YAML 프론트매터 추가"""
|
||||
frontmatter = f"""---
|
||||
title: "{file_info['title']}"
|
||||
version: "v4.0"
|
||||
date: "{date.today().isoformat()}"
|
||||
docx_file: "{docx_name}"
|
||||
---
|
||||
"""
|
||||
return frontmatter + "\n" + content
|
||||
|
||||
|
||||
def extract_file(docx_name, file_info):
|
||||
"""단일 DOCX 파일 추출"""
|
||||
docx_path = DOCX_DIR / docx_name
|
||||
if not docx_path.exists():
|
||||
print(f" [SKIP] {docx_name} - 파일 없음")
|
||||
return False
|
||||
|
||||
doc = Document(str(docx_path))
|
||||
|
||||
if file_info["type"] == "styled":
|
||||
content = extract_styled_doc(doc, file_info)
|
||||
else:
|
||||
content = extract_pattern_doc(doc, file_info)
|
||||
|
||||
# 프론트매터 추가
|
||||
content = add_frontmatter(content, file_info, docx_name)
|
||||
|
||||
# 연속 빈 줄 정리 (3줄 이상 → 2줄로)
|
||||
content = re.sub(r"\n{3,}", "\n\n", content)
|
||||
|
||||
# 파일 저장
|
||||
output_path = MD_DIR / file_info["output"]
|
||||
output_path.write_text(content, encoding="utf-8")
|
||||
print(f" [OK] {docx_name} → {file_info['output']}")
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
print("DOCX → Markdown 추출 시작")
|
||||
print(f" DOCX 디렉토리: {DOCX_DIR}")
|
||||
print(f" 출력 디렉토리: {MD_DIR}")
|
||||
print()
|
||||
|
||||
MD_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
success = 0
|
||||
for docx_name, file_info in FILE_MAP.items():
|
||||
if extract_file(docx_name, file_info):
|
||||
success += 1
|
||||
|
||||
print(f"\n완료: {success}/{len(FILE_MAP)} 파일 변환됨")
|
||||
return 0 if success == len(FILE_MAP) else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
263
contracts/scripts/sync_check.py
Normal file
263
contracts/scripts/sync_check.py
Normal file
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
DOCX ↔ Markdown 동기화 검증 스크립트
|
||||
|
||||
DOCX에서 텍스트를 추출하고 Markdown 파일의 텍스트와 비교하여
|
||||
불일치 항목을 리포트한다.
|
||||
"""
|
||||
|
||||
import difflib
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from docx import Document
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
DOCX_DIR = BASE_DIR / "docx"
|
||||
MD_DIR = BASE_DIR / "markdown"
|
||||
|
||||
# DOCX → Markdown 파일 매핑
|
||||
FILE_MAP = {
|
||||
"01_고객_서비스이용계약서_v4_0_전자서명용.docx": "01-service-agreement.md",
|
||||
"비밀유지서약서.docx": "02-nda.md",
|
||||
"영업파트너 위촉계약서.docx": "03-partner-agreement.md",
|
||||
"영업파트너 위촉계약서(단체용).docx": "04-partner-agreement-group.md",
|
||||
}
|
||||
|
||||
|
||||
def extract_text_from_docx(docx_path):
|
||||
"""DOCX에서 순수 텍스트만 추출 (개정이력 테이블 제외, 인터리빙 방식)"""
|
||||
doc = Document(str(docx_path))
|
||||
lines = []
|
||||
|
||||
from docx.oxml.ns import qn as _qn
|
||||
|
||||
body = doc.element.body
|
||||
para_idx = 0
|
||||
table_idx = 0
|
||||
skip_revision = False
|
||||
|
||||
for child in body:
|
||||
tag = child.tag.split("}")[-1] if "}" in child.tag else child.tag
|
||||
|
||||
if tag == "p":
|
||||
if para_idx < len(doc.paragraphs):
|
||||
text = doc.paragraphs[para_idx].text.strip()
|
||||
para_idx += 1
|
||||
|
||||
if "개정이력" in text:
|
||||
skip_revision = True
|
||||
continue
|
||||
if text:
|
||||
skip_revision = False
|
||||
lines.append(text)
|
||||
|
||||
elif tag == "tbl":
|
||||
if table_idx < len(doc.tables):
|
||||
table = doc.tables[table_idx]
|
||||
table_idx += 1
|
||||
|
||||
# 개정이력 테이블 건너뛰기
|
||||
if len(table.rows) > 0:
|
||||
first_row_text = [cell.text.strip() for cell in table.rows[0].cells]
|
||||
if "버전" in first_row_text and "날짜" in first_row_text:
|
||||
skip_revision = False
|
||||
continue
|
||||
|
||||
if skip_revision:
|
||||
skip_revision = False
|
||||
continue
|
||||
|
||||
for row in table.rows:
|
||||
cells = [cell.text.strip() for cell in row.cells]
|
||||
# 빈 셀만 있는 행 건너뛰기
|
||||
if not any(cells):
|
||||
continue
|
||||
row_text = " | ".join(cells)
|
||||
if row_text.strip():
|
||||
lines.append(row_text)
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def extract_text_from_markdown(md_path):
|
||||
"""Markdown에서 순수 텍스트만 추출 (프론트매터, 마크업 제거)"""
|
||||
content = md_path.read_text(encoding="utf-8")
|
||||
lines = []
|
||||
|
||||
in_frontmatter = False
|
||||
in_table = False
|
||||
|
||||
for line in content.split("\n"):
|
||||
stripped = line.strip()
|
||||
|
||||
# YAML 프론트매터 건너뛰기
|
||||
if stripped == "---":
|
||||
in_frontmatter = not in_frontmatter
|
||||
continue
|
||||
if in_frontmatter:
|
||||
continue
|
||||
|
||||
# 빈 줄 건너뛰기
|
||||
if not stripped:
|
||||
in_table = False
|
||||
continue
|
||||
|
||||
# Markdown 마크업 제거
|
||||
text = stripped
|
||||
|
||||
# 헤딩 마크업 제거
|
||||
text = re.sub(r"^#{1,6}\s+", "", text)
|
||||
|
||||
# 리스트 마크업 제거
|
||||
text = re.sub(r"^\s*[-*+]\s+", "", text)
|
||||
|
||||
# Bold/Italic 마크업 제거
|
||||
text = re.sub(r"\*\*(.+?)\*\*", r"\1", text)
|
||||
text = re.sub(r"\*(.+?)\*", r"\1", text)
|
||||
|
||||
# 블록인용 제거
|
||||
text = re.sub(r"^>\s*", "", text)
|
||||
|
||||
# 테이블 구분선 건너뛰기
|
||||
if re.match(r"^\|[\s\-|]+\|$", text):
|
||||
continue
|
||||
|
||||
# 테이블 행
|
||||
if text.startswith("|") and text.endswith("|"):
|
||||
# 파이프 제거하고 셀 텍스트 추출
|
||||
cells = [c.strip() for c in text.strip("|").split("|")]
|
||||
text = " | ".join(cells)
|
||||
|
||||
text = text.strip()
|
||||
if text:
|
||||
lines.append(text)
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def normalize_text(text):
|
||||
"""비교를 위한 텍스트 정규화"""
|
||||
# 공백 정규화
|
||||
text = re.sub(r"\s+", " ", text).strip()
|
||||
# 특수문자 정규화
|
||||
text = text.replace("\u00a0", " ") # non-breaking space
|
||||
text = text.replace("\u3000", " ") # ideographic space
|
||||
# 언더스코어 빈칸 정규화
|
||||
text = re.sub(r"_{3,}", "___", text)
|
||||
# Bold 마크업(**) 제거 (DOCX 텍스트에 리터럴 ** 포함되는 경우)
|
||||
text = re.sub(r"\*\*(.+?)\*\*", r"\1", text)
|
||||
# 선행 리스트 마커 제거 (DOCX 텍스트가 "- "로 시작하는 경우)
|
||||
text = re.sub(r"^-\s+", "", text)
|
||||
return text
|
||||
|
||||
|
||||
def compare_documents(docx_name, md_name):
|
||||
"""두 문서의 텍스트를 비교"""
|
||||
docx_path = DOCX_DIR / docx_name
|
||||
md_path = MD_DIR / md_name
|
||||
|
||||
if not docx_path.exists():
|
||||
return {"status": "error", "message": f"DOCX 파일 없음: {docx_name}"}
|
||||
if not md_path.exists():
|
||||
return {"status": "error", "message": f"Markdown 파일 없음: {md_name}"}
|
||||
|
||||
docx_lines = [normalize_text(l) for l in extract_text_from_docx(docx_path) if l.strip()]
|
||||
md_lines = [normalize_text(l) for l in extract_text_from_markdown(md_path) if l.strip()]
|
||||
|
||||
# difflib로 비교
|
||||
matcher = difflib.SequenceMatcher(None, docx_lines, md_lines)
|
||||
ratio = matcher.ratio()
|
||||
|
||||
# 차이점 추출
|
||||
diffs = []
|
||||
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
||||
if tag == "equal":
|
||||
continue
|
||||
elif tag == "replace":
|
||||
for idx in range(max(i2 - i1, j2 - j1)):
|
||||
docx_text = docx_lines[i1 + idx] if i1 + idx < i2 else "(없음)"
|
||||
md_text = md_lines[j1 + idx] if j1 + idx < j2 else "(없음)"
|
||||
diffs.append({
|
||||
"type": "변경",
|
||||
"docx": docx_text[:80],
|
||||
"markdown": md_text[:80],
|
||||
})
|
||||
elif tag == "delete":
|
||||
for idx in range(i1, i2):
|
||||
diffs.append({
|
||||
"type": "DOCX에만 존재",
|
||||
"docx": docx_lines[idx][:80],
|
||||
"markdown": "-",
|
||||
})
|
||||
elif tag == "insert":
|
||||
for idx in range(j1, j2):
|
||||
diffs.append({
|
||||
"type": "Markdown에만 존재",
|
||||
"docx": "-",
|
||||
"markdown": md_lines[idx][:80],
|
||||
})
|
||||
|
||||
return {
|
||||
"status": "ok",
|
||||
"similarity": round(ratio * 100, 1),
|
||||
"docx_lines": len(docx_lines),
|
||||
"md_lines": len(md_lines),
|
||||
"diff_count": len(diffs),
|
||||
"diffs": diffs[:20], # 상위 20개만
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("DOCX ↔ Markdown 동기화 검증")
|
||||
print("=" * 70)
|
||||
|
||||
all_ok = True
|
||||
|
||||
for docx_name, md_name in FILE_MAP.items():
|
||||
print(f"\n{'─' * 50}")
|
||||
print(f"문서: {docx_name}")
|
||||
print(f" ↔ {md_name}")
|
||||
print(f"{'─' * 50}")
|
||||
|
||||
result = compare_documents(docx_name, md_name)
|
||||
|
||||
if result["status"] == "error":
|
||||
print(f" [ERROR] {result['message']}")
|
||||
all_ok = False
|
||||
continue
|
||||
|
||||
similarity = result["similarity"]
|
||||
status_icon = "OK" if similarity >= 80 else "WARN" if similarity >= 60 else "FAIL"
|
||||
|
||||
print(f" 유사도: {similarity}% [{status_icon}]")
|
||||
print(f" DOCX 라인: {result['docx_lines']}")
|
||||
print(f" Markdown 라인: {result['md_lines']}")
|
||||
print(f" 차이점: {result['diff_count']}개")
|
||||
|
||||
if result["diffs"]:
|
||||
print(f"\n 주요 차이점 (상위 {min(len(result['diffs']), 10)}개):")
|
||||
for i, diff in enumerate(result["diffs"][:10]):
|
||||
print(f" [{diff['type']}]")
|
||||
if diff["docx"] != "-":
|
||||
print(f" DOCX: {diff['docx']}")
|
||||
if diff["markdown"] != "-":
|
||||
print(f" MD: {diff['markdown']}")
|
||||
|
||||
if similarity < 80:
|
||||
all_ok = False
|
||||
|
||||
print(f"\n{'=' * 70}")
|
||||
if all_ok:
|
||||
print("결과: 모든 문서 동기화 상태 양호")
|
||||
else:
|
||||
print("결과: 일부 문서에서 불일치 발견 - 확인 필요")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
return 0 if all_ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
279
data/interview-master-questions.sql
Normal file
279
data/interview-master-questions.sql
Normal file
@@ -0,0 +1,279 @@
|
||||
-- ============================================================
|
||||
-- 인터뷰 질문 마스터 데이터 SQL
|
||||
-- 8개 도메인 × 16개 템플릿 × 80개 질문
|
||||
--
|
||||
-- 실행 방법:
|
||||
-- 로컬: docker exec -i sam-mysql-1 mysql -u root -p samdb < docs/data/interview-master-questions.sql
|
||||
-- 개발서버: mysql -u <user> -p samdb < interview-master-questions.sql
|
||||
-- phpMyAdmin: SQL 탭에서 전체 복사 후 실행
|
||||
--
|
||||
-- 주의: 한 번만 실행할 것. 중복 실행 시 데이터가 중복됨.
|
||||
-- ============================================================
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET @tenant_id = 1;
|
||||
SET @user_id = 1;
|
||||
SET @now = NOW();
|
||||
|
||||
-- ============================================================
|
||||
-- 대분류: 제조업-방화셔터 (parent_id=null, 루트 카테고리)
|
||||
-- ============================================================
|
||||
INSERT INTO interview_categories (tenant_id, interview_project_id, parent_id, name, description, domain, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, NULL, NULL, '제조업-방화셔터', '방화셔터 제조업 인터뷰', NULL, 1, 1, @user_id, @user_id, @now, @now);
|
||||
SET @root_manufacturing = LAST_INSERT_ID();
|
||||
|
||||
-- ============================================================
|
||||
-- Domain 1: 제품 분류 체계 (product_classification)
|
||||
-- ============================================================
|
||||
INSERT INTO interview_categories (tenant_id, interview_project_id, parent_id, name, description, domain, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, NULL, @root_manufacturing, '제품 분류 체계', '제품 카테고리, 모델 코드, 분류 기준 파악', 'product_classification', 3, 1, @user_id, @user_id, @now, @now);
|
||||
SET @cat_1 = LAST_INSERT_ID();
|
||||
|
||||
-- 템플릿 1.1: 제품 카테고리 구조
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_1, '제품 카테고리 구조', 1, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_1_1 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_1_1, '귀사의 주요 제품군을 모두 나열해주세요', 'text', NULL, '쉼표 구분으로 제품군 나열', NULL, NULL, 'product_classification', 1, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_1_1, '각 제품군의 하위 모델명과 코드 체계를 알려주세요', 'table_input', '{"columns":["모델코드","모델명","비고"]}', '코드-이름 매핑 테이블', NULL, NULL, 'product_classification', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_1_1, '제품을 분류하는 기준은 무엇인가요? (소재, 용도, 크기 등)', 'multi_select', '{"choices":["소재별","용도별","크기별","설치방식별","인증여부별"]}', NULL, NULL, NULL, 'product_classification', 0, 3, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_1_1, '인증(인정) 제품과 비인증 제품의 구분이 있나요?', 'select', '{"choices":["있음","없음"]}', NULL, NULL, NULL, 'product_classification', 0, 4, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_1_1, '인증 제품의 경우 구성이 고정되나요?', 'checkbox', NULL, NULL, NULL, '{"question_index":3,"value":"있음"}', 'product_classification', 0, 5, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_1_1, '카테고리별 제품 수는 대략 몇 개인가요?', 'number', NULL, NULL, '개', NULL, 'product_classification', 0, 6, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_1_1, '제품 코드 명명 규칙을 설명해주세요 (예: KSS01의 의미)', 'text', NULL, '코드 체계의 각 자릿수 의미', NULL, NULL, 'product_classification', 0, 7, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_1_1, '기존 시스템(ERP/엑셀)에서 사용하는 제품 분류 방식을 캡처하여 업로드해주세요', 'file_upload', NULL, NULL, NULL, NULL, 'product_classification', 0, 8, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- 템플릿 1.2: 설치 유형별 분류
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_1, '설치 유형별 분류', 2, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_1_2 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_1_2, '설치 유형(벽면형, 측면형, 혼합형 등)에 따라 견적이 달라지나요?', 'select', '{"choices":["예, 크게 달라짐","약간 달라짐","달라지지 않음"]}', NULL, NULL, NULL, 'product_classification', 0, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_1_2, '각 설치 유형별로 어떤 부품이 달라지나요?', 'table_input', '{"columns":["설치유형","추가부품","제외부품","비고"]}', NULL, NULL, NULL, 'product_classification', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_1_2, '설치 유형에 따른 추가 비용 항목이 있나요?', 'text', NULL, NULL, NULL, NULL, 'product_classification', 0, 3, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- ============================================================
|
||||
-- Domain 2: BOM 구조 (bom_structure)
|
||||
-- ============================================================
|
||||
INSERT INTO interview_categories (tenant_id, interview_project_id, parent_id, name, description, domain, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, NULL, @root_manufacturing, 'BOM 구조', '완제품-부품 관계, 부품 카테고리, BOM 레벨', 'bom_structure', 4, 1, @user_id, @user_id, @now, @now);
|
||||
SET @cat_2 = LAST_INSERT_ID();
|
||||
|
||||
-- 템플릿 2.1: 완제품-부품 관계
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_2, '완제품-부품 관계', 1, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_2_1 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_2_1, '대표 제품 1개의 완제품→부품 구성을 트리로 그려주세요', 'bom_tree', NULL, '최상위 제품부터 하위 부품까지 트리 구조', NULL, NULL, 'bom_structure', 1, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_2_1, '모든 제품에 공통으로 들어가는 부품은 무엇인가요?', 'multi_select', '{"choices":["가이드레일","케이스","모터","제어기","브라켓","볼트/너트"]}', '직접 입력 가능', NULL, NULL, 'bom_structure', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_2_1, '제품별로 선택적(옵션)인 부품은 무엇인가요?', 'table_input', '{"columns":["제품명","옵션부품","적용조건"]}', NULL, NULL, NULL, 'bom_structure', 0, 3, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_2_1, 'BOM이 현재 엑셀로 관리되고 있나요? 파일을 업로드해주세요', 'file_upload', NULL, NULL, NULL, NULL, 'bom_structure', 0, 4, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_2_1, '하위 부품의 단계(레벨)는 최대 몇 단계인가요?', 'number', NULL, NULL, '단계', NULL, 'bom_structure', 0, 5, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_2_1, '부품 수량이 고정인 것과 계산이 필요한 것을 구분해주세요', 'table_input', '{"columns":["부품명","고정/계산","고정수량 또는 계산식"]}', NULL, NULL, NULL, 'bom_structure', 0, 6, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- 템플릿 2.2: 부품 카테고리
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_2, '부품 카테고리', 2, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_2_2 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_2_2, '부품을 카테고리로 분류하면 어떻게 나눠지나요? (본체, 절곡품, 전동부, 부자재 등)', 'text', NULL, '부품 분류 체계', NULL, NULL, 'bom_structure', 0, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_2_2, '각 카테고리에 속하는 부품 목록을 정리해주세요', 'table_input', '{"columns":["카테고리","부품명","규격"]}', NULL, NULL, NULL, 'bom_structure', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_2_2, '외주 구매 부품과 자체 제작 부품의 구분이 있나요?', 'select', '{"choices":["있음","없음"]}', NULL, NULL, NULL, 'bom_structure', 0, 3, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_2_2, '부자재(볼트, 너트, 패킹 등)는 별도 관리하나요?', 'checkbox', NULL, NULL, NULL, NULL, 'bom_structure', 0, 4, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- ============================================================
|
||||
-- Domain 3: 치수/변수 계산 (dimension_formula)
|
||||
-- ============================================================
|
||||
INSERT INTO interview_categories (tenant_id, interview_project_id, parent_id, name, description, domain, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, NULL, @root_manufacturing, '치수/변수 계산', '오픈 사이즈→제작 사이즈 변환, 파생 변수 계산', 'dimension_formula', 5, 1, @user_id, @user_id, @now, @now);
|
||||
SET @cat_3 = LAST_INSERT_ID();
|
||||
|
||||
-- 템플릿 3.1: 오픈 사이즈 → 제작 사이즈
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_3, '오픈 사이즈 → 제작 사이즈', 1, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_3_1 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_3_1, '고객이 입력하는 기본 치수 항목은 무엇인가요? (폭, 높이, 깊이 등)', 'multi_select', '{"choices":["폭(W)","높이(H)","깊이(D)","두께(T)","지름(Ø)"]}', NULL, NULL, NULL, 'dimension_formula', 1, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_3_1, '오픈 사이즈에서 제작 사이즈로 변환할 때 더하는 마진값은?', 'formula_input', NULL, '예: W1 = W0 + 120, H1 = H0 + 50', 'mm', NULL, 'dimension_formula', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_3_1, '제품 카테고리별로 마진값이 다른가요?', 'table_input', '{"columns":["제품카테고리","W 마진(mm)","H 마진(mm)","비고"]}', NULL, NULL, NULL, 'dimension_formula', 0, 3, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_3_1, '면적(㎡) 계산 공식을 알려주세요', 'formula_input', NULL, '예: area = W1 * H1 / 1000000', '㎡', NULL, 'dimension_formula', 0, 4, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_3_1, '중량(kg) 계산 공식을 알려주세요', 'formula_input', NULL, '예: weight = area * 단위중량(kg/㎡)', 'kg', NULL, 'dimension_formula', 0, 5, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_3_1, '기타 파생 변수가 있나요? (예: 분할 개수, 절곡 길이 등)', 'table_input', '{"columns":["변수명","계산식","단위","비고"]}', NULL, NULL, NULL, 'dimension_formula', 0, 6, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_3_1, '치수 계산에 사용하는 엑셀 수식을 캡처해주세요', 'file_upload', NULL, NULL, NULL, NULL, 'dimension_formula', 0, 7, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- 템플릿 3.2: 변수 의존 관계
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_3, '변수 의존 관계', 2, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_3_2 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_3_2, '변수 간 의존 관계를 설명해주세요 (A는 B와 C로 계산)', 'text', NULL, '계산 순서와 변수 의존성', NULL, NULL, 'dimension_formula', 0, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_3_2, '계산 순서가 중요한 변수가 있나요?', 'text', NULL, NULL, NULL, NULL, 'dimension_formula', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_3_2, '단위는 mm, m, kg 중 어떤 것을 기본으로 사용하나요?', 'select', '{"choices":["mm","m","cm","혼용"]}', NULL, NULL, NULL, 'dimension_formula', 0, 3, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- ============================================================
|
||||
-- Domain 4: 부품 구성 상세 (component_config)
|
||||
-- ============================================================
|
||||
INSERT INTO interview_categories (tenant_id, interview_project_id, parent_id, name, description, domain, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, NULL, @root_manufacturing, '부품 구성 상세', '주요 부품별 규격, 선택 기준, 특수 구성', 'component_config', 6, 1, @user_id, @user_id, @now, @now);
|
||||
SET @cat_4 = LAST_INSERT_ID();
|
||||
|
||||
-- 템플릿 4.1: 주요 부품별 상세
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_4, '주요 부품별 상세', 1, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_4_1 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_4_1, '가이드레일의 표준 길이 규격은? (예: 1219, 2438, 3305mm)', 'table_input', '{"columns":["규격코드","길이(mm)","비고"]}', NULL, NULL, NULL, 'component_config', 0, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_4_1, '가이드레일 길이 조합 규칙은? (어떤 길이를 몇 개 사용?)', 'text', NULL, '높이에 따른 가이드레일 조합 로직', NULL, NULL, 'component_config', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_4_1, '케이스(하우징) 크기별 규격과 부속품 차이를 설명해주세요', 'table_input', '{"columns":["케이스규격","적용조건","부속품"]}', NULL, NULL, NULL, 'component_config', 0, 3, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_4_1, '모터 용량 종류와 선택 기준은? (무게별? 면적별?)', 'table_input', '{"columns":["모터용량","적용범위(최소)","적용범위(최대)","단위"]}', '무게/면적 범위별 모터 매핑', NULL, NULL, 'component_config', 0, 4, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_4_1, '모터 전압 옵션은? (380V, 220V 등)', 'multi_select', '{"choices":["380V","220V","110V","DC 24V"]}', NULL, NULL, NULL, 'component_config', 0, 5, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_4_1, '제어기 종류와 선택 기준은? (노출형/매립형 등)', 'table_input', '{"columns":["제어기유형","적용조건","비고"]}', NULL, NULL, NULL, 'component_config', 0, 6, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_4_1, '절곡품(판재 가공) 목록과 각각의 치수 결정 방식은?', 'table_input', '{"columns":["절곡품명","치수결정방식","재질","두께(mm)"]}', NULL, NULL, NULL, 'component_config', 0, 7, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_4_1, '부자재(볼트, 너트, 패킹 등) 목록과 수량 결정 방식은?', 'table_input', '{"columns":["부자재명","규격","수량결정방식","기본수량"]}', NULL, NULL, NULL, 'component_config', 0, 8, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- 템플릿 4.2: 특수 구성
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_4, '특수 구성', 2, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_4_2 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_4_2, '연기차단재 등 특수 부품이 있나요? 적용 조건은?', 'text', NULL, NULL, NULL, NULL, 'component_config', 0, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_4_2, '보강재(샤프트, 파이프, 앵글 등) 사용 조건은?', 'table_input', '{"columns":["보강재명","규격","적용조건","수량"]}', NULL, NULL, NULL, 'component_config', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_4_2, '고객 요청에 따라 추가/제외되는 옵션 부품은?', 'table_input', '{"columns":["옵션부품","추가/제외","추가비용","비고"]}', NULL, NULL, NULL, 'component_config', 0, 3, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- ============================================================
|
||||
-- Domain 5: 단가 체계 (pricing_structure)
|
||||
-- ============================================================
|
||||
INSERT INTO interview_categories (tenant_id, interview_project_id, parent_id, name, description, domain, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, NULL, @root_manufacturing, '단가 체계', '단가 관리 방식, 계산 방식, 마진/LOSS율', 'pricing_structure', 7, 1, @user_id, @user_id, @now, @now);
|
||||
SET @cat_5 = LAST_INSERT_ID();
|
||||
|
||||
-- 템플릿 5.1: 단가 관리 방식
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_5, '단가 관리 방식', 1, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_5_1 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_5_1, '부품별 단가를 어디서 관리하나요? (엑셀, ERP, 구두 등)', 'select', '{"choices":["엑셀","ERP 시스템","구두/경험","기타"]}', NULL, NULL, NULL, 'pricing_structure', 0, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_5_1, '단가표 파일을 업로드해주세요', 'file_upload', NULL, NULL, NULL, NULL, 'pricing_structure', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_5_1, '단가 변경 주기는? (월/분기/연 등)', 'select', '{"choices":["수시","월 단위","분기 단위","반기 단위","연 단위"]}', NULL, NULL, NULL, 'pricing_structure', 0, 3, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_5_1, '단가에 포함되는 항목은? (재료비만? 가공비 포함?)', 'multi_select', '{"choices":["재료비","가공비","운송비","설치비","마진"]}', NULL, NULL, NULL, 'pricing_structure', 0, 4, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_5_1, '고객별/거래처별 차등 단가가 있나요?', 'select', '{"choices":["있음 (등급별)","있음 (거래처별)","없음 (일괄 동일)"]}', NULL, NULL, NULL, 'pricing_structure', 0, 5, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_5_1, 'LOSS율(손실률)을 적용하나요? 적용 방식은?', 'formula_input', NULL, '예: 실제수량 = 계산수량 × (1 + LOSS율)', '%', NULL, 'pricing_structure', 0, 6, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_5_1, '마진율 설정 방식은? (일괄? 품목별?)', 'select', '{"choices":["일괄 마진율","품목별 마진율","카테고리별 마진율","고객별 마진율"]}', NULL, NULL, NULL, 'pricing_structure', 0, 7, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- 템플릿 5.2: 단가 계산 방식
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_5, '단가 계산 방식', 2, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_5_2 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_5_2, '면적 기반 단가 품목은? (원/㎡)', 'table_input', '{"columns":["품목명","단가(원/㎡)","비고"]}', NULL, '원/㎡', NULL, 'pricing_structure', 0, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_5_2, '중량 기반 단가 품목은? (원/kg)', 'table_input', '{"columns":["품목명","단가(원/kg)","비고"]}', NULL, '원/kg', NULL, 'pricing_structure', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_5_2, '수량 기반 단가 품목은? (원/EA)', 'table_input', '{"columns":["품목명","단가(원/EA)","비고"]}', NULL, '원/EA', NULL, 'pricing_structure', 0, 3, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_5_2, '길이 기반 단가 품목은? (원/m)', 'table_input', '{"columns":["품목명","단가(원/m)","비고"]}', NULL, '원/m', NULL, 'pricing_structure', 0, 4, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_5_2, '기타 특수 단가 계산 방식이 있나요?', 'text', NULL, NULL, NULL, NULL, 'pricing_structure', 0, 5, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- ============================================================
|
||||
-- Domain 6: 수량 수식 (quantity_formula)
|
||||
-- ============================================================
|
||||
INSERT INTO interview_categories (tenant_id, interview_project_id, parent_id, name, description, domain, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, NULL, @root_manufacturing, '수량 수식', '부품별 수량 결정 규칙, 계산식, 검증', 'quantity_formula', 8, 1, @user_id, @user_id, @now, @now);
|
||||
SET @cat_6 = LAST_INSERT_ID();
|
||||
|
||||
-- 템플릿 6.1: 수량 결정 규칙
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_6, '수량 결정 규칙', 1, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_6_1 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_6_1, '고정 수량 부품 목록 (항상 1개, 2개 등)', 'table_input', '{"columns":["부품명","고정수량","비고"]}', NULL, NULL, NULL, 'quantity_formula', 0, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_6_1, '치수 기반 수량 계산 부품과 수식', 'formula_input', NULL, '예: 슬랫수량 = CEIL(H1 / 슬랫피치)', NULL, NULL, 'quantity_formula', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_6_1, '면적 기반 수량 계산 부품과 수식', 'formula_input', NULL, '예: 스크린수량 = area / 기준면적', NULL, NULL, 'quantity_formula', 0, 3, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_6_1, '중량 기반 수량 계산 부품과 수식', 'formula_input', NULL, NULL, NULL, NULL, 'quantity_formula', 0, 4, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_6_1, '올림/내림/반올림 규칙이 있는 계산은?', 'table_input', '{"columns":["계산항목","올림/내림/반올림","소수점자릿수"]}', NULL, NULL, NULL, 'quantity_formula', 0, 5, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_6_1, '여유 수량(LOSS) 적용 품목과 비율은?', 'table_input', '{"columns":["품목명","LOSS율(%)","비고"]}', NULL, NULL, NULL, 'quantity_formula', 0, 6, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- 템플릿 6.2: 수식 검증
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_6, '수식 검증', 2, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_6_2 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_6_2, '실제 견적서에서 수량 계산 예시를 보여주세요 (W=3000, H=2500일 때)', 'table_input', '{"columns":["부품명","수식","계산결과","단위"]}', NULL, NULL, NULL, 'quantity_formula', 1, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_6_2, '수식에 사용하는 함수가 있나요? (SUM, CEIL, ROUND 등)', 'multi_select', '{"choices":["CEIL (올림)","FLOOR (내림)","ROUND (반올림)","MAX","MIN","IF 조건문","SUM"]}', NULL, NULL, NULL, 'quantity_formula', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_6_2, '조건에 따라 수식이 달라지는 경우가 있나요?', 'text', NULL, '예: 폭이 3000 초과이면 분할 계산', NULL, NULL, 'quantity_formula', 0, 3, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- ============================================================
|
||||
-- Domain 7: 조건부 로직 (conditional_logic)
|
||||
-- ============================================================
|
||||
INSERT INTO interview_categories (tenant_id, interview_project_id, parent_id, name, description, domain, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, NULL, @root_manufacturing, '조건부 로직', '범위/매핑 기반 부품 자동 선택 규칙', 'conditional_logic', 9, 1, @user_id, @user_id, @now, @now);
|
||||
SET @cat_7 = LAST_INSERT_ID();
|
||||
|
||||
-- 템플릿 7.1: 범위 기반 선택
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_7, '범위 기반 선택', 1, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_7_1 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_7_1, '무게 범위별 모터 용량 선택표를 작성해주세요', 'price_table', '{"columns":["범위 시작(kg)","범위 끝(kg)","모터용량","비고"]}', NULL, NULL, NULL, 'conditional_logic', 1, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_7_1, '크기 범위별 부품 자동 선택 규칙이 있나요?', 'table_input', '{"columns":["조건(변수)","범위","선택부품","비고"]}', NULL, NULL, NULL, 'conditional_logic', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_7_1, '브라켓 크기 결정 기준은?', 'table_input', '{"columns":["조건","범위","브라켓 규격"]}', NULL, NULL, NULL, 'conditional_logic', 0, 3, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- 템플릿 7.2: 매핑 기반 선택
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_7, '매핑 기반 선택', 2, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_7_2 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_7_2, '제품 모델 → 기본 부품 세트 매핑표', 'table_input', '{"columns":["제품모델","기본부품1","기본부품2","기본부품3"]}', NULL, NULL, NULL, 'conditional_logic', 0, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_7_2, '설치 유형 → 추가 부품 매핑표', 'table_input', '{"columns":["설치유형","추가부품","수량","비고"]}', NULL, NULL, NULL, 'conditional_logic', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_7_2, '제어기 유형 → 부속품 매핑표', 'table_input', '{"columns":["제어기유형","부속품1","부속품2","부속품3"]}', NULL, NULL, NULL, 'conditional_logic', 0, 3, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_7_2, '기타 조건부 자동 선택 규칙', 'text', NULL, '위 항목에 해당하지 않는 조건-결과 매핑', NULL, NULL, 'conditional_logic', 0, 4, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- ============================================================
|
||||
-- Domain 8: 견적서 양식 (quote_format)
|
||||
-- ============================================================
|
||||
INSERT INTO interview_categories (tenant_id, interview_project_id, parent_id, name, description, domain, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, NULL, @root_manufacturing, '견적서 양식', '출력 양식, 항목 그룹, 소계/합계 구조', 'quote_format', 10, 1, @user_id, @user_id, @now, @now);
|
||||
SET @cat_8 = LAST_INSERT_ID();
|
||||
|
||||
-- 템플릿 8.1: 출력 양식
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_8, '출력 양식', 1, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_8_1 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_8_1, '현재 사용 중인 견적서 양식을 업로드해주세요', 'file_upload', NULL, NULL, NULL, NULL, 'quote_format', 1, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_8_1, '견적서에 표시되는 항목 그룹은? (재료비, 노무비, 설치비 등)', 'multi_select', '{"choices":["재료비","노무비","경비","설치비","운반비","이윤","부가세"]}', NULL, NULL, NULL, 'quote_format', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_8_1, '소계/합계 계산 구조를 설명해주세요', 'text', NULL, '항목 그룹별 소계와 최종 합계의 관계', NULL, NULL, 'quote_format', 0, 3, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_8_1, '할인 적용 방식은? (일괄? 항목별?)', 'select', '{"choices":["일괄 할인","항목별 할인","할인 없음","협의 할인"]}', NULL, NULL, NULL, 'quote_format', 0, 4, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_8_1, '부가세 표시 방식은? (별도? 포함?)', 'select', '{"choices":["별도 표시","포함 표시","선택 가능"]}', NULL, NULL, NULL, 'quote_format', 0, 5, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_8_1, '견적서에 표시하지 않는 내부 관리 항목은?', 'text', NULL, NULL, NULL, NULL, 'quote_format', 0, 6, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_8_1, '견적 번호 체계를 알려주세요', 'text', NULL, '예: Q-2026-001 형식', NULL, NULL, 'quote_format', 0, 7, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- 템플릿 8.2: 특수 요구사항
|
||||
INSERT INTO interview_templates (tenant_id, interview_category_id, name, sort_order, is_active, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (@tenant_id, @cat_8, '특수 요구사항', 2, 1, @user_id, @user_id, @now, @now);
|
||||
SET @tpl_8_2 = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO interview_questions (tenant_id, interview_template_id, question_text, question_type, options, ai_hint, expected_format, depends_on, domain, is_required, sort_order, is_active, created_by, updated_by, created_at, updated_at) VALUES
|
||||
(@tenant_id, @tpl_8_2, '산출내역서(세부 내역)를 별도로 제공하나요?', 'checkbox', NULL, NULL, NULL, NULL, 'quote_format', 0, 1, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_8_2, '위치별(층/부호) 개별 산출이 필요한가요?', 'checkbox', NULL, NULL, NULL, NULL, 'quote_format', 0, 2, 1, @user_id, @user_id, @now, @now),
|
||||
(@tenant_id, @tpl_8_2, '일괄 산출(여러 위치 합산)을 사용하나요?', 'checkbox', NULL, NULL, NULL, NULL, 'quote_format', 0, 3, 1, @user_id, @user_id, @now, @now);
|
||||
|
||||
-- ============================================================
|
||||
-- 완료 확인
|
||||
-- ============================================================
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM interview_categories WHERE interview_project_id IS NULL AND domain IS NOT NULL) AS master_categories,
|
||||
(SELECT COUNT(*) FROM interview_templates t JOIN interview_categories c ON t.interview_category_id = c.id WHERE c.interview_project_id IS NULL AND c.domain IS NOT NULL) AS master_templates,
|
||||
(SELECT COUNT(*) FROM interview_questions q JOIN interview_templates t ON q.interview_template_id = t.id JOIN interview_categories c ON t.interview_category_id = c.id WHERE c.interview_project_id IS NULL AND c.domain IS NOT NULL) AS master_questions;
|
||||
@@ -218,14 +218,24 @@
|
||||
|
||||
### 서비스 현황
|
||||
|
||||
| 서비스 | 포트 | 상태 |
|
||||
|--------|------|------|
|
||||
| Nginx | 80/443 | active |
|
||||
| Apache | 8080 | active (레거시) |
|
||||
| MySQL 8.4 | 3306 (localhost) | active |
|
||||
| Gitea | 3000 | active |
|
||||
| Next.js (PM2) | 3001 | active |
|
||||
| fail2ban | - | active |
|
||||
| 서비스 | 포트 | 상태 | 비고 |
|
||||
|--------|------|------|------|
|
||||
| Nginx | 80/443 | active | |
|
||||
| PHP 8.4 FPM | unix socket | active | SAM 앱 (api, mng, sales) |
|
||||
| PHP 7.3 FPM | unix socket | active | 5130.codebridge-x.com 레거시 전용 |
|
||||
| MySQL 8.4 | 3306 (localhost) | active | binlog 7일 보관 |
|
||||
| Gitea | 3000 | active | |
|
||||
| Next.js (PM2) | 3001 | active | |
|
||||
| fail2ban | - | active | |
|
||||
| Swap | - | active | 4G (/swapfile, fstab 등록) |
|
||||
| ~~Apache~~ | ~~8080~~ | **disabled** | 2026-03-09 비활성화 (front.5130.co.kr 미사용) |
|
||||
| ~~PHP 5.6 FPM~~ | - | **disabled** | 2026-03-09 비활성화 (미사용) |
|
||||
|
||||
### 자동 정리 (cron, hskwon)
|
||||
|
||||
| 작업 | 주기 | 설명 |
|
||||
|------|------|------|
|
||||
| Gitea repo-archive 캐시 정리 | 매주 일요일 04:00 | 7일 이상 된 캐시 파일 삭제 + 빈 디렉토리 정리 |
|
||||
|
||||
### 방화벽 (UFW) 규칙
|
||||
|
||||
|
||||
@@ -48,6 +48,59 @@ chmod 640 /home/webservice/mng/shared/.env
|
||||
|
||||
---
|
||||
|
||||
## [개발] sam-dev 리소스 관리
|
||||
|
||||
sam-dev는 2 vCPU / 3.8GB RAM으로 여러 서비스가 공존하므로 리소스 관리가 중요하다.
|
||||
|
||||
### 주요 리소스 소비자
|
||||
|
||||
| 프로세스 | 메모리 | CPU (상시) | 비고 |
|
||||
|----------|--------|-----------|------|
|
||||
| MySQL | ~1.2G (30%) | 2~5% | |
|
||||
| Gitea | ~400M (10%) | 1.8% | |
|
||||
| Next.js (PM2) | ~380M (9.5%) | 0.5% | |
|
||||
| PHP 8.4 FPM | ~250M (변동) | 요청 시 높음 | max_children=5 |
|
||||
| PHP 7.3 FPM | ~30M | idle | 5130 레거시 전용 |
|
||||
|
||||
### Swap (4G, 2026-03-09 추가)
|
||||
|
||||
Swap 없이 운영하면 메모리 부족 시 OOM Killer가 프로세스를 즉시 종료한다.
|
||||
|
||||
```bash
|
||||
# Swap 상태 확인
|
||||
swapon --show
|
||||
free -m
|
||||
|
||||
# /etc/fstab에 등록되어 있으므로 재부팅 후에도 유지됨
|
||||
# /swapfile none swap sw 0 0
|
||||
```
|
||||
|
||||
### Gitea repo-archive 캐시
|
||||
|
||||
Gitea는 Web UI에서 ZIP/TAR.GZ 다운로드 시 `/var/lib/gitea/data/repo-archive/`에 캐시를 생성한다.
|
||||
기본 설정에서는 만료가 없어 무한 증가하므로, cron으로 7일 이상 된 캐시를 정리한다.
|
||||
|
||||
```bash
|
||||
# 캐시 크기 확인
|
||||
sudo du -sh /var/lib/gitea/data/repo-archive/
|
||||
|
||||
# 수동 정리 (긴급 시)
|
||||
sudo rm -rf /var/lib/gitea/data/repo-archive/*
|
||||
|
||||
# 자동 정리: hskwon crontab (매주 일요일 04:00)
|
||||
# 0 4 * * 0 find /var/lib/gitea/data/repo-archive -type f -mtime +7 -delete
|
||||
# 0 4 * * 0 find /var/lib/gitea/data/repo-archive -type d -empty -delete
|
||||
```
|
||||
|
||||
### 비활성화된 서비스 (2026-03-09)
|
||||
|
||||
| 서비스 | 사유 | 복원 명령 |
|
||||
|--------|------|----------|
|
||||
| PHP 5.6 FPM | 미사용 (아무 사이트도 참조 안 함) | `sudo systemctl enable --now php5.6-fpm` |
|
||||
| Apache | front.5130.co.kr 미사용 | `sudo systemctl enable --now apache2` |
|
||||
|
||||
---
|
||||
|
||||
## 시스템 리소스 모니터링
|
||||
|
||||
양쪽 서버 공통 명령어:
|
||||
|
||||
@@ -92,6 +92,33 @@ gunzip -c /home/hskwon/backups/mysql/gitea_YYYYMMDD_HHMMSS.sql.gz | mysql gitea
|
||||
|
||||
---
|
||||
|
||||
## [개발] MySQL Binlog 관리
|
||||
|
||||
sam-dev에서는 리플리케이션/PITR을 사용하지 않으므로 binlog 보관을 최소화한다.
|
||||
|
||||
**설정 파일:** `/etc/mysql/mysql.conf.d/mysqld.cnf`
|
||||
|
||||
```ini
|
||||
binlog_expire_logs_seconds = 604800 # 7일 보관 (2026-03-09 설정)
|
||||
max_binlog_size = 100M
|
||||
```
|
||||
|
||||
```bash
|
||||
# binlog 상태 확인
|
||||
sudo ls -lh /var/lib/mysql/binlog.0* | wc -l # 파일 수
|
||||
sudo du -shc /var/lib/mysql/binlog.0* | tail -1 # 총 크기
|
||||
|
||||
# 수동 퍼지 (필요 시)
|
||||
mysql -u pro -p -e "PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 3 DAY);"
|
||||
|
||||
# binlog 완전 비활성화가 필요하면 (권장하지 않음)
|
||||
# mysqld.cnf에 skip-log-bin 추가 후 MySQL 재시작
|
||||
```
|
||||
|
||||
> **참고:** 운영서버(sam-prod)의 binlog는 CI/CD 백업에 활용되므로 별도 정책 적용.
|
||||
|
||||
---
|
||||
|
||||
## Slow Query 분석 (운영)
|
||||
|
||||
```bash
|
||||
|
||||
385
dev/dev_plans/document-snapshot-architecture-plan.md
Normal file
385
dev/dev_plans/document-snapshot-architecture-plan.md
Normal file
@@ -0,0 +1,385 @@
|
||||
# 문서 스냅샷 아키텍처 계획
|
||||
|
||||
> **작성일**: 2026-03-06
|
||||
> **목적**: 문서 보기/인쇄 시 HTML 스냅샷 기반 출력으로 전환 (B안 + 구조화 데이터 병행)
|
||||
> **상태**: ✅ 코드 완료 (검증 대기)
|
||||
> **영향 범위**: API(저장), React(캡처/전송), MNG(출력)
|
||||
|
||||
---
|
||||
|
||||
## 현재 진행 상태
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| **마지막 완료 작업** | Phase 2 전면 보정: API 누락 수정, 오프스크린 렌더링 적용, readOnly 자동 캡처 제거 |
|
||||
| **다음 작업** | Phase 4: 브라우저 검증 + 기존 partial 정리 |
|
||||
| **진행률** | 13/13 (100% 코드 완료, 검증 대기) |
|
||||
| **마지막 업데이트** | 2026-03-06 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 배경
|
||||
|
||||
현재 MNG 문서 보기(`show.blade.php`)는 문서 양식별로 전용 blade partial을 만들어 렌더링한다:
|
||||
- `bending-inspection-data.blade.php` (절곡 중간검사)
|
||||
- `bending-worklog.blade.php` (절곡 작업일지)
|
||||
|
||||
이 방식의 문제:
|
||||
1. **확장 불가**: 회사마다 다양한 양식이 존재 → 양식마다 blade 파일 생성 불가
|
||||
2. **스냅샷 미보장**: 하드코딩된 제품 목록/도면치수가 정책 변경 시 과거 문서를 깨뜨림
|
||||
3. **이중 렌더링**: React와 MNG에서 동일 문서를 각각 렌더링 → 불일치 발생
|
||||
|
||||
### 1.2 목표 아키텍처
|
||||
|
||||
```
|
||||
[React] 문서 저장 시
|
||||
├── 구조화 데이터 저장 (기존 유지)
|
||||
│ ├── document_data (EAV 플랫)
|
||||
│ └── work_order_items.options.inspection_data (JSON 스냅샷)
|
||||
└── rendered_html 저장 (신규)
|
||||
└── React가 렌더링한 HTML을 캡처 → documents.rendered_html에 저장
|
||||
|
||||
[MNG] 문서 보기 시
|
||||
├── rendered_html 있으면 → 그대로 출력 (렌더링 로직 0)
|
||||
└── rendered_html 없으면 → 기존 동적 렌더링 fallback
|
||||
```
|
||||
|
||||
### 1.3 핵심 원칙
|
||||
|
||||
```
|
||||
1. 하나의 view 파일로 모든 문서를 보기 (문서 양식별 blade 파일 금지)
|
||||
2. rendered_html이 있으면 무조건 그것을 사용 (완전한 스냅샷)
|
||||
3. 구조화 데이터는 편집/검색/통계용으로 병행 유지
|
||||
4. React에서만 문서 렌더링 책임 → MNG는 출력만 담당
|
||||
5. Lazy Snapshot: 조회 시 rendered_html 없으면 자동 캡처 → 저장 (점진적 스냅샷 전환)
|
||||
```
|
||||
|
||||
### 1.4 변경 승인 정책
|
||||
|
||||
| 분류 | 예시 | 승인 |
|
||||
|------|------|------|
|
||||
| 즉시 가능 | blade 템플릿 수정, 기존 partial 정리 | 불필요 |
|
||||
| 컨펌 필요 | API 저장 로직 변경, React 저장 흐름 변경 | **필수** |
|
||||
| 금지 | documents 테이블 구조 변경 (이미 rendered_html 존재) | 불필요 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 현황 분석
|
||||
|
||||
### 2.1 DB 현황
|
||||
|
||||
`documents` 테이블에 이미 `rendered_html` (LONGTEXT, nullable) 컬럼이 존재:
|
||||
- 마이그레이션: `api/database/migrations/2026_02_28_100001_add_block_data_to_documents.php`
|
||||
- 현재 값: 모든 문서에서 NULL (사용 안 됨)
|
||||
- **DB 변경 불필요**
|
||||
|
||||
### 2.2 React 현황 (구현 완료)
|
||||
|
||||
#### 캡처 원칙 A: 입력 시 저장 (Active Capture)
|
||||
|
||||
입력 화면에서 저장할 때 해당 데이터의 "문서 뷰"를 캡처. 보기(readOnly)에서는 캡처하지 않음.
|
||||
|
||||
| Save Path | 파일 | 방식 | 캡처 대상 |
|
||||
|-----------|------|------|----------|
|
||||
| 작업일지 저장 | `WorkLogModal.tsx` | contentWrapperRef.innerHTML | 작업일지 문서 뷰 |
|
||||
| 검사성적서 저장 (edit) | `InspectionReportModal.tsx` | contentWrapperRef.innerHTML | 검사 성적서 문서 뷰 |
|
||||
| 수입검사 저장 | `ImportInspectionInputModal.tsx` | 오프스크린 렌더링 (`captureRenderedHtml`) | 수입검사 성적서 문서 (`ImportInspectionDocument`) |
|
||||
| WorkerScreen 인라인 검사 저장 | `index.tsx` | 미캡처 (데이터만 저장) | 성적서 모달에서 저장 시 캡처 |
|
||||
|
||||
> **WorkerScreen 인라인 저장**: 검사 입력 시점에 성적서 문서가 렌더링되지 않으므로 rendered_html 미포함.
|
||||
> 이후 InspectionReportModal을 edit 모드로 열어 저장하면 캡처됨.
|
||||
> 향후 오프스크린 렌더링으로 확장 가능 (템플릿 로딩 등 async 의존성 해결 필요).
|
||||
|
||||
#### 캡처 원칙 B: 조회 시 자동 캡처 (Lazy Snapshot)
|
||||
|
||||
문서 조회(view/readOnly) 시 `rendered_html`이 없으면 자동 캡처하여 백그라운드 저장.
|
||||
|
||||
```
|
||||
문서 View 시
|
||||
├── rendered_html 있음 → 그대로 표시 (기존)
|
||||
└── rendered_html 없음 → 동적 렌더링 완료 후 캡처 → API로 rendered_html 저장
|
||||
(다음 조회부터는 스냅샷 사용)
|
||||
```
|
||||
|
||||
**적용 대상**:
|
||||
- readonly 문서 (제품검사 요청서 등 — 입력 없이 자동 생성되는 문서)
|
||||
- 마이그레이션 이전 기존 데이터 (rendered_html이 NULL인 과거 문서)
|
||||
- WorkerScreen 인라인 저장 후 아직 모달에서 저장하지 않은 문서
|
||||
|
||||
**구현 방식**:
|
||||
```typescript
|
||||
// 문서 표시 컴포넌트에서 (DocumentViewer, Modal 등)
|
||||
useEffect(() => {
|
||||
if (document && !document.rendered_html && isContentRendered) {
|
||||
const html = contentWrapperRef.current?.innerHTML
|
||||
|| await captureRenderedHtml(DocumentComponent, props);
|
||||
patchDocumentRenderedHtml(document.id, html); // 백그라운드 저장
|
||||
}
|
||||
}, [document, isContentRendered]);
|
||||
```
|
||||
|
||||
**고려사항**:
|
||||
- 사용자 UX 영향 없음 (백그라운드 비동기 저장)
|
||||
- 조회 권한만 있는 사용자도 트리거 가능해야 함
|
||||
- 동시 접속 시 중복 저장 가능 → 같은 HTML이므로 실질적 문제 없음
|
||||
- 캡처 타이밍: template 로드 + 데이터 바인딩 완료 후 (isContentRendered 판단 필요)
|
||||
|
||||
### 2.3 API 현황 (구현 완료)
|
||||
|
||||
- Document 모델 `$fillable`에 `rendered_html` 포함 ✅
|
||||
- `DocumentService` store/update에서 `rendered_html` 저장 ✅
|
||||
- `DocumentService` upsert에서 `rendered_html` 전달 ✅ (수입검사 경로)
|
||||
- `StoreRequest`/`UpdateRequest`에 `rendered_html` nullable string 검증 ✅
|
||||
- `UpsertRequest`에 `rendered_html` nullable string 검증 ✅
|
||||
|
||||
### 2.4 MNG 현황 (구현 완료)
|
||||
|
||||
- `show.blade.php`: rendered_html 우선 출력, 없으면 기존 동적 렌더링 fallback ✅
|
||||
- `print.blade.php`: 동일 패턴 적용 ✅
|
||||
- 전용 partial 파일 (삭제 대기):
|
||||
- `partials/bending-inspection-data.blade.php`
|
||||
- `partials/bending-worklog.blade.php`
|
||||
|
||||
---
|
||||
|
||||
## 3. 작업 범위
|
||||
|
||||
### Phase 0: 사전 정리
|
||||
|
||||
| # | 작업 항목 | 상태 | 비고 |
|
||||
|---|----------|:----:|------|
|
||||
| 0.1 | API Document 모델 $fillable 확인 및 rendered_html 추가 | ✅ | |
|
||||
| 0.2 | 기존 절곡 전용 partial 파일 정리 방침 결정 | ✅ | rendered_html 전환 후 삭제 |
|
||||
|
||||
### Phase 1: API - rendered_html 저장 지원
|
||||
|
||||
| # | 작업 항목 | 상태 | 비고 |
|
||||
|---|----------|:----:|------|
|
||||
| 1.1 | Document 모델 $fillable에 rendered_html 추가 | ✅ | |
|
||||
| 1.2 | DocumentService store/update에서 rendered_html 저장 | ✅ | |
|
||||
| 1.3 | StoreRequest/UpdateRequest에 rendered_html 검증 추가 | ✅ | nullable, string |
|
||||
| 1.4 | WorkOrderService inspection/worklog에 rendered_html 전달 | ✅ | create + update 모두 |
|
||||
|
||||
### Phase 2: React - HTML 캡처 및 전송
|
||||
|
||||
| # | 작업 항목 | 상태 | 비고 |
|
||||
|---|----------|:----:|------|
|
||||
| 2.1 | 오프스크린 렌더링 유틸리티 생성 | ✅ | `captureRenderedHtml()` — `flushSync` + `createRoot` |
|
||||
| 2.2 | InspectionReportModal 저장 시 rendered_html 포함 전송 | ✅ | contentWrapperRef.innerHTML 캡처 |
|
||||
| 2.3 | 작업일지 저장 시 rendered_html 포함 전송 | ✅ | contentWrapperRef.innerHTML 캡처 |
|
||||
| 2.4 | ImportInspectionInputModal 수입검사 저장 시 rendered_html | ✅ | 오프스크린 성적서 문서 렌더링 |
|
||||
| 2.5 | ReceivingManagement/actions saveInspectionData 파라미터 추가 | ✅ | rendered_html → /documents/upsert 전달 |
|
||||
| 2.6 | API UpsertRequest에 rendered_html 검증 추가 | ✅ | nullable string |
|
||||
|
||||
### Phase 3: MNG - 스냅샷 출력
|
||||
|
||||
| # | 작업 항목 | 상태 | 비고 |
|
||||
|---|----------|:----:|------|
|
||||
| 3.1 | show.blade.php에 rendered_html 우선 출력 로직 추가 | ✅ | |
|
||||
| 3.2 | 기존 전용 partial 파일 fallback으로 유지 (과도기) | ✅ | |
|
||||
| 3.3 | print.blade.php에도 rendered_html 출력 적용 | ✅ | 스냅샷 우선, 레거시 fallback |
|
||||
|
||||
### Phase 4: 검증 및 정리
|
||||
|
||||
| # | 작업 항목 | 상태 | 비고 |
|
||||
|---|----------|:----:|------|
|
||||
| 4.1 | 브라우저 검증 (MNG 보기/인쇄) | ⏳ | |
|
||||
| 4.2 | 기존 전용 partial 파일 삭제 | ⏳ | rendered_html 전환 완료 후 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 상세 작업 내용
|
||||
|
||||
### 4.1 HTML 캡처 방식 (Phase 2.1)
|
||||
|
||||
React에서 문서 컨텐츠 영역의 DOM을 캡처할 때 고려사항:
|
||||
|
||||
**방법 A: innerHTML 직접 추출 + CSS 인라인화**
|
||||
```typescript
|
||||
// 문서 컨텐츠 영역에 ref 부여
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 저장 시 HTML 추출
|
||||
const captureHtml = () => {
|
||||
const el = contentRef.current;
|
||||
if (!el) return '';
|
||||
|
||||
// Tailwind 클래스 → 인라인 스타일 변환 (또는 스타일시트 포함)
|
||||
// 방법 1: 계산된 스타일을 인라인으로
|
||||
// 방법 2: 필요한 Tailwind CSS를 <style> 태그로 포함
|
||||
return el.innerHTML;
|
||||
};
|
||||
```
|
||||
|
||||
**방법 B: 자체 CSS 포함 완전한 HTML 조각**
|
||||
```html
|
||||
<div class="document-snapshot">
|
||||
<style>/* 필요한 스타일만 추출 */</style>
|
||||
<!-- 문서 컨텐츠 -->
|
||||
</div>
|
||||
```
|
||||
|
||||
**권장: 방법 B** — MNG에서 Tailwind가 로드되어 있으므로, Tailwind 클래스를 그대로 사용하되 MNG에 없는 커스텀 스타일만 `<style>` 태그로 포함. MNG도 Tailwind를 사용하므로 대부분의 클래스가 호환됨.
|
||||
|
||||
### 4.2 MNG 출력 구조 (Phase 3.1)
|
||||
|
||||
```blade
|
||||
{{-- show.blade.php --}}
|
||||
@if($document->rendered_html)
|
||||
{{-- 스냅샷 모드: React가 생성한 HTML 그대로 출력 --}}
|
||||
<div class="document-snapshot-container">
|
||||
{!! $document->rendered_html !!}
|
||||
</div>
|
||||
@else
|
||||
{{-- 동적 모드: 기존 템플릿 기반 렌더링 (fallback) --}}
|
||||
@include('documents.partials.dynamic-render')
|
||||
@endif
|
||||
```
|
||||
|
||||
### 4.3 스타일 호환성 전략
|
||||
|
||||
| 항목 | React (Tailwind) | MNG (Tailwind) | 호환성 |
|
||||
|------|------------------|----------------|--------|
|
||||
| 기본 클래스 | px-2, py-1, border 등 | 동일 | 완전 호환 |
|
||||
| 반응형 | sm:, md:, lg: | inline style 정책 | 주의 필요 |
|
||||
| 커스텀 컴포넌트 | shadcn/ui | 없음 | `<style>` 포함 필요 |
|
||||
|
||||
**결론**: React에서 문서 영역은 순수 HTML+Tailwind로 렌더링 (shadcn 컴포넌트 미사용) → MNG 호환성 높음.
|
||||
단, `@media` 쿼리나 MNG에서 빌드되지 않은 Tailwind 클래스가 있을 수 있으므로 필요 시 인라인 스타일 변환.
|
||||
|
||||
### 4.4 XSS 보안 고려
|
||||
|
||||
`{!! !!}` (unescaped output) 사용 시 XSS 위험:
|
||||
- `rendered_html`은 **자체 시스템(React)에서만 생성** → 외부 입력 아님
|
||||
- API 저장 시 **sanitize** 처리 권장 (script 태그, on* 이벤트 제거)
|
||||
- 또는 CSP(Content Security Policy)로 인라인 스크립트 차단
|
||||
|
||||
---
|
||||
|
||||
## 5. 참고 파일
|
||||
|
||||
### React (수정 대상)
|
||||
- `react/src/components/production/WorkOrders/documents/InspectionReportModal.tsx` — 저장 흐름 (contentWrapperRef)
|
||||
- `react/src/components/production/WorkOrders/documents/BendingInspectionContent.tsx` — 검사 문서 렌더링
|
||||
- `react/src/components/production/WorkOrders/documents/BendingWorkLogContent.tsx` — 작업일지 렌더링
|
||||
- `react/src/components/production/WorkOrders/documents/bending/` — 절곡 섹션 컴포넌트들
|
||||
- `react/src/components/production/WorkerScreen/WorkLogModal.tsx` — 작업일지 저장 시 캡처
|
||||
- `react/src/components/material/ReceivingManagement/ImportInspectionInputModal.tsx` — 수입검사 저장 시 오프스크린 캡처
|
||||
- `react/src/components/material/ReceivingManagement/actions.ts` — saveInspectionData rendered_html 전달
|
||||
- `react/src/lib/utils/capture-rendered-html.tsx` — 오프스크린 렌더링 유틸리티 (신규)
|
||||
|
||||
### API (수정 대상)
|
||||
- `api/app/Models/Documents/Document.php` — $fillable
|
||||
- `api/app/Services/DocumentService.php` — store/update/upsert
|
||||
- `api/app/Http/Requests/Documents/StoreRequest.php` — 검증
|
||||
- `api/app/Http/Requests/Documents/UpdateRequest.php` — 검증
|
||||
- `api/app/Http/Requests/Document/UpsertRequest.php` — 검증 (수입검사 경로)
|
||||
|
||||
### MNG (수정 대상)
|
||||
- `mng/resources/views/documents/show.blade.php` — 메인 보기
|
||||
- `mng/resources/views/documents/print.blade.php` — 인쇄
|
||||
- `mng/resources/views/documents/partials/bending-inspection-data.blade.php` — 삭제 예정
|
||||
- `mng/resources/views/documents/partials/bending-worklog.blade.php` — 삭제 예정
|
||||
- `mng/app/Http/Controllers/DocumentController.php` — show()
|
||||
|
||||
### DB
|
||||
- `api/database/migrations/2026_02_28_100001_add_block_data_to_documents.php` — rendered_html 컬럼 (이미 존재)
|
||||
|
||||
### 문서
|
||||
- `docs/features/documents/README.md` — 문서관리 시스템
|
||||
- `docs/system/database/documents.md` — DB 스키마
|
||||
|
||||
---
|
||||
|
||||
## 6. 의존성 및 순서
|
||||
|
||||
```
|
||||
Phase 0 (사전 정리)
|
||||
↓
|
||||
Phase 1 (API: rendered_html 저장)
|
||||
↓
|
||||
Phase 2 (React: HTML 캡처 + 전송) ← Phase 1 완료 필요
|
||||
↓
|
||||
Phase 3 (MNG: 스냅샷 출력) ← Phase 2 완료 후 데이터 존재
|
||||
↓
|
||||
Phase 4 (검증 + 정리) ← 모든 Phase 완료 후
|
||||
```
|
||||
|
||||
Phase 1과 Phase 3의 MNG 코드 수정은 병렬 가능 (fallback 유지).
|
||||
단, 실제 데이터가 있어야 검증 가능하므로 Phase 2 완료 후 통합 검증.
|
||||
|
||||
---
|
||||
|
||||
## 7. 리스크 및 대응
|
||||
|
||||
| 리스크 | 영향 | 대응 |
|
||||
|--------|------|------|
|
||||
| HTML 용량 | 문서당 50-200KB, 수만 건 시 GB | LONGTEXT 이미 사용, 필요 시 gzip 압축 |
|
||||
| Tailwind 클래스 불일치 | MNG에서 일부 스타일 깨짐 | 인라인 스타일 변환 또는 MNG Tailwind 빌드에 포함 |
|
||||
| XSS | rendered_html에 악성 코드 | API에서 sanitize, CSP 정책 |
|
||||
| 과도기 fallback | rendered_html 없는 기존 문서 | 기존 동적 렌더링 유지 |
|
||||
| React 미경유 문서 | MNG에서만 생성된 문서 | MNG 저장 시에도 rendered_html 생성 검토 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 변경 이력
|
||||
|
||||
| 날짜 | 항목 | 변경 내용 | 파일 | 승인 |
|
||||
|------|------|----------|------|------|
|
||||
| 2026-03-06 | - | 계획 문서 작성 | - | - |
|
||||
| 2026-03-06 | Phase 0~3 | Phase 0~3 전체 구현 완료 | API/React/MNG 다수 | ✅ |
|
||||
| 2026-03-06 | Phase 2.4~2.6 | 모든 저장 경로에 rendered_html 추가 | InspectionReportModal, ImportInspectionInputModal, actions.ts | ✅ |
|
||||
| 2026-03-06 | Phase 2 보정 | API UpsertRequest rendered_html 누락 수정, DocumentService upsert() rendered_html 전달 추가 | UpsertRequest.php, DocumentService.php | ✅ |
|
||||
| 2026-03-06 | Phase 2 보정 | ImportInspection: 입력폼 캡처 → 오프스크린 성적서 렌더링으로 변경 | ImportInspectionInputModal.tsx, capture-rendered-html.tsx | ✅ |
|
||||
| 2026-03-06 | Phase 2 보정 | InspectionReportModal readOnly 자동 캡처 useEffect 제거 | InspectionReportModal.tsx | ✅ |
|
||||
| 2026-03-06 | 원칙 확장 | Lazy Snapshot 패턴 추가 — 조회 시 rendered_html 없으면 자동 캡처/저장 | 아키텍처 원칙 | - |
|
||||
|
||||
---
|
||||
|
||||
## 9. 검증 결과
|
||||
|
||||
> 작업 완료 후 이 섹션에 검증 결과 추가
|
||||
|
||||
### 9.1 성공 기준
|
||||
|
||||
| 기준 | 달성 | 비고 |
|
||||
|------|------|------|
|
||||
| React 저장 시 rendered_html이 documents 테이블에 저장 | ⏳ | |
|
||||
| mng.sam.kr/documents/36 에서 rendered_html로 문서 출력 | ⏳ | |
|
||||
| mng.sam.kr/documents/39 에서 rendered_html로 문서 출력 | ⏳ | |
|
||||
| rendered_html 없는 기존 문서가 기존대로 렌더링 | ⏳ | |
|
||||
| 인쇄 시에도 스냅샷 기반 출력 | ⏳ | |
|
||||
| 양식별 전용 blade 파일 없이 동작 | ⏳ | |
|
||||
|
||||
---
|
||||
|
||||
## 10. 자기완결성 점검 결과
|
||||
|
||||
### 10.1 체크리스트 검증
|
||||
|
||||
| # | 검증 항목 | 상태 | 비고 |
|
||||
|---|----------|:----:|------|
|
||||
| 1 | 작업 목적이 명확한가? | ✅ | 스냅샷 기반 문서 출력 |
|
||||
| 2 | 성공 기준이 정의되어 있는가? | ✅ | 9.1 참조 |
|
||||
| 3 | 작업 범위가 구체적인가? | ✅ | 5 Phase, 15개 작업 |
|
||||
| 4 | 의존성이 명시되어 있는가? | ✅ | 6. 의존성 참조 |
|
||||
| 5 | 참고 파일 경로가 정확한가? | ✅ | 5. 참고 파일 |
|
||||
| 6 | 단계별 절차가 실행 가능한가? | ✅ | 3. 작업 범위 |
|
||||
| 7 | 검증 방법이 명시되어 있는가? | ✅ | 9.1 성공 기준 |
|
||||
| 8 | 모호한 표현이 없는가? | ✅ | |
|
||||
|
||||
### 10.2 새 세션 시뮬레이션 테스트
|
||||
|
||||
| 질문 | 답변 가능 | 참조 섹션 |
|
||||
|------|:--------:|----------|
|
||||
| Q1. 이 작업의 목적은 무엇인가? | ✅ | 1.1 배경 |
|
||||
| Q2. 어디서부터 시작해야 하는가? | ✅ | Phase 0 → 1 → 2 → 3 |
|
||||
| Q3. 어떤 파일을 수정해야 하는가? | ✅ | 5. 참고 파일 |
|
||||
| Q4. 작업 완료 확인 방법은? | ✅ | 9.1 성공 기준 |
|
||||
| Q5. 막혔을 때 참고 문서는? | ✅ | 5. 참고 파일, docs/ |
|
||||
|
||||
---
|
||||
|
||||
*이 문서는 /plan 스킬로 생성되었습니다.*
|
||||
873
dev/dev_plans/fqc-document-system-plan.md
Normal file
873
dev/dev_plans/fqc-document-system-plan.md
Normal file
@@ -0,0 +1,873 @@
|
||||
# 제품검사 문서 시스템 구축 계획
|
||||
|
||||
> **작성일**: 2026-03-06
|
||||
> **목적**: 제품검사 성적서(template ID 65 보완) + 제품검사 요청서(신규 template)를 mng 양식 기반 동적 렌더링 + rendered_html 스냅샷 구조로 구현
|
||||
> **기준 문서**: `docs/dev/dev_plans/document-system-master.md`, `docs/dev/dev_plans/document-snapshot-architecture-plan.md`
|
||||
> **상태**: 확정 (2026-03-06)
|
||||
|
||||
---
|
||||
|
||||
## 현재 진행 상태
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| **마지막 완료 작업** | Phase 3 전체 완료 (통합 테스트 + fallback 검증 + 호환성 확인) |
|
||||
| **다음 작업** | 완료 — 추후 syncRequestDocument 기존 데이터 수동 실행 고려 |
|
||||
| **진행률** | 12/12 (100%) |
|
||||
| **마지막 업데이트** | 2026-03-06 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 배경
|
||||
|
||||
현재 제품검사 관련 문서가 **두 가지 방식**으로 혼재:
|
||||
- **하드코딩 React 컴포넌트** (`InspectionReportDocument.tsx`, `InspectionRequestDocument.tsx`) — mockData 기반 변환, 문서 저장 없음
|
||||
- **양식 기반 동적 렌더링** (`FqcDocumentContent.tsx`) — template ID 65 기반, 일부 구현됨 (4컬럼 단순 버전)
|
||||
|
||||
기존 중간검사/수입검사/작업일지는 이미 **mng 양식 등록 → React 동적 렌더링 → rendered_html 스냅샷** 패턴으로 동작 중. 제품검사도 동일 패턴으로 통일해야 함.
|
||||
|
||||
### 1.2 핵심 원칙
|
||||
|
||||
```
|
||||
1. mng에서 양식(template) 등록/관리 → React에서 동적 렌더링
|
||||
2. 저장 시 rendered_html 스냅샷 함께 저장
|
||||
3. mng/히스토리에서는 스냅샷 출력 (렌더링 로직 0)
|
||||
4. 기존 하드코딩 컴포넌트는 fallback으로 유지 → 점진적 전환
|
||||
```
|
||||
|
||||
### 1.3 변경 승인 정책
|
||||
|
||||
| 분류 | 예시 | 승인 |
|
||||
|------|------|------|
|
||||
| 즉시 가능 | mng 양식 항목 추가/수정, React 컴포넌트 수정 | 불필요 |
|
||||
| 컨펌 필요 | API 저장 로직 변경, 새 API 엔드포인트, DB 마이그레이션 | **필수** |
|
||||
| 금지 | documents 테이블 구조 변경, 기존 기능 삭제 | 별도 협의 |
|
||||
|
||||
### 1.4 준수 규칙
|
||||
- `docs/dev/standards/quality-checklist.md` - 품질 체크리스트
|
||||
- `docs/dev/standards/git-conventions.md` - Git 커밋 컨벤션
|
||||
- `docs/features/documents/README.md` - 문서관리 시스템 스펙
|
||||
|
||||
---
|
||||
|
||||
## 2. 현재 시스템 분석
|
||||
|
||||
### 2.1 기존 패턴 (참조 모델: 중간검사)
|
||||
|
||||
```
|
||||
[mng] DocumentTemplate (양식 등록)
|
||||
├── approval_lines: 결재선 (작성/검토/승인)
|
||||
├── basic_fields: 기본필드 (납품명, 제품명, 발주처 등)
|
||||
├── sections: 섹션 (검사기준서 이미지, 검사 DATA)
|
||||
│ └── items: 검사항목 (항목명, 기준, 측정유형)
|
||||
└── columns: 컬럼 정의 (NO, 검사항목, 검사기준, 판정)
|
||||
|
||||
[React] 동적 렌더링
|
||||
├── API로 template 조회 (GET /v1/document-templates/{id})
|
||||
├── TemplateInspectionContent / FqcDocumentContent 등으로 렌더링
|
||||
├── 편집 모드: 판정 토글, 측정값 입력
|
||||
└── 저장 시 rendered_html 캡처 → API 전송
|
||||
|
||||
[API] 저장
|
||||
├── document_data (EAV): 구조화 데이터 (검색/통계용)
|
||||
└── documents.rendered_html: HTML 스냅샷 (출력용)
|
||||
|
||||
[MNG] 출력
|
||||
├── rendered_html 있으면 → 그대로 출력 ({!! $document->rendered_html !!})
|
||||
└── 없으면 → 기존 동적 렌더링 fallback
|
||||
```
|
||||
|
||||
### 2.2 제품검사 성적서 현재 상태 (template ID 65)
|
||||
|
||||
**mng 양식 (ID 65)**: Phase 5.2에서 시더로 생성됨
|
||||
- 시더 위치: `mng/database/seeders/ProductInspectionTemplateSeeder.php` (tenant_id=287 하드코딩)
|
||||
- 결재 3인 (작성/검토/승인)
|
||||
- 기본필드 7개 (납품명, 제품명, 발주처, LOT NO, 로트크기, 검사일자, 검사자)
|
||||
- 섹션 2개 (검사기준서 이미지, 검사 DATA)
|
||||
- 검사항목 11개 (설치 후 최종검사, 모두 visual/checkbox)
|
||||
- **컬럼 4개** (NO, 검사항목, 검사기준, 판정) ← **보완 대상**
|
||||
|
||||
**React 구현 현황**:
|
||||
- `FqcDocumentContent.tsx` — template 기반 동적 렌더링 (읽기/편집 모드), **현재 4컬럼**
|
||||
- `InspectionReportDocument.tsx` — 하드코딩 fallback (**8컬럼 상세 버전**)
|
||||
- `InspectionReportModal.tsx` — 듀얼 모드 (useFqcMode: fqcDocumentMap 존재 여부로 판단)
|
||||
|
||||
**문제점**:
|
||||
1. 하드코딩 버전이 훨씬 상세함 (8컬럼, rowSpan 병합, 측정값, 검사방법/주기)
|
||||
2. template ID 65는 4컬럼으로 단순 → 하드코딩 수준으로 보완 필요
|
||||
3. rendered_html 스냅샷 저장이 FQC 문서에 아직 미적용
|
||||
|
||||
### 2.3 제품검사 요청서 현재 상태
|
||||
|
||||
**mng 양식**: 없음 (신규 생성 필요)
|
||||
|
||||
**React 구현**:
|
||||
- `InspectionRequestDocument.tsx` — 하드코딩 컴포넌트 (완성도 높음)
|
||||
- `InspectionRequestModal.tsx` — DocumentViewer + 하드코딩 컴포넌트
|
||||
- 데이터 변환: `buildRequestDocumentData()` (mockData.ts:398-423)
|
||||
|
||||
**구조**:
|
||||
- 결재라인 (작성/승인)
|
||||
- 기본정보 (수주처, 업체명, 담당자, 수주번호, 연락처, 현장명, 납품일, 현장주소, 총개소, 접수일, 검사방문요청일)
|
||||
- 입력사항 4개 섹션 (건축공사장, 자재유통업자, 공사시공자, 공사감리자)
|
||||
- 검사 요청 시 필독 (고정 텍스트)
|
||||
- 검사대상 사전 고지 정보 테이블 (No, 층수, 부호, 발주규격 가로/세로, 시공후규격 가로/세로, 변경사유)
|
||||
|
||||
---
|
||||
|
||||
## 3. 대상 범위
|
||||
|
||||
### Phase 1: 제품검사 성적서 (template ID 65) 보완
|
||||
|
||||
| # | 작업 항목 | 상태 | 비고 |
|
||||
|---|----------|:----:|------|
|
||||
| 1.1 | mng template ID 65 양식 보완 — section_items 교체 + template columns 2개로 재정의 | ✅ | items 11→22개, columns 4→2개(측정값+판정) |
|
||||
| 1.2 | FqcDocumentContent.tsx 동적 렌더링 보완 + 프론트 타입 수정 | ✅ | 8컬럼 시각 레이아웃 + rowSpan 복합키 병합 + measurement_type=none 처리 |
|
||||
| 1.3 | rendered_html 스냅샷 저장 적용 | ✅ | saveFqcDocument에 renderedHtml 파라미터 추가 + contentWrapperRef 준비 |
|
||||
| 1.4 | InspectionReportModal에서 양식 모드 기본값 전환 | ✅ | FQC 모드 우선, template 로드 실패 시 legacy fallback |
|
||||
|
||||
### Phase 2: 제품검사 요청서 (신규 template)
|
||||
|
||||
| # | 작업 항목 | 상태 | 비고 |
|
||||
|---|----------|:----:|------|
|
||||
| 2.1 | mng에 제품검사 요청서 template 신규 등록 (시더) | ✅ | template ID 66, description 컬럼 추가 |
|
||||
| 2.2 | React 동적 렌더링 컴포넌트 구현 | ✅ | FqcRequestDocumentContent, InspectionRequestModal FQC 모드 |
|
||||
| 2.3 | 요청서 문서 생성 API 연동 | ✅ | syncRequestDocument(), store/update/attachOrders 연동 |
|
||||
| 2.4 | rendered_html 스냅샷 저장 적용 (Lazy Snapshot) | ✅ | patchDocumentSnapshot, contentWrapperRef 캡처 |
|
||||
|
||||
### Phase 3: 통합 및 정리
|
||||
|
||||
| # | 작업 항목 | 상태 | 비고 |
|
||||
|---|----------|:----:|------|
|
||||
| 3.1 | InspectionDetail.tsx 모달 연동 통합 테스트 | ✅ | 요청서(legacy fallback) + 성적서(FQC 8컬럼) 정상 |
|
||||
| 3.2 | mng 문서 보기에서 스냅샷 출력 확인 | ✅ | show.blade.php rendered_html 우선 출력 패턴 코드 검증 |
|
||||
| 3.3 | 하드코딩 컴포넌트 fallback 유지 확인 | ✅ | requestDocumentId 없으면 legacy fallback 정상 동작 |
|
||||
| 3.4 | 기존 FQC 데이터 호환성 확인 | ✅ | 기존 EAV 데이터(basic_fields) 정상 표시 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 작업 절차
|
||||
|
||||
### 4.1 단계별 절차
|
||||
|
||||
```
|
||||
Phase 1: 제품검사 성적서 보완
|
||||
├── Step 1.1: mng template ID 65 시더 보완
|
||||
│ ├── template columns 재정의: 4→2개 (측정값+판정만, 나머지는 section_item 필드)
|
||||
│ ├── section_items 교체: 11개→22개 (섹션 5.1.2의 상세 항목 데이터)
|
||||
│ ├── cleanupExisting() → updateOrCreate() 패턴으로 ID 안정성 확보 (섹션 5.1.9)
|
||||
│ ├── mng에서 시더 실행 후 미리보기 확인
|
||||
│ └── 파일: mng/database/seeders/ProductInspectionTemplateSeeder.php
|
||||
│
|
||||
├── Step 1.2: FqcDocumentContent.tsx 8컬럼 렌더링 보완
|
||||
│ ├── [선행] fqcActions.ts 타입 수정: TemplateItemApi, FqcTemplateItem에 category/method/frequency/measurement_type 추가 (섹션 5.1.7)
|
||||
│ ├── [선행] transformTemplate()에서 새 필드 매핑 추가
|
||||
│ ├── 8컬럼 시각 레이아웃: 1~6 section_item 읽기전용 + 7~8 template column 편집 (섹션 5.1.1)
|
||||
│ ├── rowSpan 복합키 병합: category 단독 + method+frequency 복합키 (섹션 5.1.3)
|
||||
│ ├── 측정값 입력 분기: numeric→input, checkbox→양호/불량, none→비활성 (섹션 5.1.8)
|
||||
│ ├── 종합판정: measurement_type='none' 제외하고 계산 (섹션 5.1.8)
|
||||
│ └── 파일: fqcActions.ts + FqcDocumentContent.tsx
|
||||
│
|
||||
├── Step 1.3: rendered_html 스냅샷 적용
|
||||
│ ├── FqcDocumentContent의 wrapper div에 ref 연결
|
||||
│ ├── saveFqcDocument 호출 시 contentWrapperRef.current.innerHTML 캡처
|
||||
│ ├── fqcActions.ts의 saveFqcDocument()에 rendered_html 파라미터 추가
|
||||
│ ├── API: POST /v1/documents/upsert → DocumentService에서 rendered_html 저장 (이미 지원됨)
|
||||
│ └── 파일: fqcActions.ts, InspectionReportModal.tsx
|
||||
│
|
||||
└── Step 1.4: 양식 모드 기본값 전환
|
||||
├── InspectionReportModal.tsx에서 FQC 모드 우선 표시
|
||||
├── fqcDocumentMap 없어도 template 로드 시도
|
||||
└── 레거시 모드 fallback 유지
|
||||
|
||||
Phase 2: 제품검사 요청서 신규
|
||||
├── Step 2.1: mng template 등록 (시더 방식 확정)
|
||||
│ ├── 양식 구조 설계 (섹션 5.2의 상세 구조 참조)
|
||||
│ ├── 시더 작성 (ProductInspectionRequestTemplateSeeder)
|
||||
│ ├── mng 시더 실행 후 미리보기 확인
|
||||
│ └── 파일: mng/database/seeders/ProductInspectionRequestTemplateSeeder.php (신규)
|
||||
│
|
||||
├── Step 2.2: React 동적 렌더링
|
||||
│ ├── FqcRequestDocumentContent.tsx 신규 생성 (또는 기존 FqcDocumentContent 확장)
|
||||
│ ├── quality_documents 데이터 → 문서 데이터 매핑
|
||||
│ ├── 사전 고지 정보 테이블 (quality_document_locations → 테이블 행)
|
||||
│ ├── InspectionRequestModal에 양식 기반 모드 추가
|
||||
│ └── 파일: react/.../documents/ 디렉토리
|
||||
│
|
||||
├── Step 2.3: API 연동 (quality_document 생성 시 자동 생성 확정)
|
||||
│ ├── QualityDocumentService::store()에서 요청서 document 자동 생성
|
||||
│ ├── 기본필드 자동 매핑 (quality_document → document basic_fields)
|
||||
│ ├── 개소 데이터 자동 매핑 (quality_document_locations → 사전고지 테이블)
|
||||
│ └── 파일: api/app/Services/QualityDocumentService.php
|
||||
│
|
||||
└── Step 2.4: rendered_html 스냅샷 (Lazy Snapshot 패턴)
|
||||
├── 요청서는 readonly 문서 → "입력 시 저장" 캡처 불가
|
||||
├── Lazy Snapshot 적용: 요청서 최초 조회 시 rendered_html 없으면 자동 캡처
|
||||
├── captureRenderedHtml 유틸리티 활용 (react/src/lib/utils/capture-rendered-html.tsx)
|
||||
├── 또는 contentWrapperRef.innerHTML로 렌더링 완료 후 캡처
|
||||
├── 백그라운드 API 호출로 rendered_html 저장 (PATCH /v1/documents/{id} 또는 upsert)
|
||||
└── 참조: document-snapshot-architecture-plan.md 캡처 원칙 B
|
||||
|
||||
Phase 3: 통합 및 정리
|
||||
├── InspectionDetail.tsx에서 모달 연동 통합 테스트
|
||||
├── mng show.blade.php에서 스냅샷 출력 확인
|
||||
├── 하드코딩 fallback 정상 동작 확인
|
||||
└── 기존 FQC 데이터 호환성 확인
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 상세 작업 내용
|
||||
|
||||
### 5.1 제품검사 성적서 양식 보완 상세 (Phase 1.1)
|
||||
|
||||
#### 5.1.1 컬럼 구조 — Template columns vs Display columns (핵심 구분)
|
||||
|
||||
> **핵심**: Template columns는 EAV 편집 가능 필드만 정의. 8컬럼 시각 레이아웃은 React에서 section_item 필드 + template columns를 조합하여 렌더링.
|
||||
|
||||
**Template columns = EAV 데이터 저장용 (편집 가능 필드만)**
|
||||
|
||||
현재 4컬럼 중 실제 EAV로 저장하는 것은 `판정`(select) 하나뿐. 나머지 3개(NO, 검사항목, 검사기준)는 section_item 필드의 읽기 전용 표시.
|
||||
|
||||
**목표 template columns (2~3개)** — 시더에 반영:
|
||||
|
||||
| 컬럼 | label | column_type | width | 비고 |
|
||||
|------|-------|-------------|-------|------|
|
||||
| 1 | 측정값 | measurement | 70px | measurement_type별 입력 (numeric/checkbox) |
|
||||
| 2 | 판정 | select | 50px | 적합/부적합 |
|
||||
|
||||
> `measurement_type='none'`인 항목(작동테스트)은 측정값/판정 컬럼 모두 비활성.
|
||||
|
||||
**8컬럼 시각 레이아웃 (React 렌더링)**:
|
||||
|
||||
| 시각 컬럼 | 데이터 소스 | 편집 가능 |
|
||||
|-----------|-----------|:---------:|
|
||||
| 1. No. | section_item 순번 (category별 그룹 번호) | ❌ |
|
||||
| 2. 검사항목 | section_item.`category` | ❌ |
|
||||
| 3. 세부항목 | section_item.`item` | ❌ |
|
||||
| 4. 검사기준 | section_item.`standard` | ❌ |
|
||||
| 5. 검사방법 | section_item.`method` | ❌ |
|
||||
| 6. 검사주기 | section_item.`frequency` | ❌ |
|
||||
| 7. 측정값 | template column `measurement` → document_data EAV | ✅ |
|
||||
| 8. 판정 | template column `select` → document_data EAV | ✅ |
|
||||
|
||||
> 1~6번은 section_item의 읽기 전용 필드를 React에서 직접 렌더링. 7~8번만 template columns로 정의하여 document_data에 EAV 저장.
|
||||
|
||||
#### 5.1.2 검사항목 데이터 (시더에 반영할 section_items)
|
||||
|
||||
**방안 C 확정** — 하드코딩 `mockReportInspectionItems` (mockData.ts:426-458)을 template section_items로 이관.
|
||||
|
||||
`DocumentTemplateSectionItem` 필드 매핑:
|
||||
|
||||
| mock 필드 | DB 필드 | 설명 |
|
||||
|-----------|---------|------|
|
||||
| category | `category` | 검사 그룹명 (겉모양, 모터, 치수 등) |
|
||||
| subCategory | `item` | 세부 항목명 (가공상태, 길이 등) |
|
||||
| criteria | `standard` | 검사 기준 |
|
||||
| method | `method` | 검사 방법 |
|
||||
| frequency | `frequency` | 검사 주기 |
|
||||
| measurement_type | `measurement_type` | checkbox/numeric/none |
|
||||
|
||||
**시더 section_items 데이터** (검사 DATA 섹션):
|
||||
|
||||
```php
|
||||
// 섹션: 검사 DATA
|
||||
$items = [
|
||||
// === 1. 겉모양 (5개 세부항목, method/freq 동일: 육안검사/전수검사) ===
|
||||
['category' => '겉모양', 'item' => '가공상태', 'standard' => '흠, 녹 등 확인', 'method' => '육안검사', 'frequency' => '전수검사', 'measurement_type' => 'checkbox'],
|
||||
['category' => '겉모양', 'item' => '재봉상태', 'standard' => '이중 재봉 상태', 'method' => '육안검사', 'frequency' => '전수검사', 'measurement_type' => 'checkbox'],
|
||||
['category' => '겉모양', 'item' => '조립상태', 'standard' => '개폐 작동', 'method' => '육안검사', 'frequency' => '전수검사', 'measurement_type' => 'checkbox'],
|
||||
['category' => '겉모양', 'item' => '연기차단재', 'standard' => '접착력 확인', 'method' => '육안검사', 'frequency' => '전수검사', 'measurement_type' => 'checkbox'],
|
||||
['category' => '겉모양', 'item' => '하단마감재', 'standard' => '접착력 확인', 'method' => '육안검사', 'frequency' => '전수검사', 'measurement_type' => 'checkbox'],
|
||||
|
||||
// === 2. 모터 ===
|
||||
['category' => '모터', 'item' => '-', 'standard' => '인정제품과 동일사양', 'method' => '육안검사', 'frequency' => '전수검사', 'measurement_type' => 'checkbox'],
|
||||
|
||||
// === 3. 재질 ===
|
||||
['category' => '재질', 'item' => '-', 'standard' => 'WY-SC780 인쇄상태 확인', 'method' => '육안검사', 'frequency' => '전수검사', 'measurement_type' => 'checkbox'],
|
||||
|
||||
// === 4. 치수(오픈사이즈) (4개 세부항목, method: 체크검사) ===
|
||||
['category' => '치수(오픈사이즈)', 'item' => '길이(W)', 'standard' => '수주치수 +-30mm', 'method' => '체크검사', 'frequency' => '전수검사', 'measurement_type' => 'numeric'],
|
||||
['category' => '치수(오픈사이즈)', 'item' => '높이(H)', 'standard' => '수주치수 +-20mm', 'method' => '체크검사', 'frequency' => '전수검사', 'measurement_type' => 'numeric'],
|
||||
['category' => '치수(오픈사이즈)', 'item' => '가이드레일 간격', 'standard' => '수주치수 +-10mm', 'method' => '체크검사', 'frequency' => '전수검사', 'measurement_type' => 'numeric'],
|
||||
['category' => '치수(오픈사이즈)', 'item' => '하단막대 간격', 'standard' => '수주치수 +-10mm', 'method' => '체크검사', 'frequency' => '전수검사', 'measurement_type' => 'numeric'],
|
||||
|
||||
// === 5. 작동테스트 ===
|
||||
['category' => '작동테스트', 'item' => '개폐성능', 'standard' => '작동 유무 확인', 'method' => '', 'frequency' => '', 'measurement_type' => 'none'],
|
||||
|
||||
// === 6. 내화시험 (3개 세부항목, method: 공인시험기관, freq: 1회/5년) ===
|
||||
['category' => '내화시험', 'item' => '비차열 1시간', 'standard' => '균열게이지 불관통', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'],
|
||||
['category' => '내화시험', 'item' => '차열성', 'standard' => '이면온도 상승제한', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'],
|
||||
['category' => '내화시험', 'item' => '비손상성', 'standard' => '화염 비관통', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'],
|
||||
|
||||
// === 7. 차연시험 ===
|
||||
['category' => '차연시험', 'item' => '공기누설량', 'standard' => '25Pa 기준 0.9m3/min.m2 이하', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'],
|
||||
|
||||
// === 8. 개폐시험 (5개 세부항목) ===
|
||||
['category' => '개폐시험', 'item' => '개폐 작동', 'standard' => '10회 이상 개폐작동 이상없음', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'],
|
||||
['category' => '개폐시험', 'item' => '폐쇄 시간', 'standard' => '5초 이내 완전 폐쇄', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'],
|
||||
['category' => '개폐시험', 'item' => '평균속도(상한)', 'standard' => '0.3m/sec 이하', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'],
|
||||
['category' => '개폐시험', 'item' => '평균속도(하한)', 'standard' => '0.07m/sec 이상', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'],
|
||||
['category' => '개폐시험', 'item' => '과부하', 'standard' => '과부하 작동 이상없음', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'],
|
||||
|
||||
// === 9. 내충격시험 ===
|
||||
['category' => '내충격시험', 'item' => '-', 'standard' => '낙하높이 기준 이상없음', 'method' => '공인시험기관', 'frequency' => '1회/5년', 'measurement_type' => 'checkbox'],
|
||||
];
|
||||
```
|
||||
|
||||
#### 5.1.3 rowSpan 자동 병합 로직 (React 구현)
|
||||
|
||||
하드코딩 `InspectionReportDocument.tsx`의 `buildCoverageMap()` (라인 42-57) 패턴을 **template 데이터 기반으로 자동화**.
|
||||
|
||||
> **주의**: `method`와 `frequency`는 **복합 키**(method+frequency)로 병합해야 함.
|
||||
> 단일 필드 비교 시 frequency='전수검사'가 7+4=11행 연속으로 잘못 병합됨.
|
||||
> 실제로는 method가 다르면 (육안검사 7행 vs 체크검사 4행) 별도 그룹이어야 함.
|
||||
|
||||
```typescript
|
||||
// category는 단일 필드 병합 (같은 category 연속이면 병합)
|
||||
function buildFieldRowSpan(items: SectionItem[], field: 'category') {
|
||||
const spans: Map<number, number> = new Map();
|
||||
const covered: Set<number> = new Set();
|
||||
|
||||
let i = 0;
|
||||
while (i < items.length) {
|
||||
const value = items[i][field];
|
||||
if (!value) { i++; continue; }
|
||||
|
||||
let span = 1;
|
||||
while (i + span < items.length && items[i + span][field] === value) {
|
||||
covered.add(i + span);
|
||||
span++;
|
||||
}
|
||||
if (span > 1) spans.set(i, span);
|
||||
i += span;
|
||||
}
|
||||
return { spans, covered };
|
||||
}
|
||||
|
||||
// method+frequency는 복합 키 병합
|
||||
function buildCompositeRowSpan(items: SectionItem[]) {
|
||||
const spans: Map<number, number> = new Map();
|
||||
const covered: Set<number> = new Set();
|
||||
|
||||
let i = 0;
|
||||
while (i < items.length) {
|
||||
const key = `${items[i].method || ''}|${items[i].frequency || ''}`;
|
||||
if (!items[i].method && !items[i].frequency) { i++; continue; }
|
||||
|
||||
let span = 1;
|
||||
while (i + span < items.length) {
|
||||
const nextKey = `${items[i + span].method || ''}|${items[i + span].frequency || ''}`;
|
||||
if (nextKey !== key) break;
|
||||
covered.add(i + span);
|
||||
span++;
|
||||
}
|
||||
if (span > 1) spans.set(i, span);
|
||||
i += span;
|
||||
}
|
||||
return { spans, covered };
|
||||
}
|
||||
|
||||
// 사용: 렌더링 시
|
||||
const categoryCoverage = buildFieldRowSpan(sectionItems, 'category');
|
||||
const methodFreqCoverage = buildCompositeRowSpan(sectionItems);
|
||||
// method 컬럼과 frequency 컬럼 모두 methodFreqCoverage로 렌더링
|
||||
|
||||
// 셀 렌더링 예시
|
||||
{!categoryCoverage.covered.has(idx) && (
|
||||
<td rowSpan={categoryCoverage.spans.get(idx) || 1}>{item.category}</td>
|
||||
)}
|
||||
{!methodFreqCoverage.covered.has(idx) && (
|
||||
<td rowSpan={methodFreqCoverage.spans.get(idx) || 1}>{item.method}</td>
|
||||
)}
|
||||
{!methodFreqCoverage.covered.has(idx) && (
|
||||
<td rowSpan={methodFreqCoverage.spans.get(idx) || 1}>{item.frequency}</td>
|
||||
)}
|
||||
```
|
||||
|
||||
**병합 결과 예시** (시더 데이터 기준):
|
||||
- category `겉모양` → 5행 병합
|
||||
- category `치수(오픈사이즈)` → 4행 병합
|
||||
- method+frequency `육안검사|전수검사` → 7행 (겉모양5 + 모터1 + 재질1)
|
||||
- method+frequency `체크검사|전수검사` → 4행 (치수 4개)
|
||||
- method+frequency `공인시험기관|1회/5년` → 9행 (내화3 + 차연1 + 개폐5)
|
||||
- `작동테스트`는 method/frequency 빈값 → 병합 대상 아님
|
||||
|
||||
#### 5.1.4 rendered_html 캡처 패턴
|
||||
|
||||
> **캡처 원칙** (document-snapshot-architecture-plan.md 참조):
|
||||
> - **Active Capture**: 입력 화면에서 저장할 때 캡처. readOnly에서는 캡처하지 않음.
|
||||
> - **Lazy Snapshot**: 조회 시 rendered_html 없으면 자동 캡처/저장 (점진적 전환).
|
||||
> FQC 성적서는 Active Capture (편집 모드 저장 시), 요청서는 Lazy Snapshot (readonly 자동 캡처) 적용.
|
||||
|
||||
**패턴 1: innerHTML 직접 캡처** (참조: 중간검사 `InspectionReportModal.tsx` 라인 341-366)
|
||||
|
||||
```typescript
|
||||
// 1. ref 정의
|
||||
const contentWrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 2. 저장 시 캡처
|
||||
const handleSave = async () => {
|
||||
const renderedHtml = contentWrapperRef.current?.innerHTML || undefined;
|
||||
const result = await saveInspectionDocument({
|
||||
...data,
|
||||
rendered_html: renderedHtml,
|
||||
});
|
||||
};
|
||||
|
||||
// 3. JSX에서 ref 연결
|
||||
<div ref={contentWrapperRef}>
|
||||
<TemplateInspectionContent ... />
|
||||
</div>
|
||||
```
|
||||
|
||||
**패턴 2: 오프스크린 렌더링** (참조: 수입검사 `ImportInspectionInputModal.tsx`)
|
||||
|
||||
```typescript
|
||||
import { captureRenderedHtml } from '@/lib/utils/capture-rendered-html';
|
||||
|
||||
// 문서 컴포넌트를 오프스크린으로 렌더링 → HTML 캡처
|
||||
const html = await captureRenderedHtml(DocumentComponent, documentProps);
|
||||
```
|
||||
|
||||
- 유틸리티 위치: `react/src/lib/utils/capture-rendered-html.tsx` (flushSync + createRoot)
|
||||
- 문서가 화면에 렌더링되지 않는 상황에서 캡처할 때 사용 (Lazy Snapshot에 활용 가능)
|
||||
|
||||
**FQC 성적서 적용 (Phase 1.3)**: 패턴 1 사용. `InspectionReportModal.tsx`의 FQC 모드 저장 로직에 동일 패턴 추가.
|
||||
현재 `fqcActions.ts`의 `saveFqcDocument()`는 `POST /v1/documents/upsert` 사용 → body에 `rendered_html` 추가.
|
||||
|
||||
#### 5.1.5 FqcDocumentContent.tsx 현재 구조 (보완 대상)
|
||||
|
||||
**현재 상태** (라인 311-375):
|
||||
- 4컬럼 테이블: NO(30px) | 검사항목 | 검사기준 | 판정(28px)
|
||||
- `InspectionRow` 컴포넌트 (라인 399-454): 4개 셀 렌더링
|
||||
- 판정: readonly면 텍스트 표시, 편집모드면 양호/불량 토글 버튼
|
||||
- 종합판정: 라인 117-128 자동 계산 (하나라도 부적합이면 불합격)
|
||||
|
||||
**보완 방향**:
|
||||
1. template columns 2개(측정값+판정)로 재정의 + 8컬럼 시각 레이아웃 (섹션 5.1.1)
|
||||
2. `InspectionRow` → 8셀 렌더링 + rowSpan 복합키 병합 (섹션 5.1.3)
|
||||
3. measurement_type에 따른 셀 분기 (섹션 5.1.8):
|
||||
- `checkbox`: 양호/불량 토글 (기존)
|
||||
- `numeric`: 숫자 입력 필드 (신규)
|
||||
- `none`: 측정값/판정 모두 비활성 + 종합판정에서 제외 (작동테스트)
|
||||
4. method/frequency: section_item 필드에서 직접 렌더링 (readonly)
|
||||
5. [선행] fqcActions.ts 타입에 category/method/frequency/measurement_type 추가 (섹션 5.1.7)
|
||||
|
||||
#### 5.1.6 saveFqcDocument API 현재 구조
|
||||
|
||||
**`fqcActions.ts`** (라인 429-461):
|
||||
```typescript
|
||||
// 현재: rendered_html 미포함
|
||||
const body: Record<string, unknown> = {
|
||||
template_id: templateId,
|
||||
data: data,
|
||||
};
|
||||
if (documentId) body.document_id = documentId;
|
||||
if (itemId) body.item_id = itemId;
|
||||
if (title) body.title = title;
|
||||
// → rendered_html 추가 필요
|
||||
|
||||
const res = await fetchAPI('/api/v1/documents/upsert', { method: 'POST', body });
|
||||
```
|
||||
|
||||
**data 구조** (EAV):
|
||||
```typescript
|
||||
data: Array<{
|
||||
section_id?: number | null;
|
||||
column_id?: number | null;
|
||||
row_index: number;
|
||||
field_key: string; // 예: 'judgment', 'measured_value'
|
||||
field_value: string | null;
|
||||
}>
|
||||
```
|
||||
|
||||
#### 5.1.7 Frontend 타입 갭 수정 (Phase 1.2 선행 작업)
|
||||
|
||||
> **문제**: API는 section_items에 category, method, frequency, measurement_type 필드를 반환하지만,
|
||||
> React의 `TemplateItemApi`와 `FqcTemplateItem` 타입에 이 필드들이 누락되어 있음.
|
||||
|
||||
**현재 타입** (`fqcActions.ts:20-29`):
|
||||
```typescript
|
||||
interface TemplateItemApi {
|
||||
id: number;
|
||||
section_id: number;
|
||||
item: string;
|
||||
standard: string;
|
||||
sort_order: number;
|
||||
// ❌ category, method, frequency, measurement_type 누락
|
||||
}
|
||||
```
|
||||
|
||||
**수정 필요**:
|
||||
```typescript
|
||||
interface TemplateItemApi {
|
||||
id: number;
|
||||
section_id: number;
|
||||
item: string;
|
||||
standard: string;
|
||||
sort_order: number;
|
||||
category: string | null; // 추가
|
||||
method: string | null; // 추가
|
||||
frequency: string | null; // 추가
|
||||
measurement_type: string | null; // 추가 (checkbox/numeric/none)
|
||||
}
|
||||
```
|
||||
|
||||
**`FqcTemplateItem`** (`fqcActions.ts:152-161`)도 동일하게 필드 추가.
|
||||
|
||||
**`transformTemplate()`** (`fqcActions.ts:254-300`)에서 매핑 추가:
|
||||
```typescript
|
||||
// items 변환 시 category, method, frequency, measurement_type 포함
|
||||
items: section.items.map((item: TemplateItemApi) => ({
|
||||
...existingMapping,
|
||||
category: item.category || '',
|
||||
method: item.method || '',
|
||||
frequency: item.frequency || '',
|
||||
measurement_type: item.measurement_type || 'checkbox',
|
||||
}))
|
||||
```
|
||||
|
||||
#### 5.1.8 measurement_type='none' 처리 (작동테스트)
|
||||
|
||||
> **문제**: 작동테스트 항목은 `measurement_type='none'`인데, 현재 코드는 checkbox/numeric만 처리.
|
||||
> none 항목에 판정 UI를 표시하면 종합판정에 영향을 주게 됨.
|
||||
|
||||
**처리 방안**:
|
||||
1. **측정값 셀**: 빈 셀 또는 '-' 표시 (입력 불가)
|
||||
2. **판정 셀**: 비활성화 (hideJudgment)
|
||||
3. **종합판정 계산에서 제외**: `measurement_type='none'` 항목은 적합/부적합 집계에서 제외
|
||||
|
||||
**React 구현** (`FqcDocumentContent.tsx`의 `InspectionRow`):
|
||||
```typescript
|
||||
// measurement_type에 따른 렌더링 분기
|
||||
if (item.measurement_type === 'none') {
|
||||
// 측정값: 빈 셀
|
||||
// 판정: 빈 셀 (토글 버튼 미표시)
|
||||
return <><td>-</td><td>-</td></>;
|
||||
}
|
||||
|
||||
// 종합판정 계산 (라인 117-128 수정)
|
||||
const judgmentItems = records.filter(r =>
|
||||
r.field_key === 'judgment' &&
|
||||
// none 타입 항목 제외
|
||||
sectionItems.find(item => item.sort_order === r.row_index)?.measurement_type !== 'none'
|
||||
);
|
||||
```
|
||||
|
||||
#### 5.1.9 Template ID 안정성 대책
|
||||
|
||||
> **문제**: 시더가 `cleanupExisting()`으로 삭제 후 재생성하면 auto_increment로 ID가 변경됨.
|
||||
> `FQC_TEMPLATE_ID = 65` 하드코딩이 깨질 수 있음.
|
||||
|
||||
**대책 (2단계)**:
|
||||
|
||||
**1단계 (즉시)** — 시더 개선:
|
||||
- `cleanupExisting()` 대신 `updateOrCreate()` 패턴 사용
|
||||
- template ID를 명시적으로 지정하여 재실행 시에도 동일 ID 유지
|
||||
```php
|
||||
$template = DocumentTemplate::updateOrCreate(
|
||||
['id' => 65, 'tenant_id' => $this->tenantId],
|
||||
['name' => '제품검사 성적서', ...]
|
||||
);
|
||||
// 하위 데이터도 sync 방식으로 처리
|
||||
```
|
||||
|
||||
**2단계 (권장, 후순위)** — category 기반 조회:
|
||||
- `FQC_TEMPLATE_ID = 65` 대신 API에서 category='품질/제품검사' + name='제품검사 성적서'로 조회
|
||||
- 환경에 따라 ID가 달라도 동작하도록 유연성 확보
|
||||
- **이번 작업에서는 1단계만 적용**, 2단계는 추후 리팩토링 시 적용
|
||||
|
||||
---
|
||||
|
||||
### 5.2 제품검사 요청서 양식 설계 (Phase 2.1)
|
||||
|
||||
#### 5.2.1 양식 구조
|
||||
|
||||
```
|
||||
DocumentTemplate (제품검사 요청서)
|
||||
├── name: '제품검사 요청서'
|
||||
├── category: '품질/제품검사'
|
||||
├── title: '제 품 검 사 요 청 서'
|
||||
│
|
||||
├── approval_lines: [
|
||||
│ { name: '작성', dept: '품질', role: '담당자', sort_order: 1 },
|
||||
│ { name: '승인', dept: '경영', role: '대표', sort_order: 2 },
|
||||
│ ]
|
||||
│
|
||||
├── basic_fields: [
|
||||
│ { label: '수주처', field_key: 'client', field_type: 'text' },
|
||||
│ { label: '업체명', field_key: 'company_name', field_type: 'text' },
|
||||
│ { label: '담당자', field_key: 'manager', field_type: 'text' },
|
||||
│ { label: '수주번호', field_key: 'order_number', field_type: 'text' },
|
||||
│ { label: '담당자 연락처', field_key: 'manager_contact', field_type: 'text' },
|
||||
│ { label: '현장명', field_key: 'site_name', field_type: 'text' },
|
||||
│ { label: '납품일', field_key: 'delivery_date', field_type: 'date' },
|
||||
│ { label: '현장 주소', field_key: 'site_address', field_type: 'text' },
|
||||
│ { label: '총 개소', field_key: 'total_locations', field_type: 'number' },
|
||||
│ { label: '접수일', field_key: 'receipt_date', field_type: 'date' },
|
||||
│ { label: '검사방문요청일', field_key: 'inspection_request_date', field_type: 'date' },
|
||||
│ ]
|
||||
│
|
||||
├── sections: [
|
||||
│ { title: '건축공사장 정보', items: [
|
||||
│ { item: '현장명', measurement_type: 'text_input' },
|
||||
│ { item: '대지위치', measurement_type: 'text_input' },
|
||||
│ { item: '지번', measurement_type: 'text_input' },
|
||||
│ ]},
|
||||
│ { title: '자재유통업자 정보', items: [
|
||||
│ { item: '회사명', measurement_type: 'text_input' },
|
||||
│ { item: '주소', measurement_type: 'text_input' },
|
||||
│ { item: '대표자', measurement_type: 'text_input' },
|
||||
│ { item: '전화번호', measurement_type: 'text_input' },
|
||||
│ ]},
|
||||
│ { title: '공사시공자 정보', items: [...] }, // 회사명, 주소, 성명, 전화
|
||||
│ { title: '공사감리자 정보', items: [...] }, // 사무소명, 주소, 성명, 전화
|
||||
│ { title: '검사대상 사전 고지 정보', items: [] }, // 동적 테이블 (locations)
|
||||
│ ]
|
||||
│
|
||||
└── columns: [ // 사전 고지 정보 테이블용
|
||||
{ label: 'No.', column_type: 'text', width: '40px' },
|
||||
{ label: '층수', column_type: 'text', width: '60px' },
|
||||
{ label: '부호', column_type: 'text', width: '60px' },
|
||||
{ label: '발주 가로', column_type: 'text', width: '70px', group_name: '오픈사이즈(발주규격)' },
|
||||
{ label: '발주 세로', column_type: 'text', width: '70px', group_name: '오픈사이즈(발주규격)' },
|
||||
{ label: '시공 가로', column_type: 'text', width: '70px', group_name: '오픈사이즈(시공후규격)' },
|
||||
{ label: '시공 세로', column_type: 'text', width: '70px', group_name: '오픈사이즈(시공후규격)' },
|
||||
{ label: '변경사유', column_type: 'text' },
|
||||
]
|
||||
```
|
||||
|
||||
#### 5.2.2 데이터 매핑 (quality_document → 요청서 필드)
|
||||
|
||||
| 요청서 필드 | 데이터 소스 | 경로 |
|
||||
|------------|-----------|------|
|
||||
| 수주처 | quality_document → order → client | `qualityDocument.orders[0].order.client.name` |
|
||||
| 업체명 | tenant | `tenant.name` |
|
||||
| 담당자 | quality_document.created_by | user name |
|
||||
| 수주번호 | quality_document → order | `qualityDocument.orders[0].order.order_number` |
|
||||
| 현장명 | quality_document → order | `qualityDocument.orders[0].order.site_name` |
|
||||
| 납품일 | quality_document → order | `qualityDocument.orders[0].order.delivery_date` |
|
||||
| 총 개소 | quality_document_locations count | `qualityDocument.orders.flatMap(o => o.locations).length` |
|
||||
| 사전고지 테이블 | quality_document_locations | location별 floor/symbol/width/height/post_width/post_height/change_reason |
|
||||
|
||||
#### 5.2.3 특이사항
|
||||
- 요청서는 검사 입력이 아닌 **정보 표시 문서** (readonly)
|
||||
- 개소(location) 데이터는 `quality_document_locations` 테이블에서 가져옴
|
||||
- "검사 요청 시 필독" 고정 텍스트는 section description으로 처리
|
||||
- quality_document 생성 시 요청서 document 자동 생성 (bulkCreate 패턴 참조)
|
||||
|
||||
---
|
||||
|
||||
## 6. 확정 사항
|
||||
|
||||
| # | 항목 | 결정 내용 | 영향 범위 | 상태 |
|
||||
|---|------|----------|----------|------|
|
||||
| 1 | 성적서 양식 보완 방안 | **방안 C 확정** — 하드코딩 데이터를 template items로 완전 이관. SectionItem에 method/frequency/measurement_type 이미 존재 | mng, react | ✅ 확정 |
|
||||
| 2 | 요청서 template 생성 방법 | **시더 확정** — ProductInspectionRequestTemplateSeeder 작성 (mng/database/seeders/). 재현성 + 버전 관리 | mng | ✅ 확정 |
|
||||
| 3 | 요청서 문서 생성 시점 | **자동 생성 확정** — quality_document 생성 시 자동. 기존 성적서 bulk create 패턴과 동일 | api | ✅ 확정 |
|
||||
| 4 | 요청서 rendered_html 캡처 방식 | **Lazy Snapshot 확정** — 요청서는 readonly 문서이므로 최초 조회 시 rendered_html 없으면 자동 캡처/저장. `captureRenderedHtml` 유틸리티 또는 `contentWrapperRef.innerHTML` 활용 | react | ✅ 확정 |
|
||||
|
||||
---
|
||||
|
||||
## 6.1 에이전트 조사 결과 (2026-03-06)
|
||||
|
||||
### Template ID 65 현황
|
||||
- Seeder: MNG에만 존재 (`mng/database/seeders/ProductInspectionTemplateSeeder.php`, tenant_id=287 하드코딩)
|
||||
- 정규 관리 안됨 (migration/seeder로 재현 불가)
|
||||
- React: `FQC_TEMPLATE_ID = 65` 하드코딩 (`fqcActions.ts:348`)
|
||||
|
||||
### rendered_html 스냅샷 아키텍처 (~95% 완성)
|
||||
| 계층 | 상태 | 비고 |
|
||||
|------|------|------|
|
||||
| DB (documents.rendered_html) | ✅ | LONGTEXT, migration 완료 |
|
||||
| API (DocumentService create/update) | ✅ | rendered_html 조건부 저장 |
|
||||
| React (InspectionReportModal) | ✅ | contentWrapperRef.innerHTML 캡처 |
|
||||
| MNG (show/print.blade.php) | ✅ | rendered_html 우선 출력 + fallback |
|
||||
| FormRequest 검증 | ✅ | StoreRequest/UpdateRequest/UpsertRequest 모두 nullable string 검증 완료 |
|
||||
|
||||
### mng template 구조 핵심
|
||||
- `DocumentTemplateSectionItem` 필드: category, item, standard, tolerance, standard_criteria, method, measurement_type, frequency_n, frequency_c, frequency, regulation, field_values(JSON)
|
||||
- **8컬럼 구조를 추가 스키마 변경 없이 지원 가능** — 방안 C 근거
|
||||
- 저장 방식: Legacy EAV (approval_lines, basic_fields, sections+items, columns, section_fields, links)
|
||||
- 저장 API: `POST /api/admin/document-templates` → `saveRelations()` 트랜잭션
|
||||
|
||||
### QualityDocumentLocation 모델
|
||||
- 테이블: `quality_document_locations`
|
||||
- 필드: `quality_document_id`, `quality_document_order_id`, `order_item_id`, `post_width`, `post_height`, `change_reason`, `inspection_data`(JSON), `document_id`, `inspection_status`
|
||||
- 관계: qualityDocument, qualityDocumentOrder, orderItem, document
|
||||
- 상태: pending / completed
|
||||
|
||||
---
|
||||
|
||||
## 7. 변경 이력
|
||||
|
||||
| 날짜 | 항목 | 변경 내용 | 파일 | 승인 |
|
||||
|------|------|----------|------|------|
|
||||
| 2026-03-06 | - | 문서 초안 작성 | - | - |
|
||||
| 2026-03-06 | 컨펌 3건 | 방안 C 확정, 시더 확정, 자동 생성 확정 | - | ✅ 사용자 승인 |
|
||||
| 2026-03-06 | 에이전트 조사 | template ID 65 현황, rendered_html 현황, mng 구조 분석 반영 | - | - |
|
||||
| 2026-03-06 | 자기완결성 보완 | 시더 데이터, rowSpan 로직, rendered_html 캡처 패턴, API 구조, 타입 정의 추가 | - | - |
|
||||
| 2026-03-06 | 방법론 수정 6건 | ①컬럼 아키텍처 재설계(template 2개+시각 8컬럼) ②프론트 타입 갭 보완 ③rowSpan 복합키 알고리즘 ④Template ID 안정성 대책 ⑤measurement_type=none 처리 ⑥시더 위치 명확화(mng) | - | - |
|
||||
| 2026-03-06 | 스냅샷 아키텍처 정합성 검토 반영 | ①FormRequest 검증 ✅ 확인 ②Phase 2.4 Lazy Snapshot 전략 확정 ③참고 파일 추가(capture-rendered-html.tsx, UpsertRequest) ④캡처 원칙(Active/Lazy) 명시 ⑤오프스크린 렌더링 참조 추가 | 5.1.4, 6.1, 9 | - |
|
||||
|
||||
---
|
||||
|
||||
## 8. 참고 문서
|
||||
|
||||
- **문서관리 시스템 마스터**: `docs/dev/dev_plans/document-system-master.md`
|
||||
- **스냅샷 아키텍처**: `docs/dev/dev_plans/document-snapshot-architecture-plan.md`
|
||||
- **FQC Phase 5.2 아카이브**: `docs/dev/dev_plans/archive/document-system-product-inspection.md`
|
||||
- **문서관리 기능 스펙**: `docs/features/documents/README.md`
|
||||
- **DB 스키마**: `docs/system/database/documents.md`
|
||||
- **품질 체크리스트**: `docs/dev/standards/quality-checklist.md`
|
||||
|
||||
---
|
||||
|
||||
## 9. 참고 파일 경로
|
||||
|
||||
### React (수정 대상)
|
||||
| 파일 | 역할 | 핵심 라인 |
|
||||
|------|------|----------|
|
||||
| `react/src/components/quality/InspectionManagement/documents/FqcDocumentContent.tsx` | FQC 양식 기반 동적 렌더링 (보완 대상) | 311-375: 4컬럼 테이블, 399-454: InspectionRow |
|
||||
| `react/src/components/quality/InspectionManagement/documents/InspectionReportDocument.tsx` | 하드코딩 성적서 (참조/fallback) | 42-57: buildCoverageMap(), 198-376: 8컬럼 테이블 |
|
||||
| `react/src/components/quality/InspectionManagement/documents/InspectionReportModal.tsx` | 성적서 모달 (듀얼 모드) | 61: useFqcMode, 106-116: FQC 문서 로드, 233-262: fallback |
|
||||
| `react/src/components/quality/InspectionManagement/documents/InspectionRequestDocument.tsx` | 하드코딩 요청서 (참조/fallback) | 47-62: 결재, 66-103: 기본정보, 211-255: 사전고지 테이블 |
|
||||
| `react/src/components/quality/InspectionManagement/documents/InspectionRequestModal.tsx` | 요청서 모달 | 26-37: DocumentViewer 래퍼 |
|
||||
| `react/src/components/quality/InspectionManagement/fqcActions.ts` | FQC 서버 액션 | 348: FQC_TEMPLATE_ID=65, 429-461: saveFqcDocument() |
|
||||
| `react/src/components/quality/InspectionManagement/InspectionDetail.tsx` | 상세 페이지 (모달 연동) | 1230-1275: 모달 통합 |
|
||||
| `react/src/components/quality/InspectionManagement/mockData.ts` | 데이터 변환 함수 | 398-423: buildRequestDocumentData(), 426-458: mockReportInspectionItems |
|
||||
| `react/src/components/quality/InspectionManagement/types.ts` | 타입 정의 | 224-247: InspectionRequestDocument, 250-266: ReportInspectionItem, 269-285: InspectionReportDocument |
|
||||
| `react/src/components/quality/InspectionManagement/actions.ts` | 서버 액션 (CRUD) | |
|
||||
|
||||
### React (참조 - 기존 동적 렌더링 패턴)
|
||||
| 파일 | 역할 | 핵심 라인 |
|
||||
|------|------|----------|
|
||||
| `react/src/components/production/WorkOrders/documents/TemplateInspectionContent.tsx` | 중간검사 동적 렌더링 (참조 모델) | 917-999: 컬럼타입별 렌더링, 1105-1183: renderComplexCells() |
|
||||
| `react/src/components/production/WorkOrders/documents/InspectionReportModal.tsx` | 중간검사 모달 (rendered_html 캡처 패턴) | 167: contentWrapperRef, 341-366: handleSave+innerHTML |
|
||||
| `react/src/lib/utils/capture-rendered-html.tsx` | 오프스크린 렌더링 유틸리티 (flushSync+createRoot) | Phase 2.4 Lazy Snapshot에서 활용 가능 |
|
||||
| `react/src/components/material/ReceivingManagement/ImportInspectionInputModal.tsx` | 수입검사 오프스크린 캡처 참조 | captureRenderedHtml 사용 예시 |
|
||||
|
||||
### API (수정 대상)
|
||||
| 파일 | 역할 | 핵심 라인 |
|
||||
|------|------|----------|
|
||||
| `api/app/Services/DocumentService.php` | 문서 저장/조회 | 125: create(rendered_html), 180: update(rendered_html) |
|
||||
| `api/app/Services/QualityDocumentService.php` | 품질관리 문서 서비스 | 176-209: store(), 749-799: requestDocument(), 804-868: resultDocument() |
|
||||
| `api/app/Models/Documents/Document.php` | 문서 모델 | 76: $fillable에 rendered_html |
|
||||
| `api/app/Models/Qualitys/QualityDocumentLocation.php` | 개소 모델 | fillable: post_width, post_height, change_reason, inspection_data |
|
||||
| `api/app/Http/Requests/Document/UpsertRequest.php` | documents/upsert 검증 | rendered_html nullable string 검증 완료 |
|
||||
|
||||
### MNG (확인/수정 대상)
|
||||
| 파일 | 역할 |
|
||||
|------|------|
|
||||
| `mng/app/Http/Controllers/DocumentTemplateController.php` | 양식 CRUD |
|
||||
| `mng/resources/views/document-templates/edit.blade.php` | 양식 편집 |
|
||||
| `mng/resources/views/documents/show.blade.php` | 문서 보기 (rendered_html 우선 출력, 29-32행) |
|
||||
| `mng/resources/views/documents/print.blade.php` | 문서 인쇄 (28-32행) |
|
||||
| `mng/database/seeders/ProductInspectionTemplateSeeder.php` | template ID 65 시더 (49-182: 항목 데이터) |
|
||||
|
||||
---
|
||||
|
||||
## 10. 미커밋 변경사항 (이전 세션, 2026-03-06)
|
||||
|
||||
**api/** — order_ids 영속성, location count 수정, location data 저장:
|
||||
- `app/Http/Requests/Quality/QualityDocumentStoreRequest.php` — 수정
|
||||
- `app/Http/Requests/Quality/QualityDocumentUpdateRequest.php` — 수정
|
||||
- `app/Services/QualityDocumentService.php` — 수정
|
||||
- `app/Models/Qualitys/QualityDocumentLocation.php` — 수정
|
||||
- `database/migrations/2026_03_06_094425_add_inspection_data_to_quality_document_locations.php` — 신규
|
||||
|
||||
**react/** — `src/components/quality/InspectionManagement/actions.ts` — 수정
|
||||
|
||||
> 이 작업 시작 전에 먼저 커밋 필요
|
||||
|
||||
---
|
||||
|
||||
## 11. 세션 및 메모리 관리 정책 (Serena Optimized)
|
||||
|
||||
### 11.1 세션 시작 시 (Load Strategy)
|
||||
```javascript
|
||||
read_memory("fqc-doc-state") // 1. 상태 파악
|
||||
read_memory("fqc-doc-snapshot") // 2. 사고 흐름 복구
|
||||
read_memory("fqc-doc-active-symbols") // 3. 작업 대상 파악
|
||||
```
|
||||
|
||||
### 11.2 작업 중 관리 (Context Defense)
|
||||
| 컨텍스트 잔량 | Action | 내용 |
|
||||
|--------------|--------|------|
|
||||
| **30% 이하** | Snapshot | `write_memory("fqc-doc-snapshot", "코드변경+논의요약")` |
|
||||
| **20% 이하** | Context Purge | `write_memory("fqc-doc-active-symbols", "주요 수정 파일/함수")` |
|
||||
| **10% 이하** | Stop & Save | 최종 상태 저장 후 세션 교체 권고 |
|
||||
|
||||
### 11.3 Serena 메모리 구조
|
||||
- `fqc-doc-state`: { phase, progress, next_step, last_decision } (JSON)
|
||||
- `fqc-doc-snapshot`: 현재까지의 논의 및 코드 변경점 요약 (Text)
|
||||
- `fqc-doc-rules`: 해당 작업에서 결정된 불변의 규칙들 (Text)
|
||||
- `fqc-doc-active-symbols`: 현재 수정 중인 파일/심볼 리스트 (List)
|
||||
|
||||
---
|
||||
|
||||
## 12. 검증 결과
|
||||
|
||||
> 작업 완료 후 이 섹션에 검증 결과 추가
|
||||
|
||||
### 12.1 성공 기준
|
||||
|
||||
| # | 기준 | 달성 | 비고 |
|
||||
|---|------|:----:|------|
|
||||
| 1 | mng에서 성적서 양식 편집/미리보기 정상 | ✅ | 시더 실행 확인 |
|
||||
| 2 | mng에서 요청서 양식 편집/미리보기 정상 | ✅ | 시더 실행 확인 |
|
||||
| 3 | React에서 성적서 양식 기반 동적 렌더링 (8컬럼+rowSpan) | ✅ | 브라우저 테스트 통과 |
|
||||
| 4 | React에서 요청서 양식 기반 동적 렌더링 | ✅ | requestDocumentId 없으면 legacy fallback |
|
||||
| 5 | 저장 시 rendered_html 스냅샷 저장됨 | ✅ | Active Capture 코드 구현 완료 (검사 저장 시 동작) |
|
||||
| 6 | mng 문서 보기에서 스냅샷 정상 출력 | ✅ | show.blade.php 코드 검증 |
|
||||
| 7 | 기존 하드코딩 fallback 정상 동작 | ✅ | 요청서 legacy fallback 브라우저 테스트 통과 |
|
||||
| 8 | 기존 FQC 데이터 호환성 유지 | ✅ | 기존 EAV basic_fields 정상 표시 확인 |
|
||||
|
||||
### 12.2 테스트 시나리오
|
||||
|
||||
| 시나리오 | 예상 결과 | 실제 결과 | 상태 |
|
||||
|---------|----------|----------|------|
|
||||
| `/quality/inspections/1?mode=view` → 검사제품요청서 클릭 | 양식 기반 요청서 표시 | legacy fallback 정상 (EAV 문서 미생성 상태) | ✅ |
|
||||
| `/quality/inspections/1?mode=view` → 제품검사하기 클릭 | 양식 기반 성적서 표시 (편집 모드, 8컬럼) | FQC 8컬럼 + rowSpan 정상 | ✅ |
|
||||
| 성적서 검사 완료 후 저장 | document_data + rendered_html 저장 | 코드 구현 완료 (실 저장은 검사 진행 시) | ✅ |
|
||||
| `mng.sam.kr/documents/{id}` | rendered_html 스냅샷 출력 | show.blade.php 코드 검증 | ✅ |
|
||||
| template ID 65 없는 환경 | 하드코딩 fallback 동작 | templateLoadFailed 시 legacy 렌더링 | ✅ |
|
||||
| 치수 검사항목에 측정값 입력 | numeric input → document_data에 저장 | UI 표시 확인 (저장은 검사 진행 시) | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 13. 자기완결성 점검 결과
|
||||
|
||||
### 13.1 체크리스트 검증
|
||||
|
||||
| # | 검증 항목 | 상태 | 비고 |
|
||||
|---|----------|:----:|------|
|
||||
| 1 | 작업 목적이 명확한가? | ✅ | 섹션 1.1 |
|
||||
| 2 | 성공 기준이 정의되어 있는가? | ✅ | 섹션 12.1 |
|
||||
| 3 | 작업 범위가 구체적인가? | ✅ | 섹션 3 |
|
||||
| 4 | 의존성이 명시되어 있는가? | ✅ | Phase 순서, 미커밋 사항(섹션 10) |
|
||||
| 5 | 참고 파일 경로가 정확한가? | ✅ | 섹션 9 (라인 번호 포함) |
|
||||
| 6 | 단계별 절차가 실행 가능한가? | ✅ | 섹션 4+5 (시더 데이터, 코드 패턴 포함) |
|
||||
| 7 | 검증 방법이 명시되어 있는가? | ✅ | 섹션 12.2 |
|
||||
| 8 | 모호한 표현이 없는가? | ✅ | 방안 C 확정, 시더 확정, 자동 생성 확정 |
|
||||
|
||||
### 13.2 새 세션 시뮬레이션 테스트
|
||||
|
||||
| 질문 | 답변 가능 | 참조 섹션 |
|
||||
|------|:--------:|----------|
|
||||
| Q1. 이 작업의 목적은 무엇인가? | ✅ | 1.1 배경 |
|
||||
| Q2. 어디서부터 시작해야 하는가? | ✅ | 4.1 단계별 절차 + 10. 미커밋 사항(먼저 커밋) |
|
||||
| Q3. 어떤 파일을 수정해야 하는가? | ✅ | 9. 참고 파일 경로 (라인 번호 포함) |
|
||||
| Q4. 시더에 어떤 데이터를 넣어야 하는가? | ✅ | 5.1.2 검사항목 데이터 (PHP 배열) |
|
||||
| Q5. React rowSpan은 어떻게 구현하는가? | ✅ | 5.1.3 자동 병합 로직 (TypeScript 코드) |
|
||||
| Q6. rendered_html은 어떻게 캡처하는가? | ✅ | 5.1.4 캡처 패턴 (참조 코드) |
|
||||
| Q7. 요청서 필드는 어디서 가져오는가? | ✅ | 5.2.2 데이터 매핑 테이블 |
|
||||
| Q8. 작업 완료 확인 방법은? | ✅ | 12. 검증 결과 |
|
||||
| Q9. 막혔을 때 참고 문서는? | ✅ | 8. 참고 문서 |
|
||||
|
||||
---
|
||||
|
||||
*이 문서는 /plan 스킬로 생성되었습니다.*
|
||||
245
dev/dev_plans/mng-bending-document-matching-plan.md
Normal file
245
dev/dev_plans/mng-bending-document-matching-plan.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# MNG 절곡 문서 React 매칭 계획
|
||||
|
||||
> **작성일**: 2026-03-05
|
||||
> **목적**: MNG 절곡 문서(중간검사성적서 #36, 작업일지 #39)의 상세보기를 React와 동일하게 수정
|
||||
> **상태**: 🔄 진행중
|
||||
> **영향 범위**: mng만 (React/API 변경 없음)
|
||||
|
||||
---
|
||||
|
||||
## 현재 진행 상태
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| **마지막 완료 작업** | Task 2.2: 절곡 작업일지 전용 렌더링 블록 추가 |
|
||||
| **다음 작업** | 브라우저 검증 (사용자 확인 필요) |
|
||||
| **진행률** | 5/6 (83%) |
|
||||
| **마지막 업데이트** | 2026-03-05 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 배경
|
||||
|
||||
MNG(`mng.sam.kr/documents/36`, `/documents/39`)에서 절곡 문서의 상세보기가 React(`dev.sam.kr/production/worker-screen` > 절곡공정)와 다르다.
|
||||
|
||||
- **MNG**: DB 템플릿(`document_templates`, `document_template_columns`)을 동적으로 읽어 범용 렌더링
|
||||
- **React**: 전용 하드코딩 컴포넌트(`BendingInspectionContent.tsx`, `BendingWorkLogContent.tsx`)로 렌더링
|
||||
|
||||
React가 더 상세하고 정확하므로, MNG를 React에 맞춰 수정한다.
|
||||
|
||||
### 1.2 핵심 원칙
|
||||
|
||||
- MNG only 수정 (React/API 변경 없음)
|
||||
- DB 템플릿 컬럼 수정 + show.blade.php 전용 렌더링 블록 추가
|
||||
- 기존 범용 렌더링 로직은 유지 (다른 문서에 영향 없도록)
|
||||
|
||||
### 1.3 변경 승인 정책
|
||||
|
||||
| 분류 | 예시 | 승인 |
|
||||
|------|------|------|
|
||||
| 즉시 가능 | blade 템플릿 수정, DB 시더/컬럼 업데이트 | 불필요 |
|
||||
| 컨펌 필요 | DocumentController 로직 변경, 새 쿼리 추가 | **필수** |
|
||||
| 금지 | 테이블 구조 변경, API 엔드포인트 변경 | 별도 협의 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 차이점 분석
|
||||
|
||||
### 2.1 중간검사성적서 (#36) - 중간검사 DATA 테이블
|
||||
|
||||
**React 컬럼 구조** (`BendingInspectionContent.tsx`):
|
||||
|
||||
| 컬럼 | 타입 | 비고 |
|
||||
|------|------|------|
|
||||
| 분류 | text | KWE01 등 제품코드 |
|
||||
| 제품명 | text | 가이드레일, 케이스 등 |
|
||||
| 타입 | text | 벽면형, 측면형 등 |
|
||||
| 겉모양/절곡상태 | check | 양호/불량 체크 |
|
||||
| 길이 - 도면치수 | text | 설계값 |
|
||||
| 길이 - 측정값 | input | 입력값 |
|
||||
| 너비 - 도면치수 | text | 설계값 |
|
||||
| 너비 - 측정값 | input | 입력값 |
|
||||
| 간격 - 포인트 | text | 1~5 등 |
|
||||
| 간격 - 도면치수 | text | 설계값 |
|
||||
| 간격 - 측정값 | input | 입력값 |
|
||||
| 판정 | auto | 적/부 자동판정 |
|
||||
|
||||
**핵심 차이**:
|
||||
- React는 **7개 제품** x **다중 gapPoints**(간격 측정점)로 rowSpan 사용
|
||||
- MNG 현재 DB에는 "간격" 컬럼, "판정" 컬럼 없음
|
||||
- MNG "분류/제품명"이 하나로 합쳐져 있을 수 있음
|
||||
|
||||
**React 제품 목록** (INITIAL_PRODUCTS):
|
||||
1. 가이드레일 벽면형 (gapPoints: 5개)
|
||||
2. 가이드레일 측면형 (gapPoints: 5개)
|
||||
3. 케이스 (gapPoints: 2개)
|
||||
4. 하단마감재 (gapPoints: 2개)
|
||||
5. 하단L-BAR (gapPoints: 1개)
|
||||
6. 연기차단재 W50 (gapPoints: 1개)
|
||||
7. 연기차단재 W80 (gapPoints: 2개)
|
||||
|
||||
### 2.2 작업일지 (#39) - 절곡 전용 구조
|
||||
|
||||
**React 구조** (`BendingWorkLogContent.tsx`):
|
||||
|
||||
```
|
||||
헤더: "작업일지 (절곡)" + 문서번호/작성일자 + 결재란
|
||||
신청업체/신청내용 테이블 (수주일, 현장명, 수주처, 작업일자, 담당자, LOT NO, 연락처, 생산담당자, 출고예정일)
|
||||
제품 정보 테이블 (제품명, 재질, 마감, 유형)
|
||||
4개 카테고리 섹션:
|
||||
1. GuideRailSection (가이드레일 벽면형/측면형)
|
||||
2. BottomBarSection (하단마감재)
|
||||
3. ShutterBoxSection (셔터박스)
|
||||
4. SmokeBarrierSection (연기차단재 W50/W80)
|
||||
생산량 합계 [kg] (SUS/EGI)
|
||||
비고
|
||||
```
|
||||
|
||||
**핵심 차이**:
|
||||
- MNG 현재: 범용 work_order_items 플랫 테이블 (line 137, 섹션 없는 문서)
|
||||
- React: 절곡 전용 4개 카테고리 섹션 + 제품정보 + 생산량합계
|
||||
- React는 `bendingInfo` 데이터(work_order.options.bending_info)를 사용
|
||||
- MNG Controller에서 bending_info를 아직 view에 전달하지 않음
|
||||
|
||||
---
|
||||
|
||||
## 3. 작업 범위
|
||||
|
||||
### Phase 1: 중간검사성적서 (#36)
|
||||
|
||||
| # | 작업 항목 | 상태 | 비고 |
|
||||
|---|----------|:----:|------|
|
||||
| 1.1 | 현재 DB 템플릿 컬럼 조회 (tinker) | ✅ | template_id=60, 7컬럼 (간격/판정 포함) |
|
||||
| 1.2 | show.blade.php 절곡 검사 전용 렌더링 블록 추가 | ✅ | partials/bending-inspection-data.blade.php |
|
||||
| 1.3 | 브라우저 검증 | ⏳ | mng.sam.kr/documents/36 |
|
||||
|
||||
### Phase 2: 작업일지 (#39)
|
||||
|
||||
| # | 작업 항목 | 상태 | 비고 |
|
||||
|---|----------|:----:|------|
|
||||
| 2.1 | DocumentController에 bending_info 전달 추가 | ✅ | work_order.options에서 추출 |
|
||||
| 2.2 | show.blade.php 절곡 작업일지 전용 렌더링 블록 추가 | ✅ | partials/bending-worklog.blade.php |
|
||||
| 2.3 | 브라우저 검증 | ⏳ | mng.sam.kr/documents/39 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 상세 작업 내용
|
||||
|
||||
### 4.1 Task 1.2: show.blade.php 절곡 검사 전용 블록
|
||||
|
||||
**위치**: `show.blade.php` 섹션 렌더링 영역 (line ~400)
|
||||
**조건**: template category가 '절곡' 또는 template name에 '절곡' 포함
|
||||
|
||||
**렌더링 구조**:
|
||||
```html
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowSpan=2>분류</th>
|
||||
<th rowSpan=2>제품명</th>
|
||||
<th rowSpan=2>타입</th>
|
||||
<th rowSpan=2>겉모양/절곡상태</th>
|
||||
<th colSpan=2>길이</th>
|
||||
<th colSpan=2>너비</th>
|
||||
<th colSpan=3>간격</th>
|
||||
<th rowSpan=2>판정</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>도면치수</th><th>측정값</th>
|
||||
<th>도면치수</th><th>측정값</th>
|
||||
<th>포인트</th><th>도면치수</th><th>측정값</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 각 제품별 gapPoints 수만큼 행 (rowSpan 사용) -->
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
**데이터 소스**: `document_data` EAV + INITIAL_PRODUCTS 정적 구조
|
||||
- section_id, column_id, row_index, field_key로 조회
|
||||
- 간격 데이터: field_key `gap_point_{n}`, `gap_design_{n}`, `gap_measured_{n}`
|
||||
|
||||
### 4.2 Task 2.1: Controller bending_info 전달
|
||||
|
||||
**파일**: `mng/app/Http/Controllers/DocumentController.php` show()
|
||||
**추가 로직**:
|
||||
```php
|
||||
// 절곡 작업일지용: bending_info 추출
|
||||
$bendingInfo = null;
|
||||
if ($workOrder) {
|
||||
$woOptions = json_decode($workOrder->options, true) ?? [];
|
||||
$bendingInfo = $woOptions['bending_info'] ?? null;
|
||||
}
|
||||
```
|
||||
|
||||
**view 전달**: `'bendingInfo' => $bendingInfo`
|
||||
|
||||
### 4.3 Task 2.2: show.blade.php 절곡 작업일지 전용 블록
|
||||
|
||||
**위치**: `show.blade.php` line ~137 (작업일지 전용 영역)
|
||||
**조건**: 절곡 공정인 경우 (template name에 '절곡' 포함 또는 공정 확인)
|
||||
|
||||
**렌더링 구조**:
|
||||
1. 신청업체/신청내용 테이블
|
||||
2. 제품 정보 테이블 (제품명, 재질, 마감, 유형)
|
||||
3. 4개 카테고리 섹션 (가이드레일, 하단마감재, 셔터박스, 연기차단재)
|
||||
4. 생산량 합계 [kg] (SUS/EGI)
|
||||
5. 비고
|
||||
|
||||
**PHP 유틸 함수 필요**:
|
||||
- `getMaterialMapping($productCode, $finishMaterial)` - React utils.ts 포팅
|
||||
- `calculateProductionSummary($bendingInfo, $mapping)` - React utils.ts 포팅
|
||||
|
||||
---
|
||||
|
||||
## 5. 참고 파일
|
||||
|
||||
### React (타겟 - 읽기 전용)
|
||||
- `react/src/components/production/WorkOrders/documents/BendingInspectionContent.tsx` (547행)
|
||||
- `react/src/components/production/WorkOrders/documents/BendingWorkLogContent.tsx` (207행)
|
||||
- `react/src/components/production/WorkOrders/documents/bending/types.ts`
|
||||
- `react/src/components/production/WorkOrders/documents/bending/utils.ts`
|
||||
- `react/src/components/production/WorkOrders/documents/bending/GuideRailSection.tsx`
|
||||
- `react/src/components/production/WorkOrders/documents/bending/BottomBarSection.tsx`
|
||||
- `react/src/components/production/WorkOrders/documents/bending/ShutterBoxSection.tsx`
|
||||
- `react/src/components/production/WorkOrders/documents/bending/SmokeBarrierSection.tsx`
|
||||
- `react/src/components/production/WorkOrders/documents/bending/ProductionSummarySection.tsx`
|
||||
|
||||
### MNG (수정 대상)
|
||||
- `mng/resources/views/documents/show.blade.php` (메인 렌더링)
|
||||
- `mng/app/Http/Controllers/DocumentController.php` (show 메서드)
|
||||
|
||||
### DB 참고
|
||||
- `mng/database/seeders/MidInspectionTemplateSeeder.php` (절곡 검사 템플릿)
|
||||
- `mng/database/seeders/WorkLogTemplateSeeder.php` (절곡 작업일지 템플릿)
|
||||
|
||||
### 문서
|
||||
- `docs/features/documents/README.md` - 문서관리 시스템
|
||||
- `docs/system/database/documents.md` - DB 스키마
|
||||
- `docs/dev/dev_plans/document-system-mid-inspection.md` - 중간검사 계획
|
||||
- `docs/dev/dev_plans/document-system-work-log.md` - 작업일지 계획
|
||||
|
||||
---
|
||||
|
||||
## 6. 변경 이력
|
||||
|
||||
| 날짜 | 항목 | 변경 내용 | 파일 | 승인 |
|
||||
|------|------|----------|------|------|
|
||||
| 2026-03-05 | - | 계획 문서 작성 | - | - |
|
||||
|
||||
---
|
||||
|
||||
## 7. 검증 결과
|
||||
|
||||
> 작업 완료 후 이 섹션에 검증 결과 추가
|
||||
|
||||
### 7.1 성공 기준
|
||||
|
||||
| 기준 | 달성 | 비고 |
|
||||
|------|------|------|
|
||||
| mng.sam.kr/documents/36 검사 DATA 테이블이 React와 동일 구조 | ⏳ | rowSpan, 간격, 판정 포함 |
|
||||
| mng.sam.kr/documents/39 작업일지가 React와 동일 구조 | ⏳ | 4개 카테고리, 생산량합계 포함 |
|
||||
| 기존 다른 문서 렌더링에 영향 없음 | ⏳ | 조건 분기로 절곡 전용만 |
|
||||
316
dev/dev_plans/qms-api-integration-plan.md
Normal file
316
dev/dev_plans/qms-api-integration-plan.md
Normal file
@@ -0,0 +1,316 @@
|
||||
# 품질인정심사(QMS) API 연동 계획
|
||||
|
||||
> **작성일**: 2026-03-09
|
||||
> **상태**: 계획 수립
|
||||
> **URL**: `/quality/qms`
|
||||
> **스토리보드**: 슬라이드 19~20
|
||||
> **관련 문서**: `docs/features/quality-management/quality-certification-audit.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 현황 분석
|
||||
|
||||
### 1.1 프론트엔드 현황
|
||||
|
||||
| 항목 | 상태 | 비고 |
|
||||
|------|------|------|
|
||||
| `page.tsx` | ✅ 구현됨 | 14KB, 전체 페이지 레이아웃 |
|
||||
| `types.ts` | ✅ 구현됨 | 95줄, 타입 정의 완료 |
|
||||
| `mockData.ts` | ✅ 구현됨 | 543줄, 완전한 목업 데이터 |
|
||||
| `components/` | ✅ 구현됨 | 12개 컴포넌트 + documents/ 7개 |
|
||||
| `actions.ts` | ❌ 없음 | API 연동 0% |
|
||||
|
||||
프론트엔드는 UI가 완성되어 있으나 **100% 목업 데이터**로 동작 중.
|
||||
|
||||
### 1.2 백엔드 현황
|
||||
|
||||
| 영역 | 기존 API | 신규 필요 |
|
||||
|------|----------|-----------|
|
||||
| **1일차 (기준/매뉴얼 심사)** | ❌ 없음 | 모델, 마이그레이션, 서비스, 컨트롤러 전체 |
|
||||
| **2일차 (로트 추적 심사)** | ⚠️ 부분 존재 | 기존 API 조합 + 서류 연결 API 신규 |
|
||||
|
||||
**기존 활용 가능 API:**
|
||||
- `GET /quality/documents` — 품질관리서 목록 (2일차 1단계)
|
||||
- `GET /quality/documents/{id}` — 품질관리서 상세 + 수주/개소 (2일차 2단계)
|
||||
- `GET /quality/performance-reports` — 실적신고 (분기 필터 활용)
|
||||
- `GET /inspections` — 수입검사/중간검사 성적서
|
||||
- 출하/출고/납품 관련 기존 API
|
||||
|
||||
---
|
||||
|
||||
## 2. 작업 범위
|
||||
|
||||
### Phase 1: 2일차 (로트 추적 심사) API 연동
|
||||
|
||||
> **우선순위 높음** — 기존 API 활용 가능하여 빠르게 연동 가능
|
||||
|
||||
#### 2.1 Frontend — `actions.ts` 생성
|
||||
|
||||
```
|
||||
react/src/app/[locale]/(protected)/quality/qms/actions.ts
|
||||
```
|
||||
|
||||
| 액션 | 호출 API | 설명 |
|
||||
|------|----------|------|
|
||||
| `getQualityReports()` | `GET /quality/documents` | 품질관리서 목록 (분기 필터) |
|
||||
| `getReportRoutes(reportId)` | `GET /quality/documents/{id}` | 수주코드 + 개소 목록 |
|
||||
| `getRouteDocuments(routeId)` | 복합 조회 (아래 참조) | 개소별 관련 서류 8종 |
|
||||
| `confirmUnitInspection(unitId)` | `PATCH /qms/lot-audit/confirm` | 개소 확인 완료 처리 |
|
||||
|
||||
#### 2.2 관련 서류 조회 로직
|
||||
|
||||
2일차 3단계 "관련 서류"는 개소(Location)에 연결된 8종 서류를 조합 조회:
|
||||
|
||||
| 서류 타입 | 데이터 소스 | 조회 방식 |
|
||||
|-----------|-------------|-----------|
|
||||
| 수입검사 성적서 | `inspections` (type=IQC) | 수주의 BOM 원자재 LOT 추적 |
|
||||
| 수주서 | `orders` | 수주코드로 직접 조회 |
|
||||
| 작업일지 | `work_orders` + 작업일지 | 수주 → 작업지시 → 작업일지 |
|
||||
| 중간검사 성적서 | `inspections` (type=PQC) | 작업지시별 중간검사 |
|
||||
| 납품확인서 | `shipments` | 출하 → 납품확인서 |
|
||||
| 출고증 | `shipments` | 출하 → 출고증 |
|
||||
| 제품검사 성적서 | `quality_document_locations` | 개소별 검사 문서 (EAV) |
|
||||
| 품질관리서 | `quality_documents` | 품질관리서 원본 |
|
||||
|
||||
#### 2.3 Backend — 신규 API (최소)
|
||||
|
||||
```
|
||||
GET /api/v1/qms/lot-audit/reports — 분기별 품질관리서 목록 (전용 뷰)
|
||||
GET /api/v1/qms/lot-audit/reports/{id} — 수주코드 + 개소 + 완료 상태
|
||||
GET /api/v1/qms/lot-audit/routes/{id}/documents — 개소별 8종 서류 조합 조회
|
||||
PATCH /api/v1/qms/lot-audit/units/{id}/confirm — 확인 완료 처리
|
||||
```
|
||||
|
||||
> 기존 `quality/documents` API를 래핑하여 QMS 전용 응답 형태로 가공하는 방식 권장.
|
||||
> 8종 서류 조합 로직이 복잡하므로 **전용 서비스 메서드** 필요.
|
||||
|
||||
#### 2.4 DB 변경
|
||||
|
||||
| 변경 | 테이블 | 설명 |
|
||||
|------|--------|------|
|
||||
| 컬럼 추가 | `quality_document_locations` | `options` JSON에 `lot_audit_confirmed`, `lot_audit_confirmed_at` 추가 |
|
||||
|
||||
> 별도 테이블 없이 기존 개소(Location) 테이블의 `options` 활용 (컬럼 추가 정책 준수)
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: 1일차 (기준/매뉴얼 심사) 백엔드 구축
|
||||
|
||||
> **작업량 많음** — 완전 신규 백엔드 구축 필요
|
||||
|
||||
#### 2.1 DB 설계 (신규 테이블)
|
||||
|
||||
```
|
||||
audit_checklists (심사 점검표 마스터)
|
||||
├── id, tenant_id
|
||||
├── year, quarter
|
||||
├── type: 'standard_manual' (1일차)
|
||||
├── status: draft/in_progress/completed
|
||||
├── options: JSON
|
||||
├── created_by, timestamps, soft_delete
|
||||
|
||||
audit_checklist_categories (점검표 카테고리)
|
||||
├── id, tenant_id
|
||||
├── checklist_id (FK → audit_checklists)
|
||||
├── title: '원재료 품질관리 기준'
|
||||
├── sort_order
|
||||
├── options: JSON
|
||||
|
||||
audit_checklist_items (점검표 세부 항목)
|
||||
├── id, tenant_id
|
||||
├── category_id (FK → audit_checklist_categories)
|
||||
├── name: '수입검사 기준 확인'
|
||||
├── description
|
||||
├── is_completed: boolean
|
||||
├── completed_at, completed_by
|
||||
├── sort_order
|
||||
├── options: JSON
|
||||
|
||||
audit_standard_documents (기준 문서)
|
||||
├── id, tenant_id
|
||||
├── checklist_item_id (FK → audit_checklist_items)
|
||||
├── title, version, date
|
||||
├── document_id (FK → documents, EAV)
|
||||
├── options: JSON
|
||||
```
|
||||
|
||||
#### 2.2 Backend 구현
|
||||
|
||||
| 파일 | 역할 |
|
||||
|------|------|
|
||||
| `api/app/Models/Qualitys/AuditChecklist.php` | 심사 점검표 모델 |
|
||||
| `api/app/Models/Qualitys/AuditChecklistCategory.php` | 카테고리 모델 |
|
||||
| `api/app/Models/Qualitys/AuditChecklistItem.php` | 세부 항목 모델 |
|
||||
| `api/app/Models/Qualitys/AuditStandardDocument.php` | 기준 문서 모델 |
|
||||
| `api/app/Services/AuditChecklistService.php` | 서비스 |
|
||||
| `api/app/Http/Controllers/Api/V1/AuditChecklistController.php` | 컨트롤러 |
|
||||
| `api/database/migrations/XXXX_create_audit_checklists_table.php` | 마이그레이션 (4테이블) |
|
||||
|
||||
#### 2.3 API 엔드포인트
|
||||
|
||||
```
|
||||
GET /api/v1/qms/checklists — 점검표 목록 (연도/분기 필터)
|
||||
POST /api/v1/qms/checklists — 점검표 생성
|
||||
GET /api/v1/qms/checklists/{id} — 점검표 상세 (카테고리+항목+문서)
|
||||
PUT /api/v1/qms/checklists/{id} — 점검표 수정
|
||||
PATCH /api/v1/qms/checklists/{id}/complete — 점검표 완료 처리
|
||||
|
||||
PATCH /api/v1/qms/checklist-items/{id}/toggle — 항목 완료/미완료 토글
|
||||
GET /api/v1/qms/checklist-items/{id}/documents — 항목별 기준 문서 조회
|
||||
POST /api/v1/qms/checklist-items/{id}/documents — 기준 문서 연결
|
||||
DELETE /api/v1/qms/checklist-items/{id}/documents/{docId} — 기준 문서 연결 해제
|
||||
```
|
||||
|
||||
#### 2.4 Frontend — actions.ts 확장
|
||||
|
||||
| 액션 | 설명 |
|
||||
|------|------|
|
||||
| `getChecklists(year, quarter)` | 점검표 목록 |
|
||||
| `getChecklistDetail(id)` | 점검표 상세 (카테고리+항목+문서) |
|
||||
| `toggleChecklistItem(itemId)` | 항목 완료/미완료 토글 |
|
||||
| `getCheckItemDocuments(itemId)` | 기준 문서 조회 |
|
||||
| `confirmCheckItem(itemId)` | 기준/매뉴얼 확인 완료 |
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: 프론트엔드 목업 → API 전환
|
||||
|
||||
#### 3.1 page.tsx 수정
|
||||
|
||||
- `mockData.ts` import 제거
|
||||
- `actions.ts` import로 교체
|
||||
- `useEffect`에서 API 호출
|
||||
- 로딩/에러 상태 추가
|
||||
|
||||
#### 3.2 컴포넌트 수정
|
||||
|
||||
| 컴포넌트 | 변경 내용 |
|
||||
|----------|-----------|
|
||||
| `ReportList.tsx` | API 데이터 바인딩 |
|
||||
| `RouteList.tsx` | API 데이터 바인딩 |
|
||||
| `DocumentList.tsx` | 8종 서류 실제 조회 |
|
||||
| `InspectionModal.tsx` | 실제 검사 문서 렌더링 |
|
||||
| `Day1ChecklistPanel.tsx` | API 데이터 바인딩 |
|
||||
| `Day1DocumentSection.tsx` | 기준 문서 API 조회 |
|
||||
| `Day1DocumentViewer.tsx` | 실제 파일 미리보기 |
|
||||
| `AuditProgressBar.tsx` | 실시간 진행률 계산 |
|
||||
| `Filters.tsx` | 연도/분기 필터 API 연동 |
|
||||
|
||||
#### 3.3 mockData.ts 처리
|
||||
|
||||
- Phase 3 완료 후 `mockData.ts` 삭제
|
||||
- 또는 `USE_MOCK` 플래그 패턴 적용 (개발 편의)
|
||||
|
||||
---
|
||||
|
||||
## 3. 데이터 매핑
|
||||
|
||||
### 3.1 InspectionReport ↔ QualityDocument
|
||||
|
||||
| 프론트 (InspectionReport) | 백엔드 (QualityDocument) |
|
||||
|---------------------------|-------------------------|
|
||||
| `id` | `quality_documents.id` |
|
||||
| `code` | `quality_documents.code` (채번) |
|
||||
| `siteName` | `quality_documents.site_name` |
|
||||
| `item` | `quality_documents.options.product_type` 또는 인정특성 |
|
||||
| `routeCount` | `quality_document_orders` COUNT |
|
||||
| `totalRoutes` | `quality_document_locations` COUNT |
|
||||
| `quarter` | `performance_reports.year` + `quarter` |
|
||||
| `year` | `performance_reports.year` |
|
||||
| `quarterNum` | `performance_reports.quarter` |
|
||||
|
||||
### 3.2 RouteItem ↔ QualityDocumentOrder
|
||||
|
||||
| 프론트 (RouteItem) | 백엔드 (QualityDocumentOrder) |
|
||||
|--------------------|-------------------------------|
|
||||
| `id` | `quality_document_orders.id` |
|
||||
| `code` | `orders.order_code` |
|
||||
| `date` | `orders.order_date` |
|
||||
| `site` | `orders.site_name` |
|
||||
| `locationCount` | `quality_document_locations` COUNT |
|
||||
| `subItems` | `quality_document_locations` 변환 |
|
||||
|
||||
### 3.3 ChecklistCategory ↔ AuditChecklistCategory
|
||||
|
||||
| 프론트 (ChecklistCategory) | 백엔드 (AuditChecklistCategory) |
|
||||
|---------------------------|--------------------------------|
|
||||
| `id` | `audit_checklist_categories.id` |
|
||||
| `title` | `audit_checklist_categories.title` |
|
||||
| `subItems` | `audit_checklist_items` 관계 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 일정 산정
|
||||
|
||||
| Phase | 작업 내용 | 예상 소요 |
|
||||
|-------|----------|-----------|
|
||||
| **Phase 1** | 2일차 API 연동 (기존 API 활용) | |
|
||||
| ├ 1-1 | Backend: 전용 서비스 + 컨트롤러 + 라우트 | 1일 |
|
||||
| ├ 1-2 | Backend: 8종 서류 조합 조회 로직 | 1일 |
|
||||
| ├ 1-3 | Frontend: actions.ts 생성 + 목업 교체 | 1일 |
|
||||
| └ 1-4 | 테스트 및 디버깅 | 0.5일 |
|
||||
| **Phase 2** | 1일차 백엔드 구축 (완전 신규) | |
|
||||
| ├ 2-1 | DB 설계 + 마이그레이션 (4테이블) | 0.5일 |
|
||||
| ├ 2-2 | 모델 4개 + 관계 설정 | 0.5일 |
|
||||
| ├ 2-3 | 서비스 + 컨트롤러 + 라우트 | 1일 |
|
||||
| └ 2-4 | 초기 데이터 시딩 (점검표 마스터) | 0.5일 |
|
||||
| **Phase 3** | 프론트엔드 전환 | |
|
||||
| ├ 3-1 | 2일차 컴포넌트 API 바인딩 | 1일 |
|
||||
| ├ 3-2 | 1일차 컴포넌트 API 바인딩 | 1일 |
|
||||
| └ 3-3 | 통합 테스트 + mockData 정리 | 0.5일 |
|
||||
|
||||
**총 예상: ~8일**
|
||||
|
||||
---
|
||||
|
||||
## 5. 의존성 및 리스크
|
||||
|
||||
### 5.1 의존성
|
||||
|
||||
| 항목 | 의존 대상 | 상태 |
|
||||
|------|-----------|------|
|
||||
| 품질관리서 데이터 | `quality_documents` 실 데이터 | ✅ 운영 중 |
|
||||
| 실적신고 데이터 | `performance_reports` 실 데이터 | ✅ 운영 중 |
|
||||
| 수입검사 성적서 | `inspections` (IQC) | ✅ 운영 중 |
|
||||
| 중간검사 성적서 | `inspections` (PQC) | ⚠️ 구현 중 |
|
||||
| 작업일지 | `work_orders` 연결 | ✅ 운영 중 |
|
||||
| 출하/납품 | `shipments` | ✅ 운영 중 |
|
||||
| 기준 문서 파일 | EAV Document 시스템 | ✅ 운영 중 |
|
||||
|
||||
### 5.2 리스크
|
||||
|
||||
| 리스크 | 영향 | 완화 방안 |
|
||||
|--------|------|-----------|
|
||||
| 8종 서류 추적 로직 복잡 | Phase 1 지연 | 서류별 독립 조회 후 프론트에서 조합 |
|
||||
| 1일차 점검표 초기 데이터 부재 | Phase 2 테스트 어려움 | 시더로 기본 점검표 생성 |
|
||||
| 중간검사 미완성 | 2일차 일부 서류 누락 | 빈 상태로 표시, 추후 연동 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 권장 진행 순서
|
||||
|
||||
```
|
||||
Phase 1 (2일차 API 연동) — 3.5일
|
||||
↓
|
||||
Phase 2 (1일차 백엔드 구축) — 2.5일
|
||||
↓
|
||||
Phase 3 (프론트엔드 전환) — 2.5일
|
||||
```
|
||||
|
||||
**Phase 1을 먼저 하는 이유:**
|
||||
- 기존 API 활용으로 빠르게 실 데이터 확인 가능
|
||||
- 로트 추적은 실적신고와 직접 연결되어 비즈니스 우선순위 높음
|
||||
- Phase 2(1일차)는 독립적인 신규 개발이므로 나중에 진행 가능
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [품질인정심사 기능 문서](../../features/quality-management/quality-certification-audit.md)
|
||||
- [제품검사 관리](../../features/quality-management/inspection-management.md)
|
||||
- [생산실적신고](../../features/quality-management/performance-reports.md)
|
||||
- [통합 개선 마스터 플랜](./integrated-master-plan.md)
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-09
|
||||
1053
dev/dev_plans/quality/quality-management-plan.md
Normal file
1053
dev/dev_plans/quality/quality-management-plan.md
Normal file
File diff suppressed because it is too large
Load Diff
794
dev/guides/LOCAL_SETUP_GUIDE.md
Normal file
794
dev/guides/LOCAL_SETUP_GUIDE.md
Normal file
@@ -0,0 +1,794 @@
|
||||
# SAM 로컬 개발 환경 셋팅 가이드
|
||||
|
||||
> 최종 업데이트: 2026-03-09
|
||||
|
||||
---
|
||||
|
||||
## 1. 사전 준비
|
||||
|
||||
### 필수 소프트웨어
|
||||
|
||||
| 소프트웨어 | 버전 | 용도 | 설치 |
|
||||
|-----------|------|------|---------------------------------------------------------------|
|
||||
| **Docker Desktop** | 최신 | 컨테이너 실행 | [docker.com](https://www.docker.com/products/docker-desktop/) |
|
||||
| **Git** | 최신 | 버전 관리 | `brew install git` |
|
||||
| **mkcert** | 최신 | 로컬 SSL 인증서 | `brew install mkcert` |
|
||||
| **텍스트 에디터** | - | 코드 편집 | JetBrains IDE 권장 |
|
||||
|
||||
> Docker Desktop이 설치되면 PHP, Node.js, MySQL 등은 **별도 설치 불필요** (Docker 컨테이너 내부에서 실행)
|
||||
|
||||
### Git 서버 정보
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| Git 서버 | Gitea (자체 호스팅) |
|
||||
| 서버 주소 | `http://114.203.209.83:3000` |
|
||||
| 조직 | `SamProject` |
|
||||
|
||||
> Gitea 계정이 필요합니다. 팀장에게 계정 생성을 요청하세요.
|
||||
|
||||
---
|
||||
|
||||
## 2. 저장소 클론
|
||||
|
||||
### 디렉토리 구조
|
||||
|
||||
```
|
||||
Works/@KD_SAM/SAM/ ← 루트 (원하는 경로에 생성)
|
||||
├── api/ ← Laravel REST API
|
||||
├── mng/ ← Laravel 관리자 패널
|
||||
├── react/ ← Next.js 프론트엔드
|
||||
├── docs/ ← 기술 문서
|
||||
├── hotfix/ ← 테스트/QA 문서
|
||||
├── docker/ ← Docker 설정 (api 저장소에 포함되지 않음)
|
||||
├── design/ ← 디자인 시스템 (선택)
|
||||
└── planning/ ← 기획 문서 (선택)
|
||||
```
|
||||
|
||||
### 클론 명령어
|
||||
|
||||
```bash
|
||||
# 작업 디렉토리 생성
|
||||
mkdir -p ~/Works/@KD_SAM/SAM
|
||||
cd ~/Works/@KD_SAM/SAM
|
||||
|
||||
# 5개 저장소 클론
|
||||
git clone http://114.203.209.83:3000/SamProject/sam-api.git api
|
||||
git clone http://114.203.209.83:3000/SamProject/sam-manage.git mng
|
||||
git clone http://114.203.209.83:3000/SamProject/sam-react-prod.git react
|
||||
git clone http://114.203.209.83:3000/SamProject/sam-docs.git docs
|
||||
git clone http://114.203.209.83:3000/SamProject/sam-hotfix.git hotfix
|
||||
```
|
||||
|
||||
### 기본 브랜치 전환
|
||||
|
||||
```bash
|
||||
# api, react는 develop 브랜치에서 작업
|
||||
cd api && git checkout develop && cd ..
|
||||
cd react && git checkout develop && cd ..
|
||||
```
|
||||
|
||||
> **중요**: `main` 브랜치에서 직접 작업하지 않습니다. 항상 `develop`에서 작업합니다.
|
||||
|
||||
---
|
||||
|
||||
## 3. Docker 환경 구성
|
||||
|
||||
### 3-1. Docker 설정 파일 확인
|
||||
|
||||
`docker/` 디렉토리에 모든 Docker 설정이 포함되어 있습니다.
|
||||
|
||||
```
|
||||
docker/
|
||||
├── docker-compose.yml ← 메인 Compose 파일
|
||||
├── .env ← Compose 환경변수
|
||||
├── api/
|
||||
│ ├── Dockerfile ← PHP 8.4 + Nginx
|
||||
│ ├── nginx.conf
|
||||
│ ├── supervisord.conf
|
||||
│ └── uploads.ini
|
||||
├── mng/
|
||||
│ ├── Dockerfile ← PHP 8.4 + Nginx
|
||||
│ ├── nginx.conf
|
||||
│ ├── supervisord.conf
|
||||
│ └── uploads.ini
|
||||
├── react/
|
||||
│ └── Dockerfile ← Node 20 + Chromium (PDF용)
|
||||
├── mysql/
|
||||
│ ├── init.sql ← 초기 DB/유저 생성
|
||||
│ └── my.cnf ← MySQL 설정
|
||||
├── nginx/
|
||||
│ ├── nginx.conf ← 리버스 프록시 설정
|
||||
│ └── ssl/ ← SSL 인증서
|
||||
└── 5130/
|
||||
└── Dockerfile ← 레거시 PHP 7.3
|
||||
```
|
||||
|
||||
### 3-2. 서비스 구성
|
||||
|
||||
| 서비스 | 이미지 | 내부 포트 | 역할 |
|
||||
|--------|--------|-----------|------|
|
||||
| **nginx** | nginx:latest | 80, 443 | 리버스 프록시, SSL 종료 |
|
||||
| **api** | PHP 8.4-fpm | 9000 | REST API 백엔드 |
|
||||
| **mng** | PHP 8.4-fpm | 9000 | 관리자 패널 |
|
||||
| **react** | node:20-alpine | 3000 | Next.js 프론트엔드 |
|
||||
| **mysql** | mysql:8.0 | 3306 | 데이터베이스 |
|
||||
| **php73** | PHP 7.3-fpm | 9000 | 레거시 5130 앱 |
|
||||
|
||||
### 3-3. 네트워크
|
||||
|
||||
모든 컨테이너는 `samnet` 브릿지 네트워크로 연결됩니다.
|
||||
|
||||
```
|
||||
[브라우저] → [nginx:443] → api / mng / react (내부 라우팅)
|
||||
→ mysql (컨테이너명: sam-mysql-1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. hosts 파일 설정
|
||||
|
||||
로컬 도메인을 사용하기 위해 hosts 파일을 수정합니다.
|
||||
|
||||
```bash
|
||||
sudo nano /etc/hosts
|
||||
```
|
||||
|
||||
아래 내용을 추가:
|
||||
|
||||
```
|
||||
127.0.0.1 api.sam.kr mng.sam.kr admin.sam.kr dev.sam.kr design.sam.kr plan.sam.kr 5130.sam.kr
|
||||
127.0.0.1 sam.kr www.sam.kr sales.sam.kr demo.sam.kr
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. SSL 인증서 설정
|
||||
|
||||
로컬 HTTPS를 위한 자체 서명 인증서를 생성합니다.
|
||||
|
||||
### 5-1. mkcert 설치 및 초기화
|
||||
|
||||
```bash
|
||||
# mkcert 설치 (처음 한 번)
|
||||
brew install mkcert
|
||||
mkcert -install # 로컬 CA를 시스템에 등록
|
||||
```
|
||||
|
||||
### 5-2. 와일드카드 인증서 생성
|
||||
|
||||
```bash
|
||||
cd docker/nginx/ssl/
|
||||
|
||||
# *.sam.kr 와일드카드 인증서 생성
|
||||
mkcert "*.sam.kr" localhost 127.0.0.1 ::1
|
||||
|
||||
# 파일명 변경 (nginx.conf에서 참조하는 이름으로)
|
||||
mv _wildcard.sam.kr+3.pem sam.kr.crt
|
||||
mv _wildcard.sam.kr+3-key.pem sam.kr.key
|
||||
```
|
||||
|
||||
### 5-3. 포트 포워딩 설정 (macOS)
|
||||
|
||||
Docker가 443 포트를 4443으로 매핑하므로, 브라우저에서 표준 443 포트로 접속하려면 포트 포워딩이 필요합니다.
|
||||
|
||||
```bash
|
||||
# pfctl 규칙 적용 (443 → 4443 포워딩)
|
||||
sudo pfctl -ef docker/nginx/ssl/pf-sam.conf
|
||||
```
|
||||
|
||||
> **참고**: macOS 재부팅 시 이 설정은 초기화되므로 재부팅 후 다시 실행해야 합니다.
|
||||
|
||||
**포트 포워딩 없이 사용하려면** `https://api.sam.kr:4443` 처럼 포트 번호를 직접 지정합니다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 환경변수 (.env) 설정
|
||||
|
||||
### 6-1. API (.env)
|
||||
|
||||
```bash
|
||||
cd api
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
`.env` 파일에서 확인/수정할 항목:
|
||||
|
||||
```env
|
||||
# 앱 키 생성은 Docker 실행 후 컨테이너 내에서 수행
|
||||
# docker exec sam-api-1 php artisan key:generate
|
||||
|
||||
APP_NAME="SAM API"
|
||||
APP_ENV=local
|
||||
APP_DEBUG=true
|
||||
APP_URL=https://api.sam.kr/
|
||||
|
||||
# DB (Docker 환경에서는 docker-compose가 오버라이드)
|
||||
DB_HOST=127.0.0.1 # 로컬 직접 접속 시
|
||||
# DB_HOST=sam-mysql-1 # Docker 환경 (자동 오버라이드)
|
||||
DB_DATABASE=samdb
|
||||
DB_USERNAME=samuser
|
||||
DB_PASSWORD=sampass
|
||||
|
||||
# Swagger
|
||||
L5_SWAGGER_GENERATE_ALWAYS=true
|
||||
L5_SWAGGER_CONST_HOST=https://api.sam.kr/
|
||||
|
||||
# Sanctum 토큰 (분 단위)
|
||||
SANCTUM_ACCESS_TOKEN_EXPIRATION=120
|
||||
SANCTUM_REFRESH_TOKEN_EXPIRATION=10080
|
||||
|
||||
# 내부 통신 키 (MNG ↔ API)
|
||||
INTERNAL_EXCHANGE_SECRET= # 팀 내부 문서에서 확인
|
||||
```
|
||||
|
||||
> **API 키, Firebase, AI 서비스 키** 등 민감한 값은 팀 내부 문서(노션)에서 별도 공유합니다.
|
||||
|
||||
### 6-2. MNG (.env)
|
||||
|
||||
```bash
|
||||
cd mng
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
```env
|
||||
APP_NAME=SAM-MNG
|
||||
APP_ENV=local
|
||||
APP_DEBUG=true
|
||||
APP_URL=https://mng.sam.kr
|
||||
|
||||
DB_HOST=sam-mysql-1
|
||||
DB_DATABASE=samdb
|
||||
DB_USERNAME=samuser
|
||||
DB_PASSWORD=sampass
|
||||
|
||||
# API 서버 연동
|
||||
API_BASE_URL=https://api.sam.kr
|
||||
|
||||
# 내부 통신 키 (API와 동일한 값)
|
||||
INTERNAL_EXCHANGE_SECRET= # API의 값과 동일하게 설정
|
||||
```
|
||||
|
||||
### 6-3. React (.env.local)
|
||||
|
||||
```bash
|
||||
cd react
|
||||
cp .env.example .env.local
|
||||
```
|
||||
|
||||
```env
|
||||
NEXT_PUBLIC_APP_ENV=local
|
||||
NEXT_PUBLIC_API_URL=https://api.sam.kr
|
||||
NEXT_PUBLIC_FRONTEND_URL=https://dev.sam.kr
|
||||
NEXT_PUBLIC_AUTH_MODE=sanctum
|
||||
|
||||
# API Key (서버 사이드 전용 - NEXT_PUBLIC_ 접두사 붙이지 말 것!)
|
||||
API_KEY= # 팀 내부 문서에서 확인
|
||||
|
||||
# 개발 도구
|
||||
NEXT_PUBLIC_DEV_TOOLBAR_ENABLED=false
|
||||
|
||||
# Puppeteer (PDF 생성 - Docker에서는 자동 설정)
|
||||
PUPPETEER_EXECUTABLE_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
|
||||
```
|
||||
|
||||
### 6-4. docs / hotfix
|
||||
|
||||
별도 환경변수 설정 불필요 (순수 마크다운 문서 저장소)
|
||||
|
||||
---
|
||||
|
||||
## 7. Docker 실행
|
||||
|
||||
### 7-1. 최초 실행
|
||||
|
||||
```bash
|
||||
cd docker
|
||||
|
||||
# 이미지 빌드 + 컨테이너 시작
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
> 첫 실행 시 이미지 빌드에 5~10분 소요될 수 있습니다.
|
||||
|
||||
### 7-2. 초기 설정 (최초 1회)
|
||||
|
||||
```bash
|
||||
# API: 앱 키 생성 + 의존성 설치 + 마이그레이션
|
||||
docker exec sam-api-1 php artisan key:generate
|
||||
docker exec sam-api-1 composer install
|
||||
docker exec sam-api-1 php artisan migrate
|
||||
docker exec sam-api-1 php artisan l5-swagger:generate
|
||||
|
||||
# MNG: 앱 키 생성 + 의존성 설치
|
||||
docker exec sam-mng-1 php artisan key:generate
|
||||
docker exec sam-mng-1 composer install
|
||||
docker exec sam-mng-1 npm install
|
||||
docker exec sam-mng-1 npm run build
|
||||
```
|
||||
|
||||
> React는 Docker 컨테이너 시작 시 자동으로 `npm install` + `npm run dev`가 실행됩니다.
|
||||
|
||||
### 7-3. 일상적인 시작/종료
|
||||
|
||||
```bash
|
||||
cd docker
|
||||
|
||||
# 시작
|
||||
docker compose up -d
|
||||
|
||||
# 종료
|
||||
docker compose down
|
||||
|
||||
# 로그 확인
|
||||
docker compose logs -f # 전체
|
||||
docker compose logs -f api # API만
|
||||
docker compose logs -f react # React만
|
||||
|
||||
# 컨테이너 상태 확인
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 접속 확인
|
||||
|
||||
### 8-1. 로컬 도메인
|
||||
|
||||
| 서비스 | URL | 설명 |
|
||||
|--------|-----|------|
|
||||
| **프론트엔드** | `https://dev.sam.kr` | Next.js 사용자 화면 |
|
||||
| **API 문서** | `https://api.sam.kr/api-docs/index.html` | Swagger UI |
|
||||
| **관리자 패널** | `https://mng.sam.kr` | MNG 관리자 화면 |
|
||||
| **관리자 (별칭)** | `https://admin.sam.kr` | MNG와 동일 |
|
||||
| **디자인 시스템** | `https://design.sam.kr` | 컴포넌트 스토리북 |
|
||||
| **레거시** | `https://5130.sam.kr` | 기존 PHP 시스템 |
|
||||
|
||||
### 8-2. DB 접속 정보
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| Host | `127.0.0.1` |
|
||||
| Port | `3306` |
|
||||
| Database | `samdb` |
|
||||
| Username | `samuser` |
|
||||
| Password | `sampass` |
|
||||
| Root Password | `root` |
|
||||
| 레거시 DB | `chandj` |
|
||||
|
||||
> DBeaver, DataGrip, MySQL Workbench 등 원하는 DB 클라이언트로 접속 가능
|
||||
|
||||
### 8-3. 접속 테스트
|
||||
|
||||
```bash
|
||||
# API 응답 확인
|
||||
curl -k https://api.sam.kr/api-docs/index.html
|
||||
|
||||
# MySQL 접속 확인
|
||||
docker exec sam-mysql-1 mysql -u samuser -psampass -e "SHOW DATABASES;"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 저장소별 상세 정보
|
||||
|
||||
### 9-1. api/ (REST API 서버)
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 프레임워크 | Laravel 12 (PHP 8.4) |
|
||||
| 인증 | Sanctum (토큰 기반) |
|
||||
| API 문서 | Swagger (l5-swagger) |
|
||||
| DB | MySQL 8.0 (samdb) |
|
||||
| 역할 | 모든 비즈니스 로직의 중심, 프론트/관리자 모두 이 API 사용 |
|
||||
|
||||
**주요 명령어:**
|
||||
|
||||
```bash
|
||||
# 컨테이너 진입
|
||||
docker exec -it sam-api-1 bash
|
||||
|
||||
# 마이그레이션
|
||||
php artisan migrate
|
||||
php artisan migrate:status
|
||||
|
||||
# Swagger 재생성
|
||||
php artisan l5-swagger:generate
|
||||
|
||||
# 코드 포매터
|
||||
./vendor/bin/pint
|
||||
|
||||
# 캐시 초기화
|
||||
php artisan cache:clear
|
||||
php artisan config:clear
|
||||
php artisan route:clear
|
||||
```
|
||||
|
||||
**핵심 디렉토리:**
|
||||
|
||||
```
|
||||
api/
|
||||
├── app/
|
||||
│ ├── Http/Controllers/Api/V1/ ← API 컨트롤러
|
||||
│ ├── Http/Requests/ ← FormRequest (검증)
|
||||
│ ├── Models/ ← Eloquent 모델
|
||||
│ ├── Services/ ← 비즈니스 로직 (핵심)
|
||||
│ ├── Swagger/v1/ ← Swagger 문서 클래스
|
||||
│ └── Helpers/ApiResponse.php ← 응답 헬퍼
|
||||
├── database/migrations/ ← DB 마이그레이션
|
||||
├── routes/api.php ← API 라우트
|
||||
└── lang/ko/ ← 한국어 메시지
|
||||
```
|
||||
|
||||
### 9-2. mng/ (관리자 패널)
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 프레임워크 | Laravel 12 (PHP 8.4) |
|
||||
| 프론트엔드 | Blade + Tailwind CSS + HTMX + DaisyUI |
|
||||
| 역할 | 시스템 관리, 메뉴/권한 관리, 테넌트 관리 |
|
||||
|
||||
**주요 명령어:**
|
||||
|
||||
```bash
|
||||
# 컨테이너 진입
|
||||
docker exec -it sam-mng-1 bash
|
||||
|
||||
# 프론트 에셋 빌드
|
||||
npm run build # 프로덕션
|
||||
npm run dev # 개발 (HMR)
|
||||
|
||||
# 코드 포매터
|
||||
./vendor/bin/pint
|
||||
```
|
||||
|
||||
**핵심 디렉토리:**
|
||||
|
||||
```
|
||||
mng/
|
||||
├── app/
|
||||
│ ├── Http/Controllers/ ← 웹 컨트롤러
|
||||
│ ├── Models/ ← 독립 모델 (API와 별개)
|
||||
│ └── Services/ ← 비즈니스 로직
|
||||
├── resources/views/ ← Blade 템플릿
|
||||
├── routes/web.php ← 웹 라우트
|
||||
└── public/ ← 정적 파일
|
||||
```
|
||||
|
||||
> **주의**: MNG에서는 DB 마이그레이션을 생성/실행하지 않습니다. DB 변경은 반드시 API 프로젝트에서 수행합니다.
|
||||
|
||||
### 9-3. react/ (Next.js 프론트엔드)
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 프레임워크 | Next.js 15 (React 19, TypeScript) |
|
||||
| 스타일링 | Tailwind CSS 4 |
|
||||
| UI 라이브러리 | shadcn/ui (Radix UI 기반) |
|
||||
| 상태관리 | Zustand |
|
||||
| 폼 검증 | Zod + React Hook Form |
|
||||
| 역할 | 사용자용 ERP 프론트엔드 |
|
||||
|
||||
**주요 명령어:**
|
||||
|
||||
```bash
|
||||
# React는 Docker 컨테이너가 자동으로 dev 서버를 실행합니다.
|
||||
# 수동 실행이 필요한 경우:
|
||||
docker exec -it sam-react-1 sh
|
||||
|
||||
npm run dev # 개발 서버
|
||||
npm run build # 프로덕션 빌드
|
||||
npm run lint # ESLint
|
||||
```
|
||||
|
||||
**핵심 디렉토리:**
|
||||
|
||||
```
|
||||
react/
|
||||
├── src/
|
||||
│ ├── app/ ← Next.js App Router 페이지
|
||||
│ ├── components/
|
||||
│ │ ├── ui/ ← shadcn/ui 원자 컴포넌트
|
||||
│ │ ├── molecules/ ← 조합 컴포넌트
|
||||
│ │ ├── organisms/ ← 복합 컴포넌트
|
||||
│ │ └── [도메인]/ ← 도메인별 컴포넌트
|
||||
│ ├── lib/ ← 유틸리티, API 헬퍼
|
||||
│ └── stores/ ← Zustand 스토어
|
||||
├── public/ ← 정적 파일
|
||||
└── next.config.ts ← Next.js 설정
|
||||
```
|
||||
|
||||
**핵심 규칙:**
|
||||
- 모든 페이지는 `'use client'` 선언 필수 (폐쇄형 ERP, SSR 불필요)
|
||||
- API 호출은 Server Action을 통해 수행 (HttpOnly 쿠키 프록시)
|
||||
- `buildApiUrl()` 유틸리티 필수 사용
|
||||
|
||||
### 9-4. docs/ (기술 문서)
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 내용 | 개발 가이드, API 스펙, 기획 문서, 표준 |
|
||||
| 형식 | Markdown |
|
||||
| 설치 | 없음 (문서만 관리) |
|
||||
|
||||
```
|
||||
docs/
|
||||
├── INDEX.md ← 문서 인덱스 (여기부터 시작)
|
||||
├── dev/
|
||||
│ ├── dev_plans/ ← 개발 계획 문서
|
||||
│ ├── standards/ ← 코드/아키텍처 표준
|
||||
│ ├── guides/ ← 셋업/사용 가이드
|
||||
│ ├── changes/ ← 변경 로그
|
||||
│ └── deploys/ ← 배포 문서
|
||||
├── features/ ← 기능 스펙
|
||||
├── frontend/ ← 프론트엔드 API 스펙
|
||||
├── rules/ ← 비즈니스 규칙
|
||||
└── system/ ← 시스템 아키텍처
|
||||
```
|
||||
|
||||
### 9-5. hotfix/ (테스트/QA)
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 내용 | E2E 테스트 결과, 버그 리포트, 테스트 케이스 |
|
||||
| 형식 | Markdown + 스크린샷 |
|
||||
| 설치 | 없음 (문서만 관리) |
|
||||
|
||||
```
|
||||
hotfix/
|
||||
├── e2e/ ← E2E 테스트 정의
|
||||
├── testcase/ ← 테스트 케이스 문서
|
||||
├── reports/ ← 테스트 실행 결과
|
||||
├── screenshots/ ← 시각적 테스트 증거
|
||||
├── research/ ← 조사 자료
|
||||
├── sam.code-workspace ← VS Code 워크스페이스
|
||||
├── *-Test-Report_*.md ← 개별 테스트 보고서
|
||||
└── Fail-*_*.md ← 실패 케이스 보고서
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 주요 아키텍처 개념
|
||||
|
||||
### 10-1. 멀티테넌트
|
||||
|
||||
- 모든 데이터에 `tenant_id` 컬럼이 존재
|
||||
- `BelongsToTenant` 글로벌 스코프가 자동으로 테넌트 필터링
|
||||
- 하나의 DB에서 여러 회사(테넌트)의 데이터를 격리
|
||||
|
||||
### 10-2. 데이터 흐름
|
||||
|
||||
```
|
||||
[사용자 브라우저]
|
||||
│
|
||||
▼
|
||||
[Next.js (react/)] ──Server Action──▶ [Laravel API (api/)] ──▶ [MySQL]
|
||||
│
|
||||
[관리자 브라우저] │
|
||||
│ │
|
||||
▼ │
|
||||
[Laravel MNG (mng/)] ──내부 API 호출──────────┘
|
||||
```
|
||||
|
||||
### 10-3. 인증 방식
|
||||
|
||||
| 클라이언트 | 인증 방식 | 설명 |
|
||||
|-----------|----------|------|
|
||||
| React (웹) | Sanctum Cookie | HttpOnly 쿠키, Server Action 프록시 |
|
||||
| MNG (관리자) | 세션 + 내부 HMAC | Laravel 세션 + API 내부 통신 키 |
|
||||
| 외부 연동 | API Key + Bearer | Sanctum 토큰 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 자주 쓰는 명령어
|
||||
|
||||
### Docker 관련
|
||||
|
||||
```bash
|
||||
# 전체 시작/종료
|
||||
cd docker && docker compose up -d
|
||||
cd docker && docker compose down
|
||||
|
||||
# 컨테이너 재시작
|
||||
docker restart sam-api-1
|
||||
docker restart sam-react-1
|
||||
|
||||
# 이미지 재빌드 (Dockerfile 수정 후)
|
||||
docker compose build --no-cache api
|
||||
docker compose up -d api
|
||||
|
||||
# 볼륨 포함 완전 초기화 (⚠️ DB 데이터 삭제됨)
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
### API 개발
|
||||
|
||||
```bash
|
||||
docker exec sam-api-1 php artisan migrate # 마이그레이션
|
||||
docker exec sam-api-1 php artisan migrate:rollback # 롤백
|
||||
docker exec sam-api-1 php artisan l5-swagger:generate # Swagger 재생성
|
||||
docker exec sam-api-1 ./vendor/bin/pint # 코드 포매팅
|
||||
docker exec sam-api-1 php artisan tinker # REPL
|
||||
docker exec sam-api-1 php artisan route:list # 라우트 목록
|
||||
```
|
||||
|
||||
### MNG 개발
|
||||
|
||||
```bash
|
||||
docker exec sam-mng-1 php artisan tinker
|
||||
docker exec sam-mng-1 npm run dev # Vite HMR
|
||||
docker exec sam-mng-1 npm run build # 에셋 빌드
|
||||
docker exec sam-mng-1 ./vendor/bin/pint
|
||||
```
|
||||
|
||||
### MySQL 접속
|
||||
|
||||
```bash
|
||||
# CLI 접속
|
||||
docker exec -it sam-mysql-1 mysql -u samuser -psampass samdb
|
||||
|
||||
# 특정 쿼리 실행
|
||||
docker exec sam-mysql-1 mysql -u samuser -psampass samdb -e "SHOW TABLES;"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. 트러블슈팅
|
||||
|
||||
### SSL 인증서 오류 (브라우저에서 "안전하지 않음")
|
||||
|
||||
```bash
|
||||
# mkcert CA가 설치되지 않은 경우
|
||||
mkcert -install
|
||||
|
||||
# 인증서 재생성
|
||||
cd docker/nginx/ssl
|
||||
mkcert "*.sam.kr" localhost 127.0.0.1 ::1
|
||||
mv _wildcard.sam.kr+3.pem sam.kr.crt
|
||||
mv _wildcard.sam.kr+3-key.pem sam.kr.key
|
||||
|
||||
# Nginx 재시작
|
||||
docker restart sam-nginx-1
|
||||
```
|
||||
|
||||
### 도메인 접속이 안 될 때
|
||||
|
||||
```bash
|
||||
# hosts 파일 확인
|
||||
cat /etc/hosts | grep sam
|
||||
|
||||
# DNS 캐시 초기화 (macOS)
|
||||
sudo dscacheutil -flushcache
|
||||
sudo killall -HUP mDNSResponder
|
||||
|
||||
# Nginx 설정 유효성 확인
|
||||
docker exec sam-nginx-1 nginx -t
|
||||
```
|
||||
|
||||
### 포트 포워딩 확인 (443 → 4443)
|
||||
|
||||
```bash
|
||||
# 현재 규칙 확인
|
||||
sudo pfctl -s rules | grep 4443
|
||||
|
||||
# 규칙 재적용
|
||||
sudo pfctl -ef docker/nginx/ssl/pf-sam.conf
|
||||
|
||||
# 규칙 비활성화 (필요 시)
|
||||
sudo pfctl -d
|
||||
```
|
||||
|
||||
### DB 접속 오류
|
||||
|
||||
```bash
|
||||
# MySQL 컨테이너 상태 확인
|
||||
docker compose ps mysql
|
||||
|
||||
# MySQL 로그 확인
|
||||
docker compose logs mysql
|
||||
|
||||
# 수동 접속 테스트
|
||||
docker exec sam-mysql-1 mysql -u root -proot -e "SELECT 1;"
|
||||
```
|
||||
|
||||
### React 빌드/HMR 오류
|
||||
|
||||
```bash
|
||||
# node_modules 초기화
|
||||
docker exec sam-react-1 rm -rf node_modules .next
|
||||
docker exec sam-react-1 npm install
|
||||
docker restart sam-react-1
|
||||
```
|
||||
|
||||
### composer/npm 의존성 문제
|
||||
|
||||
```bash
|
||||
# API
|
||||
docker exec sam-api-1 composer install --no-cache
|
||||
docker exec sam-api-1 composer dump-autoload
|
||||
|
||||
# MNG
|
||||
docker exec sam-mng-1 composer install --no-cache
|
||||
docker exec sam-mng-1 npm ci
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. Git 워크플로우
|
||||
|
||||
### 브랜치 전략
|
||||
|
||||
| 브랜치 | 역할 | 규칙 |
|
||||
|--------|------|------|
|
||||
| `main` | 배포용 | squash merge로만 올림, 직접 커밋 금지 |
|
||||
| `develop` | 일상 작업 | 자유롭게 커밋 |
|
||||
| `feature/*` | 대형 기능 | 선택적 사용 (1주일+ 작업) |
|
||||
|
||||
### 커밋 메시지 형식
|
||||
|
||||
```
|
||||
type: 간결한 설명
|
||||
|
||||
type 종류:
|
||||
feat: 새 기능
|
||||
fix: 버그 수정
|
||||
refactor: 리팩토링
|
||||
docs: 문서
|
||||
chore: 설정, 빌드
|
||||
style: 코드 포매팅
|
||||
```
|
||||
|
||||
### 일상 워크플로우
|
||||
|
||||
```bash
|
||||
# 1. 작업 시작
|
||||
cd api
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
|
||||
# 2. 작업 수행 + 커밋
|
||||
git add <파일들>
|
||||
git commit -m "feat: 수주관리 삭제 기능 추가"
|
||||
|
||||
# 3. 푸시
|
||||
git push origin develop
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. 셋팅 체크리스트
|
||||
|
||||
아래 항목을 순서대로 확인하며 셋팅을 완료합니다.
|
||||
|
||||
- [ ] Docker Desktop 설치 및 실행
|
||||
- [ ] Git 설치 및 Gitea 계정 발급
|
||||
- [ ] 5개 저장소 클론 완료
|
||||
- [ ] `/etc/hosts` 파일 수정
|
||||
- [ ] mkcert 설치 + SSL 인증서 생성
|
||||
- [ ] pfctl 포트 포워딩 설정
|
||||
- [ ] api/.env 설정 (`.env.example` → `.env`)
|
||||
- [ ] mng/.env 설정
|
||||
- [ ] react/.env.local 설정
|
||||
- [ ] API Key, HMAC Key 등 민감 값 입력 (팀 공유 문서 참조)
|
||||
- [ ] `docker compose up -d --build` 실행
|
||||
- [ ] 초기 설정 실행 (key:generate, composer install, migrate)
|
||||
- [ ] `https://api.sam.kr/api-docs/index.html` 접속 확인
|
||||
- [ ] `https://mng.sam.kr` 접속 확인
|
||||
- [ ] `https://dev.sam.kr` 접속 확인
|
||||
- [ ] DB 클라이언트로 `127.0.0.1:3306` 접속 확인
|
||||
|
||||
---
|
||||
|
||||
## 15. 참고 문서
|
||||
|
||||
| 문서 | 경로 | 설명 |
|
||||
|------|------|------|
|
||||
| 문서 인덱스 | `docs/INDEX.md` | 전체 문서 목록 |
|
||||
| API 규칙 | `API_RULES.md` | API 개발 규칙 |
|
||||
| 개발 명령어 | `DEV_COMMANDS.md` | 자주 쓰는 명령어 모음 |
|
||||
| 품질 체크리스트 | `QUALITY_CHECKLIST.md` | 코드 품질 체크 항목 |
|
||||
| 빠른 참조 | `SAM_QUICK_REFERENCE.md` | 핵심 규칙 요약 |
|
||||
| SSL 가이드 | `docker/nginx/ssl/SSL_SETUP_GUIDE.md` | SSL 상세 설정 |
|
||||
|
||||
---
|
||||
|
||||
> 문의사항은 팀 슬랙 채널 또는 팀장에게 문의하세요.
|
||||
369
features/academy/fire-shutter-image-prompts.md
Normal file
369
features/academy/fire-shutter-image-prompts.md
Normal file
@@ -0,0 +1,369 @@
|
||||
# 방화셔터 백과사전 이미지 생성 프롬프트
|
||||
|
||||
> **작성일**: 2026-02-22
|
||||
> **상태**: 확정
|
||||
> **용도**: Google Gemini (Nano Banana Pro) 이미지 생성용
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
MNG 아카데미 > 방화셔터 백과사전 페이지에 삽입할 기술 일러스트레이션을 AI 이미지 생성 도구(Google Gemini)로 제작하기 위한 프롬프트 모음이다.
|
||||
|
||||
### 1.2 사용 방법
|
||||
|
||||
1. Google Gemini (Nano Banana Pro 모델)에서 프롬프트를 입력한다
|
||||
2. 생성된 이미지를 `mng/public/images/academy/fire-shutter/` 경로에 저장한다
|
||||
3. Blade 뷰에서 `<img>` 태그로 참조한다
|
||||
|
||||
### 1.3 주의사항
|
||||
|
||||
- **화면 내 모든 라벨은 영어**로 작성되어 있다 (한글 텍스트는 AI 이미지 생성 시 깨짐 현상 발생)
|
||||
- 전체 구성도, 설치 장면 등 넓은 이미지는 **16:9** 비율 권장
|
||||
- 단면도, 부품 상세 등은 **1:1** 또는 **4:3** 비율 권장
|
||||
- 생성 실패 시 프롬프트 앞에 `Detailed technical engineering illustration, clean white background, ` 를 추가한다
|
||||
|
||||
---
|
||||
|
||||
## 2. 프롬프트 목록
|
||||
|
||||
### 2.1 방화셔터 전체 구성도 (Full Component Diagram)
|
||||
|
||||
```
|
||||
Technical illustration of a fire shutter (automatic fire-rated rolling shutter) installed in a building opening, cutaway side view showing all components with English labels.
|
||||
|
||||
Show these parts clearly labeled:
|
||||
- Top: "CEILING SLAB" with "HEAD BOX / CASE" mounted below
|
||||
- Inside head box: "SHAFT" with coiled steel slats, "BALANCE SPRING", "GEAR BOX", "MOTOR", "ELECTROMAGNETIC BRAKE", "BRACKET" on both sides
|
||||
- Both sides: vertical "GUIDE RAIL" mounted on fireproof walls with "ANCHOR BOLTS"
|
||||
- Center: multiple horizontal "STEEL SLATS" hanging down in interlocking pattern
|
||||
- Bottom: "BOTTOM BAR" touching the floor with rubber seal
|
||||
- Nearby wall: "MANUAL CONTROL BOX" with UP/STOP/DOWN buttons
|
||||
- Ceiling: "SMOKE DETECTOR" and "HEAT DETECTOR"
|
||||
- Wall-mounted: "FIRE SHUTTER CONTROLLER"
|
||||
|
||||
Style: Clean technical cutaway diagram, white background, professional engineering illustration, labeled with arrows pointing to each component. Color-coded: structural parts in gray/silver, electrical parts in blue, safety parts in red. Isometric or 3/4 perspective view.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.2 슬랫 인터록킹 구조 (Slat Interlocking)
|
||||
|
||||
```
|
||||
Technical cross-section illustration showing how fire shutter steel slats interlock with each other.
|
||||
|
||||
Show 3-4 slats connected in interlocking pattern:
|
||||
- Each slat is a C-shaped or S-shaped profile made from 1.6mm EGI steel
|
||||
- The curved edges of adjacent slats hook into each other, allowing flexibility while maintaining a continuous curtain surface
|
||||
- One slat highlighted with dimension labels: "THICKNESS 1.6mm", "PITCH 75-100mm"
|
||||
- Show the slight curved profile that allows the slat to wrap around the shaft when rolled up
|
||||
- Arrow labeled "ROLLING DIRECTION"
|
||||
|
||||
Label each part: "SLAT", "INTERLOCKING JOINT", "EGI STEEL 1.6mm"
|
||||
|
||||
Style: Clean engineering cross-section diagram, white background, metallic silver color for steel. Include dimension lines. Zoomed-in detail view with magnified interlocking joint area in a callout circle.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.3 가이드레일 단면도 (Guide Rail Cross-Section)
|
||||
|
||||
```
|
||||
Technical cross-section illustration of a fire shutter guide rail mounted on a fireproof wall, viewed from top-down.
|
||||
|
||||
Show the C-channel shaped guide rail:
|
||||
- C-channel profile, steel thickness 2.3mm+
|
||||
- Inside the channel: slat edge sitting in the groove
|
||||
- Smoke seal material strips on both sides of the channel, pressing against the slat
|
||||
- Anchor bolts securing the guide rail to the concrete wall
|
||||
- Wall shown as hatched concrete pattern
|
||||
|
||||
Labels with arrows:
|
||||
- "GUIDE RAIL BODY (C-CHANNEL)"
|
||||
- "SLAT EDGE"
|
||||
- "SMOKE SEAL PACKING"
|
||||
- "ANCHOR BOLT"
|
||||
- "FIREPROOF WALL"
|
||||
- "STEEL 2.3mm+"
|
||||
|
||||
Style: Clean technical cross-section, white background, steel parts in metallic gray, seal material in orange/red, wall in light brown hatched pattern. Include dimension annotations.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.4 샤프트 어셈블리 (Shaft Assembly)
|
||||
|
||||
```
|
||||
Technical illustration showing the inside of a fire shutter head box, exploded or cutaway view.
|
||||
|
||||
Show these components assembled on or around the shaft:
|
||||
- Central pipe labeled "SHAFT" with slats attached, partially wound
|
||||
- Left side: "BRACKET" steel plate bolted to wall, with "BEARING" supporting shaft end
|
||||
- Right side: "GEAR BOX" and "MOTOR" mounted on bracket
|
||||
- "ELECTROMAGNETIC BRAKE" attached to motor assembly
|
||||
- "BALANCE SPRING" torsion spring visible inside the shaft
|
||||
- "AUTO CLOSER" device mounted near the brake
|
||||
- "LIMIT SWITCH" small switches with actuator arms
|
||||
- "HEAD BOX CASE" shown as transparent or partially removed to reveal internals
|
||||
- Wiring connections going down labeled "TO CONTROLLER"
|
||||
|
||||
Style: Exploded technical diagram or cutaway 3D illustration, white background, professional engineering style. Color-coded: mechanical parts in silver/gray, motor in dark blue, brake in red, spring in green. All labels in English with leader lines.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.5 감속기+모터+브레이크 (Gear Box + Motor + Brake Assembly)
|
||||
|
||||
```
|
||||
Technical illustration of a fire shutter drive unit assembly, showing three main components connected together.
|
||||
|
||||
Show them assembled in sequence with labels:
|
||||
1. "MOTOR (220V)" - cylindrical body with power cables
|
||||
2. "ELECTROMAGNETIC BRAKE" - disc-type brake between motor and gearbox, showing brake disc, coil, and spring
|
||||
3. "WORM GEAR BOX" - rectangular housing with cutaway revealing the worm gear and worm wheel inside
|
||||
|
||||
Assembly order shown with arrows: MOTOR → BRAKE → GEAR BOX → "OUTPUT TO SHAFT"
|
||||
Include rotation direction arrows
|
||||
|
||||
Small inset callout showing worm gear mechanism detail labeled: "WORM", "WORM WHEEL", "SELF-LOCKING"
|
||||
|
||||
Style: Technical exploded/assembly diagram, white background, metallic rendering, engineering illustration style.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.6 연동제어기 시스템 (Controller System)
|
||||
|
||||
```
|
||||
Technical schematic diagram showing the fire shutter interlock control system wiring and signal flow.
|
||||
|
||||
Layout (block diagram style):
|
||||
- Top center: "FIRE ALARM PANEL" - rectangular box
|
||||
- Left: "SMOKE DETECTOR (PHOTOELECTRIC)" - circular device on ceiling
|
||||
- Right: "HEAT DETECTOR (FIXED TEMP.)" - circular device on ceiling
|
||||
- Center: "FIRE SHUTTER CONTROLLER" - panel with LED indicators labeled "POWER", "PARTIAL CLOSE", "FULL CLOSE"
|
||||
- Below controller: "AUTO CLOSER" connected to shutter mechanism
|
||||
- Bottom left: "MANUAL CONTROL BOX" with "UP / STOP / DOWN" buttons
|
||||
- Bottom: "FIRE SHUTTER" shown schematically
|
||||
|
||||
Signal flow arrows with labels:
|
||||
- Smoke detector → Controller: "STAGE 1: PARTIAL CLOSE (1m gap)"
|
||||
- Heat detector → Controller: "STAGE 2: FULL CLOSE (floor sealed)"
|
||||
- Controller → Auto closer: "CLOSE COMMAND"
|
||||
- Controller → Speaker icon: "ALARM OUTPUT"
|
||||
- Controller ↔ Fire alarm panel: "STATUS SIGNAL"
|
||||
|
||||
Style: Clean schematic/block diagram, white background, professional electrical diagram style. Color coding: red for fire signals, blue for power, green for status. All labels in English.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.7 2단계 폐쇄 시퀀스 (2-Stage Closure Sequence)
|
||||
|
||||
```
|
||||
Technical illustration showing the two-stage closing sequence of an automatic fire shutter, presented as 3 side-by-side panels:
|
||||
|
||||
Panel 1 - Title: "NORMAL (OPEN)":
|
||||
- Fire shutter fully open, rolled up inside head box
|
||||
- People walking through the opening freely
|
||||
- Smoke and heat detectors on ceiling shown in standby (green LED)
|
||||
- Caption: "Shutter open, passage clear"
|
||||
|
||||
Panel 2 - Title: "STAGE 1: PARTIAL CLOSE":
|
||||
- Smoke detector activated (red LED, smoke wisps shown)
|
||||
- Shutter descended leaving about 1 meter gap from floor
|
||||
- A person crouching to pass under the gap
|
||||
- Alarm buzzer icon showing sound waves
|
||||
- Caption: "Smoke detected → Partial close, 1m gap for evacuation"
|
||||
|
||||
Panel 3 - Title: "STAGE 2: FULL CLOSE":
|
||||
- Heat detector activated (red LED, flames shown)
|
||||
- Shutter fully closed to floor, bottom bar sealed against floor
|
||||
- Fire and smoke on one side, clean air on other side
|
||||
- Caption: "Heat detected → Full close, fire/smoke blocked"
|
||||
|
||||
Arrow at bottom labeled "TIME SEQUENCE →"
|
||||
|
||||
Style: Clean technical illustration with slight architectural rendering, sequential format left to right, white background. People as simple silhouettes. Fire/smoke rendered subtly.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.8 롤포밍 공정 (Roll Forming Process)
|
||||
|
||||
```
|
||||
Technical illustration showing the roll forming manufacturing process for fire shutter steel slats, production line viewed from the side.
|
||||
|
||||
Show the line from left to right with labels:
|
||||
1. "UNCOILER" - Steel coil (EGI 1.6mm) being unrolled
|
||||
2. "LEVELER" - Flattening rollers correcting coil curvature
|
||||
3. "ROLL FORMING STATION" - 6-8 pairs of forming rollers progressively shaping the flat strip into C/S-shaped slat profile
|
||||
4. "CUTTING STATION" - Flying shear cutting the formed strip to length
|
||||
5. "FINISHED SLATS" - Slats stacked neatly on output table
|
||||
|
||||
Detail callout at top showing progressive cross-section shape changes: "FLAT → STAGE 1 → STAGE 2 → STAGE 3 → FINAL PROFILE"
|
||||
|
||||
Arrow at bottom: "MATERIAL FLOW →"
|
||||
Label on coil: "EGI STEEL COIL 1.6mm"
|
||||
|
||||
Style: Technical factory/manufacturing illustration, clean white background, machinery in industrial gray/green, steel in silver. Side view. Directional arrows showing material flow.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.9 현장 설치 (Field Installation)
|
||||
|
||||
```
|
||||
Technical illustration showing fire shutter installation at a construction site, depicting key installation steps in a single scene.
|
||||
|
||||
Scene showing a large building opening (about 5m wide, 4m tall) with:
|
||||
- Two workers on scaffolding installing the head box assembly at the top
|
||||
- Brackets already bolted to both side walls near the ceiling
|
||||
- Guide rails mounted vertically on walls with anchor bolts
|
||||
- Shaft with wound slat curtain being lifted up to place on brackets
|
||||
- Manual control box being mounted on adjacent wall
|
||||
- Wiring conduits visible running from controller to head box
|
||||
- Construction tools: level tool, drill, anchor bolts, wrenches
|
||||
|
||||
Labels with arrows pointing to activities:
|
||||
- "BRACKET MOUNTING"
|
||||
- "GUIDE RAIL ANCHORING"
|
||||
- "SHAFT PLACEMENT"
|
||||
- "ELECTRICAL WIRING"
|
||||
- "LEVEL CHECK"
|
||||
- "ANCHOR BOLT FIXING"
|
||||
|
||||
Style: Technical construction illustration, slightly warm tone, realistic building interior with exposed concrete. Workers wearing safety helmets and vests. Clean architectural illustration style. All text in English.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.10 유지보수 점검 (Maintenance Inspection)
|
||||
|
||||
```
|
||||
Technical illustration showing fire shutter maintenance inspection scene.
|
||||
|
||||
Show a maintenance technician inspecting a fire shutter:
|
||||
- Technician with safety vest and hard hat, holding a tablet
|
||||
- Fire shutter partially lowered (halfway) for testing
|
||||
- Close-up callout bubbles showing key inspection points:
|
||||
1. "SLAT CONDITION" - checking for deformation, rust
|
||||
2. "SMOKE SEAL CHECK" - checking guide rail seal condition
|
||||
3. "BOTTOM BAR PACKING" - checking floor seal
|
||||
4. "MOTOR / BRAKE CHECK" - head box open, listening for sounds
|
||||
5. "MANUAL BOX TEST" - pressing UP/STOP/DOWN buttons
|
||||
6. "CONTROLLER STATUS" - checking LED indicators
|
||||
|
||||
Checklist overlay in corner:
|
||||
☑ MOTOR OPEN/CLOSE TEST
|
||||
☑ DETECTOR INTERLOCK TEST
|
||||
☑ ALARM SOUND CHECK
|
||||
☑ MANUAL OPERATION CHECK
|
||||
☑ BOTTOM BAR SEAL CHECK
|
||||
|
||||
Style: Clean technical illustration, bright well-lit building interior, professional maintenance scene. Color callout bubbles with icons. All text in English.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.11 강판형 vs 스크린형 (Steel Plate vs Screen Type)
|
||||
|
||||
```
|
||||
Technical side-by-side comparison illustration of two types of fire shutters in similar building openings:
|
||||
|
||||
Left side - Title "STEEL PLATE TYPE":
|
||||
- Steel slat fire shutter in partially closed position
|
||||
- Opaque metallic surface of interlocking steel slats visible
|
||||
- Heavier, industrial appearance with thick guide rails
|
||||
- Bottom bar with rubber seal
|
||||
- Callout: "EGI STEEL 1.6mm / HEAVY / OPAQUE / HIGH SEALING"
|
||||
|
||||
Right side - Title "SCREEN / FABRIC TYPE":
|
||||
- Fabric fire shutter in partially closed position
|
||||
- Semi-transparent woven silica fiber screen, you can faintly see light through it
|
||||
- Lighter, sleeker with thin guide rails (11mm)
|
||||
- Fabric gathered at top
|
||||
- Callout: "SILICA FIBER / LIGHTWEIGHT / SEMI-TRANSPARENT / RAIL 11mm"
|
||||
|
||||
Center dividing line with "VS" label
|
||||
Bottom comparison bar: "WEIGHT: Heavy vs Light | VISIBILITY: Opaque vs See-through | RAIL WIDTH: Wide vs 11mm"
|
||||
|
||||
Style: Clean technical comparison, white background, same scale, professional product comparison layout. All text in English.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.12 주요 고장 유형 (Major Fault Types)
|
||||
|
||||
```
|
||||
Technical illustration showing 6 common fire shutter failure types in a 2x3 grid layout, each in its own panel with a red problem highlight:
|
||||
|
||||
Panel 1 - "SLAT DERAILMENT":
|
||||
- A slat edge coming out of the guide rail groove, curtain jammed
|
||||
- Red circle on problem area
|
||||
|
||||
Panel 2 - "MOTOR BURNOUT":
|
||||
- Motor with smoke marks, burnt wiring
|
||||
- Overheat warning symbol
|
||||
|
||||
Panel 3 - "BRAKE PAD WEAR":
|
||||
- Electromagnetic brake with worn disc pad
|
||||
- Side comparison: "NEW" thick pad vs "WORN" thin pad
|
||||
|
||||
Panel 4 - "CONTROLLER MALFUNCTION":
|
||||
- Controller panel with error LED, disconnected wires
|
||||
- Broken signal path indicator
|
||||
|
||||
Panel 5 - "CLOSER SPEED FAULT":
|
||||
- Shutter dropping fast, speedometer showing "0.15 m/s LIMIT EXCEEDED"
|
||||
- Governor mechanism detail
|
||||
|
||||
Panel 6 - "SMOKE SEAL FAILURE":
|
||||
- Smoke wisps leaking through guide rail gaps
|
||||
- Comparison: "NEW SEAL" vs "DEGRADED SEAL"
|
||||
|
||||
Style: Technical diagnostic illustration, white background, bordered panels. Problem areas in red/orange highlight. Clean maintenance manual style. All titles and labels in English.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 이미지 파일 관리
|
||||
|
||||
### 3.1 저장 경로
|
||||
|
||||
```
|
||||
mng/public/images/academy/fire-shutter/
|
||||
├── 01-full-component-diagram.png
|
||||
├── 02-slat-interlocking.png
|
||||
├── 03-guide-rail-cross-section.png
|
||||
├── 04-shaft-assembly.png
|
||||
├── 05-gearbox-motor-brake.png
|
||||
├── 06-controller-system.png
|
||||
├── 07-two-stage-closure.png
|
||||
├── 08-roll-forming-process.png
|
||||
├── 09-field-installation.png
|
||||
├── 10-maintenance-inspection.png
|
||||
├── 11-steel-vs-screen-type.png
|
||||
└── 12-major-fault-types.png
|
||||
```
|
||||
|
||||
### 3.2 Blade 참조 예시
|
||||
|
||||
```html
|
||||
<img src="{{ asset('images/academy/fire-shutter/01-full-component-diagram.png') }}"
|
||||
alt="방화셔터 전체 구성도"
|
||||
class="w-full rounded-lg shadow">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- `mng/resources/views/academy/fire-shutter.blade.php` - 방화셔터 백과사전 Blade 뷰
|
||||
- `mng/app/Http/Controllers/AcademyController.php` - 아카데미 컨트롤러
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-02-22
|
||||
298
features/approvals/README.md
Normal file
298
features/approvals/README.md
Normal file
@@ -0,0 +1,298 @@
|
||||
# 결재관리 시스템
|
||||
|
||||
> **작성일**: 2026-02-28
|
||||
> **상태**: Phase 2 구현 완료
|
||||
> **프로젝트**: SAM MNG (관리자 웹)
|
||||
> **우선순위**: 🔴 필수
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
SAM MNG 전자결재 시스템. 기안부터 최종 승인, 반려, 회수, 보류, 전결, 참조까지 기업 결재 프로세스를 디지털화한다.
|
||||
|
||||
### 1.2 문서 구조
|
||||
|
||||
| 문서 | 설명 |
|
||||
|------|------|
|
||||
| **README.md** (이 문서) | 시스템 전체 개요, 아키텍처, 상태 관리 |
|
||||
| [form-types.md](form-types.md) | 양식별 필드/JSON 구조/인터랙션 기술 명세 |
|
||||
| [workflows.md](workflows.md) | 상세 워크플로우 (승인/반려/회수/보류/전결/복사재기안) |
|
||||
| [api-reference.md](api-reference.md) | API 엔드포인트 명세 |
|
||||
| [ui-screens.md](ui-screens.md) | 화면별 UI 구성 및 동작 |
|
||||
| [db-changes-and-model-sync.md](db-changes-and-model-sync.md) | DB 변경사항 및 API/MNG 모델 동기화 현황 |
|
||||
|
||||
### 1.3 구현 현황
|
||||
|
||||
| Phase | 범위 | 상태 |
|
||||
|-------|------|------|
|
||||
| **Phase 1** | 순차결재, 기안/상신/승인/반려/회수 | ✅ 완료 |
|
||||
| **Phase 2** | 보류/해제, 전결, 참조 열람 추적, 복사 재기안 | ✅ 완료 |
|
||||
| **Phase 3** | 병렬결재, 위임(대결), 알림 | 미착수 |
|
||||
| **Phase 4** | ERP 연동, 결재 통계, 관리자 설정 | 미착수 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 아키텍처
|
||||
|
||||
### 2.1 기술 스택
|
||||
|
||||
| 계층 | 기술 | 설명 |
|
||||
|------|------|------|
|
||||
| 뷰 | Blade + HTMX + Alpine.js | 동적 UI, 부분 렌더링 |
|
||||
| API | Laravel Controller + Service | JSON API (내부용) |
|
||||
| 모델 | Eloquent ORM | Multi-tenant 스코프 |
|
||||
| DB | MySQL 8.0 | API 프로젝트에서 마이그레이션 관리 |
|
||||
|
||||
### 2.2 프로젝트 분리
|
||||
|
||||
```
|
||||
API (/home/aweso/sam/api)
|
||||
├── database/migrations/ ← 모든 결재 테이블 마이그레이션
|
||||
|
||||
MNG (/home/aweso/sam/mng)
|
||||
├── app/Models/Approvals/ ← 모델 (Approval, ApprovalStep, ApprovalForm, ApprovalLine, ApprovalDelegation)
|
||||
├── app/Services/ ← ApprovalService (비즈니스 로직)
|
||||
├── app/Http/Controllers/ ← ApprovalController (웹), ApprovalApiController (API)
|
||||
├── resources/views/approvals/ ← Blade 뷰
|
||||
└── routes/ ← 웹 라우트 + API 라우트
|
||||
```
|
||||
|
||||
### 2.3 핵심 클래스
|
||||
|
||||
```
|
||||
ApprovalService
|
||||
├── 목록 조회: getMyDrafts(), getPendingForMe(), getCompletedByMe(), getReferencesForMe()
|
||||
├── CRUD: createApproval(), updateApproval(), deleteApproval(), getApproval()
|
||||
├── 워크플로우: submit(), approve(), reject(), cancel(), hold(), releaseHold(), preDecide(), copyForRedraft()
|
||||
├── 참조: markAsRead()
|
||||
└── 유틸: getBadgeCounts(), getApprovalLines(), getApprovalForms(), saveApprovalSteps()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 데이터베이스
|
||||
|
||||
### 3.1 테이블 관계
|
||||
|
||||
```
|
||||
approval_forms (결재 양식)
|
||||
│ 1:N
|
||||
▼
|
||||
approvals (결재 문서)
|
||||
│ 1:N │ N:1 (self)
|
||||
▼ ▼
|
||||
approval_steps (결재 단계) approvals (parent_doc_id → 원본 문서)
|
||||
|
||||
approval_lines (결재선 템플릿) ← approvals.line_id 참조
|
||||
|
||||
approval_delegations (위임 설정) ← Phase 3 준비
|
||||
```
|
||||
|
||||
### 3.2 approvals (결재 문서)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `tenant_id` | BIGINT | 테넌트 격리 |
|
||||
| `document_number` | VARCHAR | `APR-YYMMDD-001` 형식 |
|
||||
| `form_id` | BIGINT FK | 양식 |
|
||||
| `line_id` | BIGINT FK NULL | 결재선 템플릿 |
|
||||
| `title` | VARCHAR(200) | 제목 |
|
||||
| `content` | JSON | 양식 필드 데이터 |
|
||||
| `body` | TEXT NULL | 본문 |
|
||||
| `status` | VARCHAR(20) | 문서 상태 (6가지) |
|
||||
| `is_urgent` | BOOLEAN | 긴급 여부 |
|
||||
| `drafter_id` | BIGINT FK | 기안자 |
|
||||
| `department_id` | BIGINT FK NULL | 기안 부서 |
|
||||
| `current_step` | INT | 현재 결재 단계 번호 |
|
||||
| `drafted_at` | TIMESTAMP NULL | 상신 일시 |
|
||||
| `completed_at` | TIMESTAMP NULL | 완료 일시 |
|
||||
| `recall_reason` | TEXT NULL | 회수 사유 |
|
||||
| `parent_doc_id` | BIGINT FK NULL | 재기안 원본 문서 |
|
||||
| `attachments` | JSON NULL | 첨부파일 |
|
||||
|
||||
### 3.3 approval_steps (결재 단계)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `approval_id` | BIGINT FK | 결재 문서 |
|
||||
| `step_order` | INT | 순서 (1, 2, 3...) |
|
||||
| `step_type` | VARCHAR | `approval`, `agreement`, `reference` |
|
||||
| `parallel_group` | INT NULL | 병렬 그룹 (Phase 3) |
|
||||
| `approver_id` | BIGINT FK | 결재자 |
|
||||
| `acted_by` | BIGINT FK NULL | 실제 처리자 (대결 시) |
|
||||
| `approver_name` | VARCHAR | 결재자명 스냅샷 |
|
||||
| `approver_department` | VARCHAR | 부서 스냅샷 |
|
||||
| `approver_position` | VARCHAR | 직급 스냅샷 |
|
||||
| `status` | VARCHAR(20) | 단계 상태 (5가지) |
|
||||
| `approval_type` | VARCHAR(20) | `normal`, `pre_decided`, `delegated` |
|
||||
| `comment` | TEXT NULL | 결재 의견 |
|
||||
| `acted_at` | TIMESTAMP NULL | 처리 일시 |
|
||||
| `is_read` | BOOLEAN | 참조 열람 여부 |
|
||||
| `read_at` | TIMESTAMP NULL | 열람 일시 |
|
||||
|
||||
### 3.4 approval_delegations (위임 설정, Phase 3)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `tenant_id` | BIGINT FK | |
|
||||
| `delegator_id` | BIGINT FK | 위임자 |
|
||||
| `delegate_id` | BIGINT FK | 대리인 |
|
||||
| `start_date` | DATE | 위임 시작일 |
|
||||
| `end_date` | DATE | 위임 종료일 |
|
||||
| `form_ids` | JSON NULL | 대상 양식 (NULL=전체) |
|
||||
| `notify_delegator` | BOOLEAN | 대결 시 보고 여부 |
|
||||
| `is_active` | BOOLEAN | 활성 여부 |
|
||||
| `reason` | VARCHAR(200) | 위임 사유 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 상태 관리
|
||||
|
||||
### 4.1 문서 상태 (6가지)
|
||||
|
||||
| 상태 | 코드 | 라벨 | 색상 | 설명 |
|
||||
|------|------|------|------|------|
|
||||
| 임시저장 | `draft` | 임시저장 | gray | 작성 중, 미상신 |
|
||||
| 진행 | `pending` | 진행 | blue | 결재선 순환 중 |
|
||||
| 완료 | `approved` | 완료 | green | 최종 승인 |
|
||||
| 반려 | `rejected` | 반려 | red | 결재자가 반려 |
|
||||
| 회수 | `cancelled` | 회수 | yellow | 기안자가 회수 |
|
||||
| 보류 | `on_hold` | 보류 | amber | 결재자가 보류 |
|
||||
|
||||
### 4.2 단계 상태 (5가지)
|
||||
|
||||
| 상태 | 코드 | 라벨 | 아이콘 | 설명 |
|
||||
|------|------|------|--------|------|
|
||||
| 대기 | `pending` | 대기 | 숫자 | 차례 아직 아님 |
|
||||
| 승인 | `approved` | 승인 | ✓ (녹색) | 승인 완료 |
|
||||
| 반려 | `rejected` | 반려 | ✗ (적색) | 반려 |
|
||||
| 건너뜀 | `skipped` | 건너뜀 | — (회색) | 전결/회수로 소멸 |
|
||||
| 보류 | `on_hold` | 보류 | ⏸ (노란) | 보류 중 |
|
||||
|
||||
### 4.3 결재 유형 (approval_type)
|
||||
|
||||
| 유형 | 코드 | 아이콘 | 설명 |
|
||||
|------|------|--------|------|
|
||||
| 일반결재 | `normal` | ✓ | 기본 승인 |
|
||||
| 전결 | `pre_decided` | ⚡ (남색) | 이후 단계 모두 건너뛰고 즉시 완료 |
|
||||
| 대결 | `delegated` | — | 대리인이 처리 (Phase 3) |
|
||||
|
||||
### 4.4 참여자 역할 (step_type)
|
||||
|
||||
| 역할 | 코드 | 의사결정 | 설명 |
|
||||
|------|------|---------|------|
|
||||
| 결재 | `approval` | ✅ 있음 | 승인/반려/보류/전결 가능 |
|
||||
| 합의 | `agreement` | ✅ 있음 | 타부서 동의 (승인/반려 가능) |
|
||||
| 참조 | `reference` | ❌ 없음 | 열람만 가능, 열람 추적 |
|
||||
|
||||
### 4.5 상태 전이 다이어그램
|
||||
|
||||
```
|
||||
┌─────────────────────────────┐
|
||||
│ │
|
||||
┌────────┐ submit() │ ┌─────────┐ │
|
||||
│ draft │────────────→│ │ pending │ │
|
||||
└────────┘ │ └────┬────┘ │
|
||||
▲ │ │ │
|
||||
│ │ ┌────┼─────────┬───────┐ │
|
||||
│ (수정 후 재상신) │ │ │ │ │ │
|
||||
│ │ │ approve() reject() hold()│
|
||||
│ │ │ │ │ │ │
|
||||
│ │ │ ▼ ▼ ▼ │
|
||||
│ │ │ 다음 step rejected on_hold│
|
||||
│ │ │ 또는 │ │ │
|
||||
│ │ │ approved │ releaseHold()
|
||||
│ │ │ │ │ │ │
|
||||
│ │ │ │ │ │ │
|
||||
│ │ └────┼────────┼───────┘ │
|
||||
│ │ │ │ │
|
||||
│ │ preDecide() │ │
|
||||
│ │ → approved │ │
|
||||
│ │ │ │ cancel() │
|
||||
│ │ │ │ │ │
|
||||
│ │ ▼ │ ▼ │
|
||||
│ │ ┌─────────┐ │ ┌──────────┐
|
||||
│ │ │approved │ │ │cancelled │
|
||||
│ │ └─────────┘ │ └──────────┘
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ │ copyForRedraft() │
|
||||
│ │ │ │ │
|
||||
└───────────────────┼───────┴────────┘ │
|
||||
(새 draft 생성) │ │
|
||||
│ copyForRedraft() │
|
||||
│◀──────────────────────┘
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 권한 매트릭스
|
||||
|
||||
### 5.1 누가 무엇을 할 수 있는가
|
||||
|
||||
| 액션 | 대상자 | 조건 |
|
||||
|------|--------|------|
|
||||
| **기안 작성** | 모든 사용자 | — |
|
||||
| **수정** | 기안자 | `draft` 또는 `rejected` |
|
||||
| **삭제** | 기안자 | `draft`만 |
|
||||
| **상신** | 기안자 | `draft` 또는 `rejected`, 결재선 1명 이상 |
|
||||
| **승인** | 현재 결재자 | `pending`, 자신이 현재 차례 |
|
||||
| **반려** | 현재 결재자 | `pending`, 사유 필수 |
|
||||
| **보류** | 현재 결재자 | `pending`, 사유 필수 |
|
||||
| **보류 해제** | 보류한 결재자 | `on_hold`, 자신이 보류한 건 |
|
||||
| **전결** | 현재 결재자 | `pending`, 이후 모든 단계 건너뜀 |
|
||||
| **회수** | 기안자 | `pending` 또는 `on_hold`, 첫 결재자 미처리 |
|
||||
| **복사 재기안** | 기안자 | `approved`, `rejected`, `cancelled` |
|
||||
| **참조 열람** | 참조자 | `reference` step 보유 |
|
||||
|
||||
### 5.2 회수 가능 조건 상세
|
||||
|
||||
```
|
||||
회수(cancel) 가능 여부 판단:
|
||||
|
||||
1. 문서 상태가 pending 또는 on_hold인가? → 아니면 불가
|
||||
2. 요청자가 기안자(drafter_id)인가? → 아니면 불가
|
||||
3. 첫 번째 결재자(approval/agreement)의 상태가 pending 또는 on_hold인가?
|
||||
→ 이미 approved/rejected이면 불가 (첫 결재자가 이미 처리)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 메뉴 구조
|
||||
|
||||
```
|
||||
결재관리
|
||||
├── 기안함 /approval-mgmt/drafts ← 내가 기안한 문서
|
||||
├── 결재 대기함 /approval-mgmt/pending ← 내가 결재해야 할 문서
|
||||
├── 처리 완료함 /approval-mgmt/completed ← 내가 결재한 문서
|
||||
└── 참조함 /approval-mgmt/references ← 참조 문서 (열람 추적)
|
||||
```
|
||||
|
||||
### 추가 페이지
|
||||
|
||||
| URL | 설명 |
|
||||
|-----|------|
|
||||
| `/approval-mgmt/create` | 기안 작성 |
|
||||
| `/approval-mgmt/{id}` | 상세 조회 |
|
||||
| `/approval-mgmt/{id}/edit` | 기안 수정 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 관련 문서
|
||||
|
||||
- [결재 양식 기술 명세](form-types.md) — 양식별 필드, JSON 구조, 인터랙션
|
||||
- [결재관리 워크플로우 상세](workflows.md) — 각 동작의 상세 흐름
|
||||
- [API 명세](api-reference.md) — 엔드포인트 목록 및 요청/응답 예시
|
||||
- [UI 화면 구성](ui-screens.md) — 화면별 UI 요소 및 동작
|
||||
- [기획서 원본](../../plans/approval-management-system-plan.md) — Phase 1~4 전체 기획
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-06
|
||||
594
features/approvals/api-reference.md
Normal file
594
features/approvals/api-reference.md
Normal file
@@ -0,0 +1,594 @@
|
||||
# 결재관리 API 명세
|
||||
|
||||
> **작성일**: 2026-02-28
|
||||
> **상태**: Phase 2 구현 완료
|
||||
> **Base URL**: `/api/admin/approvals`
|
||||
> **미들웨어**: `web`, `auth`, `hq.member`
|
||||
> **관련**: [README.md](README.md) | [워크플로우](workflows.md) | [UI 화면](ui-screens.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
모든 API는 JSON 응답을 반환한다. 인증은 세션 기반이며, CSRF 토큰이 필요하다.
|
||||
|
||||
### 1.1 공통 응답 형식
|
||||
|
||||
**성공:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "처리 메시지",
|
||||
"data": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
**실패 (400):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "에러 메시지"
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 공통 헤더
|
||||
|
||||
```
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
X-CSRF-TOKEN: {csrf_token}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 목록 조회 API
|
||||
|
||||
### 2.1 기안함
|
||||
|
||||
내가 기안한 문서 목록을 조회한다.
|
||||
|
||||
```
|
||||
GET /api/admin/approvals/drafts
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| 파라미터 | 타입 | 설명 |
|
||||
|---------|------|------|
|
||||
| `search` | string | 제목/문서번호 검색 |
|
||||
| `status` | string | 상태 필터 (`draft`, `pending`, `approved`, `rejected`, `cancelled`, `on_hold`) |
|
||||
| `is_urgent` | boolean | 긴급 문서만 |
|
||||
| `date_from` | date | 시작일 (YYYY-MM-DD) |
|
||||
| `date_to` | date | 종료일 (YYYY-MM-DD) |
|
||||
| `per_page` | int | 페이지당 건수 (기본 15) |
|
||||
| `page` | int | 페이지 번호 |
|
||||
|
||||
**응답:** Laravel 페이지네이션 형식
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"document_number": "APR-260228-001",
|
||||
"title": "휴가 신청",
|
||||
"status": "pending",
|
||||
"is_urgent": false,
|
||||
"form": { "id": 1, "name": "휴가신청서" },
|
||||
"steps": [...],
|
||||
"created_at": "2026-02-28T10:00:00",
|
||||
"drafted_at": "2026-02-28T10:05:00"
|
||||
}
|
||||
],
|
||||
"current_page": 1,
|
||||
"last_page": 3,
|
||||
"per_page": 15,
|
||||
"total": 42
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.2 결재 대기함
|
||||
|
||||
내가 현재 결재해야 할 문서 목록을 조회한다.
|
||||
|
||||
```
|
||||
GET /api/admin/approvals/pending
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| 파라미터 | 타입 | 설명 |
|
||||
|---------|------|------|
|
||||
| `search` | string | 제목/문서번호 검색 |
|
||||
| `is_urgent` | boolean | 긴급 문서만 |
|
||||
| `date_from` | date | 시작일 |
|
||||
| `date_to` | date | 종료일 |
|
||||
| `per_page` | int | 페이지당 건수 |
|
||||
|
||||
> 현재 사용자가 결재 차례인 문서만 표시된다. 이미 승인/반려한 문서는 표시되지 않는다.
|
||||
|
||||
---
|
||||
|
||||
### 2.3 처리 완료함
|
||||
|
||||
내가 승인 또는 반려한 문서 목록을 조회한다.
|
||||
|
||||
```
|
||||
GET /api/admin/approvals/completed
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| 파라미터 | 타입 | 설명 |
|
||||
|---------|------|------|
|
||||
| `search` | string | 제목/문서번호 검색 |
|
||||
| `status` | string | 상태 필터 |
|
||||
| `date_from` | date | 시작일 |
|
||||
| `date_to` | date | 종료일 |
|
||||
| `per_page` | int | 페이지당 건수 |
|
||||
|
||||
---
|
||||
|
||||
### 2.4 참조함
|
||||
|
||||
내가 참조자로 지정된 문서 목록을 조회한다.
|
||||
|
||||
```
|
||||
GET /api/admin/approvals/references
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| 파라미터 | 타입 | 설명 |
|
||||
|---------|------|------|
|
||||
| `search` | string | 제목/문서번호 검색 |
|
||||
| `is_read` | string | 열람 상태 필터 (`true`=열람완료, `false`=미열람) |
|
||||
| `date_from` | date | 시작일 |
|
||||
| `date_to` | date | 종료일 |
|
||||
| `per_page` | int | 페이지당 건수 |
|
||||
|
||||
---
|
||||
|
||||
## 3. CRUD API
|
||||
|
||||
### 3.1 상세 조회
|
||||
|
||||
```
|
||||
GET /api/admin/approvals/{id}
|
||||
```
|
||||
|
||||
**응답:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"tenant_id": 1,
|
||||
"document_number": "APR-260228-001",
|
||||
"form_id": 1,
|
||||
"line_id": null,
|
||||
"title": "휴가 신청",
|
||||
"content": {},
|
||||
"body": "2월 27일~28일 연차 사용 신청합니다.",
|
||||
"status": "pending",
|
||||
"is_urgent": false,
|
||||
"drafter_id": 10,
|
||||
"department_id": 3,
|
||||
"current_step": 2,
|
||||
"drafted_at": "2026-02-28T10:05:00",
|
||||
"completed_at": null,
|
||||
"recall_reason": null,
|
||||
"parent_doc_id": null,
|
||||
"form": { "id": 1, "name": "휴가신청서" },
|
||||
"drafter": { "id": 10, "name": "홍길동" },
|
||||
"line": null,
|
||||
"steps": [
|
||||
{
|
||||
"id": 1,
|
||||
"step_order": 1,
|
||||
"step_type": "approval",
|
||||
"approver_id": 20,
|
||||
"approver_name": "김과장",
|
||||
"approver_department": "경영지원팀",
|
||||
"approver_position": "과장",
|
||||
"status": "approved",
|
||||
"approval_type": "normal",
|
||||
"comment": "승인합니다.",
|
||||
"acted_at": "2026-02-28T11:00:00",
|
||||
"is_read": false,
|
||||
"read_at": null
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"step_order": 2,
|
||||
"step_type": "approval",
|
||||
"approver_id": 30,
|
||||
"approver_name": "박부장",
|
||||
"approver_department": "경영지원팀",
|
||||
"approver_position": "부장",
|
||||
"status": "pending",
|
||||
"approval_type": "normal",
|
||||
"comment": null,
|
||||
"acted_at": null,
|
||||
"is_read": false,
|
||||
"read_at": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 생성 (임시저장)
|
||||
|
||||
```
|
||||
POST /api/admin/approvals
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"form_id": 1,
|
||||
"title": "휴가 신청",
|
||||
"body": "2월 27일~28일 연차 사용",
|
||||
"is_urgent": false,
|
||||
"steps": [
|
||||
{ "user_id": 20, "step_type": "approval" },
|
||||
{ "user_id": 30, "step_type": "approval" },
|
||||
{ "user_id": 40, "step_type": "reference" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
|
||||
| 필드 | 규칙 |
|
||||
|------|------|
|
||||
| `form_id` | required, exists:approval_forms,id |
|
||||
| `title` | required, string, max:200 |
|
||||
| `body` | nullable, string |
|
||||
| `is_urgent` | boolean |
|
||||
| `steps` | nullable, array |
|
||||
| `steps.*.user_id` | required_with:steps, exists:users,id |
|
||||
| `steps.*.step_type` | required_with:steps, in:approval,agreement,reference |
|
||||
|
||||
**응답 (201):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "결재 문서가 저장되었습니다.",
|
||||
"data": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.3 수정
|
||||
|
||||
```
|
||||
PUT /api/admin/approvals/{id}
|
||||
```
|
||||
|
||||
> `draft` 또는 `rejected` 상태에서만 수정 가능
|
||||
|
||||
**Request Body:** (생성과 동일, 모든 필드 선택)
|
||||
|
||||
**Validation:**
|
||||
|
||||
| 필드 | 규칙 |
|
||||
|------|------|
|
||||
| `title` | sometimes, string, max:200 |
|
||||
| `body` | nullable, string |
|
||||
| `is_urgent` | boolean |
|
||||
| `steps` | nullable, array |
|
||||
|
||||
---
|
||||
|
||||
### 3.4 삭제
|
||||
|
||||
```
|
||||
DELETE /api/admin/approvals/{id}
|
||||
```
|
||||
|
||||
> `draft` 상태에서만 삭제 가능
|
||||
|
||||
**응답:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "결재 문서가 삭제되었습니다."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 워크플로우 API
|
||||
|
||||
### 4.1 상신
|
||||
|
||||
```
|
||||
POST /api/admin/approvals/{id}/submit
|
||||
```
|
||||
|
||||
> 기안자가 `draft`/`rejected` 문서를 결재 요청한다.
|
||||
|
||||
**Request Body:** 없음
|
||||
|
||||
**응답:** `{ "success": true, "message": "결재가 상신되었습니다.", "data": {...} }`
|
||||
|
||||
---
|
||||
|
||||
### 4.2 승인
|
||||
|
||||
```
|
||||
POST /api/admin/approvals/{id}/approve
|
||||
```
|
||||
|
||||
> 현재 결재자가 승인한다.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"comment": "승인합니다." // 선택
|
||||
}
|
||||
```
|
||||
|
||||
**응답:** `{ "success": true, "message": "승인되었습니다.", "data": {...} }`
|
||||
|
||||
---
|
||||
|
||||
### 4.3 반려
|
||||
|
||||
```
|
||||
POST /api/admin/approvals/{id}/reject
|
||||
```
|
||||
|
||||
> 현재 결재자가 반려한다. 사유 필수.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"comment": "예산 초과로 반려합니다." // 필수
|
||||
}
|
||||
```
|
||||
|
||||
**Validation:** `comment` — required, string, max:1000
|
||||
|
||||
**응답:** `{ "success": true, "message": "반려되었습니다.", "data": {...} }`
|
||||
|
||||
---
|
||||
|
||||
### 4.4 회수
|
||||
|
||||
```
|
||||
POST /api/admin/approvals/{id}/cancel
|
||||
```
|
||||
|
||||
> 기안자가 `pending`/`on_hold` 문서를 회수한다. 첫 결재자 미처리 시에만 가능.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"recall_reason": "내용 수정 필요" // 선택
|
||||
}
|
||||
```
|
||||
|
||||
**응답:** `{ "success": true, "message": "결재가 회수되었습니다.", "data": {...} }`
|
||||
|
||||
---
|
||||
|
||||
### 4.5 보류
|
||||
|
||||
```
|
||||
POST /api/admin/approvals/{id}/hold
|
||||
```
|
||||
|
||||
> 현재 결재자가 결재를 보류한다. 사유 필수.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"comment": "추가 자료 검토 필요" // 필수
|
||||
}
|
||||
```
|
||||
|
||||
**Validation:** `comment` — required, string, max:1000
|
||||
|
||||
**응답:** `{ "success": true, "message": "보류되었습니다.", "data": {...} }`
|
||||
|
||||
---
|
||||
|
||||
### 4.6 보류 해제
|
||||
|
||||
```
|
||||
POST /api/admin/approvals/{id}/release-hold
|
||||
```
|
||||
|
||||
> 보류한 결재자가 보류를 해제한다.
|
||||
|
||||
**Request Body:** 없음
|
||||
|
||||
**응답:** `{ "success": true, "message": "보류가 해제되었습니다.", "data": {...} }`
|
||||
|
||||
---
|
||||
|
||||
### 4.7 전결
|
||||
|
||||
```
|
||||
POST /api/admin/approvals/{id}/pre-decide
|
||||
```
|
||||
|
||||
> 현재 결재자가 이후 모든 결재를 건너뛰고 최종 승인한다.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"comment": "전결 처리합니다." // 선택
|
||||
}
|
||||
```
|
||||
|
||||
**응답:** `{ "success": true, "message": "전결 처리되었습니다.", "data": {...} }`
|
||||
|
||||
---
|
||||
|
||||
### 4.8 복사 재기안
|
||||
|
||||
```
|
||||
POST /api/admin/approvals/{id}/copy
|
||||
```
|
||||
|
||||
> 기안자가 `approved`/`rejected`/`cancelled` 문서를 복사하여 새 draft를 생성한다.
|
||||
|
||||
**Request Body:** 없음
|
||||
|
||||
**응답:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "문서가 복사되었습니다.",
|
||||
"data": {
|
||||
"id": 15,
|
||||
"document_number": "APR-260228-003",
|
||||
"parent_doc_id": 1,
|
||||
"status": "draft",
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 응답의 `data.id`를 사용하여 `/approval-mgmt/{id}/edit`로 이동한다.
|
||||
|
||||
---
|
||||
|
||||
### 4.9 참조 열람 추적
|
||||
|
||||
```
|
||||
POST /api/admin/approvals/{id}/mark-read
|
||||
```
|
||||
|
||||
> 참조자가 문서를 열람했음을 기록한다.
|
||||
|
||||
**Request Body:** 없음
|
||||
|
||||
**응답:** `{ "success": true, "message": "열람 처리되었습니다." }`
|
||||
|
||||
---
|
||||
|
||||
## 5. 유틸리티 API
|
||||
|
||||
### 5.1 결재선 템플릿 목록
|
||||
|
||||
```
|
||||
GET /api/admin/approvals/lines
|
||||
```
|
||||
|
||||
**응답:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "id": 1, "name": "일반 결재선", "steps": [...] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.2 양식 목록
|
||||
|
||||
```
|
||||
GET /api/admin/approvals/forms
|
||||
```
|
||||
|
||||
**응답:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "id": 1, "name": "휴가신청서", "is_active": true }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.3 미처리 건수 (뱃지)
|
||||
|
||||
```
|
||||
GET /api/admin/approvals/badge-counts
|
||||
```
|
||||
|
||||
**응답:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"pending": 3,
|
||||
"draft": 1,
|
||||
"reference_unread": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 필드 | 설명 |
|
||||
|------|------|
|
||||
| `pending` | 내가 결재해야 할 문서 수 |
|
||||
| `draft` | 내 임시저장 문서 수 |
|
||||
| `reference_unread` | 미열람 참조 문서 수 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 라우트 전체 목록
|
||||
|
||||
| Method | Path | 컨트롤러 메서드 | 이름 | 설명 |
|
||||
|--------|------|---------------|------|------|
|
||||
| GET | `/drafts` | `drafts` | `drafts` | 기안함 |
|
||||
| GET | `/pending` | `pending` | `pending` | 결재 대기함 |
|
||||
| GET | `/completed` | `completed` | `completed` | 처리 완료함 |
|
||||
| GET | `/references` | `references` | `references` | 참조함 |
|
||||
| GET | `/lines` | `lines` | `lines` | 결재선 템플릿 |
|
||||
| GET | `/forms` | `forms` | `forms` | 양식 목록 |
|
||||
| GET | `/badge-counts` | `badgeCounts` | `badge-counts` | 뱃지 건수 |
|
||||
| POST | `/` | `store` | `store` | 생성 |
|
||||
| GET | `/{id}` | `show` | `show` | 상세 |
|
||||
| PUT | `/{id}` | `update` | `update` | 수정 |
|
||||
| DELETE | `/{id}` | `destroy` | `destroy` | 삭제 |
|
||||
| POST | `/{id}/submit` | `submit` | `submit` | 상신 |
|
||||
| POST | `/{id}/approve` | `approve` | `approve` | 승인 |
|
||||
| POST | `/{id}/reject` | `reject` | `reject` | 반려 |
|
||||
| POST | `/{id}/cancel` | `cancel` | `cancel` | 회수 |
|
||||
| POST | `/{id}/hold` | `hold` | `hold` | 보류 |
|
||||
| POST | `/{id}/release-hold` | `releaseHold` | `release-hold` | 보류 해제 |
|
||||
| POST | `/{id}/pre-decide` | `preDecide` | `pre-decide` | 전결 |
|
||||
| POST | `/{id}/copy` | `copyForRedraft` | `copy` | 복사 재기안 |
|
||||
| POST | `/{id}/mark-read` | `markAsRead` | `mark-read` | 열람 추적 |
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [README.md](README.md) — 시스템 전체 개요
|
||||
- [워크플로우 상세](workflows.md) — 각 동작의 상세 흐름
|
||||
- [UI 화면 구성](ui-screens.md) — 화면별 동작
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-02-28
|
||||
286
features/approvals/db-changes-and-model-sync.md
Normal file
286
features/approvals/db-changes-and-model-sync.md
Normal file
@@ -0,0 +1,286 @@
|
||||
# 결재관리 DB 변경사항 및 API 모델 동기화 현황
|
||||
|
||||
> **작성일**: 2026-03-09
|
||||
> **상태**: 조사 완료
|
||||
> **관련**: [README.md](README.md) | [API 명세](api-reference.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
2026-02-27 ~ 2026-03-05 기간에 결재관리 테이블에 대규모 컬럼 추가가 이루어졌다. 이 문서는 변경된 DB 스키마와 API/MNG 프로젝트 간 모델 동기화 상태를 기록한다.
|
||||
|
||||
### 1.2 핵심 발견
|
||||
|
||||
- 마이그레이션 **15개** 실행 (API 프로젝트에서 관리)
|
||||
- MNG 모델: ✅ 모든 신규 컬럼 반영 완료
|
||||
- API 모델: ❌ **`$fillable`/`$casts` 미반영** — 오류 원인 가능성
|
||||
|
||||
---
|
||||
|
||||
## 2. 마이그레이션 변경 타임라인
|
||||
|
||||
### 2.1 Phase 2 확장 (2026-02-27)
|
||||
|
||||
| 마이그레이션 파일 | 대상 테이블 | 작업 |
|
||||
|------------------|-----------|------|
|
||||
| `add_columns_to_approvals_table` | `approvals` | `line_id`, `body`, `is_urgent`, `department_id` 추가 |
|
||||
| `add_columns_to_approval_steps_table` | `approval_steps` | `approver_name`, `approver_department`, `approver_position` 추가 |
|
||||
| `add_phase2_columns_to_approval_steps_table` | `approval_steps` | `parallel_group`, `acted_by`, `approval_type` 추가 |
|
||||
| `add_phase2_columns_to_approvals_table` | `approvals` | `recall_reason`, `parent_doc_id` 추가 |
|
||||
| `create_approval_delegations_table` | `approval_delegations` | 위임 테이블 신규 생성 |
|
||||
| `add_linkable_to_approvals_table` | `approvals` | `linkable_type`, `linkable_id` 추가 (다형성) |
|
||||
|
||||
### 2.2 도메인 연동 (2026-02-28)
|
||||
|
||||
| 마이그레이션 파일 | 대상 테이블 | 작업 |
|
||||
|------------------|-----------|------|
|
||||
| `add_approval_id_to_leaves_table` | `leaves` | `approval_id` FK 추가 |
|
||||
| `insert_leave_approval_form` | `approval_forms` | 휴가신청 양식 데이터 등록 |
|
||||
|
||||
### 2.3 양식 확장 (2026-03-03 ~ 03-04)
|
||||
|
||||
| 마이그레이션 파일 | 대상 테이블 | 작업 |
|
||||
|------------------|-----------|------|
|
||||
| `insert_attendance_approval_forms` | `approval_forms` | 근태신청, 사유서 양식 등록 |
|
||||
| `add_body_template_to_approval_forms` | `approval_forms` | `body_template` 컬럼 추가 |
|
||||
| `insert_expense_approval_form` | `approval_forms` | 지출결의서 양식 + body_template 등록 |
|
||||
| `update_expense_approval_form_body_template` | `approval_forms` | 지출결의서 body_template 고도화 |
|
||||
|
||||
### 2.4 추적 기능 (2026-03-05)
|
||||
|
||||
| 마이그레이션 파일 | 대상 테이블 | 작업 |
|
||||
|------------------|-----------|------|
|
||||
| `add_drafter_read_at_to_approvals_table` | `approvals` | `drafter_read_at` 추가 |
|
||||
| `add_resubmit_count_to_approvals_table` | `approvals` | `resubmit_count` 추가 |
|
||||
| `add_rejection_history_to_approvals_table` | `approvals` | `rejection_history` 추가 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 추가된 컬럼 상세
|
||||
|
||||
### 3.1 `approvals` 테이블 (11개 컬럼 추가)
|
||||
|
||||
| 컬럼 | 타입 | 기본값 | 추가일 | 용도 |
|
||||
|------|------|--------|--------|------|
|
||||
| `line_id` | BIGINT FK NULL | NULL | 02-27 | 결재선 템플릿 참조 |
|
||||
| `body` | LONGTEXT NULL | NULL | 02-27 | 문서 본문 HTML |
|
||||
| `is_urgent` | BOOLEAN | false | 02-27 | 긴급 여부 |
|
||||
| `department_id` | BIGINT NULL | NULL | 02-27 | 기안 부서 |
|
||||
| `recall_reason` | TEXT NULL | NULL | 02-27 | 회수 사유 |
|
||||
| `parent_doc_id` | BIGINT FK NULL | NULL | 02-27 | 재기안 원본 문서 |
|
||||
| `linkable_type` | VARCHAR NULL | NULL | 02-27 | 다형성 모델 타입 |
|
||||
| `linkable_id` | BIGINT NULL | NULL | 02-27 | 다형성 모델 ID |
|
||||
| `drafter_read_at` | TIMESTAMP NULL | NULL | 03-05 | 기안자 열람 시각 |
|
||||
| `resubmit_count` | TINYINT UNSIGNED | 0 | 03-05 | 재상신 횟수 |
|
||||
| `rejection_history` | JSON NULL | NULL | 03-05 | 반려 이력 배열 |
|
||||
|
||||
### 3.2 `approval_steps` 테이블 (6개 컬럼 추가)
|
||||
|
||||
| 컬럼 | 타입 | 기본값 | 추가일 | 용도 |
|
||||
|------|------|--------|--------|------|
|
||||
| `approver_name` | VARCHAR(50) NULL | NULL | 02-27 | 결재자명 스냅샷 |
|
||||
| `approver_department` | VARCHAR(100) NULL | NULL | 02-27 | 결재자 부서 스냅샷 |
|
||||
| `approver_position` | VARCHAR(50) NULL | NULL | 02-27 | 결재자 직급 스냅샷 |
|
||||
| `parallel_group` | INT NULL | NULL | 02-27 | 병렬 결재 그룹 (Phase 3) |
|
||||
| `acted_by` | BIGINT FK NULL | NULL | 02-27 | 실제 처리자 (대결) |
|
||||
| `approval_type` | VARCHAR(20) | 'normal' | 02-27 | normal/pre_decided/delegated |
|
||||
|
||||
### 3.3 `approval_forms` 테이블 (1개 컬럼 추가)
|
||||
|
||||
| 컬럼 | 타입 | 기본값 | 추가일 | 용도 |
|
||||
|------|------|--------|--------|------|
|
||||
| `body_template` | TEXT NULL | NULL | 03-04 | HTML 양식 렌더링 템플릿 |
|
||||
|
||||
### 3.4 `approval_delegations` 테이블 (신규 생성)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `tenant_id` | BIGINT FK | 테넌트 격리 |
|
||||
| `delegator_id` | BIGINT FK | 위임자 |
|
||||
| `delegate_id` | BIGINT FK | 대리인 |
|
||||
| `start_date` | DATE | 위임 시작일 |
|
||||
| `end_date` | DATE | 위임 종료일 |
|
||||
| `form_ids` | JSON NULL | 대상 양식 (NULL=전체) |
|
||||
| `notify_delegator` | BOOLEAN | 대결 시 보고 여부 |
|
||||
| `is_active` | BOOLEAN | 활성 여부 |
|
||||
| `reason` | VARCHAR(200) | 위임 사유 |
|
||||
|
||||
---
|
||||
|
||||
## 4. API/MNG 모델 동기화 현황
|
||||
|
||||
### 4.1 Approval 모델 비교
|
||||
|
||||
| 항목 | MNG (`mng/app/Models/Approvals/Approval.php`) | API (`api/app/Models/Tenants/Approval.php`) |
|
||||
|------|:---:|:---:|
|
||||
| `line_id` in $fillable | ✅ | ❌ |
|
||||
| `body` in $fillable | ✅ | ❌ |
|
||||
| `is_urgent` in $fillable/$casts | ✅ boolean | ❌ |
|
||||
| `department_id` in $fillable | ✅ | ❌ |
|
||||
| `recall_reason` in $fillable | ✅ | ❌ |
|
||||
| `parent_doc_id` in $fillable | ✅ | ❌ |
|
||||
| `linkable_type/id` in $fillable | ✅ | ✅ |
|
||||
| `drafter_read_at` in $fillable/$casts | ✅ datetime | ❌ |
|
||||
| `resubmit_count` in $fillable/$casts | ✅ integer | ❌ |
|
||||
| `rejection_history` in $fillable/$casts | ✅ array | ❌ |
|
||||
|
||||
### 4.2 ApprovalStep 모델 비교
|
||||
|
||||
| 항목 | MNG | API |
|
||||
|------|:---:|:---:|
|
||||
| `approver_name` in $fillable | ✅ | ❌ |
|
||||
| `approver_department` in $fillable | ✅ | ❌ |
|
||||
| `approver_position` in $fillable | ✅ | ❌ |
|
||||
| `parallel_group` in $fillable | ✅ | ❌ |
|
||||
| `acted_by` in $fillable | ✅ | ❌ |
|
||||
| `approval_type` in $fillable | ✅ | ❌ |
|
||||
|
||||
### 4.3 ApprovalForm 모델 비교
|
||||
|
||||
| 항목 | MNG | API |
|
||||
|------|:---:|:---:|
|
||||
| `body_template` in $fillable | ✅ | ❌ |
|
||||
|
||||
### 4.4 ApprovalDelegation 모델
|
||||
|
||||
| 항목 | MNG | API |
|
||||
|------|:---:|:---:|
|
||||
| 모델 파일 존재 | ✅ | ❌ 미생성 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 오류 영향 분석
|
||||
|
||||
### 5.1 API 모델 미반영으로 인한 잠재적 오류
|
||||
|
||||
API 프로젝트의 모델 `$fillable`에 신규 컬럼이 누락되어, API 엔드포인트를 통한 결재 문서 처리 시 다음 오류가 발생할 수 있다:
|
||||
|
||||
| 증상 | 원인 | 영향 범위 |
|
||||
|------|------|----------|
|
||||
| `create()`/`update()` 시 신규 필드 저장 안 됨 | `$fillable` 미포함 → mass assignment 차단 | API v1 결재 CRUD |
|
||||
| JSON 필드(`rejection_history`) 문자열로 반환 | `$casts` 미정의 → 타입 변환 안 됨 | API 응답 파싱 오류 |
|
||||
| `drafter_read_at` 날짜 비교 실패 | `$casts` datetime 미정의 → Carbon 미변환 | 열람 추적 기능 |
|
||||
| `is_urgent` 비교 오류 | `$casts` boolean 미정의 → 문자열 비교 | 긴급 필터링 |
|
||||
| 위임(delegation) 기능 완전 불가 | 모델 자체 미생성 | Phase 3 기능 전체 |
|
||||
|
||||
### 5.2 MNG는 정상
|
||||
|
||||
MNG 프로젝트의 모델은 모든 신규 컬럼이 `$fillable`, `$casts`, `$attributes`에 반영되어 있으며, `ApprovalService`에서 정상 사용 중이다.
|
||||
|
||||
```
|
||||
MNG 정상 동작 확인 기능:
|
||||
✅ 반려 이력 저장 (rejection_history)
|
||||
✅ 재상신 횟수 추적 (resubmit_count)
|
||||
✅ 기안자 열람 추적 (drafter_read_at)
|
||||
✅ 결재자 스냅샷 저장 (approver_name/department/position)
|
||||
✅ 전결 처리 (approval_type = pre_decided)
|
||||
✅ 회수 사유 기록 (recall_reason)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 수정 필요 파일 목록
|
||||
|
||||
### 6.1 API 모델 업데이트 필요
|
||||
|
||||
| 파일 | 수정 내용 |
|
||||
|------|----------|
|
||||
| `api/app/Models/Tenants/Approval.php` | `$fillable`에 9개 필드, `$casts`에 4개 필드 추가 |
|
||||
| `api/app/Models/Tenants/ApprovalStep.php` | `$fillable`에 6개 필드 추가 |
|
||||
| `api/app/Models/Tenants/ApprovalForm.php` | `$fillable`에 `body_template` 추가 |
|
||||
| `api/app/Models/Tenants/ApprovalDelegation.php` | 모델 파일 신규 생성 |
|
||||
|
||||
### 6.2 Approval.php 수정 상세
|
||||
|
||||
**`$fillable` 추가 필요:**
|
||||
|
||||
```php
|
||||
'line_id',
|
||||
'body',
|
||||
'is_urgent',
|
||||
'department_id',
|
||||
'recall_reason',
|
||||
'parent_doc_id',
|
||||
'drafter_read_at',
|
||||
'resubmit_count',
|
||||
'rejection_history',
|
||||
```
|
||||
|
||||
**`$casts` 추가 필요:**
|
||||
|
||||
```php
|
||||
'drafter_read_at' => 'datetime',
|
||||
'resubmit_count' => 'integer',
|
||||
'rejection_history' => 'array',
|
||||
'is_urgent' => 'boolean',
|
||||
```
|
||||
|
||||
### 6.3 ApprovalStep.php 수정 상세
|
||||
|
||||
**`$fillable` 추가 필요:**
|
||||
|
||||
```php
|
||||
'approver_name',
|
||||
'approver_department',
|
||||
'approver_position',
|
||||
'parallel_group',
|
||||
'acted_by',
|
||||
'approval_type',
|
||||
```
|
||||
|
||||
### 6.4 ApprovalForm.php 수정 상세
|
||||
|
||||
**`$fillable` 추가 필요:**
|
||||
|
||||
```php
|
||||
'body_template',
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 연관 테이블 참조 변경
|
||||
|
||||
결재 시스템과 연동된 다른 테이블의 변경사항:
|
||||
|
||||
| 테이블 | 추가 컬럼 | 추가일 | 용도 |
|
||||
|--------|----------|--------|------|
|
||||
| `leaves` | `approval_id` (BIGINT FK) | 02-28 | 휴가 ↔ 결재 연동 |
|
||||
| `purchases` | `approval_id` (BIGINT FK) | (기존) | 구매 ↔ 결재 연동 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 등록된 결재 양식 (13종)
|
||||
|
||||
2026-02-28 ~ 03-07 기간에 마이그레이션으로 등록된 양식:
|
||||
|
||||
| 코드 | 양식명 | 카테고리 | 등록일 |
|
||||
|------|--------|---------|--------|
|
||||
| `leave` | 휴가신청서 | request | 02-28 |
|
||||
| `attendance_request` | 근태신청서 | request | 03-03 |
|
||||
| `reason_report` | 사유서 | request | 03-03 |
|
||||
| `expense` | 지출결의서 | expense | 03-04 |
|
||||
| `employment_cert` | 재직증명서 | request | 03-05 |
|
||||
| `career_cert` | 경력증명서 | request | 03-05 |
|
||||
| `appointment_cert` | 위촉증명서 | request | 03-05 |
|
||||
| `resignation` | 사직서 | request | 03-06 |
|
||||
| `seal_usage` | 사용인감계 | request | 03-06 |
|
||||
| `delegation` | 위임장 | request | 03-06 |
|
||||
| `board_minutes` | 이사회의사록 | request | 03-06 |
|
||||
| `quotation` | 견적서 | request | 03-06 |
|
||||
| `official_letter` | 공문서 | request | 03-07 |
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [결재관리 시스템 개요](README.md) — 아키텍처, 상태 관리, 권한
|
||||
- [API 명세](api-reference.md) — 20개 엔드포인트 상세
|
||||
- [워크플로우 상세](workflows.md) — 승인/반려/회수/보류/전결 흐름
|
||||
- [기획서 원본](../../plans/approval-management-system-plan.md) — Phase 1~4 전체 기획
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-09
|
||||
999
features/approvals/form-types.md
Normal file
999
features/approvals/form-types.md
Normal file
@@ -0,0 +1,999 @@
|
||||
# 결재 양식 기술 명세
|
||||
|
||||
> **작성일**: 2026-03-06
|
||||
> **상태**: Phase 2 구현 완료
|
||||
> **관련**: [README.md](README.md) | [워크플로우](workflows.md) | [API 명세](api-reference.md) | [UI 화면](ui-screens.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
SAM MNG 결재관리의 **기안함 양식** 기술 명세. 각 양식의 필드 구조, JSON Content 데이터 형식, UI 인터랙션, 특수 로직을 정의한다.
|
||||
|
||||
### 1.2 양식 목록
|
||||
|
||||
| 코드 | 양식명 | 분류 | Blade 파일 | 설명 |
|
||||
|------|--------|------|------------|------|
|
||||
| `BUSINESS_DRAFT` | 업무기안서 | 일반 | (body 편집기) | 일반 업무 보고·요청 |
|
||||
| `leave` | 휴가신청 | 인사/근태 | `_leave-form.blade.php` | 연차, 휴가, 근태 신청 |
|
||||
| `attendance_request` | 근태신청 | 인사/근태 | `_leave-form.blade.php` | 외근, 출장, 조퇴 등 |
|
||||
| `reason_report` | 사유서 | 인사/근태 | `_leave-form.blade.php` | 지각, 결근 등 사유 소명 |
|
||||
| `resignation` | 사직서 | 인사/근태 | `_resignation-form.blade.php` | 퇴직 서류 |
|
||||
| `employment_cert` | 재직증명서 | 증명서 | `_certificate-form.blade.php` | 재직 증명 발급 (PDF) |
|
||||
| `career_cert` | 경력증명서 | 증명서 | `_career-cert-form.blade.php` | 경력 증명 발급 (PDF) |
|
||||
| `appointment_cert` | 위촉증명서 | 증명서 | `_appointment-cert-form.blade.php` | 위촉/임명 증명 발급 (PDF) |
|
||||
| `pr_expense` | 지출품의서 | 품의 | `_purchase-request-form.blade.php` | 지출 전 사전 승인 |
|
||||
| `pr_contract` | 계약체결품의서 | 품의 | `_purchase-request-form.blade.php` | 계약 체결 전 승인 |
|
||||
| `pr_purchase` | 구매품의서 | 품의 | `_purchase-request-form.blade.php` | 물품 구매 전 승인 |
|
||||
| `pr_trip` | 출장품의서 | 품의 | `_purchase-request-form.blade.php` | 출장 계획 승인 |
|
||||
| `pr_settlement` | 비용정산품의서 | 품의 | `_purchase-request-form.blade.php` | 비용 정산 승인 |
|
||||
| `expense` | 지출결의서 | 재무 | `_expense-form.blade.php` | 법인카드/송금/자동이체 지출 |
|
||||
|
||||
### 1.3 공통 구조
|
||||
|
||||
모든 양식은 동일한 패턴으로 동작한다:
|
||||
|
||||
```
|
||||
양식 선택 (form_id)
|
||||
↓
|
||||
양식별 Blade 파셜 렌더링 (create.blade.php 내 조건부 display)
|
||||
↓
|
||||
사용자 입력 → Alpine.js / JavaScript 인터랙션
|
||||
↓
|
||||
getFormData() → JSON content 생성
|
||||
↓
|
||||
ApprovalService::createApproval() → Approval.content (JSON 컬럼) 저장
|
||||
```
|
||||
|
||||
### 1.4 양식 선택 UI (2단계 분류 + 설명 카드)
|
||||
|
||||
양식 선택은 **2단계 드롭다운 + 설명 카드** 레이아웃으로 구성된다.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ 양식 * │
|
||||
│ ┌──── 30% ────────┐ ┌─────────────── 70% ───────────────────────────┐ │
|
||||
│ │ 📋 품의 ▼ │ │ ┌─────────────────────────────────────────┐ │ │
|
||||
│ │ │ │ │ 📋 지출품의서 │ │ │
|
||||
│ │ 지출품의서 ▼ │ │ │ 지출이 발생하기 전 사전 승인을 받는 │ │ │
|
||||
│ │ │ │ │ 문서입니다. 예산 범위 내에서 지출 항목과 │ │ │
|
||||
│ │ │ │ │ 금액을 기재하여 사전에 승락을 받습니다. │ │ │
|
||||
│ └──────────────────┘ │ └─────────────────────────────────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 1단계: 분류 선택 (`form_category`)
|
||||
|
||||
| 분류 | 아이콘 | 포함 양식 |
|
||||
|------|--------|----------|
|
||||
| 일반 | 📄 | 업무기안서 |
|
||||
| 인사/근태 | 👤 | 휴가신청, 근태신청, 사유서, 사직서 |
|
||||
| 증명서 | 📜 | 재직증명서, 경력증명서, 위촉증명서 |
|
||||
| 품의 | 📋 | 지출품의서, 계약체결품의서, 구매품의서, 출장품의서, 비용정산품의서 |
|
||||
| 재무 | 💰 | 지출결의서 |
|
||||
|
||||
#### 2단계: 양식 선택 (`form_id`)
|
||||
|
||||
- 1단계 분류 선택 시 해당 분류에 속하지 않는 양식은 `display:none` + `disabled`
|
||||
- 분류 내 첫 번째 양식 자동 선택
|
||||
|
||||
#### 설명 카드 (`formDescriptions`)
|
||||
|
||||
- 양식 선택 시 우측에 해당 양식의 아이콘/제목/설명 텍스트 표시
|
||||
- 14종 전체 양식에 대한 설명 정의 (create/edit 공통)
|
||||
- 색상: 양식별 Tailwind 테마 색상 (`border-*-200 bg-*-50`)
|
||||
|
||||
#### 핵심 JavaScript 함수
|
||||
|
||||
| 함수 | 설명 |
|
||||
|------|------|
|
||||
| `buildCategoryOptions()` | 사용 가능한 카테고리만 `form_category` 옵션으로 생성 |
|
||||
| `filterFormsByCategory(cat)` | 선택된 분류 외 양식 옵션 숨김/비활성화 |
|
||||
| `selectCategoryByFormId(formId)` | formId로 카테고리 역산하여 자동 선택 |
|
||||
| `updateFormDescription(formId)` | 설명 카드 DOM 업데이트 |
|
||||
|
||||
### 1.5 파일 구조
|
||||
|
||||
```
|
||||
resources/views/approvals/
|
||||
├── create.blade.php ← 기안 작성 (2단계 양식 선택 + 설명 카드 + 동적 폼)
|
||||
├── edit.blade.php ← 기안 수정 (create와 동일한 2단계 선택 구조)
|
||||
├── show.blade.php ← 상세 조회 (양식별 조회 컴포넌트)
|
||||
└── partials/
|
||||
├── _leave-form.blade.php ← 휴가신청 폼
|
||||
├── _expense-form.blade.php ← 지출결의서 폼
|
||||
├── _expense-show.blade.php ← 지출결의서 조회
|
||||
├── _purchase-request-form.blade.php ← 품의서 5종 통합 폼 (Alpine.js)
|
||||
├── _purchase-request-show.blade.php ← 품의서 5종 통합 조회
|
||||
├── _certificate-form.blade.php ← 재직증명서 폼
|
||||
├── _certificate-show.blade.php ← 재직증명서 조회
|
||||
├── _career-cert-form.blade.php ← 경력증명서 폼
|
||||
├── _career-cert-show.blade.php ← 경력증명서 조회
|
||||
├── _appointment-cert-form.blade.php ← 위촉증명서 폼
|
||||
├── _appointment-cert-show.blade.php ← 위촉증명서 조회
|
||||
├── _resignation-form.blade.php ← 사직서 폼
|
||||
├── _resignation-show.blade.php ← 사직서 조회
|
||||
├── _approval-stamp-table.blade.php ← 결재 도장 테이블
|
||||
└── _approval-line-editor.blade.php ← 결재선 편집기
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 휴가신청 (leave)
|
||||
|
||||
### 2.1 폼 필드
|
||||
|
||||
| 필드 ID | 라벨 | 타입 | 필수 | 기본값 | 설명 |
|
||||
|---------|------|------|------|--------|------|
|
||||
| `leave-user-id` | 신청자 | select | 필수 | `auth()->id()` | 활성 사원 목록 |
|
||||
| `leave-type` | 유형 | select | 필수 | - | 휴가/근태신청/사유서 |
|
||||
| `leave-start-date` | 시작일 | date | 필수 | - | `YYYY-MM-DD` |
|
||||
| `leave-end-date` | 종료일 | date | 필수 | - | `YYYY-MM-DD` |
|
||||
| `leave-reason` | 사유 | textarea | 선택 | - | 자유 텍스트 |
|
||||
|
||||
### 2.2 Content JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"user_id": "10",
|
||||
"leave_type": "연차",
|
||||
"start_date": "2026-03-06",
|
||||
"end_date": "2026-03-07",
|
||||
"reason": "개인 사유"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 특수 로직
|
||||
|
||||
- **자동 선택**: 로그인 사용자가 기본 선택 (`auth()->id()`)
|
||||
- **직원 목록**: `$employees` Props로 전달 (활성 사원만)
|
||||
- **단순 구조**: Alpine.js 없이 Blade 폼으로 구현
|
||||
|
||||
---
|
||||
|
||||
## 3. 지출결의서 (expense)
|
||||
|
||||
### 3.1 폼 구조 (Alpine.js 기반)
|
||||
|
||||
```javascript
|
||||
x-data="expenseForm(initialData, authUserName, initialFiles, cardsData, accountsData)"
|
||||
```
|
||||
|
||||
### 3.2 기본 정보 필드
|
||||
|
||||
| 필드 | 라벨 | 타입 | 필수 | 기본값 |
|
||||
|------|------|------|------|--------|
|
||||
| `expense_type` | 지출형식 | radio | 필수 | `corporate_card` |
|
||||
| `tax_invoice` | 세금계산서 | radio | 필수 | `normal` |
|
||||
| `write_date` | 작성일자 | date | 선택 | 오늘 |
|
||||
| `approval_date` | 결재일자 | date | 선택 | 오늘 |
|
||||
| `department` | 부서 | text | 선택 | `경리부` |
|
||||
| `writer_name` | 이름 | text | 선택 | 인증 사용자명 |
|
||||
|
||||
### 3.3 지출형식별 선택
|
||||
|
||||
| 지출형식 | 코드 | 연결 데이터 |
|
||||
|---------|------|------------|
|
||||
| 법인카드 | `corporate_card` | `$cards` → `selected_card` |
|
||||
| 송금 | `transfer` | `$accounts` → `selected_account` |
|
||||
| 자동이체 출금 | `auto_transfer` | `$accounts` → `selected_account` |
|
||||
| 현금/가지급정산 | `cash_advance` | 없음 |
|
||||
|
||||
**법인카드 선택 시 저장 구조:**
|
||||
|
||||
```json
|
||||
{
|
||||
"selected_card": {
|
||||
"id": 1,
|
||||
"card_name": "삼성카드",
|
||||
"card_company": "삼성",
|
||||
"card_number_last4": "1234",
|
||||
"card_holder_name": "홍길동"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**계좌 선택 시 저장 구조:**
|
||||
|
||||
```json
|
||||
{
|
||||
"selected_account": {
|
||||
"id": 1,
|
||||
"bank_name": "국민은행",
|
||||
"account_number": "123-456-789012",
|
||||
"account_holder": "주일기업"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 세금계산서 옵션
|
||||
|
||||
| 옵션 | 코드 |
|
||||
|------|------|
|
||||
| 일반 | `normal` |
|
||||
| 이월발행 | `deferred` |
|
||||
| 없음 | `none` |
|
||||
|
||||
### 3.5 내역 테이블
|
||||
|
||||
**동적 rows** (`.items` 배열):
|
||||
|
||||
| 필드 | 라벨 | 타입 | 설명 |
|
||||
|------|------|------|------|
|
||||
| `date` | 일자 | date | `YYYY-MM-DD` |
|
||||
| `description` | 적요 | text | 지출 설명 |
|
||||
| `amount` | 금액 | number | 콤마 제거 정수 |
|
||||
| `vendor` | 거래처 | text | Autocomplete 검색 |
|
||||
| `vendor_id` | 거래처 ID | hidden | API 연결 ID |
|
||||
| `vendor_biz_no` | 사업자번호 | hidden | 자동 채움 |
|
||||
| `bank` | 은행명 | text | 수동 입력 |
|
||||
| `account_no` | 계좌번호 | text | 수동 입력 |
|
||||
| `depositor` | 예금주 | text | 수동 입력 |
|
||||
| `remark` | 비고 | text | 메모 |
|
||||
|
||||
### 3.6 Content JSON (전체)
|
||||
|
||||
```json
|
||||
{
|
||||
"expense_type": "corporate_card",
|
||||
"tax_invoice": "normal",
|
||||
"write_date": "2026-03-06",
|
||||
"approval_date": "2026-03-06",
|
||||
"department": "경리부",
|
||||
"writer_name": "홍길동",
|
||||
"items": [
|
||||
{
|
||||
"date": "2026-03-05",
|
||||
"description": "사무용품 구매",
|
||||
"amount": 150000,
|
||||
"vendor": "오피스디포",
|
||||
"vendor_id": 123,
|
||||
"vendor_biz_no": "123-45-67890",
|
||||
"bank": "",
|
||||
"account_no": "",
|
||||
"depositor": "",
|
||||
"remark": ""
|
||||
}
|
||||
],
|
||||
"total_amount": 150000,
|
||||
"attachment_memo": "영수증 첨부",
|
||||
"selected_card": { ... },
|
||||
"selected_account": null
|
||||
}
|
||||
```
|
||||
|
||||
### 3.7 특수 기능
|
||||
|
||||
#### 거래처 검색 (Autocomplete)
|
||||
|
||||
```
|
||||
입력 → 250ms 디바운싱 → API 호출 → 드롭다운 렌더링
|
||||
|
||||
API: /barobill/tax-invoice/search-partners?keyword=...
|
||||
키보드: ↑↓(네비게이션), Enter(선택), Esc(닫기)
|
||||
마우스: 항목 클릭(선택)
|
||||
```
|
||||
|
||||
#### 금액 입력 포맷팅
|
||||
|
||||
```
|
||||
입력 시: 콤마 제거 → 정수 저장 (parseMoney)
|
||||
표시 시: 콤마 포맷 (formatMoney)
|
||||
합계: totalAmount getter → footer 실시간 업데이트
|
||||
```
|
||||
|
||||
#### 파일 업로드
|
||||
|
||||
```
|
||||
드래그 앤 드롭 + 파일 입력
|
||||
최대: 20MB
|
||||
형식: pdf, doc, docx, xls, xlsx, ppt, pptx, txt, jpg, jpeg, png, gif, zip, rar
|
||||
API: POST /api/admin/approvals/upload-file
|
||||
진행률: XHR 업로드 이벤트
|
||||
```
|
||||
|
||||
#### 카드/계좌 연동
|
||||
|
||||
```
|
||||
카드 선택 → 모든 내역 행에 "결제카드" 자동 표시
|
||||
계좌 선택 → 모든 내역 행에 "은행/계좌/예금주" 자동 채움
|
||||
```
|
||||
|
||||
### 3.8 조회 화면 (_expense-show.blade.php)
|
||||
|
||||
| 섹션 | 내용 |
|
||||
|------|------|
|
||||
| 기본 정보 | 지출형식, 세금계산서, 작성일, 결재일, 부서, 이름 |
|
||||
| 선택 카드/계좌 | 유색 박스로 표시 |
|
||||
| 내역 테이블 | 읽기 전용, `number_format()` 금액 |
|
||||
| 첨부서류 메모 | `whitespace-pre-wrap` |
|
||||
| 첨부파일 목록 | 다운로드 링크 + 파일 크기 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 증명서 양식 공통
|
||||
|
||||
### 4.1 공통 패턴
|
||||
|
||||
모든 증명서 양식은 동일한 패턴을 따른다:
|
||||
|
||||
```
|
||||
사원 선택 → loadXxxInfo(userId) → API 호출 → 읽기 전용 필드 자동 채움
|
||||
↓
|
||||
일부 필드만 수정 가능
|
||||
↓
|
||||
미리보기 모달 (인쇄 가능)
|
||||
```
|
||||
|
||||
### 4.2 공통 함수
|
||||
|
||||
| 함수 | 설명 |
|
||||
|------|------|
|
||||
| `loadXxxInfo(userId)` | 사원 선택 시 인적/재직 정보 로드 |
|
||||
| `openXxxPreview()` | 미리보기 모달 열기 |
|
||||
| `printXxxPreview()` | 미리보기 인쇄 (`window.print()`) |
|
||||
| `closeXxxPreview()` | 미리보기 닫기 |
|
||||
| `onXxxPurposeChange()` | 용도 선택 시 직접입력 필드 표시 |
|
||||
|
||||
### 4.3 조회 화면 공통
|
||||
|
||||
- 읽기 전용 필드 표시
|
||||
- PDF 다운로드: `route('api.admin.approvals.cert-pdf', $approval->id)`
|
||||
|
||||
---
|
||||
|
||||
## 5. 재직증명서 (employment_cert)
|
||||
|
||||
### 5.1 폼 필드
|
||||
|
||||
| 섹션 | 필드 ID | 라벨 | 타입 | 수정 | 설명 |
|
||||
|------|---------|------|------|------|------|
|
||||
| 인적사항 | `cert-name` | 성명 | text | readonly | DB 자동 채움 |
|
||||
| | `cert-resident` | 주민등록번호 | text | readonly | DB 자동 채움 |
|
||||
| | `cert-address` | 주소 | text | editable | 직접 입력 |
|
||||
| 재직사항 | `cert-company` | 회사명 | text | readonly | DB 자동 채움 |
|
||||
| | `cert-business-num` | 사업자번호 | text | readonly | DB 자동 채움 |
|
||||
| | `cert-department` | 근무부서 | text | readonly | DB 자동 채움 |
|
||||
| | `cert-position` | 직급 | text | readonly | DB 자동 채움 |
|
||||
| | `cert-hire-date` | 재직기간 | text | readonly | DB 자동 채움 |
|
||||
| 발급정보 | `cert-purpose-select` | 사용용도 | select | editable | 드롭다운 선택 |
|
||||
| | (custom) | 기타 용도 | text | editable | "기타" 선택 시 표시 |
|
||||
| | `cert-issue-date` | 발급일 | text | readonly | `now()->format('Y-m-d')` |
|
||||
|
||||
### 5.2 Content JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "홍길동",
|
||||
"resident_number": "900101-1XXXXXX",
|
||||
"address": "서울특별시 강남구",
|
||||
"company_name": "(주)코드브릿지엑스",
|
||||
"business_num": "123-45-67890",
|
||||
"department": "개발팀",
|
||||
"position": "과장",
|
||||
"hire_date": "2020-03-01",
|
||||
"purpose": "은행 제출용",
|
||||
"issue_date": "2026-03-06"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 경력증명서 (career_cert)
|
||||
|
||||
### 6.1 폼 필드 (재직증명서 대비 추가/변경)
|
||||
|
||||
| 섹션 | 필드 ID | 라벨 | 타입 | 수정 | 설명 |
|
||||
|------|---------|------|------|------|------|
|
||||
| 인적사항 | `cc-birth-date` | 생년월일 | text | readonly | DB 자동 채움 |
|
||||
| 경력사항 | `cc-ceo-name` | 대표자 | text | readonly | DB 자동 채움 |
|
||||
| | `cc-phone` | 대표전화 | text | readonly | DB 자동 채움 |
|
||||
| | `cc-company-address` | 소재지 | text | readonly | DB 자동 채움 |
|
||||
| | `cc-department` | 소속부서 | text | readonly | DB 자동 채움 |
|
||||
| | `cc-position` | 직위/직급 | text | readonly | DB 자동 채움 |
|
||||
| | `cc-hire-date` | 근무기간 시작 | text | readonly | DB 자동 채움 |
|
||||
| | `cc-resign-date` | 근무기간 종료 | date | editable | 직접 입력 |
|
||||
| | `cc-job-description` | 담당업무 | text | editable | 직접 입력 |
|
||||
| 발급정보 | 용도 | select | editable | + "이직 제출용" 옵션 |
|
||||
|
||||
### 6.2 Content JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "홍길동",
|
||||
"birth_date": "1990-01-01",
|
||||
"address": "서울특별시 강남구",
|
||||
"company_name": "(주)코드브릿지엑스",
|
||||
"business_num": "123-45-67890",
|
||||
"ceo_name": "김대표",
|
||||
"phone": "02-1234-5678",
|
||||
"company_address": "서울특별시 강남구 테헤란로",
|
||||
"department": "개발팀",
|
||||
"position": "과장",
|
||||
"hire_date": "2020-03-01",
|
||||
"resign_date": "2026-02-28",
|
||||
"job_description": "웹 애플리케이션 개발",
|
||||
"purpose": "이직 제출용",
|
||||
"issue_date": "2026-03-06"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 위촉증명서 (appointment_cert)
|
||||
|
||||
### 7.1 폼 필드
|
||||
|
||||
| 섹션 | 필드 ID | 라벨 | 타입 | 수정 | 설명 |
|
||||
|------|---------|------|------|------|------|
|
||||
| 인적사항 | `ac-name` | 성명 | text | readonly | DB 자동 채움 |
|
||||
| | `ac-resident` | 주민등록번호 | text | readonly | DB 자동 채움 |
|
||||
| | `ac-department` | 소속 | text | readonly | DB 자동 채움 |
|
||||
| | `ac-phone` | 연락처 | text | editable | 직접 입력 |
|
||||
| 위촉정보 | `ac-hire-date` | 위촉기간 시작 | text | readonly | DB 자동 채움 |
|
||||
| | `ac-resign-date` | 위촉기간 종료 | date | editable | 직접 입력 |
|
||||
| | `ac-contract-type` | 계약자격 | text | editable | 직접 입력 |
|
||||
| 발급정보 | `ac-purpose-select` | 용도 | select | editable | 드롭다운 선택 |
|
||||
| | `ac-issue-date` | 발급일 | text | readonly | 자동 설정 |
|
||||
| (숨김) | `ac-company-name` | 회사명 | hidden | - | 미리보기용 |
|
||||
| | `ac-ceo-name` | 대표자명 | hidden | - | 미리보기용 |
|
||||
|
||||
### 7.2 Content JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "홍길동",
|
||||
"resident_number": "900101-1XXXXXX",
|
||||
"department": "기술자문팀",
|
||||
"phone": "010-1234-5678",
|
||||
"hire_date": "2024-01-01",
|
||||
"resign_date": "2026-12-31",
|
||||
"contract_type": "기술자문위원",
|
||||
"purpose": "관공서 제출용",
|
||||
"issue_date": "2026-03-06"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 사직서 (resignation)
|
||||
|
||||
### 8.1 폼 필드
|
||||
|
||||
| 섹션 | 필드 ID | 라벨 | 타입 | 수정 | 필수 |
|
||||
|------|---------|------|------|------|------|
|
||||
| 인적사항 | `rg-department` | 소속 | text | readonly | - |
|
||||
| | `rg-position` | 직위 | text | readonly | - |
|
||||
| | `rg-name` | 성명 | text | readonly | - |
|
||||
| | `rg-resident` | 주민등록번호 | text | readonly | - |
|
||||
| | `rg-hire-date` | 입사일 | text | readonly | - |
|
||||
| | `rg-resign-date` | 퇴사(예정)일 | date | editable | 필수 |
|
||||
| | `rg-address` | 주소 | text | editable | - |
|
||||
| 사직사유 | `rg-reason-select` | 사유 | select | editable | 필수 |
|
||||
| | (custom) | 기타 사유 | text | editable | - |
|
||||
| 제출일 | `rg-issue-date` | 제출일 | text | readonly | - |
|
||||
|
||||
### 8.2 사직사유 옵션
|
||||
|
||||
| 옵션 |
|
||||
|------|
|
||||
| 일신상의 사유 |
|
||||
| 가사 사정 |
|
||||
| 건강상의 이유 |
|
||||
| 진학/학업 |
|
||||
| 이직 |
|
||||
| 기타 (직접입력) |
|
||||
|
||||
### 8.3 Content JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"department": "개발팀",
|
||||
"position": "대리",
|
||||
"name": "홍길동",
|
||||
"resident_number": "900101-1XXXXXX",
|
||||
"hire_date": "2020-03-01",
|
||||
"resign_date": "2026-04-01",
|
||||
"address": "서울특별시 강남구",
|
||||
"reason": "이직",
|
||||
"issue_date": "2026-03-06"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 품의서 5종 공통 (_purchase-request-form/show)
|
||||
|
||||
### 9.1 통합 Alpine.js 컴포넌트
|
||||
|
||||
품의서 5종은 **단일 Blade 파일**(`_purchase-request-form.blade.php`)에서 `prType` 프로퍼티로 동적 전환된다.
|
||||
|
||||
```javascript
|
||||
x-data="purchaseRequestForm(initialData, authUserName, initialFiles)"
|
||||
```
|
||||
|
||||
#### 타입 전환 메커니즘
|
||||
|
||||
```
|
||||
create.blade.php → switchFormMode()
|
||||
↓
|
||||
code.startsWith('pr_') 감지
|
||||
↓
|
||||
#purchase-request-form-container display: block
|
||||
↓
|
||||
setTimeout(50ms) → _x_dataStack[0].setPrType(code)
|
||||
↓
|
||||
Alpine.js x-if 분기 → 해당 폼 렌더링
|
||||
```
|
||||
|
||||
#### prType 코드 및 라벨
|
||||
|
||||
| prType | 라벨 | 색상 |
|
||||
|--------|------|------|
|
||||
| `pr_expense` | 지출품의서 | `bg-orange-50 text-orange-700` |
|
||||
| `pr_contract` | 계약체결품의서 | `bg-purple-50 text-purple-700` |
|
||||
| `pr_purchase` | 구매품의서 | `bg-blue-50 text-blue-700` |
|
||||
| `pr_trip` | 출장품의서 | `bg-green-50 text-green-700` |
|
||||
| `pr_settlement` | 비용정산품의서 | `bg-teal-50 text-teal-700` |
|
||||
|
||||
### 9.2 공통 필드 (모든 품의서)
|
||||
|
||||
| 필드 | 라벨 | 타입 | 기본값 |
|
||||
|------|------|------|--------|
|
||||
| `write_date` | 작성일자 | date | 오늘 |
|
||||
| `department` | 요청부서 | text | - |
|
||||
| `writer_name` | 요청자 | text | 인증 사용자명 |
|
||||
| `attachment_memo` | 첨부서류 메모 | textarea | - |
|
||||
| `files` | 파일 업로드 | file[] | - |
|
||||
|
||||
### 9.3 공통 함수
|
||||
|
||||
| 함수 | 설명 |
|
||||
|------|------|
|
||||
| `setPrType(type)` | 외부에서 prType 설정 (switchFormMode에서 호출) |
|
||||
| `getFormData()` | prType별 다른 JSON 구조 반환 (base에 `pr_type` 포함) |
|
||||
| `addItem()` | 내역 행 추가 |
|
||||
| `removeItem(index)` | 내역 행 삭제 |
|
||||
| `formatMoney(val)` | 숫자 → 콤마 포맷 |
|
||||
| `parseMoney(str)` | 콤마 문자열 → 정수 |
|
||||
| `prVendorSearch(target, fieldName)` | 범용 거래처 Autocomplete 검색 |
|
||||
|
||||
### 9.4 조회 화면 분기 (show.blade.php)
|
||||
|
||||
```php
|
||||
// show.blade.php에서 pr_ prefix로 분기
|
||||
@if(str_starts_with($approval->form?->code ?? '', 'pr_'))
|
||||
@include('approvals.partials._purchase-request-show', ['content' => $content])
|
||||
@endif
|
||||
```
|
||||
|
||||
`_purchase-request-show.blade.php`에서 `$content['pr_type']`으로 5종 분기 렌더링.
|
||||
|
||||
---
|
||||
|
||||
## 10. 지출품의서 (pr_expense)
|
||||
|
||||
### 10.1 추가 필드
|
||||
|
||||
| 필드 | 라벨 | 타입 | 필수 |
|
||||
|------|------|------|------|
|
||||
| `expense_category` | 지출항목 | text | 선택 |
|
||||
| `usage_date` | 사용일자 | date | 선택 |
|
||||
| `purpose` | 사용목적 | textarea | 필수 |
|
||||
|
||||
### 10.2 내역 테이블
|
||||
|
||||
| 컬럼 | 라벨 | 타입 |
|
||||
|------|------|------|
|
||||
| `description` | 항목 | text |
|
||||
| `amount` | 금액 | number (콤마 포맷) |
|
||||
| `remark` | 비고 | text |
|
||||
|
||||
### 10.3 Content JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"pr_type": "pr_expense",
|
||||
"write_date": "2026-03-06",
|
||||
"department": "개발팀",
|
||||
"writer_name": "홍길동",
|
||||
"expense_category": "사무용품",
|
||||
"usage_date": "2026-03-05",
|
||||
"purpose": "업무용 모니터 구매",
|
||||
"items": [
|
||||
{ "description": "27인치 모니터", "amount": 350000, "remark": "LG전자" }
|
||||
],
|
||||
"total_amount": 350000,
|
||||
"attachment_memo": "견적서 첨부"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 계약체결품의서 (pr_contract)
|
||||
|
||||
### 11.1 추가 필드
|
||||
|
||||
| 필드 | 라벨 | 타입 | 필수 |
|
||||
|------|------|------|------|
|
||||
| `contract_party` | 계약상대방 | text + Autocomplete | 필수 |
|
||||
| `contract_party_biz_no` | 사업자번호 | text (자동) | - |
|
||||
| `contract_content` | 계약내용 | textarea | 필수 |
|
||||
| `contract_period_start` | 계약기간 시작 | date | 선택 |
|
||||
| `contract_period_end` | 계약기간 종료 | date | 선택 |
|
||||
| `contract_amount` | 계약금액 | number (콤마) | 필수 |
|
||||
| `contract_conditions` | 주요조건 | textarea | 선택 |
|
||||
|
||||
### 11.2 Content JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"pr_type": "pr_contract",
|
||||
"write_date": "2026-03-06",
|
||||
"department": "경영지원팀",
|
||||
"writer_name": "홍길동",
|
||||
"contract_party": "(주)에이비씨",
|
||||
"contract_party_biz_no": "123-45-67890",
|
||||
"contract_content": "연간 IT 유지보수 계약",
|
||||
"contract_period_start": "2026-04-01",
|
||||
"contract_period_end": "2027-03-31",
|
||||
"contract_amount": 12000000,
|
||||
"contract_conditions": "월 1회 정기점검, 장애 발생 시 4시간 내 대응",
|
||||
"attachment_memo": "계약서 초안 첨부"
|
||||
}
|
||||
```
|
||||
|
||||
### 11.3 특수 로직
|
||||
|
||||
- **거래처 검색**: `prVendorSearch(formData, 'contract_party')` — 계약상대방 필드에 Autocomplete 적용
|
||||
- 선택 시 `contract_party_biz_no` 자동 채움
|
||||
|
||||
---
|
||||
|
||||
## 12. 구매품의서 (pr_purchase)
|
||||
|
||||
### 12.1 추가 필드
|
||||
|
||||
| 필드 | 라벨 | 타입 | 필수 |
|
||||
|------|------|------|------|
|
||||
| `vendor` | 납품업체 | text + Autocomplete | 선택 |
|
||||
| `vendor_biz_no` | 사업자번호 | text (자동) | - |
|
||||
| `delivery_date` | 납품예정일 | date | 선택 |
|
||||
| `delivery_location` | 납품장소 | text | 선택 |
|
||||
|
||||
### 12.2 내역 테이블
|
||||
|
||||
| 컬럼 | 라벨 | 타입 |
|
||||
|------|------|------|
|
||||
| `name` | 품목 | text |
|
||||
| `spec` | 규격 | text |
|
||||
| `quantity` | 수량 | number |
|
||||
| `unit_price` | 단가 | number (콤마) |
|
||||
| `amount` | 금액 | number (자동: 수량×단가) |
|
||||
| `remark` | 비고 | text |
|
||||
|
||||
### 12.3 Content JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"pr_type": "pr_purchase",
|
||||
"write_date": "2026-03-06",
|
||||
"department": "생산팀",
|
||||
"writer_name": "홍길동",
|
||||
"vendor": "(주)공급사",
|
||||
"vendor_biz_no": "987-65-43210",
|
||||
"delivery_date": "2026-03-20",
|
||||
"delivery_location": "본사 1층 창고",
|
||||
"items": [
|
||||
{ "name": "A4용지", "spec": "80g 500매", "quantity": 10, "unit_price": 25000, "amount": 250000, "remark": "" }
|
||||
],
|
||||
"total_amount": 250000,
|
||||
"attachment_memo": ""
|
||||
}
|
||||
```
|
||||
|
||||
### 12.4 특수 로직
|
||||
|
||||
- **금액 자동 계산**: `quantity × unit_price → amount` (x-effect 반응)
|
||||
- **거래처 검색**: `prVendorSearch(formData, 'vendor')` — 납품업체 필드에 Autocomplete 적용
|
||||
|
||||
---
|
||||
|
||||
## 13. 출장품의서 (pr_trip)
|
||||
|
||||
### 13.1 추가 필드
|
||||
|
||||
| 필드 | 라벨 | 타입 | 필수 |
|
||||
|------|------|------|------|
|
||||
| `destination` | 출장지 | text | 필수 |
|
||||
| `trip_period_start` | 출장기간 시작 | date | 필수 |
|
||||
| `trip_period_end` | 출장기간 종료 | date | 필수 |
|
||||
| `trip_purpose` | 출장목적 | textarea | 필수 |
|
||||
|
||||
### 13.2 일정표 (items)
|
||||
|
||||
| 컬럼 | 라벨 | 타입 |
|
||||
|------|------|------|
|
||||
| `date` | 일자 | date |
|
||||
| `schedule` | 일정 | text |
|
||||
| `remark` | 비고 | text |
|
||||
|
||||
### 13.3 경비 내역 (expenses)
|
||||
|
||||
| 필드 | 라벨 | 타입 |
|
||||
|------|------|------|
|
||||
| `transport` | 교통비 | number (콤마) |
|
||||
| `accommodation` | 숙박비 | number (콤마) |
|
||||
| `meals` | 식비 | number (콤마) |
|
||||
| `others` | 기타 | number (콤마) |
|
||||
| (자동) | 합계 | number (합산) |
|
||||
|
||||
### 13.4 Content JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"pr_type": "pr_trip",
|
||||
"write_date": "2026-03-06",
|
||||
"department": "영업팀",
|
||||
"writer_name": "홍길동",
|
||||
"destination": "부산 해운대",
|
||||
"trip_period_start": "2026-03-10",
|
||||
"trip_period_end": "2026-03-11",
|
||||
"trip_purpose": "거래처 방문 및 현장 점검",
|
||||
"items": [
|
||||
{ "date": "2026-03-10", "schedule": "거래처 미팅", "remark": "오전 10시" },
|
||||
{ "date": "2026-03-11", "schedule": "현장 점검 및 복귀", "remark": "" }
|
||||
],
|
||||
"expenses": {
|
||||
"transport": 120000,
|
||||
"accommodation": 80000,
|
||||
"meals": 40000,
|
||||
"others": 0
|
||||
},
|
||||
"total_amount": 240000,
|
||||
"attachment_memo": ""
|
||||
}
|
||||
```
|
||||
|
||||
### 13.5 조회 화면 특수 구조
|
||||
|
||||
- **일정표**: 테이블 형태로 일자/일정/비고 렌더링
|
||||
- **경비 카드**: 교통비/숙박비/식비/기타 4개 항목 + 합계를 카드 그리드로 표시
|
||||
|
||||
---
|
||||
|
||||
## 14. 비용정산품의서 (pr_settlement)
|
||||
|
||||
### 14.1 추가 필드
|
||||
|
||||
| 필드 | 라벨 | 타입 | 필수 |
|
||||
|------|------|------|------|
|
||||
| `settlement_period_start` | 정산기간 시작 | date | 선택 |
|
||||
| `settlement_period_end` | 정산기간 종료 | date | 선택 |
|
||||
| `payment_method` | 지급방법 | radio | 필수 |
|
||||
|
||||
### 14.2 지급방법 옵션
|
||||
|
||||
| 값 | 라벨 |
|
||||
|----|------|
|
||||
| `corporate_card` | 법인카드 사용 |
|
||||
| `personal_advance` | 개인 선지출 (환급 요청) |
|
||||
|
||||
### 14.3 내역 테이블
|
||||
|
||||
| 컬럼 | 라벨 | 타입 |
|
||||
|------|------|------|
|
||||
| `date` | 사용일자 | date |
|
||||
| `description` | 항목 | text |
|
||||
| `amount` | 금액 | number (콤마) |
|
||||
| `remark` | 비고 | text |
|
||||
|
||||
### 14.4 Content JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"pr_type": "pr_settlement",
|
||||
"write_date": "2026-03-06",
|
||||
"department": "개발팀",
|
||||
"writer_name": "홍길동",
|
||||
"settlement_period_start": "2026-02-01",
|
||||
"settlement_period_end": "2026-02-28",
|
||||
"payment_method": "personal_advance",
|
||||
"items": [
|
||||
{ "date": "2026-02-15", "description": "택시비", "amount": 25000, "remark": "야근 귀가" },
|
||||
{ "date": "2026-02-20", "description": "회의 다과", "amount": 15000, "remark": "팀 미팅" }
|
||||
],
|
||||
"total_amount": 40000,
|
||||
"attachment_memo": "영수증 첨부"
|
||||
}
|
||||
```
|
||||
|
||||
### 14.5 조회 화면 특수 구조
|
||||
|
||||
- **지급방법 표시**: `corporate_card` → "법인카드 사용", `personal_advance` → "개인 선지출 (환급 요청)"
|
||||
- 해당 라벨을 뱃지 형태로 표시
|
||||
|
||||
---
|
||||
|
||||
## 15. 결재 도장 테이블 (_approval-stamp-table.blade.php)
|
||||
|
||||
### 15.1 구조
|
||||
|
||||
전통 한글 결재 양식의 도장 테이블을 구현한다.
|
||||
|
||||
```
|
||||
┌──────┬────────┬────────┬────────┐
|
||||
│ │ 과장 │ 부장 │ 이사 │ ← 1행: 직급 헤더
|
||||
│ 결재 ├────────┼────────┼────────┤
|
||||
│ │ [승인] │ [대기] │ [대기] │ ← 2행: 서명/도장 영역
|
||||
│ ├────────┼────────┼────────┤
|
||||
│ │ 김과장 │ 박부장 │ 이이사 │ ← 3행: 이름 + 처리일
|
||||
│ │ 03/06 │ │ │
|
||||
└──────┴────────┴────────┴────────┘
|
||||
```
|
||||
|
||||
### 15.2 상태별 표시
|
||||
|
||||
| 상태 | approval_type | 표시 | 색상 |
|
||||
|------|---------------|------|------|
|
||||
| 승인 | `normal` | 빨간 원형 "승인" | `bg-red-500` |
|
||||
| 전결 | `pre_decided` | 파란 원형 "전결" | `bg-blue-500` |
|
||||
| 반려 | - | 빨간 원형 "반려" | `bg-red-500` |
|
||||
| 보류 | - | 주황 원형 "보류" | `bg-amber-500` |
|
||||
| 건너뜀 | - | 회색 "-" | `bg-gray-300` |
|
||||
|
||||
---
|
||||
|
||||
## 16. 결재선 편집기 (_approval-line-editor.blade.php)
|
||||
|
||||
### 16.1 2패널 구조
|
||||
|
||||
```
|
||||
┌─────────────────────┬─────────────────────┐
|
||||
│ 인원 목록 │ 결재선 │
|
||||
│ │ │
|
||||
│ [검색 input] │ [템플릿 선택 ▼] │
|
||||
│ │ │
|
||||
│ ▼ 개발팀 │ ① 김과장 (결재) [✗] │
|
||||
│ 홍길동 과장 [+] │ ② 박부장 (합의) [✗] │
|
||||
│ 김영희 대리 [+] │ ③ 이대리 (참조) [✗] │
|
||||
│ │ │
|
||||
│ ▼ 경영지원팀 │ (드래그로 순서 변경) │
|
||||
│ 박부장 부장 [+] │ │
|
||||
│ │ │
|
||||
├─────────────────────┴─────────────────────┤
|
||||
│ 결재: 1명 합의: 1명 참조: 1명 합계: 3명 │
|
||||
└───────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 16.2 기능
|
||||
|
||||
| 기능 | 설명 |
|
||||
|------|------|
|
||||
| **인원 검색** | 이름/부서 실시간 검색 |
|
||||
| **부서별 접기** | 부서 헤더 클릭으로 인원 접기/펼치기 |
|
||||
| **드래그 정렬** | SortableJS로 결재선 순서 변경 |
|
||||
| **유형 선택** | 각 단계별 approval/agreement/reference 선택 |
|
||||
| **템플릿 로드** | 저장된 결재선 템플릿 드롭다운 |
|
||||
|
||||
### 16.3 데이터 소스
|
||||
|
||||
```
|
||||
API: /api/admin/tenant-users/list
|
||||
|
||||
응답:
|
||||
[
|
||||
{
|
||||
"department_id": 1,
|
||||
"department_name": "개발팀",
|
||||
"users": [
|
||||
{ "id": 10, "name": "홍길동", "position": "과장", "job_title": "팀장" }
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 16.4 Hidden Inputs (form 전송)
|
||||
|
||||
```html
|
||||
<input type="hidden" name="steps[0][user_id]" value="10">
|
||||
<input type="hidden" name="steps[0][step_type]" value="approval">
|
||||
<input type="hidden" name="steps[1][user_id]" value="20">
|
||||
<input type="hidden" name="steps[1][step_type]" value="agreement">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17. ApprovalForm 모델
|
||||
|
||||
### 17.1 테이블 스키마
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `tenant_id` | BIGINT FK | 테넌트 격리 |
|
||||
| `name` | VARCHAR | 양식명 (예: "휴가신청서") |
|
||||
| `code` | VARCHAR UNIQUE | 양식 코드 (예: `leave`) |
|
||||
| `category` | ENUM | `request`, `expense`, `certificate`, `expense_estimate` |
|
||||
| `template` | JSON | 필드 정의 메타데이터 |
|
||||
| `body_template` | LONGTEXT NULL | HTML 본문 템플릿 |
|
||||
| `is_active` | BOOLEAN | 활성 여부 |
|
||||
|
||||
### 17.2 카테고리
|
||||
|
||||
#### DB 카테고리 (ApprovalForm.category)
|
||||
|
||||
| 카테고리 | 설명 | 양식 코드 |
|
||||
|---------|------|----------|
|
||||
| `request` | 신청서 | `leave`, `attendance_request`, `reason_report` |
|
||||
| `expense` | 지출결의서 | `expense` |
|
||||
| `certificate` | 증명서/서류 | `employment_cert`, `career_cert`, `appointment_cert`, `resignation` |
|
||||
| `expense_estimate` | 품의서 | `pr_expense`, `pr_contract`, `pr_purchase`, `pr_trip`, `pr_settlement` |
|
||||
|
||||
#### UI 분류 (formCategoryMap — 2단계 선택용)
|
||||
|
||||
| UI 분류 | 양식 코드 |
|
||||
|---------|----------|
|
||||
| 일반 | `BUSINESS_DRAFT` |
|
||||
| 인사/근태 | `leave`, `attendance_request`, `reason_report`, `resignation` |
|
||||
| 증명서 | `employment_cert`, `career_cert`, `appointment_cert` |
|
||||
| 품의 | `pr_expense`, `pr_contract`, `pr_purchase`, `pr_trip`, `pr_settlement` |
|
||||
| 재무 | `expense` |
|
||||
|
||||
> **참고**: DB 카테고리와 UI 분류는 별도 매핑이다. DB는 `approval_forms.category` ENUM이고, UI 분류는 JavaScript `formCategoryMap` 객체로 정의된다.
|
||||
|
||||
---
|
||||
|
||||
## 18. 양식별 저장/조회 흐름
|
||||
|
||||
### 18.1 저장 (create/update)
|
||||
|
||||
```
|
||||
사용자 입력
|
||||
↓
|
||||
getFormData() (JavaScript)
|
||||
↓
|
||||
POST /api/admin/approvals
|
||||
body: { form_id, title, content: {...}, body, steps: [...] }
|
||||
↓
|
||||
ApprovalService::createApproval()
|
||||
↓
|
||||
Approval.content = JSON encode → DB 저장
|
||||
```
|
||||
|
||||
### 18.2 조회 (show)
|
||||
|
||||
```
|
||||
GET /approval-mgmt/{id}
|
||||
↓
|
||||
ApprovalController::show()
|
||||
↓
|
||||
Blade: show.blade.php
|
||||
↓
|
||||
양식 코드별 분기:
|
||||
leave → (본문에 인라인 표시)
|
||||
expense → @include('_expense-show')
|
||||
pr_* → @include('_purchase-request-show') ← str_starts_with 매칭
|
||||
employment_cert → @include('_certificate-show')
|
||||
career_cert → @include('_career-cert-show')
|
||||
appointment_cert → @include('_appointment-cert-show')
|
||||
resignation → @include('_resignation-show')
|
||||
```
|
||||
|
||||
> **품의서 분기**: `str_starts_with($approval->form?->code ?? '', 'pr_')` 조건으로 5종 모두 단일 include로 처리. `_purchase-request-show.blade.php` 내부에서 `$content['pr_type']`으로 세부 분기.
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [README.md](README.md) — 결재관리 시스템 전체 개요
|
||||
- [워크플로우 상세](workflows.md) — 승인/반려/회수/보류/전결 흐름
|
||||
- [API 명세](api-reference.md) — 엔드포인트별 요청/응답
|
||||
- [UI 화면 구성](ui-screens.md) — 화면별 UI 요소 및 동작
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-06
|
||||
381
features/approvals/ui-screens.md
Normal file
381
features/approvals/ui-screens.md
Normal file
@@ -0,0 +1,381 @@
|
||||
# 결재관리 UI 화면 구성
|
||||
|
||||
> **작성일**: 2026-02-28
|
||||
> **상태**: Phase 2 구현 완료
|
||||
> **기술**: Blade + HTMX + Alpine.js + Tailwind CSS
|
||||
> **관련**: [README.md](README.md) | [워크플로우](workflows.md) | [API 명세](api-reference.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
결재관리 화면은 MNG(관리자 웹)에서 Blade 템플릿으로 구현되며, API 호출은 `fetch()`를 사용한다.
|
||||
|
||||
### 1.1 파일 구조
|
||||
|
||||
```
|
||||
resources/views/approvals/
|
||||
├── drafts.blade.php ← 기안함 (목록)
|
||||
├── pending.blade.php ← 결재 대기함 (목록)
|
||||
├── completed.blade.php ← 처리 완료함 (목록)
|
||||
├── references.blade.php ← 참조함 (목록)
|
||||
├── create.blade.php ← 기안 작성
|
||||
├── edit.blade.php ← 기안 수정
|
||||
├── show.blade.php ← 상세 조회 + 결재 처리
|
||||
└── partials/
|
||||
├── _status-badge.blade.php ← 상태 뱃지 컴포넌트
|
||||
└── _step-progress.blade.php ← 결재 단계 진행 표시
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 목록 화면
|
||||
|
||||
### 2.1 기안함 (`/approval-mgmt/drafts`)
|
||||
|
||||
내가 기안한 모든 문서를 표시한다.
|
||||
|
||||
**UI 구성:**
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 기안함 [+ 새 기안] │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ [검색] [상태 필터 ▼] [긴급만 □] [날짜 범위] │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ 문서번호 │ 제목 │ 양식 │ 상태 │ 기안일 │
|
||||
│ APR-260228-001│ 휴가 신청 │ 휴가서 │ 🟢완료 │ 02-28 │
|
||||
│ APR-260228-002│ 출장 보고 │ 출장서 │ 🔵진행 │ 02-28 │
|
||||
│ APR-260227-001│ 경비 청구 │ 경비서 │ ⬜임시 │ 02-27 │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ [◀ 이전] 1 / 3 [다음 ▶] │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**상태 필터:** 전체, 임시저장, 진행, 완료, 반려, 회수, 보류
|
||||
|
||||
---
|
||||
|
||||
### 2.2 결재 대기함 (`/approval-mgmt/pending`)
|
||||
|
||||
내가 현재 결재해야 할 문서를 표시한다.
|
||||
|
||||
**UI 구성:**
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 결재 대기함 [뱃지: 3건] │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ 문서번호 │ 제목 │ 기안자 │ 양식 │ 상신일 │
|
||||
│ 🔴 APR-260..│ 긴급 승인 │ 홍길동 │ 구매서 │ 02-28 │
|
||||
│ APR-260..│ 휴가 신청 │ 김영희 │ 휴가서 │ 02-27 │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
> 긴급 문서는 🔴 아이콘과 함께 상단에 표시
|
||||
|
||||
---
|
||||
|
||||
### 2.3 참조함 (`/approval-mgmt/references`)
|
||||
|
||||
내가 참조자로 지정된 문서를 표시한다.
|
||||
|
||||
**UI 구성:**
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 참조함 │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ [전체] [미열람 (5)] [열람완료] │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ 문서번호 │ 제목 │ 기안자 │ 상태 │ 열람 │
|
||||
│ APR-260228-001│ 회의록 │ 박부장 │ 🟢완료 │ ❌미열람│
|
||||
│ APR-260227-003│ 인사발령 │ 이팀장 │ 🔵진행 │ ✅열람 │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**열람 추적:**
|
||||
- 문서 클릭 시 `mark-read` API가 자동 호출된다
|
||||
- 미열람/열람완료 탭으로 필터링 가능
|
||||
- 미열람 건수가 뱃지로 표시된다
|
||||
|
||||
---
|
||||
|
||||
## 3. 상세 화면 (`/approval-mgmt/{id}`)
|
||||
|
||||
### 3.1 전체 레이아웃
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 결재 상세 [수정] [목록으로] │
|
||||
│ APR-260228-001 │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 상태: [🔵 진행] [🔴 긴급] │
|
||||
│ 양식: 휴가신청서 기안자: 홍길동 │
|
||||
│ 기안일: 2026-02-28 10:05 완료일: - │
|
||||
│ 원본 문서: APR-260225-003 (재기안 시 표시) │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ 회수 사유 (cancelled 상태에서만) │ │
|
||||
│ │ 내용 수정이 필요하여 회수합니다. │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 제목: 2월 연차 사용 신청 │
|
||||
│ 본문: 2월 27일~28일 연차 사용합니다... │
|
||||
│ │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 결재 진행 │
|
||||
│ ┌────────────────────────────────────────────────┐ │
|
||||
│ │ [결재 단계 프로그레스 바] │ │
|
||||
│ │ ✓김과장(승인) → ●박부장(대기) → ③이사(대기) │ │
|
||||
│ └────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 결재 의견 │
|
||||
│ ┌────────────────────────────────────────────────┐ │
|
||||
│ │ ✓ 김과장 2026-02-28 11:00 │ │
|
||||
│ │ 승인합니다. │ │
|
||||
│ └────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 결재 처리 (현재 결재자에게만 표시) │
|
||||
│ [결재 의견 textarea] │
|
||||
│ [승인] [반려] [보류] [전결] │
|
||||
│ │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ 보류 해제 (on_hold + 보류한 본인에게만) │
|
||||
│ [보류 해제] │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ 회수 (기안자 + pending/on_hold) │
|
||||
│ [회수 사유 textarea] │
|
||||
│ [결재 회수] │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ 복사 재기안 (기안자 + approved/rejected/cancelled) │
|
||||
│ [복사하여 재기안] │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 조건부 섹션 표시
|
||||
|
||||
| 섹션 | 표시 조건 |
|
||||
|------|----------|
|
||||
| **수정 버튼** | 기안자 + `draft`/`rejected` |
|
||||
| **회수 사유** | `cancelled` + `recall_reason` 존재 |
|
||||
| **원본 문서 링크** | `parent_doc_id` 존재 (재기안 문서) |
|
||||
| **결재 처리** | `pending` + 현재 결재자 |
|
||||
| **보류 해제** | `on_hold` + 보류한 본인 |
|
||||
| **회수** | 기안자 + `pending`/`on_hold` |
|
||||
| **복사 재기안** | 기안자 + `approved`/`rejected`/`cancelled` |
|
||||
|
||||
---
|
||||
|
||||
## 4. 파셜 컴포넌트
|
||||
|
||||
### 4.1 상태 뱃지 (`_status-badge.blade.php`)
|
||||
|
||||
문서 상태를 색상 뱃지로 표시한다.
|
||||
|
||||
| 상태 | 라벨 | 스타일 |
|
||||
|------|------|--------|
|
||||
| `draft` | 임시저장 | `bg-gray-100 text-gray-700` |
|
||||
| `pending` | 진행 | `bg-blue-100 text-blue-700` |
|
||||
| `approved` | 완료 | `bg-green-100 text-green-700` |
|
||||
| `rejected` | 반려 | `bg-red-100 text-red-700` |
|
||||
| `cancelled` | 회수 | `bg-yellow-100 text-yellow-700` |
|
||||
| `on_hold` | 보류 | `bg-amber-100 text-amber-700` |
|
||||
|
||||
---
|
||||
|
||||
### 4.2 결재 단계 프로그레스 (`_step-progress.blade.php`)
|
||||
|
||||
결재선의 각 단계를 가로 프로그레스 바로 표시한다.
|
||||
|
||||
**단계 아이콘:**
|
||||
|
||||
| 상태 | 아이콘 | 배경색 | 텍스트색 |
|
||||
|------|--------|--------|---------|
|
||||
| `approved` (normal) | ✓ | `bg-green-500` | white |
|
||||
| `approved` (pre_decided) | ⚡ | `bg-indigo-500` | white |
|
||||
| `rejected` | ✗ | `bg-red-500` | white |
|
||||
| `on_hold` | ⏸ | `bg-amber-400` | white |
|
||||
| `skipped` | — | `bg-gray-300` | gray |
|
||||
| `pending` (현재 차례) | 번호 | `bg-blue-500` | white |
|
||||
| `pending` (대기) | 번호 | `bg-gray-200` | gray |
|
||||
|
||||
**레이아웃:**
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ✓ ──── ⚡ ──── — ──── — ──── ● ──── 3 │
|
||||
│ 김과장 박부장 이사장 팀장 최대리 참조자 │
|
||||
│ 경영팀 경영팀 대표실 개발팀 개발팀 인사팀 │
|
||||
│ (승인) (전결) (건너뜀)(건너뜀)(대기) (참조) │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**특수 표시:**
|
||||
- **전결** step: ⚡ 아이콘 + "전결" 라벨 (남색)
|
||||
- **보류** step: ⏸ 아이콘 + "보류" 라벨 (노란색)
|
||||
- **건너뜀** step: 이름에 취소선 (line-through)
|
||||
- **참조** step: 별도 구분 없이 동일 프로그레스 바에 표시
|
||||
- **연결선**: 단계 사이 가로선 (`border-t-2`)
|
||||
|
||||
---
|
||||
|
||||
## 5. 결재 처리 인터랙션
|
||||
|
||||
### 5.1 승인
|
||||
|
||||
```
|
||||
[승인 버튼 클릭]
|
||||
→ confirm("승인하시겠습니까?")
|
||||
→ POST /api/admin/approvals/{id}/approve
|
||||
body: { comment: "의견 텍스트" }
|
||||
→ 성공 시: 토스트("승인되었습니다") + 페이지 리로드
|
||||
```
|
||||
|
||||
### 5.2 반려
|
||||
|
||||
```
|
||||
[반려 버튼 클릭]
|
||||
→ comment 빈 값 체크 → 경고 토스트("반려 시 사유를 입력해주세요")
|
||||
→ confirm("반려하시겠습니까?")
|
||||
→ POST /api/admin/approvals/{id}/reject
|
||||
body: { comment: "사유" }
|
||||
→ 성공 시: 토스트("반려되었습니다") + 페이지 리로드
|
||||
```
|
||||
|
||||
### 5.3 보류
|
||||
|
||||
```
|
||||
[보류 버튼 클릭]
|
||||
→ comment 빈 값 체크 → 경고 토스트("보류 사유를 입력해주세요")
|
||||
→ confirm("이 결재를 보류하시겠습니까?")
|
||||
→ POST /api/admin/approvals/{id}/hold
|
||||
body: { comment: "사유" }
|
||||
→ 성공 시: 토스트("보류되었습니다") + 페이지 리로드
|
||||
```
|
||||
|
||||
### 5.4 전결
|
||||
|
||||
```
|
||||
[전결 버튼 클릭]
|
||||
→ confirm("전결 처리하시겠습니까?\n이후 모든 결재를 건너뛰고 문서를 최종 승인합니다.")
|
||||
→ POST /api/admin/approvals/{id}/pre-decide
|
||||
body: { comment: "의견(선택)" }
|
||||
→ 성공 시: 토스트("전결 처리되었습니다") + 페이지 리로드
|
||||
```
|
||||
|
||||
### 5.5 보류 해제
|
||||
|
||||
```
|
||||
[보류 해제 버튼 클릭]
|
||||
→ confirm("보류를 해제하시겠습니까?")
|
||||
→ POST /api/admin/approvals/{id}/release-hold
|
||||
→ 성공 시: 토스트("보류가 해제되었습니다") + 페이지 리로드
|
||||
```
|
||||
|
||||
### 5.6 회수
|
||||
|
||||
```
|
||||
[결재 회수 버튼 클릭]
|
||||
→ confirm("결재를 회수하시겠습니까? 이 작업은 되돌릴 수 없습니다.")
|
||||
→ POST /api/admin/approvals/{id}/cancel
|
||||
body: { recall_reason: "사유(선택)" }
|
||||
→ 성공 시: 토스트("결재가 회수되었습니다") + 페이지 리로드
|
||||
```
|
||||
|
||||
### 5.7 복사 재기안
|
||||
|
||||
```
|
||||
[복사하여 재기안 버튼 클릭]
|
||||
→ confirm("이 문서를 복사하여 새 결재를 작성하시겠습니까?")
|
||||
→ POST /api/admin/approvals/{id}/copy
|
||||
→ 성공 시: 토스트("문서가 복사되었습니다")
|
||||
→ /approval-mgmt/{newId}/edit로 이동
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 결재 의견 표시
|
||||
|
||||
상세 페이지에서 결재 의견이 있는 step을 카드 형태로 표시한다.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────┐
|
||||
│ ✓ 김과장 2026-02-28 11:00 │
|
||||
│ 승인합니다. │
|
||||
├──────────────────────────────────────┤
|
||||
│ ⚡ 박부장 (전결) 2026-02-28 14:00 │
|
||||
│ 전결 처리합니다. │
|
||||
├──────────────────────────────────────┤
|
||||
│ ⏸ 이사장 (보류) 2026-02-28 15:00 │
|
||||
│ 추가 자료 검토 필요 │
|
||||
├──────────────────────────────────────┤
|
||||
│ ✗ 팀장 2026-02-28 16:00 │
|
||||
│ 예산 초과로 반려합니다. │
|
||||
└──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**아이콘 색상:**
|
||||
- ✓ 승인: 녹색 (`bg-green-100 text-green-600`)
|
||||
- ⚡ 전결: 남색 (`bg-indigo-100 text-indigo-600`)
|
||||
- ⏸ 보류: 노란색 (`bg-amber-100 text-amber-600`)
|
||||
- ✗ 반려: 적색 (`bg-red-100 text-red-600`)
|
||||
|
||||
---
|
||||
|
||||
## 7. 참조함 열람 추적 UI
|
||||
|
||||
### 7.1 탭 필터
|
||||
|
||||
```
|
||||
[전체] [미열람 (5)] [열람완료]
|
||||
```
|
||||
|
||||
- 탭 클릭 시 `is_read` 파라미터로 API 재호출
|
||||
- 미열람 탭에 건수 뱃지 표시
|
||||
|
||||
### 7.2 열람 상태 표시
|
||||
|
||||
| 상태 | 표시 |
|
||||
|------|------|
|
||||
| 미열람 | `bg-red-100 text-red-700` "미열람" |
|
||||
| 열람완료 | `bg-green-100 text-green-700` "열람완료" |
|
||||
|
||||
### 7.3 자동 열람 처리
|
||||
|
||||
문서 행 클릭 시:
|
||||
1. `mark-read` API 호출 (비동기)
|
||||
2. 상세 페이지로 이동
|
||||
|
||||
---
|
||||
|
||||
## 8. 버튼 스타일 가이드
|
||||
|
||||
| 버튼 | 색상 | Tailwind 클래스 |
|
||||
|------|------|----------------|
|
||||
| 승인 | 녹색 | `bg-green-600 hover:bg-green-700` |
|
||||
| 반려 | 적색 | `bg-red-600 hover:bg-red-700` |
|
||||
| 보류 | 노란색 | `bg-amber-500 hover:bg-amber-600` |
|
||||
| 전결 | 남색 | `bg-indigo-600 hover:bg-indigo-700` |
|
||||
| 보류 해제 | 노란색 | `bg-amber-500 hover:bg-amber-600` |
|
||||
| 회수 | 노란색 | `bg-yellow-500 hover:bg-yellow-600` |
|
||||
| 복사 재기안 | 회색 | `bg-gray-600 hover:bg-gray-700` |
|
||||
| 수정 | 회색 | `bg-gray-600 hover:bg-gray-700` |
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [README.md](README.md) — 시스템 전체 개요
|
||||
- [워크플로우 상세](workflows.md) — 각 동작의 상세 흐름
|
||||
- [API 명세](api-reference.md) — 엔드포인트별 요청/응답
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-02-28
|
||||
565
features/approvals/workflows.md
Normal file
565
features/approvals/workflows.md
Normal file
@@ -0,0 +1,565 @@
|
||||
# 결재관리 워크플로우 상세
|
||||
|
||||
> **작성일**: 2026-02-28
|
||||
> **상태**: Phase 2 구현 완료
|
||||
> **관련**: [README.md](README.md) | [API 명세](api-reference.md) | [UI 화면](ui-screens.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
이 문서는 결재관리 시스템의 각 동작(Action)에 대한 상세 워크플로우를 정의한다.
|
||||
모든 워크플로우는 `ApprovalService`에서 트랜잭션으로 처리된다.
|
||||
|
||||
### 1.1 용어 정의
|
||||
|
||||
| 용어 | 설명 |
|
||||
|------|------|
|
||||
| **기안자** | 결재 문서를 작성한 사람 (`drafter_id`) |
|
||||
| **현재 결재자** | 결재선에서 현재 차례인 사람 (가장 작은 `step_order`의 `pending` step) |
|
||||
| **결재자** | `step_type`이 `approval` 또는 `agreement`인 참여자 |
|
||||
| **참조자** | `step_type`이 `reference`인 참여자 (의사결정 권한 없음) |
|
||||
| **전결** | 현재 결재자가 이후 모든 결재를 건너뛰고 즉시 최종 승인 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 기안 작성 (createApproval)
|
||||
|
||||
### 2.1 흐름
|
||||
|
||||
```
|
||||
사용자 → [양식 선택] → [제목/본문 입력] → [결재선 설정] → [임시저장]
|
||||
│
|
||||
▼
|
||||
새 Approval 생성
|
||||
status = 'draft'
|
||||
current_step = 0
|
||||
```
|
||||
|
||||
### 2.2 조건
|
||||
|
||||
- 모든 로그인 사용자가 작성 가능
|
||||
- `form_id` 필수 (양식 선택)
|
||||
- 결재선(steps)은 저장 시 선택사항 (상신 시 필수)
|
||||
|
||||
### 2.3 처리 로직
|
||||
|
||||
1. 문서번호 자동 채번 (`APR-YYMMDD-001` 형식)
|
||||
2. `numbering_sequences` 테이블로 일일 순번 관리
|
||||
3. 결재선 설정 시 `approval_steps` 저장 + 사용자 정보 스냅샷 (이름, 부서, 직급)
|
||||
4. `status = 'draft'`, `current_step = 0`
|
||||
|
||||
---
|
||||
|
||||
## 3. 상신 (submit)
|
||||
|
||||
### 3.1 흐름
|
||||
|
||||
```
|
||||
기안자 → [상신 버튼] → 유효성 검사 → 결재선 검사 → 상신 완료
|
||||
│
|
||||
▼
|
||||
status = 'pending'
|
||||
current_step = 1
|
||||
drafted_at = now()
|
||||
```
|
||||
|
||||
### 3.2 조건
|
||||
|
||||
| 조건 | 설명 |
|
||||
|------|------|
|
||||
| 문서 상태 | `draft` 또는 `rejected` |
|
||||
| 결재선 | 결재/합의 step 1명 이상 필수 |
|
||||
| 요청자 | 기안자만 |
|
||||
|
||||
### 3.3 처리 로직
|
||||
|
||||
1. `isSubmittable()` 검증 → `draft` 또는 `rejected`인지 확인
|
||||
2. 결재/합의 step 존재 확인
|
||||
3. **반려 후 재상신인 경우**: 모든 step을 `pending`으로 초기화 (comment, acted_at도 초기화)
|
||||
4. `status → pending`, `drafted_at → now()`, `current_step → 1`
|
||||
|
||||
### 3.4 반려 후 재상신
|
||||
|
||||
```
|
||||
rejected 문서
|
||||
│
|
||||
├── 기안자가 내용 수정 (updateApproval)
|
||||
│
|
||||
└── 상신 (submit)
|
||||
├── 모든 steps → pending (초기화)
|
||||
├── status → pending
|
||||
└── current_step → 1 (처음부터 다시)
|
||||
```
|
||||
|
||||
> 반려 후 재상신 시 결재선이 초기화되므로, 이전 결재 의견(comment)은 사라진다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 승인 (approve)
|
||||
|
||||
### 4.1 흐름
|
||||
|
||||
```
|
||||
현재 결재자 → [의견 입력(선택)] → [승인 버튼]
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
│ 현재 step │
|
||||
│ status → 'approved' │
|
||||
│ comment → (입력값) │
|
||||
│ acted_at → now() │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
┌─────────────────┴─────────────────┐
|
||||
│ │
|
||||
다음 pending step 있음 마지막 결재자
|
||||
│ │
|
||||
current_step 갱신 status → 'approved'
|
||||
(다음 순서 결재자 대기) completed_at → now()
|
||||
```
|
||||
|
||||
### 4.2 조건
|
||||
|
||||
| 조건 | 설명 |
|
||||
|------|------|
|
||||
| 문서 상태 | `pending` |
|
||||
| 요청자 | 현재 차례 결재자 (`approver_id === auth()->id()`) |
|
||||
|
||||
### 4.3 처리 로직
|
||||
|
||||
1. `isActionable()` 검증 → `pending` 상태인지 확인
|
||||
2. `getCurrentApproverStep()` → 현재 차례 step 조회
|
||||
3. 현재 step → `approved` + comment + acted_at
|
||||
4. 다음 pending 결재/합의 step 조회
|
||||
- **있으면**: `current_step` 갱신
|
||||
- **없으면**: 문서 `approved` + `completed_at`
|
||||
|
||||
### 4.4 순차결재 순서 결정
|
||||
|
||||
```
|
||||
step_order = 1 (결재) → step_order = 2 (합의) → step_order = 3 (결재)
|
||||
│ │ │
|
||||
1번째 승인 → 2번째 승인 → 3번째 승인 → 문서 완료
|
||||
```
|
||||
|
||||
> 결재와 합의는 동일한 순차 흐름을 따른다. `step_order` 순서대로 처리된다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 반려 (reject)
|
||||
|
||||
### 5.1 흐름
|
||||
|
||||
```
|
||||
현재 결재자 → [반려 사유 입력(필수)] → [반려 버튼]
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
│ 현재 step │
|
||||
│ status → 'rejected' │
|
||||
│ comment → (사유) │
|
||||
│ acted_at → now() │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
▼
|
||||
문서 status → 'rejected'
|
||||
completed_at → now()
|
||||
```
|
||||
|
||||
### 5.2 조건
|
||||
|
||||
| 조건 | 설명 |
|
||||
|------|------|
|
||||
| 문서 상태 | `pending` |
|
||||
| 요청자 | 현재 차례 결재자 |
|
||||
| 반려 사유 | **필수** (빈 값 불가) |
|
||||
|
||||
### 5.3 처리 로직
|
||||
|
||||
1. `isActionable()` 검증
|
||||
2. 현재 결재자 확인
|
||||
3. 반려 사유 빈 값 체크
|
||||
4. 현재 step → `rejected` + comment + acted_at
|
||||
5. 문서 → `rejected` + completed_at
|
||||
|
||||
### 5.4 반려 후 가능한 동작
|
||||
|
||||
```
|
||||
rejected 문서
|
||||
│
|
||||
├── 기안자가 수정 → 재상신 (submit)
|
||||
│ └── 결재선 초기화, 처음부터 다시 진행
|
||||
│
|
||||
└── 기안자가 복사 재기안 (copyForRedraft)
|
||||
└── 새 문서 생성 (draft), 원본은 그대로 유지
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 회수 (cancel)
|
||||
|
||||
### 6.1 흐름
|
||||
|
||||
```
|
||||
기안자 → [회수 사유 입력(선택)] → [회수 버튼]
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
│ 회수 가능 여부 판단 │
|
||||
│ (첫 결재자 미처리?) │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
┌───────────┴───────────┐
|
||||
│ │
|
||||
첫 결재자 첫 결재자 이미
|
||||
pending/on_hold 승인/반려
|
||||
│ │
|
||||
회수 진행 회수 불가
|
||||
│ (에러 반환)
|
||||
▼
|
||||
모든 pending/on_hold steps → 'skipped'
|
||||
문서 status → 'cancelled'
|
||||
recall_reason → (입력값)
|
||||
completed_at → now()
|
||||
```
|
||||
|
||||
### 6.2 조건
|
||||
|
||||
| 조건 | 설명 |
|
||||
|------|------|
|
||||
| 문서 상태 | `pending` 또는 `on_hold` |
|
||||
| 요청자 | 기안자만 (`drafter_id === auth()->id()`) |
|
||||
| 첫 결재자 상태 | `pending` 또는 `on_hold` (이미 처리했으면 불가) |
|
||||
|
||||
### 6.3 회수 가능 판단 로직
|
||||
|
||||
```php
|
||||
// 1단계: 문서 상태 확인
|
||||
$approval->isCancellable() // pending 또는 on_hold
|
||||
|
||||
// 2단계: 기안자 확인
|
||||
$approval->drafter_id === auth()->id()
|
||||
|
||||
// 3단계: 첫 결재자 상태 확인
|
||||
$firstStep = steps.approvalOnly().orderBy('step_order').first()
|
||||
$firstStep->status === 'pending' || 'on_hold' // 미처리 상태여야 함
|
||||
```
|
||||
|
||||
### 6.4 처리 로직
|
||||
|
||||
1. `isCancellable()` 검증 → `pending` 또는 `on_hold`
|
||||
2. 기안자 확인
|
||||
3. 첫 번째 결재/합의 step의 상태 확인 → `pending`/`on_hold`이 아니면 거부
|
||||
4. 모든 `pending`/`on_hold` steps → `skipped`
|
||||
5. 문서 → `cancelled` + `recall_reason` + `completed_at`
|
||||
|
||||
---
|
||||
|
||||
## 7. 보류 (hold)
|
||||
|
||||
### 7.1 흐름
|
||||
|
||||
```
|
||||
현재 결재자 → [보류 사유 입력(필수)] → [보류 버튼]
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
│ 현재 step │
|
||||
│ status → 'on_hold' │
|
||||
│ comment → (사유) │
|
||||
│ acted_at → now() │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
▼
|
||||
문서 status → 'on_hold'
|
||||
```
|
||||
|
||||
### 7.2 조건
|
||||
|
||||
| 조건 | 설명 |
|
||||
|------|------|
|
||||
| 문서 상태 | `pending` (`isHoldable()`) |
|
||||
| 요청자 | 현재 차례 결재자 |
|
||||
| 보류 사유 | **필수** (빈 값 불가) |
|
||||
|
||||
### 7.3 처리 로직
|
||||
|
||||
1. `isHoldable()` 검증 → `pending` 상태인지 확인
|
||||
2. `getCurrentApproverStep()` → 현재 차례 step 조회
|
||||
3. 현재 결재자 확인 (`approver_id === auth()->id()`)
|
||||
4. 보류 사유 빈 값 체크
|
||||
5. 현재 step → `on_hold` + comment + acted_at
|
||||
6. 문서 → `on_hold`
|
||||
|
||||
### 7.4 보류 상태의 영향
|
||||
|
||||
```
|
||||
on_hold 상태에서:
|
||||
├── 다른 결재자는 아무 동작 불가 (결재 흐름 중단)
|
||||
├── 기안자는 회수 가능 (첫 결재자가 미처리 상태이면)
|
||||
└── 보류한 결재자만 보류 해제 가능
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 보류 해제 (releaseHold)
|
||||
|
||||
### 8.1 흐름
|
||||
|
||||
```
|
||||
보류한 결재자 → [보류 해제 버튼]
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
│ on_hold step │
|
||||
│ status → 'pending' │
|
||||
│ comment → null │
|
||||
│ acted_at → null │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
▼
|
||||
문서 status → 'pending'
|
||||
(결재 흐름 재개)
|
||||
```
|
||||
|
||||
### 8.2 조건
|
||||
|
||||
| 조건 | 설명 |
|
||||
|------|------|
|
||||
| 문서 상태 | `on_hold` (`isHoldReleasable()`) |
|
||||
| 요청자 | 보류한 본인만 (`on_hold` step의 `approver_id === auth()->id()`) |
|
||||
|
||||
### 8.3 처리 로직
|
||||
|
||||
1. `isHoldReleasable()` 검증 → `on_hold` 상태인지 확인
|
||||
2. `on_hold` 상태인 step 조회
|
||||
3. 해당 step의 `approver_id`가 현재 사용자인지 확인
|
||||
4. step → `pending` + comment/acted_at 초기화
|
||||
5. 문서 → `pending`
|
||||
|
||||
---
|
||||
|
||||
## 9. 전결 (preDecide)
|
||||
|
||||
### 9.1 흐름
|
||||
|
||||
```
|
||||
현재 결재자 → [의견 입력(선택)] → [전결 버튼] → 확인 팝업
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
│ 현재 step │
|
||||
│ status → 'approved' │
|
||||
│ approval_type → │
|
||||
│ 'pre_decided' │
|
||||
│ comment → (입력값) │
|
||||
│ acted_at → now() │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
▼
|
||||
이후 모든 pending
|
||||
approval/agreement steps
|
||||
→ status = 'skipped'
|
||||
│
|
||||
▼
|
||||
문서 status → 'approved'
|
||||
completed_at → now()
|
||||
```
|
||||
|
||||
### 9.2 조건
|
||||
|
||||
| 조건 | 설명 |
|
||||
|------|------|
|
||||
| 문서 상태 | `pending` (`isActionable()`) |
|
||||
| 요청자 | 현재 차례 결재자 |
|
||||
|
||||
### 9.3 처리 로직
|
||||
|
||||
1. `isActionable()` 검증
|
||||
2. `getCurrentApproverStep()` → 현재 차례 step 조회
|
||||
3. 현재 결재자 확인
|
||||
4. 현재 step → `approved` + `approval_type = 'pre_decided'` + comment + acted_at
|
||||
5. 이후 모든 pending 결재/합의 steps → `skipped`
|
||||
6. 문서 → `approved` + `completed_at`
|
||||
|
||||
### 9.4 전결 예시
|
||||
|
||||
```
|
||||
step_order=1 (이사장, 결재) → approved (normal)
|
||||
step_order=2 (부장, 결재) → approved (pre_decided) ← 여기서 전결
|
||||
step_order=3 (과장, 합의) → skipped (전결로 건너뜀)
|
||||
step_order=4 (팀장, 결재) → skipped (전결로 건너뜀)
|
||||
step_order=5 (참조자, 참조) → (참조는 영향 없음, 그대로 유지)
|
||||
|
||||
문서 → approved, completed_at = now()
|
||||
```
|
||||
|
||||
> 전결은 결재/합의 step만 건너뛴다. 참조 step은 영향받지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 10. 복사 재기안 (copyForRedraft)
|
||||
|
||||
### 10.1 흐름
|
||||
|
||||
```
|
||||
기안자 → [복사하여 재기안 버튼]
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ 원본 문서에서 복사 │
|
||||
│ ├── form_id │
|
||||
│ ├── title │
|
||||
│ ├── content (양식 데이터) │
|
||||
│ ├── body │
|
||||
│ ├── is_urgent │
|
||||
│ ├── department_id │
|
||||
│ └── 결재선 (모두 pending) │
|
||||
└─────────────┬───────────────┘
|
||||
│
|
||||
▼
|
||||
새 문서 생성 (status = 'draft')
|
||||
parent_doc_id = 원본.id
|
||||
새 문서번호 채번
|
||||
│
|
||||
▼
|
||||
수정 페이지로 이동
|
||||
(/approval-mgmt/{newId}/edit)
|
||||
```
|
||||
|
||||
### 10.2 조건
|
||||
|
||||
| 조건 | 설명 |
|
||||
|------|------|
|
||||
| 원본 문서 상태 | `approved`, `rejected`, `cancelled` (`isCopyable()`) |
|
||||
| 요청자 | 기안자만 (`drafter_id === auth()->id()`) |
|
||||
|
||||
### 10.3 처리 로직
|
||||
|
||||
1. `isCopyable()` 검증 → `approved`/`rejected`/`cancelled` 중 하나
|
||||
2. 기안자 확인
|
||||
3. 새 문서 생성:
|
||||
- 새 문서번호 채번
|
||||
- 원본의 양식, 제목, 내용, 본문, 긴급 여부, 부서 복사
|
||||
- `parent_doc_id = 원본.id`
|
||||
- `status = 'draft'`, `current_step = 0`
|
||||
4. 결재선 복사: 원본의 모든 steps를 새 문서에 복사 (모두 `pending` 상태)
|
||||
5. 새 문서의 edit 페이지로 리다이렉트
|
||||
|
||||
### 10.4 원본과의 관계
|
||||
|
||||
```
|
||||
원본 문서 (approved/rejected/cancelled)
|
||||
│
|
||||
└── parent_doc_id로 연결
|
||||
│
|
||||
▼
|
||||
새 문서 (draft)
|
||||
├── 상세 페이지에서 "원본 문서" 링크 표시
|
||||
└── 기안자가 내용 수정 후 상신 가능
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 참조 열람 추적 (markAsRead)
|
||||
|
||||
### 11.1 흐름
|
||||
|
||||
```
|
||||
참조자 → [참조함 목록에서 문서 클릭]
|
||||
│
|
||||
├── markAsRead API 호출
|
||||
│ ├── is_read → true
|
||||
│ └── read_at → now()
|
||||
│
|
||||
└── 상세 페이지로 이동
|
||||
```
|
||||
|
||||
### 11.2 조건
|
||||
|
||||
| 조건 | 설명 |
|
||||
|------|------|
|
||||
| 요청자 | 해당 문서의 참조자 (`step_type = 'reference'`) |
|
||||
|
||||
### 11.3 처리 로직
|
||||
|
||||
1. 현재 사용자의 참조 step 조회
|
||||
2. `is_read = false`인 step → `is_read = true`, `read_at = now()`
|
||||
3. 이미 열람한 경우 중복 업데이트 없음 (`where('is_read', false)`)
|
||||
|
||||
---
|
||||
|
||||
## 12. 전체 상태 전이 요약
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ draft ──submit()──→ pending ──approve()──→ (다음 step 또는) │
|
||||
│ ▲ │ │ approved │
|
||||
│ │ │ │ │
|
||||
│ │ │ ├──reject()──→ rejected │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ ├── 수정 → submit() │
|
||||
│ │ │ │ │ (재상신, draft X) │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ └── copyForRedraft() │
|
||||
│ │ │ │ → 새 draft 생성 │
|
||||
│ │ │ │ │
|
||||
│ │ │ ├──hold()──→ on_hold │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ ├── releaseHold() │
|
||||
│ │ │ │ │ → pending 복원 │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ └── cancel() (기안자) │
|
||||
│ │ │ │ → cancelled │
|
||||
│ │ │ │ │
|
||||
│ │ │ ├──preDecide()──→ approved │
|
||||
│ │ │ │ (이후 steps → skipped) │
|
||||
│ │ │ │ │
|
||||
│ │ │ └──cancel()──→ cancelled │
|
||||
│ │ │ (기안자, 첫결재자 미처리 시) │
|
||||
│ │ │ │ │
|
||||
│ │ │ └── copyForRedraft() │
|
||||
│ │ │ → 새 draft 생성 │
|
||||
│ │ │ │
|
||||
│ │ └── approved ──copyForRedraft() │
|
||||
│ │ → 새 draft 생성 │
|
||||
│ │ │
|
||||
│ └── updateApproval() (draft/rejected 상태에서 수정) │
|
||||
│ │
|
||||
└───────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. 에러 케이스 정리
|
||||
|
||||
| 동작 | 에러 조건 | 에러 메시지 |
|
||||
|------|----------|------------|
|
||||
| submit | 상태가 draft/rejected 아님 | "상신할 수 없는 상태입니다." |
|
||||
| submit | 결재선 없음 | "결재선을 설정해주세요." |
|
||||
| approve | 상태가 pending 아님 | "승인할 수 없는 상태입니다." |
|
||||
| approve | 현재 결재자 아님 | "현재 결재자가 아닙니다." |
|
||||
| reject | 상태가 pending 아님 | "반려할 수 없는 상태입니다." |
|
||||
| reject | 사유 미입력 | "반려 사유를 입력해주세요." |
|
||||
| cancel | 상태가 pending/on_hold 아님 | "회수할 수 없는 상태입니다." |
|
||||
| cancel | 기안자 아님 | "기안자만 회수할 수 있습니다." |
|
||||
| cancel | 첫 결재자 이미 처리 | "첫 번째 결재자가 이미 처리하여 회수할 수 없습니다." |
|
||||
| hold | 상태가 pending 아님 | "보류할 수 없는 상태입니다." |
|
||||
| hold | 현재 결재자 아님 | "현재 결재자가 아닙니다." |
|
||||
| hold | 사유 미입력 | "보류 사유를 입력해주세요." |
|
||||
| releaseHold | 상태가 on_hold 아님 | "보류 해제할 수 없는 상태입니다." |
|
||||
| releaseHold | 보류한 본인 아님 | "보류한 결재자만 해제할 수 있습니다." |
|
||||
| preDecide | 상태가 pending 아님 | "전결할 수 없는 상태입니다." |
|
||||
| preDecide | 현재 결재자 아님 | "현재 결재자가 아닙니다." |
|
||||
| copyForRedraft | 상태가 approved/rejected/cancelled 아님 | "복사할 수 없는 상태입니다." |
|
||||
| copyForRedraft | 기안자 아님 | "기안자만 복사할 수 있습니다." |
|
||||
| update | 상태가 draft/rejected 아님 | "수정할 수 없는 상태입니다." |
|
||||
| delete | 상태가 draft 아님 | "삭제할 수 없는 상태입니다." |
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [README.md](README.md) — 시스템 전체 개요
|
||||
- [API 명세](api-reference.md) — 엔드포인트별 요청/응답
|
||||
- [UI 화면 구성](ui-screens.md) — 화면별 동작
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-02-28
|
||||
@@ -1,9 +1,9 @@
|
||||
# 바로빌 카카오톡 (알림톡/친구톡) 연동
|
||||
|
||||
> **문서 버전**: 1.0
|
||||
> **문서 버전**: 1.1
|
||||
> **작성일**: 2026-02-14
|
||||
> **최종 수정**: 2026-02-24
|
||||
> **상태**: 운영 중 (전자계약 알림톡 발송 완료)
|
||||
> **최종 수정**: 2026-02-27
|
||||
> **상태**: 운영 중 (알림톡 + SMS + 환경별 분기 완료)
|
||||
> **대상 프로젝트**: MNG
|
||||
|
||||
---
|
||||
@@ -25,7 +25,11 @@
|
||||
| 채널 연동 (바로빌↔카카오) | **완료** (2026-02-20) | 바로빌 관리 URL에서 채널 연동 처리 |
|
||||
| 바로빌 파트너 과금 설정 | **완료** (2026-02-23) | 바로빌 측에서 파트너사 과금 설정 완료 |
|
||||
| 알림톡 템플릿 v1 검수 | **완료** (2026-02-22) | `전자계약_서명요청`, `전자계약_리마인드` 2종 승인 |
|
||||
| 알림톡 템플릿 v2 검수 | **심사 중** (2026-02-24 접수) | 버튼 URL에 `#{토큰}` 변수 포함 2종 재등록 |
|
||||
| 알림톡 템플릿 v2 검수 | **완료** (2026-02-25) | 버튼 URL에 `#{토큰}` 변수 포함 3종 승인 |
|
||||
| 알림톡 `전자계약_완료` | **완료** (2026-02-26) | 서명 완료 알림 발송용 템플릿 승인 |
|
||||
| 역할 기반 알림 분기 | **완료** (2026-02-26) | 본사=이메일, 상대방=알림톡/SMS |
|
||||
| 환경별 템플릿 분기 | **완료** (2026-02-27) | `_DEV` 접미사 개발 템플릿 등록 |
|
||||
| DEV 템플릿 검수 | **심사 중** (2026-02-27 접수) | 개발서버용 3종 (`admin.codebridge-x.com`) |
|
||||
|
||||
> 상세 등록 가이드: [카카오톡 알림톡 채널 및 템플릿 등록 가이드](../../guides/카카오톡-알림톡-채널-템플릿-등록.md)
|
||||
|
||||
@@ -310,14 +314,24 @@ $params = [
|
||||
| 전달 결과 확인 (2단계) | **구현 완료** | 2026-02-24 |
|
||||
| 로그인 페이지 서명 확인 | **성공** | 2026-02-24 |
|
||||
|
||||
### 5.3 대기 중인 항목
|
||||
### 5.3 완료된 추가 항목 (2026-02-26~27)
|
||||
|
||||
| 항목 | 상태 | 비고 |
|
||||
|------|------|------|
|
||||
| 템플릿 v2 승인 | **심사 중** | 버튼 URL에 `#{토큰}` 변수 포함 |
|
||||
| v2 승인 후 코드 수정 | **대기** | 동적 서명 URL을 버튼에 전달 |
|
||||
| `전자계약_완료` 템플릿 | **미등록** | 서명 완료 알림 발송용 |
|
||||
| SMS 대체발송 | **미설정** | 발신번호 등록 필요 |
|
||||
| 템플릿 v2 승인 | **완료** | 버튼 URL에 `#{토큰}` 변수 포함 3종 승인 |
|
||||
| `전자계약_완료` 템플릿 | **완료** | 서명 완료 알림 발송 — PDF 다운로드 버튼 |
|
||||
| 역할 기반 알림 분기 | **완료** | 본사(creator)=이메일, 상대방(counterpart)=알림톡 |
|
||||
| OTP SMS 발송 | **완료** | 상대방에게 SMS로 인증코드 발송 |
|
||||
| 환경별 템플릿 분기 | **완료** | `resolveTemplateName()` — `_DEV` 접미사 자동 적용 |
|
||||
| 서명 PDF 재생성 | **완료** | `downloadDocument()`에서 완료 계약 PDF 자동 재생성 |
|
||||
|
||||
> 상세 가이드: [전자계약 알림톡/SMS 환경별 설정 가이드](./esign-notification-guide.md)
|
||||
|
||||
### 5.4 대기 중인 항목
|
||||
|
||||
| 항목 | 상태 | 비고 |
|
||||
|------|------|------|
|
||||
| DEV 템플릿 검수 | **심사 중** | `admin.codebridge-x.com` 도메인 3종 |
|
||||
| 친구톡 발송 | **대기** | 채널 친구 추가 후 가능 |
|
||||
| 대량 발송 | **대기** | 단건 안정화 후 |
|
||||
|
||||
@@ -377,37 +391,7 @@ $buttons = [
|
||||
|
||||
---
|
||||
|
||||
## 8. API 측 바로빌 연동 (세금계산서)
|
||||
|
||||
MNG의 카카오톡 연동 외에, API(`api/`)에서도 바로빌 서비스를 사용한다:
|
||||
|
||||
### 8.1 바로빌 설정 API
|
||||
|
||||
| HTTP | URI | 설명 |
|
||||
|------|-----|------|
|
||||
| GET | `/v1/barobill-settings` | 바로빌 설정 조회 |
|
||||
| PUT | `/v1/barobill-settings` | 바로빌 설정 저장 (사업자번호, 인증키, 자동발행 등) |
|
||||
| POST | `/v1/barobill-settings/test-connection` | 연동 테스트 |
|
||||
|
||||
### 8.2 세금계산서 발행
|
||||
|
||||
`BarobillService`는 세금계산서 발행/취소/국세청 전송 상태 조회도 담당한다:
|
||||
|
||||
| API 메서드 | 설명 |
|
||||
|-----------|------|
|
||||
| `issueTaxInvoice()` | 세금계산서 발행 (RegistAndIssueTaxInvoice) |
|
||||
| `cancelTaxInvoice()` | 세금계산서 취소 |
|
||||
| `checkNtsSendStatus()` | 국세청 전송 상태 조회 |
|
||||
| `checkBusinessNumber()` | 사업자번호 휴폐업 조회 |
|
||||
| `testConnection()` | GetAccessToken으로 연동 테스트 |
|
||||
|
||||
**인증키 보안:** `cert_key`는 Laravel `Crypt` 파사드로 자동 암/복호화
|
||||
|
||||
→ 상세: [세금계산서 관리](../finance/tax-invoices.md)
|
||||
|
||||
---
|
||||
|
||||
## 9. 참고 자료
|
||||
## 8. 참고 자료
|
||||
|
||||
- [바로빌 API 문서](https://dev.barobill.co.kr)
|
||||
- [카카오비즈니스 채널 관리](https://business.kakao.com)
|
||||
@@ -420,6 +404,7 @@ MNG의 카카오톡 연동 외에, API(`api/`)에서도 바로빌 서비스를
|
||||
|
||||
| 날짜 | 버전 | 변경 내용 |
|
||||
|------|------|----------|
|
||||
| 2026-02-27 | 1.1 | 역할 기반 알림, OTP SMS, 환경별 템플릿 분기, 완료 알림톡 추가 |
|
||||
| 2026-02-24 | 1.0 | 전자계약 알림톡 연동 완료, 트러블슈팅 문서화, v2 템플릿 가이드 추가 |
|
||||
| 2026-02-14 | 0.2 | 전자계약(E-Sign) 알림톡 연동 활용 계획 추가 |
|
||||
| 2026-02-14 | 0.1 | 초안 작성 - 코드 구현 완료, 실 서비스 연동 대기 |
|
||||
|
||||
250
features/barobill-kakaotalk/esign-notification-guide.md
Normal file
250
features/barobill-kakaotalk/esign-notification-guide.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# 전자계약 알림톡/SMS 환경별 설정 가이드
|
||||
|
||||
> **작성일**: 2026-02-27
|
||||
> **상태**: 운영 중
|
||||
> **대상 프로젝트**: MNG
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
전자계약(E-Sign) 시스템의 카카오톡 알림톡, SMS, 이메일 발송을 **3개 환경(로컬/개발/운영)**에서 올바르게 설정하고 테스트하기 위한 가이드이다.
|
||||
|
||||
### 1.2 핵심 원칙
|
||||
|
||||
- **역할 기반 알림**: 본사(creator)는 이메일, 상대방(counterpart)은 카카오톡/SMS
|
||||
- **환경별 템플릿 분리**: 운영은 원본 템플릿, 개발은 `_DEV` 접미사 템플릿 사용
|
||||
- **URL 자동 분기**: `config('app.url')`로 환경별 도메인 자동 적용
|
||||
|
||||
---
|
||||
|
||||
## 2. 환경별 설정
|
||||
|
||||
### 2.1 도메인 및 APP_URL
|
||||
|
||||
| 환경 | `APP_ENV` | `APP_URL` | 알림톡 버튼 URL 도메인 |
|
||||
|------|-----------|-----------|----------------------|
|
||||
| 로컬 (Docker) | `local` | `https://mng.sam.kr` | 로컬 — 알림톡 미사용 |
|
||||
| 개발 서버 | `local` | `https://admin.codebridge-x.com` | `admin.codebridge-x.com` |
|
||||
| 운영 서버 | `production` | `https://mng.codebridge-x.com` | `mng.codebridge-x.com` |
|
||||
|
||||
### 2.2 바로빌 서버 모드
|
||||
|
||||
`barobill_members.server_mode` 컬럼으로 바로빌 API 엔드포인트를 결정한다:
|
||||
|
||||
| server_mode | WSDL (카카오톡) | WSDL (SMS) | 용도 |
|
||||
|-------------|----------------|------------|------|
|
||||
| `test` | `testws.baroservice.com/KAKAOTALK.asmx` | `testws.baroservice.com/SMS.asmx` | 테스트 |
|
||||
| `production` | `ws.baroservice.com/KAKAOTALK.asmx` | `ws.baroservice.com/SMS.asmx` | 실제 발송 |
|
||||
|
||||
> `server_mode`는 환경(로컬/개발/운영)과 독립적이다. 개발서버에서도 `production` 모드로 실제 발송 가능.
|
||||
|
||||
### 2.3 알림톡 템플릿 환경별 분기
|
||||
|
||||
코드에서 `resolveTemplateName()` 메서드가 `APP_ENV`에 따라 템플릿명을 자동 결정한다:
|
||||
|
||||
```php
|
||||
private function resolveTemplateName(string $baseName): string
|
||||
{
|
||||
return $baseName . (app()->environment('production') ? '' : '_DEV');
|
||||
}
|
||||
```
|
||||
|
||||
| 기본 템플릿명 | 운영 (`production`) | 개발/로컬 (기타) |
|
||||
|-------------|--------------------|--------------------|
|
||||
| `전자계약_서명요청` | `전자계약_서명요청` | `전자계약_서명요청_DEV` |
|
||||
| `전자계약_완료` | `전자계약_완료` | `전자계약_완료_DEV` |
|
||||
| `전자계약_리마인드` | `전자계약_리마인드` | `전자계약_리마인드_DEV` |
|
||||
|
||||
---
|
||||
|
||||
## 3. 등록된 알림톡 템플릿
|
||||
|
||||
### 3.1 운영 템플릿 (mng.codebridge-x.com)
|
||||
|
||||
| 템플릿명 | 용도 | 상태 | 버튼 URL |
|
||||
|---------|------|------|---------|
|
||||
| `전자계약_서명요청` | 서명 요청 알림 | 승인 완료 | `https://mng.codebridge-x.com/esign/sign/#{토큰}` |
|
||||
| `전자계약_완료` | 서명 완료 알림 | 승인 완료 | `https://mng.codebridge-x.com/esign/sign/#{토큰}` |
|
||||
| `전자계약_리마인드` | 서명 독촉 알림 | 승인 완료 | `https://mng.codebridge-x.com/esign/sign/#{토큰}` |
|
||||
|
||||
### 3.2 개발 템플릿 (admin.codebridge-x.com)
|
||||
|
||||
| 템플릿명 | 용도 | 상태 | 버튼 URL |
|
||||
|---------|------|------|---------|
|
||||
| `전자계약_서명요청_DEV` | 서명 요청 알림 | 심사 중 | `https://admin.codebridge-x.com/esign/sign/#{토큰}` |
|
||||
| `전자계약_완료_DEV` | 서명 완료 알림 | 심사 중 | `https://admin.codebridge-x.com/esign/sign/#{토큰}` |
|
||||
| `전자계약_리마인드_DEV` | 서명 독촉 알림 | 심사 중 | `https://admin.codebridge-x.com/esign/sign/#{토큰}` |
|
||||
|
||||
> 개발 템플릿 본문은 운영 템플릿과 동일하며, 버튼 URL 도메인만 다르다.
|
||||
|
||||
### 3.3 템플릿 변수
|
||||
|
||||
| 변수 | 용도 | 사용 템플릿 |
|
||||
|------|------|-----------|
|
||||
| `#{이름}` | 서명자 이름 | 서명요청, 완료, 리마인드 |
|
||||
| `#{계약명}` | 계약 제목 | 서명요청, 완료, 리마인드 |
|
||||
| `#{기한}` | 서명 기한 | 서명요청, 리마인드 |
|
||||
| `#{완료일}` | 계약 완료일 | 완료 |
|
||||
| `#{토큰}` | 서명자 액세스 토큰 | 버튼 URL |
|
||||
|
||||
---
|
||||
|
||||
## 4. 역할 기반 알림 흐름
|
||||
|
||||
### 4.1 전체 흐름
|
||||
|
||||
```
|
||||
① 계약 발송 ─→ 본사: 이메일 / 상대방: 카카오톡 알림톡
|
||||
② OTP 인증 ─→ 본사: 이메일 / 상대방: SMS
|
||||
③ 다음 서명자 ─→ 본사: 이메일 / 상대방: 카카오톡 알림톡
|
||||
④ 서명 완료 ─→ 본사: 이메일(PDF) / 상대방: 카카오톡(PDF 다운로드)
|
||||
```
|
||||
|
||||
### 4.2 역할 판별
|
||||
|
||||
```php
|
||||
$isCounterpart = $signer->role === EsignSigner::ROLE_COUNTERPART;
|
||||
```
|
||||
|
||||
| 역할 | 상수 | 알림톡 | SMS(OTP) | 이메일 |
|
||||
|------|------|--------|----------|--------|
|
||||
| 본사 (creator) | `ROLE_CREATOR` | ❌ | ❌ | ✅ 항상 |
|
||||
| 상대방 (counterpart) | `ROLE_COUNTERPART` | ✅ 우선 | ✅ OTP만 | ✅ 폴백 |
|
||||
|
||||
### 4.3 이메일 폴백 조건
|
||||
|
||||
상대방(counterpart)에게도 이메일을 보내는 경우:
|
||||
- 전화번호가 없을 때 (`$signer->phone` 없음)
|
||||
- 알림톡 발송 실패 시 (`$alimtalkFailed = true`)
|
||||
- 발송 방식이 `email` 또는 `both`일 때
|
||||
|
||||
### 4.4 완료 알림 특수 처리
|
||||
|
||||
완료 알림톡 버튼은 **서명 페이지가 아닌 문서 다운로드 URL**로 강제 변경된다:
|
||||
|
||||
```php
|
||||
// sendCompletionAlimtalk() 내부
|
||||
$documentUrl = config('app.url') . '/esign/sign/' . $signer->access_token . '/api/document';
|
||||
|
||||
// 버튼 URL 강제 변경 (서명페이지 → 문서 다운로드)
|
||||
if (str_contains($btn[$urlKey], '/esign/sign/') && !str_contains($btn[$urlKey], '/api/document')) {
|
||||
$btn[$urlKey] = $documentUrl;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. SMS (OTP 인증)
|
||||
|
||||
### 5.1 발송 조건
|
||||
|
||||
상대방(counterpart)이 `alimtalk` 또는 `both` 발송 방식이고 전화번호가 있을 때 SMS로 OTP 발송:
|
||||
|
||||
```php
|
||||
if (in_array($sendMethod, ['alimtalk', 'both'])
|
||||
&& $signer->phone
|
||||
&& $signer->role === EsignSigner::ROLE_COUNTERPART) {
|
||||
$this->sendOtpViaSms($contract, $signer, $otpCode);
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 SMS 발송 파라미터
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| API | `BarobillService::sendSMSMessage()` |
|
||||
| 발신번호 | `barobill_members.manager_hp` |
|
||||
| 수신번호 | `esign_signers.phone` |
|
||||
| 메시지 | `[SAM] 전자계약 인증코드: {코드} (5분 이내 입력)` |
|
||||
| OTP 유효시간 | 5분 |
|
||||
| 최대 시도 | 5회 |
|
||||
|
||||
### 5.3 SMS 실패 시 이메일 폴백
|
||||
|
||||
SMS 발송 실패 → 이메일 OTP 폴백 → 이메일도 없으면 500 에러 반환.
|
||||
|
||||
---
|
||||
|
||||
## 6. 바로빌 템플릿 등록 절차
|
||||
|
||||
### 6.1 관리자 페이지
|
||||
|
||||
```
|
||||
https://www.barobill.co.kr 로그인 → 카카오톡 → 템플릿관리
|
||||
```
|
||||
|
||||
### 6.2 DEV 템플릿 등록 시 주의사항
|
||||
|
||||
1. **본문**: 운영 템플릿과 **완전히 동일** (1글자도 다르면 안 됨)
|
||||
2. **버튼 URL**: 도메인만 `admin.codebridge-x.com`으로 변경
|
||||
3. **템플릿명**: 운영 이름 + `_DEV` 접미사 (예: `전자계약_서명요청_DEV`)
|
||||
4. **검수 기간**: 영업일 기준 2~3일
|
||||
|
||||
### 6.3 새 템플릿 추가 시 체크리스트
|
||||
|
||||
- [ ] 바로빌에서 운영용 + 개발용 2개 등록
|
||||
- [ ] 코드에서 `resolveTemplateName('기본명')`으로 호출
|
||||
- [ ] 본문의 변수 치환 로직 추가 (str_replace)
|
||||
- [ ] 버튼 URL의 `#{토큰}` 치환 확인
|
||||
- [ ] 2단계 검증 (SendKey → GetSendKakaotalk) 포함
|
||||
|
||||
---
|
||||
|
||||
## 7. 관련 파일
|
||||
|
||||
| 파일 | 역할 |
|
||||
|------|------|
|
||||
| `app/Http/Controllers/ESign/EsignApiController.php` | 계약 발송, `sendAlimtalk()`, `resolveTemplateName()` |
|
||||
| `app/Http/Controllers/ESign/EsignPublicController.php` | OTP SMS, 완료 알림톡, `sendCompletionAlimtalk()` |
|
||||
| `app/Services/Barobill/BarobillService.php` | SOAP 클라이언트 (`sendATKakaotalkEx`, `sendSMSMessage`) |
|
||||
| `app/Models/ESign/EsignSigner.php` | `ROLE_CREATOR`, `ROLE_COUNTERPART` 상수 |
|
||||
| `app/Mail/EsignCompletedMail.php` | 완료 이메일 (PDF 다운로드 링크) |
|
||||
| `app/Services/ESign/PdfSignatureService.php` | 서명 PDF 합성 (`mergeSignatures`) |
|
||||
|
||||
---
|
||||
|
||||
## 8. 트러블슈팅
|
||||
|
||||
### 8.1 환경별 템플릿 미스매치
|
||||
|
||||
**증상**: `ResultCode=4` (템플릿 데이터 일치 오류)
|
||||
**원인**: 개발서버에서 운영용 템플릿(`전자계약_서명요청`)으로 발송 시 버튼 URL 도메인 불일치
|
||||
**해결**: DEV 템플릿 등록 후 `APP_ENV`가 `production`이 아닌지 확인
|
||||
|
||||
### 8.2 서명 PDF 누락 (이메일)
|
||||
|
||||
**증상**: 완료 이메일의 다운로드 링크가 서명 없는 초안 PDF 반환
|
||||
**원인**: `mergeSignatures()` 실패 → `signed_file_path` 미설정 → preview PDF 폴백
|
||||
**해결**: `downloadDocument()`가 완료 상태에서 자동 재생성 시도. 로그에서 trace 확인:
|
||||
|
||||
```bash
|
||||
# 개발서버 로그 확인
|
||||
ssh pro@114.203.209.83 "tail -100 /home/webservice/mng/storage/logs/laravel.log | grep 'PDF 서명'"
|
||||
```
|
||||
|
||||
**주요 실패 원인**:
|
||||
- `storage/fonts/Pretendard-Regular.ttf` 폰트 파일 누락
|
||||
- FPDI/TCPDF 패키지 미설치 → `composer install` 필요
|
||||
- `storage/app/esign/{tenant_id}/signed/` 디렉토리 권한 문제
|
||||
|
||||
### 8.3 MNG 모델 상수 누락
|
||||
|
||||
**증상**: `Undefined constant App\Models\ESign\EsignSigner::ROLE_COUNTERPART`
|
||||
**원인**: API 프로젝트와 MNG 프로젝트의 모델이 독립적 — API에만 상수 정의됨
|
||||
**해결**: MNG `EsignSigner.php`에도 동일한 상수 추가 (2026-02-26 핫픽스 완료)
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [바로빌 카카오톡 연동 README](./README.md) — SOAP API 전체 연동 가이드
|
||||
- [E-Sign 기술 설계](../../projects/e-sign/technical-design.md) — 전자계약 아키텍처
|
||||
- [E-Sign API 명세](../../projects/e-sign/api-specification.md) — API 엔드포인트
|
||||
- [알림톡 연동 계획](../../plans/esign-alimtalk-integration.md) — 초기 계획 (구현 완료)
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-02-27
|
||||
173
features/business-card-request.md
Normal file
173
features/business-card-request.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# 명함신청 관리
|
||||
|
||||
> **작성일**: 2026-02-25
|
||||
> **상태**: 구현 완료
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
영업파트너가 명함을 신청하면 본사에서 제작소에 의뢰하고, 완료 후 처리하는 3단계 워크플로우를 제공한다.
|
||||
|
||||
### 1.2 워크플로우
|
||||
|
||||
```
|
||||
요청(pending) ──제작의뢰──→ 제작중(ordered) ──처리완료──→ 완료(processed)
|
||||
노랑 파랑 초록
|
||||
```
|
||||
|
||||
### 1.3 메뉴 구조
|
||||
|
||||
| 메뉴 | URL | 대상 | 설명 |
|
||||
|------|-----|------|------|
|
||||
| 파트너 명함신청 | `/sales/business-cards` | 모든 사용자 | 신청폼 + 내 이력 |
|
||||
| 명함신청 처리 | `/sales/business-cards/manage` | 관리자 전용 | 3단계 처리 + 뱃지 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 테이블 구조
|
||||
|
||||
### 2.1 `business_card_requests`
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | bigint | PK |
|
||||
| `tenant_id` | bigint | 테넌트 ID |
|
||||
| `user_id` | bigint | 신청자 ID |
|
||||
| `name` | varchar(50) | 성함 |
|
||||
| `phone` | varchar(20) | 전화번호 |
|
||||
| `title` | varchar(50) | 직함 (nullable) |
|
||||
| `email` | varchar(100) | 이메일 (nullable) |
|
||||
| `quantity` | int | 수량 (기본 100) |
|
||||
| `memo` | text | 비고 (nullable) |
|
||||
| `status` | varchar(20) | 상태: `pending`, `ordered`, `processed` |
|
||||
| `ordered_by` | bigint | 제작의뢰 처리자 ID (nullable) |
|
||||
| `ordered_at` | timestamp | 제작의뢰 일시 (nullable) |
|
||||
| `processed_by` | bigint | 처리완료 처리자 ID (nullable) |
|
||||
| `processed_at` | timestamp | 처리완료 일시 (nullable) |
|
||||
| `process_memo` | text | 처리 메모 (nullable) |
|
||||
| `created_at` | timestamp | 생성일 |
|
||||
| `updated_at` | timestamp | 수정일 |
|
||||
|
||||
**인덱스**: `(tenant_id, status)`, `user_id`
|
||||
|
||||
---
|
||||
|
||||
## 3. 상태 전이
|
||||
|
||||
```
|
||||
pending ──→ ordered ──→ processed
|
||||
│ ▲
|
||||
└── (역방향 전이 없음) ──┘
|
||||
```
|
||||
|
||||
| 상태 | 라벨 | 색상 | 설명 |
|
||||
|------|------|------|------|
|
||||
| `pending` | 요청 | 노랑 | 파트너가 신청, 관리자 확인 대기 |
|
||||
| `ordered` | 제작의뢰 | 파랑 | 관리자가 제작소에 의뢰 |
|
||||
| `processed` | 처리완료 | 초록 | 제작 완료, 전달 완료 |
|
||||
|
||||
---
|
||||
|
||||
## 4. API 엔드포인트
|
||||
|
||||
| Method | Path | 이름 | 설명 |
|
||||
|--------|------|------|------|
|
||||
| GET | `/sales/business-cards` | `sales.business-cards.index` | 파트너 명함신청 (신청폼 + 이력) |
|
||||
| POST | `/sales/business-cards` | `sales.business-cards.store` | 신청 등록 |
|
||||
| GET | `/sales/business-cards/manage` | `sales.business-cards.manage` | 관리자 처리 화면 |
|
||||
| POST | `/sales/business-cards/{id}/order` | `sales.business-cards.order` | 제작의뢰 (관리자) |
|
||||
| POST | `/sales/business-cards/{id}/process` | `sales.business-cards.process` | 처리완료 (관리자) |
|
||||
|
||||
---
|
||||
|
||||
## 5. 파일 구조
|
||||
|
||||
### 5.1 API 프로젝트
|
||||
|
||||
| 파일 | 설명 |
|
||||
|------|------|
|
||||
| `database/migrations/2026_02_24_100000_create_business_card_requests_table.php` | 테이블 생성 |
|
||||
| `database/migrations/2026_02_25_100000_add_ordered_columns_to_business_card_requests_table.php` | ordered 컬럼 추가 |
|
||||
|
||||
### 5.2 MNG 프로젝트
|
||||
|
||||
| 파일 | 설명 |
|
||||
|------|------|
|
||||
| `app/Models/Sales/BusinessCardRequest.php` | 모델 (상태 상수, 스코프, 헬퍼) |
|
||||
| `app/Services/Sales/BusinessCardRequestService.php` | 서비스 (CRUD, 통계, 뱃지) |
|
||||
| `app/Http/Controllers/Sales/BusinessCardRequestController.php` | 컨트롤러 |
|
||||
| `app/Providers/ViewServiceProvider.php` | 사이드바 뱃지 연동 |
|
||||
| `routes/web.php` | 라우트 5개 |
|
||||
| `resources/views/sales/business-cards/admin-index.blade.php` | 관리자 뷰 |
|
||||
| `resources/views/sales/business-cards/partner-index.blade.php` | 파트너 뷰 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 화면 구성
|
||||
|
||||
### 6.1 파트너 명함신청 (`partner-index`)
|
||||
|
||||
```
|
||||
┌─ 회사 정보 안내 (코드브릿지엑스) ──────────────┐
|
||||
├─ 신청 폼 ─────────────────────────────────────┤
|
||||
│ 성함* │ 직함 │ 전화번호* │ 이메일 │
|
||||
│ 수량 │ 메모 │ [명함 신청하기] │
|
||||
├─ 내 신청 이력 ────────────────────────────────┤
|
||||
│ 신청일 │ 성함 │ 직함 │ 전화번호 │ 수량 │ 상태 │
|
||||
│ (요청=노랑, 제작중=파랑, 처리완료=초록) │
|
||||
└───────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- 로그인 사용자 정보(name, phone, email)로 자동 채움
|
||||
- 관리자도 동일한 화면 접근 가능
|
||||
|
||||
### 6.2 명함신청 처리 (`admin-index`)
|
||||
|
||||
```
|
||||
┌─ 통계 ──────────────────────────────────────┐
|
||||
│ 신규요청(노랑) │ 제작의뢰(파랑) │ 오늘처리(초록) │ 전체 │
|
||||
├─────────────────┬───────────────────────────┤
|
||||
│ 신규 요청 │ 제작 중 │
|
||||
│ [제작의뢰] 버튼 │ 의뢰일 + [처리완료] 버튼 │
|
||||
├─────────────────┴───────────────────────────┤
|
||||
│ 처리 완료 이력 (하단 스크롤 테이블) │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- 사이드바 뱃지: 요청 + 제작의뢰 합산 건수 표시
|
||||
- 처리 버튼 클릭 시 `showConfirm()` 확인 다이얼로그
|
||||
|
||||
---
|
||||
|
||||
## 7. 뱃지 연동
|
||||
|
||||
`ViewServiceProvider`에서 `BusinessCardRequestService::getPendingCount()`를 호출하여 사이드바 메뉴 뱃지에 대기 건수를 표시한다.
|
||||
|
||||
- **카운트 기준**: `pending` + `ordered` 합산
|
||||
- **표시 위치**: "명함신청 처리" 메뉴 (`sales.business-cards.manage`)
|
||||
- **0건일 때**: 뱃지 미표시
|
||||
|
||||
---
|
||||
|
||||
## 8. 메뉴 등록 정보
|
||||
|
||||
| ID | parent_id | 이름 | URL | sort_order |
|
||||
|----|-----------|------|-----|------------|
|
||||
| 15507 | 15456 | 파트너 명함신청 | `/sales/business-cards` | 5 |
|
||||
| 15508 | 15456 | 명함신청 처리 | `/sales/business-cards/manage` | 6 |
|
||||
|
||||
> 영업파트너에게는 "파트너 명함신청"만 보이도록 메뉴 권한 설정 필요
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- 참고 패턴: `api/app/Models/CompanyRequest.php` (상태 관리 모델)
|
||||
- 참고 뷰: `mng/resources/views/sales/managers/approvals.blade.php` (2분할 레이아웃)
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-02-25
|
||||
284
features/credit-evaluation/README.md
Normal file
284
features/credit-evaluation/README.md
Normal file
@@ -0,0 +1,284 @@
|
||||
# 신용평가 시스템 (쿠콘 연동)
|
||||
|
||||
> **작성일**: 2026-03-02
|
||||
> **상태**: 운영중
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
SAM에서 거래처/협력업체의 **기업 신용정보를 조회**하여, 거래 안전성을 사전 판단하는 시스템이다.
|
||||
|
||||
### 1.2 핵심 원칙
|
||||
|
||||
- **쿠콘(KooCon/나이스평가정보)** API로 기업 신용정보 7개 항목 조회
|
||||
- **국세청 공공데이터포털** API로 사업자등록 상태(영업/휴업/폐업) 확인
|
||||
- 모든 조회 결과는 DB에 원본 저장 (감사 추적용)
|
||||
- 테넌트별 월 5건 무료, 초과 시 건당 2,000원 과금
|
||||
|
||||
---
|
||||
|
||||
## 2. 시스템 구조
|
||||
|
||||
### 2.1 전체 흐름
|
||||
|
||||
```
|
||||
사용자 (SAM MNG)
|
||||
│
|
||||
▼
|
||||
CreditController::search()
|
||||
│
|
||||
├──▶ CooconService::getAllCreditInfo()
|
||||
│ ├── OA08: 기업 기본정보
|
||||
│ ├── OA12: 신용요약정보
|
||||
│ ├── OA13: 단기연체정보
|
||||
│ ├── OA14: 신용도판단정보 (KCI)
|
||||
│ ├── OA15: 신용도판단정보 (CB)
|
||||
│ ├── OA16: 당좌거래정지정보
|
||||
│ └── OA17: 법정관리/워크아웃
|
||||
│
|
||||
├──▶ NtsBusinessService::getBusinessStatus()
|
||||
│ └── 국세청 사업자등록 상태 조회
|
||||
│
|
||||
└──▶ CreditInquiry::createFromApiResponse()
|
||||
└── DB에 조회 이력 저장
|
||||
```
|
||||
|
||||
### 2.2 파트너 구조
|
||||
|
||||
| 역할 | 대상 | 설명 |
|
||||
|------|------|------|
|
||||
| **API 제공사** | 쿠콘(KooCon) / 나이스평가정보 | 기업 신용정보 API 플랫폼 |
|
||||
| **파트너사** | (주)코드브릿지엑스 | API 키 보유, 쿠콘과 직접 계약 |
|
||||
| **이용사** | 각 테넌트 (주일, 경동 등) | SAM을 통해 신용조회 실행 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 쿠콘(KooCon) API
|
||||
|
||||
### 3.1 API 엔드포인트
|
||||
|
||||
| 환경 | URL |
|
||||
|------|-----|
|
||||
| 테스트 | `https://dev2.coocon.co.kr:8443/sol/gateway/oapi_relay.jsp` |
|
||||
| 운영 | `https://sgw.coocon.co.kr/sol/gateway/oapi_relay.jsp` |
|
||||
|
||||
### 3.2 인증 방식
|
||||
|
||||
- **API_KEY**: 쿠콘에서 발급받은 인증키 (DB `coocon_configs` 테이블에서 관리)
|
||||
- **API_ID**: 조회할 API 식별자 (OA08~OA17)
|
||||
- **TR_SEQ**: 거래일련번호 (중복 방지용, `YmdHis` + 마이크로초 6자리)
|
||||
|
||||
### 3.3 요청 형식
|
||||
|
||||
```json
|
||||
{
|
||||
"API_KEY": "발급받은_API_키",
|
||||
"API_ID": "OA12",
|
||||
"TR_SEQ": "20260302173000123456",
|
||||
"COMPANY_KEY": "1234567890"
|
||||
}
|
||||
```
|
||||
|
||||
- **Method**: POST
|
||||
- **Content-Type**: application/json
|
||||
- **Timeout**: 30초
|
||||
|
||||
### 3.4 API 목록
|
||||
|
||||
| API ID | 상수명 | 설명 | 데이터 출처 |
|
||||
|--------|--------|------|------------|
|
||||
| `OA08` | `API_COMPANY_INFO` | 기업 기본정보 | 나이스평가정보 |
|
||||
| `OA12` | `API_CREDIT_SUMMARY` | 신용요약정보 (이슈 건수 요약) | 나이스평가정보 |
|
||||
| `OA13` | `API_SHORT_TERM_OVERDUE` | 단기연체정보 | 한국신용정보원 |
|
||||
| `OA14` | `API_NEGATIVE_INFO_KCI` | 신용도판단정보 (KCI) | 한국신용정보원 + 공공정보 |
|
||||
| `OA15` | `API_NEGATIVE_INFO_CB` | 신용도판단정보 (CB) | 신용정보사 |
|
||||
| `OA16` | `API_SUSPENSION_INFO` | 당좌거래정지정보 | 금융결제원 |
|
||||
| `OA17` | `API_WORKOUT_INFO` | 법정관리/워크아웃정보 | 법원 |
|
||||
|
||||
### 3.5 응답 형식
|
||||
|
||||
```json
|
||||
{
|
||||
"RSLT_CD": "00000000",
|
||||
"RSLT_MSG": "정상처리되었습니다.",
|
||||
"RSLT_DATA": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
- `RSLT_CD === '00000000'`: 성공
|
||||
- 기타 값: 에러 (에러 메시지는 `RSLT_MSG`에 포함)
|
||||
|
||||
---
|
||||
|
||||
## 4. 국세청 사업자등록 조회 API
|
||||
|
||||
### 4.1 API 정보
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|------|
|
||||
| URL | `https://api.odcloud.kr/api/nts-businessman/v1/status` |
|
||||
| 인증 | serviceKey (쿼리 파라미터) |
|
||||
| 출처 | 공공데이터포털 |
|
||||
|
||||
### 4.2 상태 코드
|
||||
|
||||
| 코드 | 상태 | 설명 |
|
||||
|------|------|------|
|
||||
| `01` | 계속사업자 | 정상 영업 중 |
|
||||
| `02` | 휴업자 | 영업 중지 |
|
||||
| `03` | 폐업자 | 사업 종료 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 데이터베이스
|
||||
|
||||
### 5.1 `coocon_configs` — API 설정
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `name` | VARCHAR(100) | 설정 이름 |
|
||||
| `environment` | ENUM('test', 'production') | 환경 |
|
||||
| `api_key` | VARCHAR(100) | 쿠콘 API 키 |
|
||||
| `base_url` | VARCHAR(255) | API 기본 URL |
|
||||
| `description` | TEXT | 설명 |
|
||||
| `is_active` | BOOLEAN | 활성화 여부 |
|
||||
|
||||
> **규칙**: 환경당 1개만 활성화 가능. 새 설정 활성화 시 기존 설정은 자동 비활성화.
|
||||
|
||||
### 5.2 `credit_inquiries` — 조회 이력
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `tenant_id` | BIGINT FK | 테넌트 |
|
||||
| `inquiry_key` | VARCHAR(32) UNIQUE | 조회 고유키 |
|
||||
| `company_key` | VARCHAR(20) | 사업자번호/법인번호 |
|
||||
| `company_name` | VARCHAR | 업체명 |
|
||||
| `user_id` | BIGINT FK | 조회자 |
|
||||
| `inquired_at` | TIMESTAMP | 조회 일시 |
|
||||
| `nts_status` | VARCHAR(20) | 국세청 상태 |
|
||||
| `nts_status_code` | VARCHAR(2) | 국세청 상태코드 |
|
||||
| `short_term_overdue_cnt` | UINT | 단기연체 건수 |
|
||||
| `negative_info_kci_cnt` | UINT | KCI 건수 |
|
||||
| `negative_info_pb_cnt` | UINT | 공공정보 건수 |
|
||||
| `negative_info_cb_cnt` | UINT | CB 건수 |
|
||||
| `suspension_info_cnt` | UINT | 당좌거래정지 건수 |
|
||||
| `workout_cnt` | UINT | 법정관리/워크아웃 건수 |
|
||||
| `raw_*` | JSON | 각 API 원본 응답 (7개 + NTS) |
|
||||
| `status` | ENUM | success / partial / failed |
|
||||
|
||||
---
|
||||
|
||||
## 6. 과금 정책
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|------|
|
||||
| 월 무료 할당량 | **5건** |
|
||||
| 초과 건당 요금 | **2,000원** |
|
||||
| 계산식 | `max(0, (조회건수 - 5)) × 2,000` |
|
||||
|
||||
### 요금 예시
|
||||
|
||||
| 월 조회 건수 | 무료 | 유료 | 요금 |
|
||||
|-------------|------|------|------|
|
||||
| 3건 | 3 | 0 | 0원 |
|
||||
| 5건 | 5 | 0 | 0원 |
|
||||
| 10건 | 5 | 5 | 10,000원 |
|
||||
| 20건 | 5 | 15 | 30,000원 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 환경 설정
|
||||
|
||||
### 7.1 테스트/운영 분리
|
||||
|
||||
| 환경 | API URL | 설명 |
|
||||
|------|---------|------|
|
||||
| 테스트 | `dev2.coocon.co.kr:8443` | 개발/검증용 (과금 없음) |
|
||||
| 운영 | `sgw.coocon.co.kr` | 실 서비스 (과금 발생) |
|
||||
|
||||
- `coocon_configs` 테이블에서 환경별로 별도 설정 관리
|
||||
- 각 환경에서 `is_active=true`인 설정 1개만 사용
|
||||
|
||||
### 7.2 필요한 설정
|
||||
|
||||
| 항목 | 관리 위치 | 설명 |
|
||||
|------|----------|------|
|
||||
| 쿠콘 API 키 | DB (`coocon_configs`) | 쿠콘에서 발급 |
|
||||
| 쿠콘 API URL | DB (`coocon_configs`) | 환경별 URL |
|
||||
| 국세청 API 키 | 코드 내 하드코딩 | 공공데이터포털 발급 |
|
||||
|
||||
---
|
||||
|
||||
## 8. MNG 라우트
|
||||
|
||||
| Method | Path | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/credit/inquiry` | 조회 이력 목록 |
|
||||
| POST | `/credit/inquiry/search` | 신용정보 조회 실행 |
|
||||
| POST | `/credit/inquiry/test` | API 연결 테스트 |
|
||||
| GET | `/credit/inquiry/{key}/raw` | 원본 데이터 조회 |
|
||||
| GET | `/credit/inquiry/{key}/report` | 리포트 조회 |
|
||||
| DELETE | `/credit/inquiry/{id}` | 이력 삭제 |
|
||||
| GET | `/credit/usage` | 조회회수 집계 |
|
||||
| GET | `/credit/settings` | 설정 관리 |
|
||||
| POST | `/credit/settings` | 설정 생성 |
|
||||
| PUT | `/credit/settings/{id}` | 설정 수정 |
|
||||
| DELETE | `/credit/settings/{id}` | 설정 삭제 |
|
||||
| POST | `/credit/settings/{id}/toggle` | 활성화 토글 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 에러 코드
|
||||
|
||||
### 9.1 쿠콘 API
|
||||
|
||||
| 코드 | 설명 |
|
||||
|------|------|
|
||||
| `NO_CONFIG` | API 설정 없음 |
|
||||
| `HTTP_ERROR` | HTTP 통신 오류 |
|
||||
| `EXCEPTION` | 예외 발생 |
|
||||
| `RSLT_CD ≠ 00000000` | 쿠콘 API 에러 (RSLT_MSG 참조) |
|
||||
|
||||
### 9.2 국세청 API
|
||||
|
||||
| 코드 | 설명 |
|
||||
|------|------|
|
||||
| `INVALID_FORMAT` | 사업자번호 형식 오류 |
|
||||
| `NOT_FOUND` | 조회 결과 없음 |
|
||||
| `HTTP_ERROR` | HTTP 통신 오류 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 관련 파일
|
||||
|
||||
### MNG 프로젝트
|
||||
|
||||
| 구분 | 경로 |
|
||||
|------|------|
|
||||
| 컨트롤러 | `app/Http/Controllers/Credit/CreditController.php` |
|
||||
| 컨트롤러 | `app/Http/Controllers/Credit/CreditUsageController.php` |
|
||||
| 서비스 | `app/Services/Coocon/CooconService.php` |
|
||||
| 서비스 | `app/Services/Nts/NtsBusinessService.php` |
|
||||
| 모델 | `app/Models/Coocon/CooconConfig.php` |
|
||||
| 모델 | `app/Models/Credit/CreditInquiry.php` |
|
||||
| 뷰 | `resources/views/credit/inquiry/index.blade.php` |
|
||||
| 뷰 | `resources/views/credit/usage/index.blade.php` |
|
||||
| 뷰 | `resources/views/credit/settings/index.blade.php` |
|
||||
|
||||
### API 프로젝트 (마이그레이션)
|
||||
|
||||
| 경로 |
|
||||
|------|
|
||||
| `database/migrations/2026_01_22_192637_create_coocon_configs_table.php` |
|
||||
| `database/migrations/2026_01_22_201143_create_credit_inquiries_table.php` |
|
||||
| `database/migrations/2026_01_22_203001_add_company_info_to_credit_inquiries_table.php` |
|
||||
| `database/migrations/2026_01_28_163000_add_tenant_id_to_credit_inquiries_table.php` |
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-02
|
||||
@@ -111,10 +111,12 @@ DRAFT → PENDING → APPROVED
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [MNG 문서관리 시스템 상세](mng-document-system.md) — MNG 화면 구성, 탭별 기능, 서식 빌더, EAV 저장 패턴 상세
|
||||
- [MNG 문서양식관리](mng-document-template.md) — 서식 생성/편집, Legacy/Block Builder, 프리셋, 연결품목 관리
|
||||
- [DB 스키마 — 문서/전자서명](../../system/database/documents.md)
|
||||
- [게시판 시스템](../boards/README.md) — 유사한 EAV 패턴 적용
|
||||
- Swagger: `/api-docs` → Documents 섹션
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-02-27
|
||||
**최종 업데이트**: 2026-03-06
|
||||
|
||||
738
features/documents/mng-document-system.md
Normal file
738
features/documents/mng-document-system.md
Normal file
@@ -0,0 +1,738 @@
|
||||
# MNG 문서관리 시스템 상세 기술 명세
|
||||
|
||||
> **작성일**: 2026-03-06
|
||||
> **상태**: 운영 중
|
||||
> **프로젝트**: SAM MNG (관리자 웹)
|
||||
> **관련**: [README.md](README.md) (API 명세)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
블라인드/스크린 제조 현장의 **검사 성적서, 작업일지, 수입검사 기록** 등 품질/생산 문서를 전자화하여 관리하는 시스템. 문서 양식(Template)을 정의하면 EAV 패턴으로 데이터를 동적 저장하며, 다단계 결재 워크플로우를 지원한다.
|
||||
|
||||
### 1.2 핵심 특징
|
||||
|
||||
| 특징 | 설명 |
|
||||
|------|------|
|
||||
| **EAV 패턴** | 양식별로 다른 필드를 하나의 `document_data` 테이블에 저장 |
|
||||
| **2가지 양식 빌더** | 레거시 빌더 (DB 정규화) + 블록 빌더 (A4 JSON 스키마) |
|
||||
| **결재 워크플로우** | 작성 → 검토 → 승인 (다단계 순차 결재) |
|
||||
| **자동 데이터 매핑** | 작업지시서/수주 데이터에서 기본필드 자동 채움 |
|
||||
| **다형성 연결** | work_order, sales_order 등 다양한 모델과 연결 |
|
||||
| **자재 LOT 추적** | 검사 문서에서 투입 자재의 LOT 이력 조회 |
|
||||
|
||||
### 1.3 문서 구조
|
||||
|
||||
| 문서 | 설명 |
|
||||
|------|------|
|
||||
| [README.md](README.md) | API 엔드포인트, 모델 요약, FormRequest |
|
||||
| **이 문서** | MNG 화면별 상세, 동작원리, 데이터 흐름 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 메뉴/탭 구조
|
||||
|
||||
```
|
||||
생산 관리
|
||||
└── 문서관리
|
||||
├── 문서 목록 /documents ← 문서 검색/필터/관리
|
||||
├── 새 문서 작성 /documents/create ← 템플릿 선택 → 폼 입력
|
||||
├── 문서 상세 /documents/{id} ← 읽기 전용 + 결재 현황
|
||||
├── 문서 수정 /documents/{id}/edit ← DRAFT/REJECTED만
|
||||
├── 인쇄 /documents/{id}/print ← 성적서 인쇄용
|
||||
│
|
||||
└── 문서양식 관리
|
||||
├── 양식 목록 /document-templates ← 양식 검색/관리
|
||||
├── 새 양식 (레거시) /document-templates/create ← 레거시 빌더
|
||||
├── 양식 수정 /document-templates/{id}/edit ← 자동 빌더 판별
|
||||
├── 양식 디자이너 /document-templates/block-create ← 블록 빌더
|
||||
└── 블록 수정 /document-templates/{id}/block-edit ← 블록 빌더 수정
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 파일 구조
|
||||
|
||||
```
|
||||
mng/
|
||||
├── app/Http/Controllers/
|
||||
│ ├── DocumentController.php ← 문서 CRUD 화면
|
||||
│ └── DocumentTemplateController.php ← 양식 관리 화면
|
||||
├── app/Models/Documents/
|
||||
│ ├── Document.php ← 문서 모델
|
||||
│ ├── DocumentApproval.php ← 결재 단계
|
||||
│ ├── DocumentData.php ← EAV 데이터
|
||||
│ ├── DocumentTemplate.php ← 양식 마스터
|
||||
│ └── ... (기타 템플릿 관련 모델)
|
||||
└── resources/views/
|
||||
├── documents/
|
||||
│ ├── index.blade.php ← 문서 목록
|
||||
│ ├── edit.blade.php ← 문서 작성/수정
|
||||
│ ├── show.blade.php ← 문서 상세
|
||||
│ └── print.blade.php ← 인쇄 전용
|
||||
└── document-templates/
|
||||
├── index.blade.php ← 양식 목록
|
||||
├── edit.blade.php ← 레거시 빌더
|
||||
├── block-editor.blade.php ← 블록 빌더
|
||||
└── partials/
|
||||
├── block-palette.blade.php ← 블록 타입 목록
|
||||
├── block-canvas.blade.php ← 편집 캔버스
|
||||
└── block-properties.blade.php ← 속성 패널
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 데이터베이스 아키텍처
|
||||
|
||||
### 4.1 테이블 관계도
|
||||
|
||||
```
|
||||
document_templates (양식 마스터)
|
||||
├── 1:N → document_template_approval_lines (결재선 정의)
|
||||
├── 1:N → document_template_basic_fields (기본필드 정의)
|
||||
├── 1:N → document_template_sections (섹션 정의)
|
||||
│ └── 1:N → document_template_section_items (검사항목)
|
||||
├── 1:N → document_template_columns (테이블 컬럼 정의)
|
||||
├── 1:N → document_template_section_fields (섹션 필드)
|
||||
├── 1:N → document_template_links (외부 연결 정의)
|
||||
│ └── 1:N → document_template_link_values (템플릿 레벨 연결값)
|
||||
│
|
||||
└── 1:N → documents (문서 인스턴스)
|
||||
├── 1:N → document_approvals (결재 진행)
|
||||
├── 1:N → document_data (EAV 필드값)
|
||||
├── 1:N → document_attachments (첨부파일)
|
||||
└── 1:N → document_links (문서 레벨 연결)
|
||||
```
|
||||
|
||||
### 4.2 documents (문서)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `tenant_id` | BIGINT FK | 테넌트 격리 |
|
||||
| `template_id` | BIGINT FK | 사용 양식 |
|
||||
| `document_no` | VARCHAR UNIQUE | 문서번호 (자동 채번) |
|
||||
| `title` | VARCHAR | 문서 제목 |
|
||||
| `status` | VARCHAR(20) | 상태 (5가지) |
|
||||
| `linkable_type` | VARCHAR NULL | 다형성 모델 타입 |
|
||||
| `linkable_id` | BIGINT NULL | 다형성 모델 ID |
|
||||
| `submitted_at` | TIMESTAMP NULL | 결재 요청 일시 |
|
||||
| `completed_at` | TIMESTAMP NULL | 결재 완료 일시 |
|
||||
| `created_by` | BIGINT FK | 작성자 |
|
||||
| `deleted_at` | TIMESTAMP NULL | 소프트 삭제 |
|
||||
|
||||
**인덱스**: `(tenant_id, status)`, `document_no`, `(linkable_type, linkable_id)`
|
||||
|
||||
### 4.3 document_data (EAV 필드값)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `document_id` | BIGINT FK | 소속 문서 |
|
||||
| `section_id` | BIGINT FK NULL | 소속 섹션 (NULL=기본필드) |
|
||||
| `column_id` | BIGINT FK NULL | 소속 컬럼 (테이블 데이터용) |
|
||||
| `row_index` | INT | 테이블 행 번호 (기본: 0) |
|
||||
| `field_key` | VARCHAR | 필드 식별자 (`bf_1`, `cf_2`, `col_3`) |
|
||||
| `field_value` | TEXT NULL | 실제 값 |
|
||||
|
||||
**인덱스**: `(document_id, section_id)`, `(document_id, field_key)`
|
||||
|
||||
### 4.4 document_approvals (결재)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `document_id` | BIGINT FK | 소속 문서 |
|
||||
| `user_id` | BIGINT FK | 결재자 |
|
||||
| `step` | INT | 결재 순서 (1, 2, 3...) |
|
||||
| `role` | VARCHAR | 역할 (작성, 검토, 승인) |
|
||||
| `status` | VARCHAR(20) | PENDING / APPROVED / REJECTED |
|
||||
| `comment` | TEXT NULL | 결재 의견 |
|
||||
| `acted_at` | TIMESTAMP NULL | 처리 일시 |
|
||||
|
||||
**인덱스**: `(document_id, step)`, `(user_id, status)`
|
||||
|
||||
### 4.5 document_attachments (첨부파일)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `document_id` | BIGINT FK | 소속 문서 |
|
||||
| `file_id` | BIGINT FK | File 모델 연결 |
|
||||
| `attachment_type` | VARCHAR | `general`, `signature`, `image`, `reference` |
|
||||
| `description` | VARCHAR NULL | 설명 |
|
||||
| `created_by` | BIGINT FK | 업로드자 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 양식(Template) 시스템
|
||||
|
||||
### 5.1 두 가지 빌더 방식
|
||||
|
||||
| 방식 | 필드명 | 저장 구조 | UI | 상태 |
|
||||
|------|--------|----------|-----|------|
|
||||
| **레거시 빌더** | `builder_type = null` | 정규화 테이블들 | `edit.blade.php` | 기존 양식용 |
|
||||
| **블록 빌더** | `builder_type = 'block'` | `schema` JSON | `block-editor.blade.php` | 신규 양식용 |
|
||||
|
||||
**자동 판별 로직:**
|
||||
|
||||
```php
|
||||
// DocumentTemplateController::edit()
|
||||
if ($template->isBlockBuilder()) {
|
||||
return $this->blockEdit($id); // block-editor.blade.php
|
||||
} else {
|
||||
return view('document-templates.edit'); // 레거시
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 양식 마스터 (document_templates)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `name` | VARCHAR | 양식명 (예: "제품검사 성적서") |
|
||||
| `category` | VARCHAR | 분류 (common_codes 기반) |
|
||||
| `title` | VARCHAR NULL | 문서 제목 템플릿 |
|
||||
| `company_name` | VARCHAR NULL | 회사명 |
|
||||
| `company_address` | VARCHAR NULL | 회사 주소 |
|
||||
| `company_contact` | VARCHAR NULL | 연락처 |
|
||||
| `footer_remark_label` | VARCHAR NULL | 비고란 라벨 |
|
||||
| `footer_judgement_label` | VARCHAR NULL | 판정란 라벨 |
|
||||
| `footer_judgement_options` | JSON NULL | 판정 선택지 (적합/부적합) |
|
||||
| `builder_type` | VARCHAR NULL | `block` 또는 NULL |
|
||||
| `schema` | JSON NULL | 블록 빌더 JSON 스키마 |
|
||||
| `page_config` | JSON NULL | 페이지 설정 (A4, 여백 등) |
|
||||
| `is_active` | BOOLEAN | 활성 여부 |
|
||||
|
||||
### 5.3 레거시 빌더 구성 요소
|
||||
|
||||
#### 결재선 (document_template_approval_lines)
|
||||
|
||||
```
|
||||
step 1: 작성 (작성자 본인)
|
||||
step 2: 검토 (팀장)
|
||||
step 3: 승인 (부장)
|
||||
```
|
||||
|
||||
| 컬럼 | 설명 |
|
||||
|------|------|
|
||||
| `name` | 라벨 (작성, 검토, 승인) |
|
||||
| `dept` | 부서 |
|
||||
| `role` | 역할 |
|
||||
| `sort_order` | 순서 |
|
||||
|
||||
#### 기본필드 (document_template_basic_fields)
|
||||
|
||||
문서 상단의 고정 필드 영역.
|
||||
|
||||
| 컬럼 | 설명 |
|
||||
|------|------|
|
||||
| `label` | 필드 라벨 (품명, LOT NO, 납기일 등) |
|
||||
| `field_key` | 식별자 (EAV 저장 시 사용) |
|
||||
| `field_type` | 입력 타입 (text, date, number, item_search) |
|
||||
| `default_value` | 기본값 |
|
||||
| `sort_order` | 순서 |
|
||||
|
||||
**EAV 저장 시 field_key 패턴:**
|
||||
|
||||
```
|
||||
bf_1 → 기본필드 ID 1 (예: 품명)
|
||||
bf_2 → 기본필드 ID 2 (예: LOT NO)
|
||||
bf_3 → 기본필드 ID 3 (예: 납기일)
|
||||
```
|
||||
|
||||
#### 섹션 (document_template_sections)
|
||||
|
||||
검사 기준서의 섹션 단위.
|
||||
|
||||
| 컬럼 | 설명 |
|
||||
|------|------|
|
||||
| `title` | 섹션 제목 (예: "겉모양 검사", "치수 검사") |
|
||||
| `image_path` | 도해 이미지 경로 (검사 부위 도면) |
|
||||
| `sort_order` | 순서 |
|
||||
|
||||
#### 검사항목 (document_template_section_items)
|
||||
|
||||
각 섹션 내의 개별 검사항목.
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `category` | VARCHAR | 구분 (겉모양, 치수, 재질) |
|
||||
| `item` | VARCHAR | 검사항목명 |
|
||||
| `standard` | VARCHAR | 검사기준 (100mm ±5mm) |
|
||||
| `tolerance` | JSON NULL | 허용오차 (min/max) |
|
||||
| `standard_criteria` | VARCHAR NULL | 판정기준 |
|
||||
| `method` | VARCHAR | 검사방법 (육안, 측정) |
|
||||
| `measurement_type` | VARCHAR NULL | 측정 유형 |
|
||||
| `frequency_n` | INT NULL | 검사건수 N |
|
||||
| `frequency_c` | INT NULL | 합격건수 C |
|
||||
| `frequency` | VARCHAR NULL | 검사빈도 텍스트 |
|
||||
| `field_values` | JSON NULL | 확장 필드 (마이그레이션 없이 추가) |
|
||||
|
||||
#### 테이블 컬럼 (document_template_columns)
|
||||
|
||||
검사 데이터 테이블의 컬럼 정의.
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `label` | VARCHAR | 컬럼 라벨 |
|
||||
| `width` | INT NULL | 너비 (px) |
|
||||
| `column_type` | VARCHAR | `text`, `check`, `complex`, `measurement`, `select` |
|
||||
| `group_name` | VARCHAR NULL | 상단 병합 헤더명 |
|
||||
| `sub_labels` | JSON NULL | complex 타입 하위 라벨 |
|
||||
| `sort_order` | INT | 순서 |
|
||||
|
||||
**컬럼 타입 상세:**
|
||||
|
||||
| 타입 | 설명 | 예시 |
|
||||
|------|------|------|
|
||||
| `text` | 단순 텍스트 입력 | 비고, 메모 |
|
||||
| `check` | 체크박스 (합격/부적합) | 외관 검사 합격 여부 |
|
||||
| `complex` | 여러 서브필드 조합 | 측정값 + 단위 + 판정 |
|
||||
| `measurement` | 수치 입력 | 길이: 100.5mm |
|
||||
| `select` | 드롭다운 선택 | 판정: 합격/불합격/보류 |
|
||||
|
||||
#### 외부 연결 (document_template_links)
|
||||
|
||||
템플릿에서 외부 테이블 데이터를 참조하기 위한 정의.
|
||||
|
||||
| 컬럼 | 설명 |
|
||||
|------|------|
|
||||
| `link_key` | 연결 식별자 |
|
||||
| `label` | 화면 라벨 |
|
||||
| `link_type` | `single` (1개 선택) / `multiple` (다중 선택) |
|
||||
| `source_table` | 소스 테이블 (`items`, `processes`, `users`) |
|
||||
| `search_params` | API 검색 추가 조건 (JSON) |
|
||||
| `display_fields` | 표시 필드 (title, subtitle) |
|
||||
| `is_required` | 필수 여부 |
|
||||
|
||||
### 5.4 블록 빌더 구조
|
||||
|
||||
**페이지 설정 (page_config):**
|
||||
|
||||
```json
|
||||
{
|
||||
"size": "A4",
|
||||
"orientation": "portrait",
|
||||
"margin": {
|
||||
"top": 20,
|
||||
"right": 15,
|
||||
"bottom": 20,
|
||||
"left": 15
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**스키마 (schema):**
|
||||
|
||||
블록 배열로 레이아웃 정의. 드래그앤드롭으로 편집.
|
||||
|
||||
```json
|
||||
{
|
||||
"blocks": [
|
||||
{ "type": "text", "x": 0, "y": 0, "width": 100, "content": "검사 성적서" },
|
||||
{ "type": "table", "x": 0, "y": 50, "columns": [...], "rows": [...] },
|
||||
{ "type": "image", "x": 200, "y": 100, "src": "..." }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**블록 빌더 UI (3패널):**
|
||||
|
||||
```
|
||||
┌──────────┬────────────────────┬──────────┐
|
||||
│ 블록 │ │ 속성 │
|
||||
│ 팔레트 │ A4 캔버스 │ 패널 │
|
||||
│ │ │ │
|
||||
│ [텍스트] │ ┌──────────────┐ │ 너비: _ │
|
||||
│ [이미지] │ │ 드래그앤드롭 │ │ 높이: _ │
|
||||
│ [표] │ │ 블록 배치 │ │ 색상: _ │
|
||||
│ [선] │ │ │ │ 폰트: _ │
|
||||
│ [도형] │ └──────────────┘ │ │
|
||||
└──────────┴────────────────────┴──────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. EAV 데이터 저장 패턴
|
||||
|
||||
### 6.1 핵심 개념
|
||||
|
||||
하나의 `document_data` 테이블에 **모든 양식의 모든 필드값**을 저장. 양식이 다르면 field_key가 다르고, 같은 양식이라도 섹션/행이 다르면 section_id/row_index로 구분.
|
||||
|
||||
### 6.2 저장 구조
|
||||
|
||||
```
|
||||
document_data 레코드 예시:
|
||||
|
||||
기본필드 (상단 고정 영역):
|
||||
┌─────────────┬────────────┬───────────┬───────────┬───────────┬─────────────┐
|
||||
│ document_id │ section_id │ column_id │ row_index │ field_key │ field_value │
|
||||
├─────────────┼────────────┼───────────┼───────────┼───────────┼─────────────┤
|
||||
│ 42 │ NULL │ NULL │ 0 │ bf_1 │ 블라인드A │ ← 품명
|
||||
│ 42 │ NULL │ NULL │ 0 │ bf_2 │ LOT-2026-001│ ← LOT NO
|
||||
│ 42 │ NULL │ NULL │ 0 │ bf_3 │ 2026-03-15 │ ← 납기일
|
||||
├─────────────┼────────────┼───────────┼───────────┼───────────┼─────────────┤
|
||||
|
||||
테이블 데이터 (섹션별 검사 결과):
|
||||
│ 42 │ 10 │ 20 │ 0 │ col_20 │ 합격 │ ← 섹션10, 컬럼20, 1행
|
||||
│ 42 │ 10 │ 20 │ 1 │ col_20 │ 부적합 │ ← 섹션10, 컬럼20, 2행
|
||||
│ 42 │ 10 │ 21 │ 0 │ col_21 │ 100.5 │ ← 섹션10, 컬럼21, 1행
|
||||
└─────────────┴────────────┴───────────┴───────────┴───────────┴─────────────┘
|
||||
```
|
||||
|
||||
### 6.3 field_key 네이밍 규칙
|
||||
|
||||
| 접두사 | 의미 | 예시 |
|
||||
|--------|------|------|
|
||||
| `bf_` | 기본필드 (BasicField) | `bf_1`, `bf_2` |
|
||||
| `cf_` | 섹션필드 (SectionField) | `cf_5`, `cf_6` |
|
||||
| `col_` | 컬럼 데이터 | `col_20`, `col_21` |
|
||||
|
||||
### 6.4 데이터 조회 패턴
|
||||
|
||||
```php
|
||||
// 기본필드 값 조회
|
||||
$data = DocumentData::where('document_id', $id)
|
||||
->whereNull('section_id')
|
||||
->get()
|
||||
->keyBy('field_key');
|
||||
|
||||
$productName = $data['bf_1']->field_value;
|
||||
|
||||
// 섹션별 테이블 데이터 조회
|
||||
$rows = DocumentData::where('document_id', $id)
|
||||
->where('section_id', $sectionId)
|
||||
->get()
|
||||
->groupBy('row_index');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 결재 워크플로우
|
||||
|
||||
### 7.1 상태 전이
|
||||
|
||||
```
|
||||
DRAFT (작성중)
|
||||
│
|
||||
├── submit() → PENDING (결재중)
|
||||
│ │
|
||||
│ ├── approve() [step 1] → 다음 step 대기
|
||||
│ ├── approve() [step 2] → 다음 step 대기
|
||||
│ ├── approve() [마지막] → APPROVED (승인)
|
||||
│ │
|
||||
│ └── reject() → REJECTED (반려)
|
||||
│ │
|
||||
│ └── edit → submit() → PENDING (재요청)
|
||||
│
|
||||
└── cancel() → CANCELLED (취소)
|
||||
```
|
||||
|
||||
### 7.2 상태값 및 라벨
|
||||
|
||||
| 코드 | 라벨 | 색상 | 편집 가능 |
|
||||
|------|------|------|----------|
|
||||
| `DRAFT` | 작성중 | gray | 예 |
|
||||
| `PENDING` | 결재중 | yellow | 아니오 |
|
||||
| `APPROVED` | 승인 | green | 아니오 |
|
||||
| `REJECTED` | 반려 | red | 예 (수정 후 재요청) |
|
||||
| `CANCELLED` | 취소 | gray | 아니오 |
|
||||
|
||||
### 7.3 결재 단계 (Approval)
|
||||
|
||||
```
|
||||
DocumentTemplateApprovalLine (양식 정의)
|
||||
↓ (문서 생성 시 복사)
|
||||
DocumentApproval (문서별 결재 레코드)
|
||||
|
||||
step 1: 작성 → PENDING → 결재자 승인 → APPROVED
|
||||
step 2: 검토 → PENDING → 결재자 승인 → APPROVED
|
||||
step 3: 승인 → PENDING → 결재자 승인 → APPROVED → 문서 전체 APPROVED
|
||||
```
|
||||
|
||||
### 7.4 결재 판단 메서드
|
||||
|
||||
```php
|
||||
// Document 모델
|
||||
canEdit() // DRAFT 또는 REJECTED
|
||||
canSubmit() // DRAFT 또는 REJECTED
|
||||
canApprove() // PENDING (현재 결재자만)
|
||||
canCancel() // DRAFT 또는 PENDING (작성자만)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 자동 데이터 매핑
|
||||
|
||||
### 8.1 개요
|
||||
|
||||
문서 작성/수정 시, 연결된 작업지시서(work_order)/수주(order) 데이터에서 기본필드를 **자동으로 채움**. 사용자 입력 부담을 줄이고 데이터 정확성을 보장.
|
||||
|
||||
### 8.2 검사 성적서 매핑 (field_key 기반)
|
||||
|
||||
| field_key | 라벨 | 소스 |
|
||||
|-----------|------|------|
|
||||
| `product_name` | 품명 | `workOrderItem.item_name` |
|
||||
| `specification` | 규격 | `workOrderItem.specification` |
|
||||
| `lot_no` | LOT NO | `order.order_no` |
|
||||
| `lot_size` | LOT 크기 | `"N 개소"` (개소 수 기반) |
|
||||
| `client` | 발주처 | `order.client_name` |
|
||||
| `site_name` | 현장명 | `workOrder.project_name` |
|
||||
| `inspection_date` | 검사일 | `workOrderItem.options.inspection_data.inspected_at` |
|
||||
| `inspector` | 검사자 | 검사자 이름 |
|
||||
|
||||
### 8.3 작업일지 매핑 (label 기반)
|
||||
|
||||
| label 포함 문자열 | 소스 |
|
||||
|------------------|------|
|
||||
| `발주처` | `order.client_name` |
|
||||
| `현장명` | `workOrder.project_name` |
|
||||
| `작업일자` | `now()` |
|
||||
| `LOT NO`, `LOT` | `order.order_no` |
|
||||
| `납기일`, `납기` | `order.delivery_date` |
|
||||
| `작업지시번호` | `workOrder.work_order_no` |
|
||||
| `수주일` | `order.received_at` 또는 `order.created_at` |
|
||||
|
||||
### 8.4 자동 매핑 흐름
|
||||
|
||||
```
|
||||
문서 작성/수정 페이지 로드
|
||||
↓
|
||||
DocumentController::edit()
|
||||
↓
|
||||
resolveAndBackfillBasicFields($template, $document)
|
||||
↓
|
||||
linkable_type 확인 (work_order? order?)
|
||||
↓
|
||||
field_key 또는 label 매칭
|
||||
↓
|
||||
DB에 값이 없으면 → 소스 데이터에서 resolve
|
||||
↓
|
||||
뷰에 자동 채움된 값 전달
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 자재 LOT 추적
|
||||
|
||||
### 9.1 개요
|
||||
|
||||
검사 성적서에서 해당 작업지시의 **투입 자재 LOT 이력**을 조회. `stock_transactions` 테이블의 OUT(투입)/IN(취소) 트랜잭션을 상쇄하여 순수 투입량을 계산.
|
||||
|
||||
### 9.2 추적 구조
|
||||
|
||||
```
|
||||
work_orders (작업지시)
|
||||
│
|
||||
├── stock_transactions (재고 트랜잭션)
|
||||
│ ├── OUT (투입): qty < 0
|
||||
│ └── IN (취소/반납): qty > 0
|
||||
│ → 순수 투입량 = ABS(SUM(qty)) where qty < 0
|
||||
│
|
||||
└── work_order_material_inputs (개소별 투입자재)
|
||||
└── stock_lots (LOT 정보) JOIN
|
||||
```
|
||||
|
||||
### 9.3 표시 내용
|
||||
|
||||
| 항목 | 설명 |
|
||||
|------|------|
|
||||
| 자재명 | 투입된 원자재/부자재 이름 |
|
||||
| LOT 번호 | 자재의 LOT 식별 번호 |
|
||||
| 투입 수량 | OUT 트랜잭션 합계 (절대값) |
|
||||
| 투입일 | 트랜잭션 일시 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 화면별 상세
|
||||
|
||||
### 10.1 문서 목록 (/documents)
|
||||
|
||||
**필터 항목:**
|
||||
|
||||
| 필터 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| 검색 | text | 문서번호 또는 제목 |
|
||||
| 상태 | dropdown | DRAFT, PENDING, APPROVED, REJECTED, CANCELLED, 휴지통(admin) |
|
||||
| 양식분류 | dropdown | category |
|
||||
| 템플릿 | dropdown | template_id |
|
||||
| 날짜 범위 | date | created_at (from ~ to) |
|
||||
|
||||
**목록 테이블 컬럼:**
|
||||
|
||||
```
|
||||
문서번호 | 제목 | 양식 | 상태 | 작성자 | 작성일 | 결재현황
|
||||
```
|
||||
|
||||
### 10.2 문서 작성/수정 (/documents/create, /documents/{id}/edit)
|
||||
|
||||
**폼 구성:**
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ 템플릿 선택 (읽기전용) │
|
||||
│ 제목 (필수) │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ 기본 필드 (template.basicFields) │
|
||||
│ ┌─────────────────┬─────────────────┐ │
|
||||
│ │ 품명: [자동채움] │ LOT NO: [자동] │ │
|
||||
│ │ 납기일: [날짜] │ 발주처: [자동] │ │
|
||||
│ └─────────────────┴─────────────────┘ │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ 섹션 1: 겉모양 검사 │
|
||||
│ ┌──────────────────────────────────────┐ │
|
||||
│ │ 도해 이미지 (있으면) │ │
|
||||
│ ├──────┬──────┬──────┬──────┬──────┤ │
|
||||
│ │ 구분 │ 항목 │ 기준 │ 결과1│ 결과2│ │
|
||||
│ ├──────┼──────┼──────┼──────┼──────┤ │
|
||||
│ │ 치수 │ 길이 │±5mm │ [ ] │ [ ] │ │
|
||||
│ │ 외관 │ 흠집 │ 없음 │ [✓] │ [✓] │ │
|
||||
│ ├──────┴──────┴──────┴──────┴──────┤ │
|
||||
│ │ [+ 행 추가] [행 삭제] │ │
|
||||
│ └──────────────────────────────────────┘ │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ 외부 연결 (template.links) │
|
||||
│ 품목 선택: [검색 드롭다운] │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ 첨부파일 │
|
||||
│ [일반 문서] [서명 이미지] [검사 사진] [참고 자료] │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ [임시저장] [결재 요청] │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 10.3 문서 상세 (/documents/{id})
|
||||
|
||||
**읽기 전용 표시:**
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ 문서번호: DOC-260306-001 상태: [🟢 승인] │
|
||||
│ 제목: 블라인드A 검사 성적서 │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ 기본 필드 (읽기 전용) │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ 검사 데이터 테이블 (읽기 전용) │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ 결재 현황 │
|
||||
│ ┌────────┬────────┬────────┐ │
|
||||
│ │ 작성 │ 검토 │ 승인 │ │
|
||||
│ │ 홍길동 │ 김과장 │ 박부장 │ │
|
||||
│ │ ✓승인 │ ✓승인 │ ●대기 │ │
|
||||
│ └────────┴────────┴────────┘ │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ 자재 투입 LOT (작업지시 연결 시) │
|
||||
│ ┌────────┬──────────┬──────┬──────┐ │
|
||||
│ │ 자재명 │ LOT 번호 │ 수량 │ 투입일│ │
|
||||
│ └────────┴──────────┴──────┴──────┘ │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ 첨부파일 목록 │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ [수정] [인쇄] [결재 승인] [결재 반려] │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 10.4 인쇄 (/documents/{id}/print)
|
||||
|
||||
성적서 형식의 인쇄 전용 화면. `window.print()` 호출. 작업지시 관련 자재(work_order_items) 데이터 포함.
|
||||
|
||||
### 10.5 양식 목록 (/document-templates)
|
||||
|
||||
**필터:**
|
||||
- 검색: 양식명, 제목, 분류
|
||||
- 카테고리: common_codes 기반 + 기존 데이터 폴백
|
||||
- 활성 상태: 활성 / 비활성 / 휴지통(admin)
|
||||
|
||||
**HTMX**: 필터 변경 시 테이블 영역만 부분 로드
|
||||
|
||||
---
|
||||
|
||||
## 11. 첨부파일 유형
|
||||
|
||||
| 유형 | 코드 | 용도 | 예시 |
|
||||
|------|------|------|------|
|
||||
| 일반 문서 | `general` | PDF, 엑셀 등 | 규격서, 보고서 |
|
||||
| 서명 이미지 | `signature` | 검사 완료 서명 | 검사자 서명 사진 |
|
||||
| 검사 사진 | `image` | 검사 증빙 사진 | 불량 부위 촬영 |
|
||||
| 참고 자료 | `reference` | 참고용 문서 | KS 규격, 작업 지침 |
|
||||
|
||||
---
|
||||
|
||||
## 12. API 연동 (MNG → API)
|
||||
|
||||
MNG 뷰에서 데이터 저장/삭제는 **API 서버를 호출**하여 처리. GET 요청(뷰 렌더링)은 MNG 컨트롤러가 직접 처리.
|
||||
|
||||
| 작업 | MNG (GET 요청) | API (POST/PUT/DELETE) |
|
||||
|------|---------------|----------------------|
|
||||
| 목록 조회 | `DocumentController::index()` | `GET /v1/documents` |
|
||||
| 상세 조회 | `DocumentController::show()` | `GET /v1/documents/{id}` |
|
||||
| 생성 | 폼 표시만 | `POST /v1/documents` |
|
||||
| 수정 | 폼 표시만 | `PATCH /v1/documents/{id}` |
|
||||
| 삭제 | - | `DELETE /v1/documents/{id}` |
|
||||
| 결재 요청 | - | `POST /v1/documents/{id}/submit` |
|
||||
| 승인 | - | `POST /v1/documents/{id}/approve` |
|
||||
| 반려 | - | `POST /v1/documents/{id}/reject` |
|
||||
|
||||
---
|
||||
|
||||
## 13. 카테고리 해결 로직
|
||||
|
||||
양식 카테고리는 **common_codes 테이블**에서 조회하되, 없으면 **기존 데이터에서 추출**하여 폴백.
|
||||
|
||||
```php
|
||||
// DocumentTemplateController::getCategories()
|
||||
$categories = CommonCode::where('group', 'document_category')
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
|
||||
if ($categories->isEmpty()) {
|
||||
// 폴백: 기존 템플릿의 category 값에서 중복 제거
|
||||
$categories = DocumentTemplate::distinct('category')
|
||||
->pluck('category')
|
||||
->filter();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. 검사항목 확장 (field_values JSON)
|
||||
|
||||
`document_template_section_items.field_values` JSON 컬럼으로 마이그레이션 없이 새 필드를 추가할 수 있다.
|
||||
|
||||
```json
|
||||
{
|
||||
"custom_field_1": "추가 기준값",
|
||||
"min_value": 95.0,
|
||||
"max_value": 105.0,
|
||||
"unit": "mm"
|
||||
}
|
||||
```
|
||||
|
||||
> options JSON 컬럼 정책(`docs/standards/options-column-policy.md`) 준용
|
||||
|
||||
---
|
||||
|
||||
## 15. HTMX 전체 페이지 로드 규칙
|
||||
|
||||
문서관리 페이지들은 JavaScript를 사용하므로 HTMX 부분 로드 시 스크립트 미실행 문제가 있다. 컨트롤러에서 HX-Request 감지 시 **HX-Redirect로 전체 페이지 리로드 강제**.
|
||||
|
||||
```php
|
||||
if ($request->header('HX-Request')) {
|
||||
return response('', 200)->header('HX-Redirect', route('documents.index'));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [README.md](README.md) — API 엔드포인트, 모델 요약, FormRequest
|
||||
- [DB 스키마 — 문서/전자서명](../../system/database/documents.md) — 테이블 상세
|
||||
- [게시판 시스템](../boards/README.md) — 유사한 EAV 패턴 참고
|
||||
- [결재관리 시스템](../approvals/README.md) — 별도 결재 시스템 (문서관리와 독립)
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-06
|
||||
826
features/documents/mng-document-template.md
Normal file
826
features/documents/mng-document-template.md
Normal file
@@ -0,0 +1,826 @@
|
||||
# MNG 문서양식관리 (Document Template Management)
|
||||
|
||||
> **작성일**: 2026-03-06
|
||||
> **상태**: 운영 중
|
||||
> **라우트**: `/document-templates`
|
||||
> **관련**: [README.md](README.md) | [MNG 문서관리](mng-document-system.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
문서관리 시스템에서 사용하는 **서식(Template)**을 생성, 편집, 복제, 관리하는 기능. 검사 성적서, 작업지시서 등 다양한 문서 양식을 정의하며, 2가지 빌더 타입을 지원한다.
|
||||
|
||||
| 빌더 | builder_type | UI 명칭 | 설명 |
|
||||
|------|-------------|---------|------|
|
||||
| **Legacy Builder** | `legacy` 또는 null | 새 양식 | 탭 기반 폼 UI (순수 JavaScript) |
|
||||
| **Block Builder** | `block` | 양식 디자이너 | WYSIWYG 캔버스 편집기 (Alpine.js + SortableJS) |
|
||||
|
||||
> **명칭 변경 이력**: Block Builder의 UI 표시 명칭이 '블록 빌더' → '양식 디자이너'로 변경됨 (2026-02-28)
|
||||
|
||||
**핵심 기능:**
|
||||
- 결재선, 기본필드, 검사 기준서, 테이블 컬럼 정의
|
||||
- EAV 데이터 구조의 서식 스키마 관리
|
||||
- 양식 복제 (연결품목 제외)
|
||||
- 프리셋 자동 제안 (카테고리별)
|
||||
- 소프트 삭제 + 휴지통 관리 (슈퍼어드민)
|
||||
|
||||
---
|
||||
|
||||
## 2. 라우트
|
||||
|
||||
### 2.1 웹 라우트 (페이지)
|
||||
|
||||
```
|
||||
GET /document-templates → index (목록)
|
||||
GET /document-templates/create → create (Legacy 신규 생성)
|
||||
GET /document-templates/block-create → blockCreate (양식 디자이너 신규 생성)
|
||||
GET /document-templates/{id}/edit → edit (Legacy 편집)
|
||||
GET /document-templates/{id}/block-edit → blockEdit (양식 디자이너 편집)
|
||||
```
|
||||
|
||||
### 2.2 API 라우트 (CRUD + 기능)
|
||||
|
||||
```
|
||||
Prefix: /api/admin/document-templates (HQ 관리자 전용)
|
||||
|
||||
GET / → index (HTMX 테이블)
|
||||
POST / → store (생성)
|
||||
GET /{id} → show (상세 조회)
|
||||
PUT /{id} → update (수정)
|
||||
DELETE /{id} → destroy (소프트 삭제)
|
||||
DELETE /{id}/force → forceDestroy (영구삭제, 슈퍼어드민)
|
||||
POST /{id}/restore → restore (복원, 슈퍼어드민)
|
||||
POST /{id}/toggle-active → toggleActive (활성 토글)
|
||||
POST /{id}/duplicate → duplicate (복제)
|
||||
POST /upload-image → uploadImage (이미지 업로드)
|
||||
GET /admin/common-codes/{group} → getCommonCodes (공통코드 조회)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 모델 구조
|
||||
|
||||
### 3.1 모델 관계도
|
||||
|
||||
```
|
||||
DocumentTemplate (서식 마스터)
|
||||
├── 1:N DocumentTemplateApprovalLine (결재선)
|
||||
├── 1:N DocumentTemplateBasicField (기본필드)
|
||||
├── 1:N DocumentTemplateSection (섹션/기준서)
|
||||
│ └── 1:N DocumentTemplateSectionItem (섹션 항목)
|
||||
├── 1:N DocumentTemplateSectionField (섹션 필드)
|
||||
├── 1:N DocumentTemplateColumn (테이블 컬럼)
|
||||
└── 1:N DocumentTemplateLink (연결 설정)
|
||||
└── 1:N DocumentTemplateLinkValue (연결 값)
|
||||
```
|
||||
|
||||
### 3.2 DocumentTemplate 핵심 필드
|
||||
|
||||
```php
|
||||
// 기본 정보
|
||||
builder_type // 'legacy' | 'block'
|
||||
name // 양식명
|
||||
category // 분류명
|
||||
title // 문서 제목
|
||||
|
||||
// 회사 정보
|
||||
company_name // 회사명
|
||||
company_address // 회사 주소
|
||||
company_contact // 회사 연락처
|
||||
|
||||
// 하단 설정
|
||||
footer_remark_label // 비고 라벨
|
||||
footer_judgement_label // 판정 라벨
|
||||
footer_judgement_options // array - 판정 선택지
|
||||
|
||||
// Block Builder 전용
|
||||
schema // array - 블록 스키마 (JSON)
|
||||
page_config // array - 페이지 설정 (A4/A3, 여백 등)
|
||||
|
||||
// 연결 (레거시)
|
||||
linked_item_ids // array - 연결 품목 ID 목록
|
||||
linked_process_id // int - 연결 공정 ID
|
||||
|
||||
// 상태
|
||||
is_active // boolean - 활성 여부
|
||||
deleted_at // timestamp - 소프트 삭제
|
||||
deleted_by // int - 삭제자
|
||||
```
|
||||
|
||||
**Helper 메서드:**
|
||||
|
||||
```php
|
||||
isBlockBuilder(): bool // builder_type === 'block'
|
||||
isLegacyBuilder(): bool // builder_type !== 'block'
|
||||
```
|
||||
|
||||
### 3.3 DocumentTemplateApprovalLine (결재선)
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `template_id` | FK | 서식 ID |
|
||||
| `name` | string | 결재자 이름/직책 |
|
||||
| `department` | string | 부서 |
|
||||
| `role` | string | 역할 (작성/검토/승인) |
|
||||
| `sort_order` | int | 순서 |
|
||||
|
||||
### 3.4 DocumentTemplateBasicField (기본필드)
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `template_id` | FK | 서식 ID |
|
||||
| `field_key` | string | 필드 키 (bf_ 접두사) |
|
||||
| `label` | string | 라벨 |
|
||||
| `field_type` | string | text, date, select 등 |
|
||||
| `default_value` | string | 기본값 |
|
||||
| `is_required` | boolean | 필수 여부 |
|
||||
| `sort_order` | int | 순서 |
|
||||
| `options` | array | 선택지 (select 타입) |
|
||||
|
||||
### 3.5 DocumentTemplateSection (섹션/검사 기준서)
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `template_id` | FK | 서식 ID |
|
||||
| `title` | string | 섹션 제목 |
|
||||
| `image_path` | string | 섹션 이미지 경로 |
|
||||
| `sort_order` | int | 순서 |
|
||||
|
||||
**하위 관계:**
|
||||
|
||||
```
|
||||
Section 1:N SectionItem
|
||||
├── category // 카테고리 (그룹핑)
|
||||
├── name // 항목명
|
||||
├── standard // 기준
|
||||
├── tolerance_type // 공차 유형 (symmetric/asymmetric/range/limit)
|
||||
├── tolerance_plus // +공차
|
||||
├── tolerance_minus // -공차
|
||||
├── reference_value // 기준값
|
||||
├── method // 검사방법
|
||||
├── measurement_type // 측정유형
|
||||
└── frequency // 검사주기
|
||||
```
|
||||
|
||||
### 3.6 DocumentTemplateColumn (테이블 컬럼)
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `template_id` | FK | 서식 ID |
|
||||
| `label` | string | 컬럼 라벨 |
|
||||
| `group_name` | string | 그룹명 (다단계 "/" 구분) |
|
||||
| `width` | int | 컬럼 너비 |
|
||||
| `column_type` | string | text, check, complex, select, measurement |
|
||||
| `sub_labels` | array | complex 타입 하위 라벨 |
|
||||
| `sort_order` | int | 순서 |
|
||||
|
||||
### 3.7 DocumentTemplateLink (연결 설정)
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `template_id` | FK | 서식 ID |
|
||||
| `link_key` | string | 연결 키 |
|
||||
| `label` | string | 라벨 |
|
||||
| `link_type` | string | `single` / `multiple` |
|
||||
| `source_table` | string | `items` / `processes` / `users` |
|
||||
| `search_params` | array | 검색 파라미터 |
|
||||
| `display_fields` | array | 표시 필드 |
|
||||
| `is_required` | boolean | 필수 여부 |
|
||||
| `sort_order` | int | 순서 |
|
||||
|
||||
**하위 관계:**
|
||||
|
||||
```
|
||||
Link 1:N LinkValue
|
||||
├── link_id // FK → Link
|
||||
├── linkable_id // 연결 엔티티 ID
|
||||
└── (source_table에 따라 items/processes/users 참조)
|
||||
```
|
||||
|
||||
**레거시 호환 처리:**
|
||||
|
||||
```php
|
||||
// 신규 links가 있으면 사용
|
||||
if ($template->links->isNotEmpty()) {
|
||||
// template_links + link_values 사용
|
||||
}
|
||||
|
||||
// 레거시만 있으면 가상 엔트리 생성
|
||||
if (!empty($template->linked_item_ids)) {
|
||||
return [['link_key' => 'items', 'values' => [...]]]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 컨트롤러 상세
|
||||
|
||||
### 4.1 DocumentTemplateController (웹)
|
||||
|
||||
| 메서드 | 동작 |
|
||||
|--------|------|
|
||||
| `index()` | HTMX 요청 → HX-Redirect 반환 (전체 페이지 로드 강제) |
|
||||
| `create()` | Legacy 신규 생성 폼 렌더링 |
|
||||
| `edit($id)` | Legacy 편집. 양식 디자이너 타입이면 `blockEdit`으로 자동 리다이렉트 |
|
||||
| `blockCreate()` | 양식 디자이너 신규 생성 (빈 캔버스) |
|
||||
| `blockEdit($id)` | 양식 디자이너 편집 (스키마 로드) |
|
||||
|
||||
**공통 데이터 준비:**
|
||||
|
||||
```php
|
||||
// 현재 테넌트 조회
|
||||
$tenantId = getCurrentTenant(); // 세션의 selected_tenant_id
|
||||
|
||||
// 카테고리 목록 = common_codes + 기존 템플릿 카테고리
|
||||
$categories = getCategories();
|
||||
|
||||
// 기본필드 키 옵션
|
||||
$basicFieldKeys = getBasicFieldKeys(); // common_codes 'doc_template_basic_field'
|
||||
```
|
||||
|
||||
### 4.2 DocumentTemplateApiController (API)
|
||||
|
||||
#### `index()` — HTMX 테이블 조회
|
||||
|
||||
| 파라미터 | 타입 | 설명 |
|
||||
|---------|------|------|
|
||||
| `search` | string | 양식명/분류 검색 |
|
||||
| `category` | string | 분류 필터 |
|
||||
| `is_active` | string | `1` / `0` / `TRASHED` (휴지통) |
|
||||
|
||||
```php
|
||||
// 휴지통 모드 (슈퍼어드민 전용)
|
||||
if ($isActive === 'TRASHED') {
|
||||
$query->onlyTrashed();
|
||||
}
|
||||
```
|
||||
|
||||
#### `store()` / `update()` — 생성/수정
|
||||
|
||||
```
|
||||
요청 데이터
|
||||
↓
|
||||
검증 (직접 validate, FormRequest 미사용)
|
||||
↓
|
||||
연결품목 중복 검증 (checkLinkedItemDuplicates)
|
||||
↓
|
||||
DB::transaction 시작
|
||||
↓
|
||||
Template 생성/수정
|
||||
↓
|
||||
saveRelations() — 관계 데이터 upsert
|
||||
↓
|
||||
DB::transaction 완료
|
||||
↓
|
||||
JSON 응답
|
||||
```
|
||||
|
||||
#### `duplicate()` — 양식 복제
|
||||
|
||||
```php
|
||||
$source = DocumentTemplate::with([...all relationships...]);
|
||||
|
||||
$newTemplate = DocumentTemplate::create([
|
||||
...원본 데이터,
|
||||
'name' => request('name', '원본 (복사)'),
|
||||
'is_active' => false, // 비활성으로 생성
|
||||
'linked_item_ids' => null, // 연결품목 제외
|
||||
'linked_process_id' => null, // 연결공정 제외
|
||||
]);
|
||||
|
||||
// 각 관계 데이터 복사 (approvalLines, basicFields, sections, columns...)
|
||||
// linkValues는 복사 안 함 (동일 분류 내 중복 방지)
|
||||
```
|
||||
|
||||
#### `forceDestroy()` — 영구삭제
|
||||
|
||||
```php
|
||||
// 사전 검사: 참조하는 문서 존재 여부
|
||||
$documentCount = Document::withTrashed()
|
||||
->where('template_id', $id)
|
||||
->count();
|
||||
|
||||
if ($documentCount > 0) {
|
||||
return 422; // "이 양식을 사용한 문서 {count}건이 있어 삭제 불가"
|
||||
}
|
||||
```
|
||||
|
||||
#### `uploadImage()` — 이미지 업로드
|
||||
|
||||
```
|
||||
요청 (multipart)
|
||||
↓
|
||||
ApiTokenService::exchangeToken($userId, $tenantId)
|
||||
↓
|
||||
API /files/upload 호출 (Bearer 토큰)
|
||||
↓
|
||||
응답: file_path (1/temp/2026/02/xxx.jpg)
|
||||
↓
|
||||
최종 URL: http://api.sam.kr/storage/tenants/{file_path}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 저장 메커니즘 (saveRelations)
|
||||
|
||||
### 5.1 upsert 전략
|
||||
|
||||
| 관계 | 방식 | 이유 |
|
||||
|------|------|------|
|
||||
| approvalLines | 전체 삭제 → 재생성 | ID 참조 없음 |
|
||||
| basicFields | 전체 삭제 → 재생성 | ID 참조 없음 |
|
||||
| **sections** | **ID 보존 upsert** | document_data가 section_id 참조 |
|
||||
| **sectionItems** | **ID 보존 upsert** | section 하위 항목 |
|
||||
| **columns** | **ID 보존 upsert** | document_data가 column_id 참조 |
|
||||
| sectionFields | 전체 삭제 → 재생성 | ID 참조 없음 |
|
||||
| links + linkValues | 전체 삭제 → 재생성 | ID 참조 없음 |
|
||||
|
||||
### 5.2 ID 보존 upsert 로직
|
||||
|
||||
```php
|
||||
// 1. 요청 ID 수집
|
||||
$incomingIds = collect($data['sections'])->pluck('id')->filter();
|
||||
|
||||
// 2. 요청에 없는 항목 삭제
|
||||
$template->sections()
|
||||
->whereNotIn('id', $incomingIds)
|
||||
->each(function($s) {
|
||||
$s->items()->delete();
|
||||
$s->delete();
|
||||
});
|
||||
|
||||
// 3. 각 항목 upsert
|
||||
foreach ($data['sections'] as $section) {
|
||||
if (!empty($section['id']) && $existing = $template->sections()->find($section['id'])) {
|
||||
$existing->update($sectionData); // 기존: update
|
||||
} else {
|
||||
DocumentTemplateSection::create([...]); // 신규: create
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **ID 보존이 필수인 이유**: `document_data` 테이블이 `section_id`, `column_id`를 FK로 참조한다. 양식 수정 시 ID가 변경되면 기존 문서 데이터와의 매핑이 깨진다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 화면 구성
|
||||
|
||||
### 6.1 목록 화면 (`index.blade.php`)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ 문서양식관리 │
|
||||
│ [+ 새 양식] [+ 양식 디자이너] │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ 필터: [검색어] [분류 ▼] [활성/비활성/휴지통 ▼] │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ # │ 양식명 │ 분류 │ 활성 │ 수정일 │ 액션 │
|
||||
│ 1 │ FQC... │ 검사 │ ✅ │ 03-06 │ 편집 복제 삭제 │
|
||||
│ 2 │ 수입... │ 검사 │ ✅ │ 03-05 │ 편집 복제 삭제 │
|
||||
│ ...│ │ │ │ │ │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**HTMX 테이블 로드:**
|
||||
|
||||
```html
|
||||
<div id="template-table"
|
||||
hx-get="/api/admin/document-templates"
|
||||
hx-trigger="load, filterSubmit from:body"
|
||||
hx-swap="innerHTML">
|
||||
</div>
|
||||
```
|
||||
|
||||
**액션 버튼:**
|
||||
- **편집**: 새 양식 → `/document-templates/{id}/edit`, 양식 디자이너 → `/document-templates/{id}/block-edit`
|
||||
- **복제**: `duplicateTemplate(id)` — 이름 입력 모달 후 POST
|
||||
- **삭제**: `confirmDelete(id)` — 확인 후 DELETE
|
||||
- **미리보기**: `previewTemplate(id)` — 모달 표시
|
||||
- **활성 토글**: `toggleActive(id)` — POST toggle-active
|
||||
- **복원/영구삭제**: 휴지통 모드에서만 표시 (슈퍼어드민)
|
||||
|
||||
### 6.2 Legacy Builder 편집 화면 (`edit.blade.php`)
|
||||
|
||||
**4개 탭 구조:**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ [기본정보] [기본필드] [검사 기준서] [테이블 컬럼] │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ (각 탭 콘텐츠) │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ [미리보기] [저장] [취소] │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 탭 1: 기본정보
|
||||
|
||||
| 필드 | 설명 |
|
||||
|------|------|
|
||||
| 양식명 | 서식 이름 (필수) |
|
||||
| 제목 | 문서 제목 |
|
||||
| 분류 | 카테고리 (common_codes + 기존값) |
|
||||
| 회사명 | 문서 헤더 회사명 |
|
||||
| 회사 주소/연락처 | 문서 헤더 |
|
||||
| 활성 | 체크박스 |
|
||||
| 결재선 | 동적 행 추가/삭제 (이름, 부서, 역할) |
|
||||
|
||||
#### 탭 2: 기본필드
|
||||
|
||||
| 항목 | 설명 |
|
||||
|------|------|
|
||||
| 필드 키 | `bf_` 접두사 (common_codes에서 선택) |
|
||||
| 라벨 | 표시 라벨 |
|
||||
| 필드 타입 | text, date, select 등 |
|
||||
| 기본값 | 문서 생성 시 자동 입력 |
|
||||
| 필수 여부 | 체크박스 |
|
||||
|
||||
#### 탭 3: 검사 기준서
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ 섹션 1: [제목 입력] [이미지 업로드] [+ 항목 추가] │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ 카테고리 │ 항목 │ 기준 │ 공차 │ 기준값 │ ... │ │
|
||||
│ │ 외관 │ 색상 │ 기준 │ ±0.5 │ 5.0 │ ... │ │
|
||||
│ │ 외관 │ 흠집 │ 무 │ │ │ ... │ │
|
||||
│ └──────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 섹션 2: [제목 입력] [이미지 업로드] [+ 항목 추가] │
|
||||
│ ... │
|
||||
│ [+ 섹션 추가] │
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**공차 유형:**
|
||||
|
||||
| 유형 | 입력 | 표시 예 |
|
||||
|------|------|--------|
|
||||
| `symmetric` | ± 값 | ±0.5 |
|
||||
| `asymmetric` | +값, -값 | +0.3 / -0.2 |
|
||||
| `range` | 최소~최대 | 4.5 ~ 5.5 |
|
||||
| `limit` | 상한 또는 하한 | ≤ 10 |
|
||||
|
||||
#### 탭 4: 테이블 컬럼
|
||||
|
||||
| 항목 | 설명 |
|
||||
|------|------|
|
||||
| 라벨 | 컬럼 헤더 |
|
||||
| 그룹명 | 다단계 그룹 ("/" 구분) |
|
||||
| 너비 | 컬럼 너비 (px 또는 %) |
|
||||
| 컬럼 타입 | text, check, complex, select, measurement |
|
||||
| 하위 라벨 | complex 타입 시 sub_labels |
|
||||
|
||||
**자동 컬럼 생성:**
|
||||
|
||||
```
|
||||
[기준서에서 자동 생성] 버튼 클릭
|
||||
↓
|
||||
검사 기준서 섹션의 항목들을 분석
|
||||
↓
|
||||
카테고리 그룹별 컬럼 자동 생성
|
||||
↓
|
||||
measurement_type에 따라 컬럼 타입 결정
|
||||
```
|
||||
|
||||
### 6.3 양식 디자이너 편집 화면 (`block-editor.blade.php`)
|
||||
|
||||
**3패널 레이아웃:**
|
||||
|
||||
```
|
||||
┌──────────┬──────────────────────────┬───────────┐
|
||||
│ 팔레트 │ 캔버스 │ 속성 패널 │
|
||||
│ (220px) │ (flex: 1) │ (300px) │
|
||||
│ │ │ │
|
||||
│ 기본: │ ┌──────────────────────┐ │ 선택 블록: │
|
||||
│ □ 제목 │ │ [제목 블록] │ │ │
|
||||
│ □ 문단 │ │ [문단 블록] │ │ 제목: ... │
|
||||
│ □ 테이블 │ │ [테이블 블록] │ │ 크기: ... │
|
||||
│ □ 컬럼 │ │ [입력 필드 블록] │ │ 정렬: ... │
|
||||
│ □ 구분선 │ │ │ │ │
|
||||
│ □ 여백 │ └──────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ 폼: │ │ │
|
||||
│ □ 텍스트 │ │ │
|
||||
│ □ 숫자 │ │ │
|
||||
│ □ 날짜 │ │ │
|
||||
│ □ 선택 │ │ │
|
||||
│ □ 체크 │ │ │
|
||||
│ □ 텍스트영역│ │ │
|
||||
│ □ 서명 │ │ │
|
||||
└──────────┴──────────────────────────┴───────────┘
|
||||
```
|
||||
|
||||
**블록 타입 (15개):**
|
||||
|
||||
| 분류 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| 기본 | `heading` | 제목 (h1~h6) |
|
||||
| 기본 | `paragraph` | 문단 텍스트 |
|
||||
| 기본 | `table` | 테이블 (행/열 편집) |
|
||||
| 기본 | `columns` | 다단 컬럼 레이아웃 |
|
||||
| 기본 | `divider` | 구분선 |
|
||||
| 기본 | `spacer` | 여백 |
|
||||
| 폼 | `text_field` | 텍스트 입력 |
|
||||
| 폼 | `number_field` | 숫자 입력 |
|
||||
| 폼 | `date_field` | 날짜 입력 |
|
||||
| 폼 | `select_field` | 선택 드롭다운 |
|
||||
| 폼 | `checkbox_field` | 체크박스 |
|
||||
| 폼 | `textarea_field` | 긴 텍스트 입력 |
|
||||
| 폼 | `signature_field` | 서명 영역 |
|
||||
|
||||
**Alpine.js 상태 관리:**
|
||||
|
||||
```javascript
|
||||
blockEditor(initialSchema, templateId) {
|
||||
blocks: [], // 블록 배열
|
||||
selectedBlockId: null, // 현재 선택 블록
|
||||
history: [], // Undo/Redo 스택 (최대 50)
|
||||
historyIndex: -1,
|
||||
pageConfig: { // 페이지 설정
|
||||
size: 'A4', // A4 / A3
|
||||
orientation: 'portrait', // portrait / landscape
|
||||
margins: { top, right, bottom, left }
|
||||
},
|
||||
templateName: '',
|
||||
category: ''
|
||||
}
|
||||
```
|
||||
|
||||
**키보드 단축키:**
|
||||
|
||||
| 단축키 | 기능 |
|
||||
|--------|------|
|
||||
| `Ctrl+Z` / `Cmd+Z` | Undo |
|
||||
| `Ctrl+Shift+Z` / `Cmd+Shift+Z` | Redo |
|
||||
| `Ctrl+S` / `Cmd+S` | 저장 |
|
||||
|
||||
**SortableJS:**
|
||||
- 캔버스 내 블록 드래그-앤-드롭 정렬
|
||||
- 팔레트에서 캔버스로 블록 추가
|
||||
|
||||
---
|
||||
|
||||
## 7. 미리보기 시스템
|
||||
|
||||
### 7.1 Legacy Builder 미리보기
|
||||
|
||||
```javascript
|
||||
buildDocumentPreviewHtml(data)
|
||||
├── 결재란 테이블 (역할별 칸)
|
||||
├── 기본필드 (2열 15:35:15:35 비율)
|
||||
├── 섹션별 이미지 (title + image 또는 placeholder)
|
||||
├── 검사 데이터 테이블
|
||||
│ ├── 다단계 그룹 헤더 (group_name "/" 구분)
|
||||
│ ├── sub_labels (complex 컬럼)
|
||||
│ ├── 항목 행 (카테고리 그룹핑)
|
||||
│ └── 측정치 셀 (measurement_type별 렌더)
|
||||
└── 비고/종합판정 섹션
|
||||
```
|
||||
|
||||
### 7.2 양식 디자이너 미리보기
|
||||
|
||||
```javascript
|
||||
buildBlockPreviewHtml(data)
|
||||
├── 블록 타입별 HTML 렌더링
|
||||
├── 폼 필드 placeholder 표시
|
||||
└── A4/A3 레이아웃 시뮬레이션
|
||||
```
|
||||
|
||||
### 7.3 이미지 URL 처리
|
||||
|
||||
```javascript
|
||||
_previewImageUrl(imagePath)
|
||||
├── http(s):// 시작 → 그대로 사용
|
||||
├── /^\d+\// 패턴 → API tenant storage URL 생성
|
||||
│ → http://api.sam.kr/storage/tenants/{imagePath}
|
||||
└── 기타 → MNG local storage (/storage/{imagePath})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 분류(Category) 관리
|
||||
|
||||
### 8.1 소스 (우선순위)
|
||||
|
||||
1. **common_codes** (code_group = `document_category`, is_active = true)
|
||||
- tenant_id가 있는 것 우선 (테넌트 전용)
|
||||
- tenant_id가 null인 것도 포함 (공통)
|
||||
- code 기준 중복 제거 (테넌트 우선)
|
||||
2. **기존 템플릿의 category** (common_codes에 없는 값)
|
||||
- 기존 이름 그대로 추가
|
||||
|
||||
### 8.2 연동 공통코드 그룹
|
||||
|
||||
| 그룹 | 용도 |
|
||||
|------|------|
|
||||
| `document_category` | 문서 분류 |
|
||||
| `doc_template_basic_field` | 기본필드 키 옵션 |
|
||||
| `doc_inspection_method` | 검사방법 |
|
||||
| `doc_measurement_type` | 측정유형 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 프리셋 시스템
|
||||
|
||||
### 9.1 테이블
|
||||
|
||||
```
|
||||
document_template_field_presets
|
||||
├── name // 프리셋 이름
|
||||
├── category // 대상 카테고리
|
||||
├── description // 설명
|
||||
└── field_definitions // array - 필드 정의 목록
|
||||
[{ field_key, label, field_type, options, ... }]
|
||||
```
|
||||
|
||||
### 9.2 동작
|
||||
|
||||
```
|
||||
분류(Category) 변경
|
||||
↓
|
||||
매칭 프리셋 검색
|
||||
↓
|
||||
기존 section_fields가 비어있으면
|
||||
↓
|
||||
"'{category}' 카테고리에 맞는 프리셋을 적용할까요?" 확인
|
||||
↓
|
||||
승인 시 field_definitions 자동 적용
|
||||
```
|
||||
|
||||
> **주의**: 초기 로드 시에는 제안하지 않음. 분류 변경 시에만 제안.
|
||||
|
||||
---
|
||||
|
||||
## 10. 연결품목 중복 검증
|
||||
|
||||
### 10.1 규칙
|
||||
|
||||
같은 category 내 서로 다른 템플릿이 동일한 items를 연결할 수 없다.
|
||||
|
||||
### 10.2 검증 로직
|
||||
|
||||
```php
|
||||
checkLinkedItemDuplicates($templateId, $category, $itemIds)
|
||||
|
||||
// 1. 같은 category의 다른 템플릿 조회
|
||||
$otherTemplates = DocumentTemplate::where('category', $category)
|
||||
->where('id', '!=', $templateId)
|
||||
->get();
|
||||
|
||||
// 2. 각 템플릿의 연결품목 수집
|
||||
foreach ($otherTemplates as $other) {
|
||||
// 레거시: linked_item_ids (JSON 배열)
|
||||
// 신규: template_links → linkValues (source_table = 'items')
|
||||
$existingItemIds = ...;
|
||||
}
|
||||
|
||||
// 3. 교집합 검사
|
||||
$duplicates = array_intersect($itemIds, $existingItemIds);
|
||||
if (!empty($duplicates)) {
|
||||
return 422; // 중복 항목 목록과 함께 오류 반환
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. JavaScript 상태 관리 (Legacy Builder)
|
||||
|
||||
### 11.1 templateState 객체
|
||||
|
||||
```javascript
|
||||
const templateState = {
|
||||
// 기본정보
|
||||
id, name, category, title,
|
||||
company_name, company_address, company_contact,
|
||||
footer_remark_label, footer_judgement_label,
|
||||
footer_judgement_options,
|
||||
is_active,
|
||||
|
||||
// 관계 데이터
|
||||
approval_lines: [], // 결재선
|
||||
basic_fields: [], // 기본필드
|
||||
sections: [], // 섹션 + items
|
||||
columns: [], // 테이블 컬럼
|
||||
section_fields: [], // 섹션 필드
|
||||
template_links: [], // 연결 설정 + values
|
||||
};
|
||||
```
|
||||
|
||||
### 11.2 저장 흐름
|
||||
|
||||
```
|
||||
사용자 입력 (Blade 폼)
|
||||
↓
|
||||
templateState 객체 갱신
|
||||
↓
|
||||
saveTemplate() 호출
|
||||
↓
|
||||
fetch POST/PUT /api/admin/document-templates
|
||||
↓
|
||||
DocumentTemplateApiController::store/update()
|
||||
↓
|
||||
검증 → 중복 검사 → DB 트랜잭션 → saveRelations()
|
||||
↓
|
||||
JSON 응답
|
||||
↓
|
||||
showToast() 메시지
|
||||
↓
|
||||
htmx.trigger('#template-table', 'filterSubmit') → 테이블 새로고침
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. 양식 디자이너(Block Builder) vs 새 양식(Legacy Builder) 비교
|
||||
|
||||
| 항목 | 양식 디자이너 | 새 양식 |
|
||||
|------|:------------:|:-------------:|
|
||||
| builder_type | `block` | `legacy` 또는 null |
|
||||
| 편집 UI | WYSIWYG 캔버스 (Alpine.js) | 탭 폼 (순수 JavaScript) |
|
||||
| 데이터 저장 | `schema` JSON 컬럼 | 관계 테이블 (7개) |
|
||||
| Undo/Redo | 히스토리 스택 (최대 50) | 불가 |
|
||||
| 블록 타입 | 15개 (기본 6 + 폼 7 + 기타 2) | N/A |
|
||||
| 드래그-앤-드롭 | SortableJS | 불가 |
|
||||
| 페이지 설정 | A4/A3, 여백, 방향 | 없음 |
|
||||
| 복제 | 스키마 JSON 복사 | 각 관계 데이터 개별 복사 |
|
||||
| 미리보기 함수 | `buildBlockPreviewHtml()` | `buildDocumentPreviewHtml()` |
|
||||
| 적합 용도 | 자유 레이아웃 문서 | 정형화된 검사 성적서 |
|
||||
|
||||
---
|
||||
|
||||
## 13. 권한 및 보안
|
||||
|
||||
### 13.1 미들웨어
|
||||
|
||||
- **웹 라우트**: 일반 인증 (auth)
|
||||
- **API 라우트**: HQ 관리자 미들웨어 (`admin` prefix)
|
||||
|
||||
### 13.2 슈퍼어드민 전용 기능
|
||||
|
||||
| 기능 | 엔드포인트 |
|
||||
|------|-----------|
|
||||
| 영구삭제 | `DELETE /{id}/force` |
|
||||
| 복원 | `POST /{id}/restore` |
|
||||
| 휴지통 조회 | `GET /?is_active=TRASHED` |
|
||||
|
||||
### 13.3 삭제 보호
|
||||
|
||||
- 소프트 삭제: `deleted_at` + `deleted_by` 기록
|
||||
- 영구삭제 전 참조 문서 검사 (Document 테이블)
|
||||
- 참조 문서가 있으면 영구삭제 불가 (422 응답)
|
||||
|
||||
---
|
||||
|
||||
## 14. API 프로젝트 연동
|
||||
|
||||
### 14.1 API 서비스
|
||||
|
||||
```php
|
||||
// DocumentTemplateService (API)
|
||||
list(array $params): LengthAwarePaginator
|
||||
// 필터: is_active, category, search
|
||||
|
||||
show(int $id): DocumentTemplate
|
||||
// 전체 관계 로드 (approvalLines, basicFields, sections, columns...)
|
||||
```
|
||||
|
||||
### 14.2 API 엔드포인트
|
||||
|
||||
```
|
||||
GET /v1/document-templates → index (목록)
|
||||
GET /v1/document-templates/{id} → show (상세)
|
||||
```
|
||||
|
||||
> API는 **읽기 전용**. 서식 생성/수정은 MNG에서만 수행.
|
||||
|
||||
---
|
||||
|
||||
## 15. 주요 파일 경로
|
||||
|
||||
| 기능 | 경로 |
|
||||
|------|------|
|
||||
| 웹 컨트롤러 | `mng/app/Http/Controllers/DocumentTemplateController.php` |
|
||||
| API 컨트롤러 | `mng/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php` |
|
||||
| 모델 (8개) | `mng/app/Models/DocumentTemplate*.php` |
|
||||
| 뷰 - 목록 | `mng/resources/views/document-templates/index.blade.php` |
|
||||
| 뷰 - Legacy 편집 | `mng/resources/views/document-templates/edit.blade.php` |
|
||||
| 뷰 - 양식 디자이너 | `mng/resources/views/document-templates/block-editor.blade.php` |
|
||||
| 뷰 - 테이블 | `mng/resources/views/document-templates/partials/table.blade.php` |
|
||||
| 뷰 - 미리보기 | `mng/resources/views/document-templates/partials/preview-modal.blade.php` |
|
||||
| API 서비스 | `api/app/Services/DocumentTemplateService.php` |
|
||||
| API 모델 | `api/app/Models/Documents/DocumentTemplate*.php` |
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [README.md](README.md) — 문서관리 시스템 개요 (API 중심)
|
||||
- [MNG 문서관리](mng-document-system.md) — 문서 생성/편집/결재 (서식을 사용하는 측)
|
||||
- [DB 스키마 — 문서](../../system/database/documents.md)
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-06
|
||||
129
features/planning/README.md
Normal file
129
features/planning/README.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# 주일기업 기획 메뉴
|
||||
|
||||
> **작성일**: 2026-03-06
|
||||
> **상태**: 운영 중
|
||||
> **프로젝트**: SAM MNG (관리자 웹)
|
||||
> **라우트 접두사**: `/juil`
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
블라인드/스크린 제조업체의 현장 관리를 위한 기획 도구 모음. 견적부터 공사, 준공까지의 업무 흐름과 현장 기록(사진대지), 회의 기록(STT/AI 요약)을 제공한다.
|
||||
|
||||
### 1.2 문서 구조
|
||||
|
||||
| 문서 | 설명 |
|
||||
|------|------|
|
||||
| **README.md** (이 문서) | 전체 개요, 메뉴 구조, 아키텍처 |
|
||||
| [construction-photos.md](construction-photos.md) | 공사현장 사진대지 기술 명세 |
|
||||
| [meeting-minutes.md](meeting-minutes.md) | 회의록 작성 기술 명세 (STT/AI 통합) |
|
||||
| [planning-views.md](planning-views.md) | 견적/프로젝트/워크플로우 화면 명세 |
|
||||
|
||||
### 1.3 하위 메뉴 구조
|
||||
|
||||
```
|
||||
주일기업 기획
|
||||
├── 견적/입찰/공사관리 /juil/estimate
|
||||
├── 프로젝트관리/기성청구 /juil/project
|
||||
├── 업무 Workflow /juil/workflow
|
||||
├── 공사현장 사진대지 /juil/construction-photos
|
||||
└── 회의록 작성 /juil/meeting-minutes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 아키텍처
|
||||
|
||||
### 2.1 기술 스택
|
||||
|
||||
| 계층 | 기술 | 설명 |
|
||||
|------|------|------|
|
||||
| 뷰 | Blade + React (인라인) + Babel | 브라우저 트랜스파일 React 컴포넌트 |
|
||||
| API | Laravel Controller + Service | JSON API (AJAX) |
|
||||
| 모델 | Eloquent ORM | Multi-tenant (BelongsToTenant) |
|
||||
| 파일 저장 | Google Cloud Storage | 사진, 오디오 파일 |
|
||||
| AI | Gemini API (Vertex AI) | 요약, 화자 분리 |
|
||||
| STT | Google Speech-to-Text V1/V2 + Web Speech API | 음성 인식 |
|
||||
|
||||
### 2.2 프로젝트 파일 구조
|
||||
|
||||
```
|
||||
mng/
|
||||
├── app/Http/Controllers/
|
||||
│ ├── PlanningController.php ← 견적/프로젝트/워크플로우
|
||||
│ ├── ConstructionSitePhotoController.php ← 사진대지 CRUD + 파일 관리
|
||||
│ └── MeetingMinuteController.php ← 회의록 CRUD + AI 기능
|
||||
├── app/Services/
|
||||
│ ├── ConstructionSitePhotoService.php ← 사진대지 비즈니스 로직
|
||||
│ └── MeetingMinuteService.php ← 회의록 + AI 통합 로직
|
||||
├── app/Models/
|
||||
│ ├── ConstructionSitePhoto.php ← 사진대지 모델
|
||||
│ ├── ConstructionSitePhotoRow.php ← 사진 행 모델
|
||||
│ ├── MeetingMinute.php ← 회의록 모델
|
||||
│ └── MeetingMinuteSegment.php ← 회의 세그먼트 모델
|
||||
└── resources/views/juil/
|
||||
├── estimate.blade.php ← 견적/입찰/공사관리
|
||||
├── project.blade.php ← 프로젝트관리/기성청구
|
||||
├── workflow.blade.php ← 업무 Workflow
|
||||
├── construction-photos.blade.php ← 사진대지 SPA
|
||||
└── meeting-minutes.blade.php ← 회의록 SPA
|
||||
```
|
||||
|
||||
### 2.3 기능별 구현 현황
|
||||
|
||||
| 기능 | 구현 방식 | 백엔드 | DB |
|
||||
|------|----------|--------|-----|
|
||||
| 견적/입찰/공사관리 | React 뷰 (목데이터) | PlanningController (뷰 반환만) | 없음 |
|
||||
| 프로젝트관리/기성청구 | React 뷰 (목데이터) | PlanningController (뷰 반환만) | 없음 |
|
||||
| 업무 Workflow | React 뷰 (정적 데이터) | PlanningController (뷰 반환만) | 없음 |
|
||||
| 공사현장 사진대지 | React SPA + API | Controller + Service | 2 테이블 |
|
||||
| 회의록 작성 | React SPA + API | Controller + Service + AI | 2 테이블 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 외부 서비스 의존성
|
||||
|
||||
| 서비스 | 용도 | 추적 |
|
||||
|--------|------|------|
|
||||
| **Google Cloud Storage** | 사진/오디오 파일 저장 | `AiTokenHelper::saveGcsStorageUsage()` |
|
||||
| **Google Speech-to-Text V2 (Chirp2)** | 자동 화자 분리 (최우선) | `AiTokenHelper::saveSttUsage()` |
|
||||
| **Google Speech-to-Text V1** | 화자 분리 (V2 실패 시 폴백) | `AiTokenHelper::saveSttUsage()` |
|
||||
| **Gemini API (Vertex AI)** | 요약 생성 + 화자 재분배 | `AiTokenHelper::saveGeminiUsage()` |
|
||||
| **Web Speech API** | 브라우저 음성 입력 (현장명/설명) | `logSttUsage()` |
|
||||
|
||||
### 3.1 도메인 용어 힌트 (STT 정확도 향상)
|
||||
|
||||
```
|
||||
블라인드, 스크린, 롤스크린, 허니콤, 버티컬,
|
||||
원단, 바텀레일, 헤드레일, 브라켓,
|
||||
주일, 경동, 주일블라인드, 경동블라인드,
|
||||
수주, 발주, 납기, 출하, 재고, 원가, 단가,
|
||||
SAM, ERP, MES
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. HTMX 전체 페이지 로드 규칙
|
||||
|
||||
모든 `/juil/*` 페이지는 React 인라인 컴포넌트를 사용하므로, HTMX 부분 로드 시 스크립트가 실행되지 않는다. 각 컨트롤러 메서드에서 HTMX 요청 감지 시 **HX-Redirect로 전체 페이지 리로드를 강제**한다.
|
||||
|
||||
```php
|
||||
if ($request->header('HX-Request')) {
|
||||
return response('', 200)->header('HX-Redirect', route('juil.estimate'));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 관련 문서
|
||||
|
||||
- [공사현장 사진대지](construction-photos.md) — GCS 파일 관리, 행 구조, 음성 입력
|
||||
- [회의록 작성](meeting-minutes.md) — STT/화자분리/AI 요약, 오디오 녹음
|
||||
- [견적/프로젝트/워크플로우](planning-views.md) — React 뷰 구성, 업무 프로세스 정의
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-06
|
||||
275
features/planning/construction-photos.md
Normal file
275
features/planning/construction-photos.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# 공사현장 사진대지
|
||||
|
||||
> **작성일**: 2026-03-06
|
||||
> **상태**: 운영 중
|
||||
> **라우트**: `/juil/construction-photos`
|
||||
> **관련**: [README.md](README.md) | [회의록](meeting-minutes.md) | [뷰 화면](planning-views.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
건설/시공 현장의 작업 과정을 **작업전/작업중/작업후** 3단계 사진으로 기록하고 관리하는 기능. Google Cloud Storage에 사진을 저장하며, 음성 입력(Web Speech API)으로 현장명과 설명을 입력할 수 있다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 라우트
|
||||
|
||||
```
|
||||
/juil/construction-photos
|
||||
├── GET / → index (목록 페이지)
|
||||
├── GET /list → list (JSON 목록)
|
||||
├── POST / → store (새 사진대지 등록)
|
||||
├── POST /log-stt-usage → logSttUsage (STT 시간 기록)
|
||||
├── GET /{id} → show (상세 조회)
|
||||
├── PUT /{id} → update (메타데이터 수정)
|
||||
├── DELETE /{id} → destroy (삭제)
|
||||
├── POST /{id}/rows → addRow (행 추가)
|
||||
├── DELETE /{id}/rows/{rowId} → deleteRow (행 삭제)
|
||||
├── POST /{id}/rows/{rowId}/upload → uploadPhoto (사진 업로드)
|
||||
├── DELETE /{id}/rows/{rowId}/photo/{type} → deletePhoto (사진 삭제)
|
||||
└── GET /{id}/rows/{rowId}/download/{type} → downloadPhoto (다운로드)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 데이터베이스
|
||||
|
||||
### 3.1 construction_site_photos (사진대지)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `tenant_id` | BIGINT FK | 테넌트 격리 |
|
||||
| `user_id` | BIGINT FK | 등록자 |
|
||||
| `site_name` | VARCHAR(200) | 현장명 (필수) |
|
||||
| `work_date` | DATE | 작업일자 (필수) |
|
||||
| `description` | TEXT NULL | 설명 |
|
||||
| `deleted_at` | TIMESTAMP NULL | 소프트 삭제 |
|
||||
|
||||
**인덱스**: `tenant_id`, `user_id`, `(tenant_id, work_date)`
|
||||
|
||||
### 3.2 construction_site_photo_rows (사진 행)
|
||||
|
||||
각 사진대지는 1개 이상의 행을 가지며, 각 행에 3개 타입(before/during/after) 사진 저장.
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `construction_site_photo_id` | BIGINT FK | 부모 (cascade delete) |
|
||||
| `sort_order` | INT | 정렬 순서 (0부터) |
|
||||
| `before_photo_path` | VARCHAR(500) NULL | 작업전 GCS 경로 |
|
||||
| `before_photo_gcs_uri` | VARCHAR(500) NULL | 작업전 GCS URI |
|
||||
| `before_photo_size` | INT UNSIGNED NULL | 작업전 파일크기 (bytes) |
|
||||
| `during_photo_path` | VARCHAR(500) NULL | 작업중 GCS 경로 |
|
||||
| `during_photo_gcs_uri` | VARCHAR(500) NULL | 작업중 GCS URI |
|
||||
| `during_photo_size` | INT UNSIGNED NULL | 작업중 파일크기 (bytes) |
|
||||
| `after_photo_path` | VARCHAR(500) NULL | 작업후 GCS 경로 |
|
||||
| `after_photo_gcs_uri` | VARCHAR(500) NULL | 작업후 GCS URI |
|
||||
| `after_photo_size` | INT UNSIGNED NULL | 작업후 파일크기 (bytes) |
|
||||
|
||||
### 3.3 테이블 관계
|
||||
|
||||
```
|
||||
construction_site_photos
|
||||
│ 1:N
|
||||
▼
|
||||
construction_site_photo_rows (sort_order ASC)
|
||||
├── before_photo_* (작업전)
|
||||
├── during_photo_* (작업중)
|
||||
└── after_photo_* (작업후)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. API 명세
|
||||
|
||||
### 4.1 목록 조회
|
||||
|
||||
```
|
||||
GET /juil/construction-photos/list
|
||||
```
|
||||
|
||||
| 파라미터 | 타입 | 설명 |
|
||||
|---------|------|------|
|
||||
| `search` | string | 현장명 검색 |
|
||||
| `date_from` | date | 시작일 |
|
||||
| `date_to` | date | 종료일 |
|
||||
| `per_page` | int | 페이지당 건수 |
|
||||
|
||||
### 4.2 생성
|
||||
|
||||
```
|
||||
POST /juil/construction-photos
|
||||
```
|
||||
|
||||
| 필드 | 규칙 | 설명 |
|
||||
|------|------|------|
|
||||
| `site_name` | required, max:200 | 현장명 |
|
||||
| `work_date` | required, date | 작업일자 |
|
||||
| `description` | nullable, max:2000 | 설명 |
|
||||
|
||||
> 생성 시 빈 행 1개 자동 추가
|
||||
|
||||
### 4.3 사진 업로드
|
||||
|
||||
```
|
||||
POST /juil/construction-photos/{id}/rows/{rowId}/upload
|
||||
```
|
||||
|
||||
| 필드 | 규칙 | 설명 |
|
||||
|------|------|------|
|
||||
| `type` | required, in:before,during,after | 사진 타입 |
|
||||
| `photo` | required, image, mimes:jpeg,jpg,png,webp, max:10240 | 최대 10MB |
|
||||
|
||||
### 4.4 사진 다운로드
|
||||
|
||||
```
|
||||
GET /juil/construction-photos/{id}/rows/{rowId}/download/{type}?inline=1
|
||||
```
|
||||
|
||||
| 파라미터 | 설명 |
|
||||
|---------|------|
|
||||
| `inline=1` | 브라우저 표시 (미지정 시 다운로드) |
|
||||
|
||||
---
|
||||
|
||||
## 5. GCS 저장 구조
|
||||
|
||||
### 5.1 경로 패턴
|
||||
|
||||
```
|
||||
construction-site-photos/{tenant_id}/{photo_id}/{row_id}_{timestamp}_{type}.{ext}
|
||||
```
|
||||
|
||||
**예시:**
|
||||
|
||||
```
|
||||
construction-site-photos/1/42/15_1709723456_before.jpg
|
||||
construction-site-photos/1/42/15_1709723456_during.jpg
|
||||
construction-site-photos/1/42/15_1709723456_after.png
|
||||
```
|
||||
|
||||
### 5.2 업로드 흐름
|
||||
|
||||
```
|
||||
클라이언트 (Canvas 이미지 압축: 1920px, quality 80%)
|
||||
↓
|
||||
FormData (multipart) 전송
|
||||
↓
|
||||
컨트롤러: uploadPhoto()
|
||||
↓
|
||||
서비스: uploadPhoto()
|
||||
├── 기존 사진 있으면 GCS에서 삭제
|
||||
├── GCS에 업로드
|
||||
├── DB에 path + uri + size 저장
|
||||
└── AiTokenHelper::saveGcsStorageUsage() 호출
|
||||
↓
|
||||
응답: { success, data: Photo with rows }
|
||||
```
|
||||
|
||||
### 5.3 삭제 흐름
|
||||
|
||||
```
|
||||
사진 삭제: GCS 파일 삭제 → DB 필드 null
|
||||
행 삭제: 행 내 모든 사진 GCS 삭제 → 행 삭제 → sort_order 재정렬
|
||||
사진대지 삭제: 모든 행의 모든 사진 GCS 삭제 → soft delete
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 음성 입력 (Web Speech API)
|
||||
|
||||
### 6.1 VoiceInputButton 컴포넌트
|
||||
|
||||
현장명, 설명 필드에 음성으로 텍스트 입력 가능.
|
||||
|
||||
```javascript
|
||||
// Web Speech Recognition 설정
|
||||
recognition.lang = 'ko-KR';
|
||||
recognition.continuous = true;
|
||||
recognition.interimResults = true;
|
||||
recognition.maxAlternatives = 1;
|
||||
```
|
||||
|
||||
### 6.2 인식 상태
|
||||
|
||||
| 상태 | 표시 | 설명 |
|
||||
|------|------|------|
|
||||
| interim (미확정) | 이탤릭 + 회색 | 인식 중간 결과, 2초 후 소실 |
|
||||
| final (확정) | 일반체 + 진한색 | 확정 텍스트, 영구 저장 |
|
||||
|
||||
### 6.3 사용량 추적
|
||||
|
||||
```
|
||||
STT 사용 종료 시:
|
||||
duration = Math.max(1, (Date.now() - startTime) / 1000)
|
||||
↓
|
||||
POST /juil/construction-photos/log-stt-usage
|
||||
body: { duration_seconds }
|
||||
↓
|
||||
AiTokenHelper::saveSttUsage('공사현장사진대지-음성입력', seconds)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. UI 구성 (React)
|
||||
|
||||
### 7.1 사진 타입별 색상
|
||||
|
||||
| 타입 | 라벨 | 배경색 | 뱃지색 |
|
||||
|------|------|--------|--------|
|
||||
| `before` | 작업전 | `bg-blue-50` | `bg-blue-100 text-blue-800` |
|
||||
| `during` | 작업중 | `bg-yellow-50` | `bg-yellow-100 text-yellow-800` |
|
||||
| `after` | 작업후 | `bg-green-50` | `bg-green-100 text-green-800` |
|
||||
|
||||
### 7.2 행 관리
|
||||
|
||||
- **행 추가**: sort_order 자동 계산 (마지막 + 1)
|
||||
- **행 삭제**: 최소 1개 행 유지 필수
|
||||
- **행별 사진**: 각 행에 3개 타입 사진 독립 업로드/삭제
|
||||
|
||||
---
|
||||
|
||||
## 8. 모델 메서드
|
||||
|
||||
### 8.1 ConstructionSitePhoto
|
||||
|
||||
```php
|
||||
user() # BelongsTo User (등록자)
|
||||
rows() # HasMany Row (sort_order ASC)
|
||||
getPhotoCount(): int # 전체 사진 개수 (모든 행의 사진 합계)
|
||||
```
|
||||
|
||||
### 8.2 ConstructionSitePhotoRow
|
||||
|
||||
```php
|
||||
constructionSitePhoto() # BelongsTo 부모
|
||||
hasPhoto(string $type): bool # 특정 타입 사진 존재 여부
|
||||
getPhotoCount(): int # 이 행의 사진 개수 (0~3)
|
||||
```
|
||||
|
||||
### 8.3 ConstructionSitePhotoService
|
||||
|
||||
```php
|
||||
getList(array $filters) # 검색/필터 목록 (페이지네이션)
|
||||
create(array $data) # 생성 + 빈 행 1개 자동 추가
|
||||
update(ConstructionSitePhoto, array $data) # 메타데이터만 수정
|
||||
delete(ConstructionSitePhoto) # GCS 전체 삭제 → soft delete
|
||||
uploadPhoto(Row, UploadedFile, string $type) # GCS 업로드 + DB 기록
|
||||
deletePhotoByType(Row, string $type) # 특정 타입 GCS 삭제
|
||||
addRow(ConstructionSitePhoto) # 행 추가 (sort_order 자동)
|
||||
deleteRow(Row) # 행 내 GCS 삭제 → 행 삭제 → 재정렬
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [README.md](README.md) — 기획 메뉴 전체 개요
|
||||
- [회의록 작성](meeting-minutes.md) — STT/AI 통합 회의 기록
|
||||
- [견적/프로젝트/워크플로우](planning-views.md) — 화면 명세
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-06
|
||||
456
features/planning/meeting-minutes.md
Normal file
456
features/planning/meeting-minutes.md
Normal file
@@ -0,0 +1,456 @@
|
||||
# 회의록 작성
|
||||
|
||||
> **작성일**: 2026-03-06
|
||||
> **상태**: 운영 중
|
||||
> **라우트**: `/juil/meeting-minutes`
|
||||
> **관련**: [README.md](README.md) | [사진대지](construction-photos.md) | [뷰 화면](planning-views.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
음성으로 회의 내용을 기록하고, **Google STT(화자 분리)** + **Gemini AI(요약/결정사항/액션아이템)** 로 자동 정리하는 회의록 시스템. 브라우저 MediaRecorder로 녹음하고, GCS에 오디오를 저장하며, 세그먼트(화자별 발화)를 관리한다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 라우트
|
||||
|
||||
```
|
||||
/juil/meeting-minutes
|
||||
├── GET / → index (목록 페이지)
|
||||
├── GET /list → list (JSON 목록)
|
||||
├── POST / → store (새 회의록 생성)
|
||||
├── POST /log-stt-usage → logSttUsage (STT 시간 기록)
|
||||
├── GET /{id} → show (상세 조회 + segments)
|
||||
├── PUT /{id} → update (메타데이터 수정)
|
||||
├── DELETE /{id} → destroy (삭제)
|
||||
├── POST /{id}/segments → saveSegments (세그먼트 저장)
|
||||
├── POST /{id}/upload-audio → uploadAudio (오디오 업로드)
|
||||
├── POST /{id}/summarize → summarize (AI 요약 생성)
|
||||
├── POST /{id}/diarize → diarize (자동 화자 분리)
|
||||
└── GET /{id}/download-audio → downloadAudio (오디오 다운로드)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 데이터베이스
|
||||
|
||||
### 3.1 meeting_minutes (회의록)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `tenant_id` | BIGINT FK | 테넌트 격리 |
|
||||
| `user_id` | BIGINT FK | 작성자 |
|
||||
| `title` | VARCHAR(300) | 제목 (기본: "무제 회의록") |
|
||||
| `folder` | VARCHAR(100) NULL | 폴더 분류 |
|
||||
| `participants` | JSON NULL | 참여자 목록 배열 |
|
||||
| `meeting_date` | DATE | 회의 날짜 |
|
||||
| `meeting_time` | TIME NULL | 회의 시작 시간 |
|
||||
| `duration_seconds` | INT UNSIGNED | 녹음 총 시간(초) |
|
||||
| `audio_file_path` | VARCHAR(500) NULL | 오디오 GCS 경로 |
|
||||
| `audio_gcs_uri` | VARCHAR(500) NULL | 오디오 GCS URI |
|
||||
| `audio_file_size` | BIGINT UNSIGNED NULL | 오디오 파일 크기 (bytes) |
|
||||
| `full_transcript` | LONGTEXT NULL | 전체 트랜스크립트 |
|
||||
| `summary` | LONGTEXT NULL | AI 요약 |
|
||||
| `decisions` | JSON NULL | 결정사항 배열 |
|
||||
| `action_items` | JSON NULL | 액션아이템 배열 |
|
||||
| `status` | VARCHAR(20) | 상태 (5가지) |
|
||||
| `stt_language` | VARCHAR(10) | STT 언어 (기본: ko-KR) |
|
||||
| `deleted_at` | TIMESTAMP NULL | 소프트 삭제 |
|
||||
|
||||
**인덱스**: `tenant_id`, `user_id`, `(tenant_id, meeting_date)`, `status`
|
||||
|
||||
### 3.2 meeting_minute_segments (세그먼트)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | |
|
||||
| `meeting_minute_id` | BIGINT FK | 회의록 (cascade delete) |
|
||||
| `segment_order` | INT UNSIGNED | 순서 |
|
||||
| `speaker_name` | VARCHAR(100) | 화자 이름 (기본: "화자 1") |
|
||||
| `speaker_label` | VARCHAR(20) NULL | 화자 라벨/번호 |
|
||||
| `text` | TEXT | 발화 텍스트 |
|
||||
| `start_time_ms` | INT UNSIGNED | 시작 시간 (ms, 기본: 0) |
|
||||
| `end_time_ms` | INT UNSIGNED NULL | 종료 시간 (ms) |
|
||||
| `is_manual_speaker` | BOOLEAN | 수동 화자 전환 여부 (기본: true) |
|
||||
|
||||
**인덱스**: `meeting_minute_id`, `(meeting_minute_id, segment_order)`
|
||||
|
||||
### 3.3 테이블 관계
|
||||
|
||||
```
|
||||
meeting_minutes
|
||||
│ 1:N
|
||||
▼
|
||||
meeting_minute_segments (segment_order ASC)
|
||||
├── speaker_name (화자명)
|
||||
├── text (발화 내용)
|
||||
└── start_time_ms / end_time_ms (타임스탬프)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 상태 관리
|
||||
|
||||
### 4.1 상태값
|
||||
|
||||
| 상태 | 코드 | 색상 | 설명 |
|
||||
|------|------|------|------|
|
||||
| 초안 | `DRAFT` | 회색 | 생성 직후, 편집 가능 |
|
||||
| 녹음중 | `RECORDING` | 빨강 | (클라이언트 상태) |
|
||||
| 처리중 | `PROCESSING` | 노랑 | AI 요약/화자분리 처리 중 |
|
||||
| 완료 | `COMPLETED` | 초록 | AI 처리 완료 |
|
||||
| 실패 | `FAILED` | 빨강 | AI 처리 실패 |
|
||||
|
||||
### 4.2 상태 전이
|
||||
|
||||
```
|
||||
DRAFT
|
||||
↓ [오디오 업로드, 세그먼트 추가]
|
||||
DRAFT (계속 편집)
|
||||
↓ [summarize() 호출]
|
||||
PROCESSING
|
||||
↓
|
||||
COMPLETED (성공) 또는 FAILED (실패)
|
||||
|
||||
DRAFT
|
||||
↓ [diarize() 호출 → 화자 분리]
|
||||
DRAFT (세그먼트 갱신, 상태 유지)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API 명세
|
||||
|
||||
### 5.1 목록 조회
|
||||
|
||||
```
|
||||
GET /juil/meeting-minutes/list
|
||||
```
|
||||
|
||||
| 파라미터 | 타입 | 설명 |
|
||||
|---------|------|------|
|
||||
| `search` | string | 제목 검색 |
|
||||
| `date_from` | date | 시작일 |
|
||||
| `date_to` | date | 종료일 |
|
||||
| `status` | string | 상태 필터 |
|
||||
| `per_page` | int | 페이지당 건수 |
|
||||
|
||||
### 5.2 생성
|
||||
|
||||
```
|
||||
POST /juil/meeting-minutes
|
||||
```
|
||||
|
||||
| 필드 | 규칙 | 설명 |
|
||||
|------|------|------|
|
||||
| `title` | nullable, max:300 | 제목 (미입력 시 "무제 회의록") |
|
||||
| `folder` | nullable, max:100 | 폴더 분류 |
|
||||
| `participants` | nullable, array | 참여자 목록 |
|
||||
| `meeting_date` | required, date | 회의 날짜 |
|
||||
| `meeting_time` | nullable | 회의 시간 |
|
||||
| `stt_language` | nullable, max:10 | STT 언어 (기본: ko-KR) |
|
||||
|
||||
### 5.3 세그먼트 저장
|
||||
|
||||
```
|
||||
POST /juil/meeting-minutes/{id}/segments
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"segments": [
|
||||
{
|
||||
"speaker_name": "김과장",
|
||||
"speaker_label": "1",
|
||||
"text": "블라인드 납기일 확인 필요합니다.",
|
||||
"start_time_ms": 0,
|
||||
"end_time_ms": 5000,
|
||||
"is_manual_speaker": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **전처리**: 빈 텍스트 필터링, 언더스코어 노이즈 제거, 다중 공백 정규화
|
||||
> **자동 생성**: `full_transcript` = `[화자명] 발화텍스트\n...` 형식
|
||||
|
||||
### 5.4 오디오 업로드
|
||||
|
||||
```
|
||||
POST /juil/meeting-minutes/{id}/upload-audio
|
||||
```
|
||||
|
||||
| 필드 | 규칙 | 설명 |
|
||||
|------|------|------|
|
||||
| `audio` | required, file | webm/mp3 등 |
|
||||
| `duration_seconds` | required, integer, min:1 | 녹음 시간(초) |
|
||||
|
||||
### 5.5 AI 요약 생성
|
||||
|
||||
```
|
||||
POST /juil/meeting-minutes/{id}/summarize
|
||||
```
|
||||
|
||||
**요청**: 없음 (서버에서 `full_transcript` 사용)
|
||||
|
||||
**응답 예시:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "AI 요약이 완료되었습니다.",
|
||||
"data": {
|
||||
"summary": "블라인드 납품 일정과 현장 설치 계획을 논의했습니다...",
|
||||
"decisions": [
|
||||
"납품일을 3월 15일로 확정",
|
||||
"현장 실측은 3월 10일 진행"
|
||||
],
|
||||
"action_items": [
|
||||
{
|
||||
"assignee": "김과장",
|
||||
"task": "거래처에 납기 확인 연락",
|
||||
"deadline": "2026-03-08"
|
||||
}
|
||||
],
|
||||
"status": "COMPLETED"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.6 자동 화자 분리
|
||||
|
||||
```
|
||||
POST /juil/meeting-minutes/{id}/diarize
|
||||
```
|
||||
|
||||
| 필드 | 설명 | 기본값 |
|
||||
|------|------|--------|
|
||||
| `min_speakers` | 최소 화자 수 | 2 |
|
||||
| `max_speakers` | 최대 화자 수 | 6 |
|
||||
|
||||
**응답:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "자동 화자 분리가 완료되었습니다. (3명 감지)",
|
||||
"data": { /* Meeting with segments */ },
|
||||
"speaker_count": 3
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. AI 통합 상세
|
||||
|
||||
### 6.1 화자 분리 (Diarization) 3단계 폴백
|
||||
|
||||
```
|
||||
[1단계] Google STT V2 (Chirp2) ← 최우선
|
||||
│ speechToTextWithDiarizationAuto()
|
||||
│ 최신 모델, 높은 정확도
|
||||
│ 도메인 용어 힌트 포함
|
||||
│
|
||||
↓ (실패 시)
|
||||
[2단계] Google STT V1 (latest_long) ← 폴백
|
||||
│ 안정적이지만 약간 덜 정확
|
||||
│
|
||||
↓ (1명만 인식 시)
|
||||
[3단계] Gemini AI 화자 재분배
|
||||
splitSpeakersWithGemini()
|
||||
대화 맥락/호칭/질답 패턴/어투 변화 분석
|
||||
2명 이상으로 재분배
|
||||
```
|
||||
|
||||
### 6.2 요약 생성 (Gemini API)
|
||||
|
||||
```
|
||||
입력: full_transcript (전체 트랜스크립트)
|
||||
↓
|
||||
Gemini API 호출
|
||||
├── 모드 1: Vertex AI (projectId, region, JWT)
|
||||
└── 모드 2: Google AI Studio (API key) ← 폴백
|
||||
│
|
||||
│ Temperature: 0.3 (결정적)
|
||||
│ Max tokens: 4096
|
||||
↓
|
||||
출력 JSON:
|
||||
{
|
||||
"summary": "3-5문장 요약",
|
||||
"decisions": ["결정사항 1", "..."],
|
||||
"action_items": [
|
||||
{ "assignee": "담당자", "task": "할일", "deadline": "기한" }
|
||||
],
|
||||
"keywords": ["키워드1", "..."]
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 Gemini 화자 재분배
|
||||
|
||||
Google STT가 1명만 인식할 때 Gemini로 대화 맥락 분석:
|
||||
|
||||
```
|
||||
입력: 단일 화자 트랜스크립트 + 예상 화자 수
|
||||
↓
|
||||
Gemini 프롬프트:
|
||||
- 대화 맥락 분석 (호칭, 질답, 어투 변화)
|
||||
- 지정된 수의 화자로 분리
|
||||
↓
|
||||
출력: 화자별 세그먼트 배열
|
||||
→ DB 세그먼트 교체
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 오디오 관리 (GCS)
|
||||
|
||||
### 7.1 GCS 경로 패턴
|
||||
|
||||
```
|
||||
meeting-minutes/{tenant_id}/{meeting_id}/{timestamp}.webm
|
||||
```
|
||||
|
||||
### 7.2 녹음 흐름
|
||||
|
||||
```
|
||||
브라우저 MediaRecorder API
|
||||
├── navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
├── new MediaRecorder(stream)
|
||||
├── recorder.ondataavailable → webm 블롭 수집
|
||||
└── 녹음 종료 → FormData로 업로드
|
||||
↓
|
||||
POST /{id}/upload-audio
|
||||
├── GCS 업로드
|
||||
├── DB: audio_file_path, audio_gcs_uri, audio_file_size, duration_seconds
|
||||
└── AiTokenHelper::saveGcsStorageUsage()
|
||||
```
|
||||
|
||||
### 7.3 다운로드
|
||||
|
||||
```
|
||||
GET /{id}/download-audio
|
||||
→ GCS에서 파일 콘텐츠 다운로드
|
||||
→ Content-Disposition: attachment; filename="{title}.webm"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 세그먼트 처리 로직
|
||||
|
||||
### 8.1 저장 시 전처리
|
||||
|
||||
```php
|
||||
// 1. 빈 텍스트 필터링
|
||||
trim($segment['text']) !== ''
|
||||
|
||||
// 2. 언더스코어 노이즈 제거
|
||||
str_replace('_', '', $text)
|
||||
|
||||
// 3. 다중 공백 정규화
|
||||
preg_replace('/\s{2,}/', ' ', $text)
|
||||
```
|
||||
|
||||
### 8.2 전체 트랜스크립트 자동 생성
|
||||
|
||||
```
|
||||
[김과장] 블라인드 납기일 확인 필요합니다.
|
||||
[박부장] 3월 15일로 확정합시다.
|
||||
[김과장] 네, 거래처에 연락하겠습니다.
|
||||
```
|
||||
|
||||
### 8.3 화자 분리 결과 세그먼트 변환
|
||||
|
||||
```
|
||||
Google STT 결과 → MeetingMinuteSegment 변환:
|
||||
{
|
||||
segment_order: 순서,
|
||||
speaker_name: "화자 N",
|
||||
speaker_label: "N",
|
||||
text: 발화 텍스트,
|
||||
start_time_ms: 시작시간,
|
||||
end_time_ms: 종료시간,
|
||||
is_manual_speaker: false // 자동 분리
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. UI 구성 (React)
|
||||
|
||||
### 9.1 화자 색상
|
||||
|
||||
| 화자 | 배경색 | 뱃지색 |
|
||||
|------|--------|--------|
|
||||
| 화자 1 | `bg-blue-50` | `bg-blue-100 text-blue-800` |
|
||||
| 화자 2 | `bg-green-50` | `bg-green-100 text-green-800` |
|
||||
| 화자 3 | `bg-purple-50` | `bg-purple-100 text-purple-800` |
|
||||
| 화자 4 | `bg-orange-50` | `bg-orange-100 text-orange-800` |
|
||||
|
||||
### 9.2 지원 언어
|
||||
|
||||
| 코드 | 라벨 |
|
||||
|------|------|
|
||||
| `ko-KR` | 한국어 |
|
||||
| `en-US` | English |
|
||||
| `ja-JP` | 日本語 |
|
||||
| `zh-CN` | 中文 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 사용량 추적
|
||||
|
||||
| 추적 항목 | 레이블 | Helper |
|
||||
|----------|--------|--------|
|
||||
| Web Speech API 사용 | `회의록-음성인식` | `AiTokenHelper::saveSttUsage()` |
|
||||
| Google STT V1 화자 분리 | `회의록-화자분리` | `AiTokenHelper::saveSttUsage()` |
|
||||
| Google STT V2 화자 분리 | `회의록-화자분리(Chirp2)` | `AiTokenHelper::saveSttUsage()` |
|
||||
| GCS 오디오 저장 | `회의록-GCS저장` | `AiTokenHelper::saveGcsStorageUsage()` |
|
||||
| Gemini 요약/분리 | `회의록-AI요약` | `AiTokenHelper::saveGeminiUsage()` |
|
||||
|
||||
---
|
||||
|
||||
## 11. 모델 메서드
|
||||
|
||||
### 11.1 MeetingMinute
|
||||
|
||||
```php
|
||||
user() # BelongsTo User
|
||||
segments() # HasMany Segment (segment_order ASC)
|
||||
getFormattedDurationAttribute() # "H:MM:SS" 또는 "MM:SS"
|
||||
```
|
||||
|
||||
**Cast**: `participants`, `decisions`, `action_items` → array, `meeting_date` → date
|
||||
|
||||
### 11.2 MeetingMinuteService
|
||||
|
||||
```php
|
||||
# CRUD
|
||||
getList(array $filters) # 검색/필터 목록
|
||||
create(array $data) # 생성 (DRAFT)
|
||||
update(MeetingMinute, array $data) # 수정
|
||||
delete(MeetingMinute) # GCS 삭제 → soft delete
|
||||
|
||||
# 세그먼트
|
||||
saveSegments(MeetingMinute, array $segments) # 전처리 + 저장 + 트랜스크립트 생성
|
||||
uploadAudio(MeetingMinute, UploadedFile, int $seconds) # GCS 업로드
|
||||
logSttUsage(int $seconds) # STT 사용량 기록
|
||||
|
||||
# AI
|
||||
generateSummary(MeetingMinute) # Gemini 요약 생성
|
||||
processDiarization(MeetingMinute, int $min, int $max) # 3단계 화자 분리
|
||||
splitSpeakersWithGemini(string $text, int $expected) # Gemini 화자 재분배
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [README.md](README.md) — 기획 메뉴 전체 개요
|
||||
- [공사현장 사진대지](construction-photos.md) — GCS 파일 관리, 음성 입력
|
||||
- [견적/프로젝트/워크플로우](planning-views.md) — 화면 명세
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-06
|
||||
222
features/planning/planning-views.md
Normal file
222
features/planning/planning-views.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# 견적/프로젝트/워크플로우 화면 명세
|
||||
|
||||
> **작성일**: 2026-03-06
|
||||
> **상태**: 뷰 구현 완료 (목데이터 기반, API 미연동)
|
||||
> **라우트**: `/juil/estimate`, `/juil/project`, `/juil/workflow`
|
||||
> **관련**: [README.md](README.md) | [사진대지](construction-photos.md) | [회의록](meeting-minutes.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
3개 화면 모두 **React 인라인 컴포넌트**(Babel 브라우저 트랜스파일)로 구현. 현재는 정적/목데이터 기반이며, 향후 API 연동 예정. PlanningController에서 뷰만 반환한다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 견적/입찰/공사관리 (/juil/estimate)
|
||||
|
||||
### 2.1 개요
|
||||
|
||||
블라인드/스크린 설치 프로젝트의 견적서 작성, 입찰 관리, 공사 진행 현황을 한 화면에서 관리.
|
||||
|
||||
### 2.2 데이터 구조 (initialEstimates)
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: "string",
|
||||
name: "프로젝트명",
|
||||
client: "고객사명",
|
||||
status: "견적중|입찰|계약|공사중|준공",
|
||||
amount: number, // 금액
|
||||
startDate: "YYYY-MM-DD",
|
||||
endDate: "YYYY-MM-DD",
|
||||
manager: "담당자명",
|
||||
items: [ // 품목 내역
|
||||
{ name: "품목명", quantity: number, unitPrice: number }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 공사관리 정보 (initialConstructionData)
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: "string",
|
||||
estimateId: "string", // 연결 견적
|
||||
siteName: "현장명",
|
||||
address: "현장 주소",
|
||||
progress: number, // 진행률 (0~100)
|
||||
workers: number, // 투입 인원
|
||||
safetyChecks: [ // 안전점검
|
||||
{ date: "YYYY-MM-DD", result: "합격|불합격", inspector: "점검자" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 상태별 배지 색상
|
||||
|
||||
| 상태 | 색상 |
|
||||
|------|------|
|
||||
| 견적중 | 파랑 |
|
||||
| 입찰 | 보라 |
|
||||
| 계약 | 초록 |
|
||||
| 공사중 | 주황 |
|
||||
| 준공 | 회색 |
|
||||
|
||||
### 2.5 SAM 연계
|
||||
|
||||
- 견적서 작성 시 SAM 견적 시스템(`features/quotes/`) 데이터 활용 가능
|
||||
- 향후 `/juil/estimate` ↔ SAM 견적 API 연동 계획
|
||||
|
||||
---
|
||||
|
||||
## 3. 프로젝트관리/기성청구 (/juil/project)
|
||||
|
||||
### 3.1 개요
|
||||
|
||||
계약된 프로젝트의 현장 관리, 발주/청구/인건비 상태 추적, 기성 청구 관리.
|
||||
|
||||
### 3.2 데이터 구조 (initialProjects)
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: "string",
|
||||
name: "프로젝트명",
|
||||
client: "발주처",
|
||||
contractAmount: number, // 계약금액
|
||||
status: "진행중|완료|보류",
|
||||
sites: [ // 현장 목록
|
||||
{
|
||||
name: "현장명",
|
||||
address: "주소",
|
||||
progress: number // 진행률
|
||||
}
|
||||
],
|
||||
orders: [ // 발주 내역
|
||||
{
|
||||
vendor: "거래처",
|
||||
amount: number,
|
||||
status: "발주|납품|정산"
|
||||
}
|
||||
],
|
||||
claims: [ // 기성 청구
|
||||
{
|
||||
round: number, // 차수
|
||||
amount: number, // 청구금액
|
||||
claimDate: "YYYY-MM-DD",
|
||||
status: "청구|승인|입금"
|
||||
}
|
||||
],
|
||||
laborCosts: [ // 인건비
|
||||
{
|
||||
month: "YYYY-MM",
|
||||
amount: number,
|
||||
workers: number
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 금액 포맷 함수
|
||||
|
||||
```javascript
|
||||
fmt(amount) // 1,234,567 (쉼표 포맷)
|
||||
fmtBillion(amount) // 12.3억 (억 단위 축약)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 업무 Workflow (/juil/workflow)
|
||||
|
||||
### 4.1 개요
|
||||
|
||||
블라인드/스크린 사업의 전체 업무 프로세스를 단계별로 시각화. 각 프로세스에 담당 부서, 산출물, 서브스텝을 정의.
|
||||
|
||||
### 4.2 프로세스 데이터 구조
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: "S1-1", // 프로세스 ID
|
||||
phase: "영업", // Phase 명
|
||||
name: "정보 수집", // 프로세스 이름
|
||||
icon: "icon-name", // 아이콘
|
||||
dept: "영업팀", // 담당 부서
|
||||
color: "#3B82F6", // 테마 색상
|
||||
description: "프로세스 설명",
|
||||
documents: [ // 산출물 목록
|
||||
"현장조사서", "고객요구사항서"
|
||||
],
|
||||
subSteps: [ // 상세 서브스텝
|
||||
{
|
||||
name: "서브스텝명",
|
||||
description: "상세 설명",
|
||||
responsible: "담당자/팀",
|
||||
output: "산출물"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 업무 Phase 목록
|
||||
|
||||
| Phase | ID 범위 | 설명 |
|
||||
|-------|---------|------|
|
||||
| **영업** | S1-1 ~ S1-4 | 정보 수집 → 현장 실측 → 고객 미팅 → 프로젝트 검토 |
|
||||
| **견적서 작성** | S2-1 ~ S2-4 | 물량 산출 → 단가 산정 → 견적가 산출 → 견적서 작성/검토 |
|
||||
| **입찰** | S3-* | 입찰 준비 → 제출 → 결과 확인 |
|
||||
| **계약** | S4-* | 계약 협상 → 계약 체결 |
|
||||
| **공사** | S5-* | 자재 발주 → 시공 → 현장 관리 |
|
||||
| **준공** | S6-* | 검수 → 하자보수 → 준공 정산 |
|
||||
|
||||
### 4.4 SAM 연계 포인트
|
||||
|
||||
```javascript
|
||||
// 견적서 작성 Phase에서 SAM 견적 화면으로 연결
|
||||
{ samLink: '/juil/estimate', label: '견적서 작성 바로가기' }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 공통 특징
|
||||
|
||||
### 5.1 HTMX 전체 페이지 로드
|
||||
|
||||
3개 화면 모두 React 컴포넌트 사용하므로 HTMX 부분 로드 불가:
|
||||
|
||||
```php
|
||||
// PlanningController의 모든 메서드
|
||||
if ($request->header('HX-Request')) {
|
||||
return response('', 200)->header('HX-Redirect', route('juil.xxx'));
|
||||
}
|
||||
return view('juil.xxx');
|
||||
```
|
||||
|
||||
### 5.2 현재 구현 상태
|
||||
|
||||
| 항목 | 상태 |
|
||||
|------|------|
|
||||
| UI 화면 | 구현 완료 (React 인라인) |
|
||||
| 목데이터 | 블레이드에 하드코딩 |
|
||||
| API 연동 | 미연동 (향후 계획) |
|
||||
| DB 테이블 | 미생성 (향후 계획) |
|
||||
| CRUD 기능 | 뷰 조회만 (생성/수정/삭제 미구현) |
|
||||
|
||||
### 5.3 향후 개발 방향
|
||||
|
||||
1. 견적/프로젝트 DB 테이블 설계 (API 프로젝트)
|
||||
2. API 엔드포인트 구현
|
||||
3. React 컴포넌트 API 연동
|
||||
4. SAM 견적 시스템과 데이터 동기화
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [README.md](README.md) — 기획 메뉴 전체 개요
|
||||
- [공사현장 사진대지](construction-photos.md) — GCS 파일 관리, 음성 입력
|
||||
- [회의록 작성](meeting-minutes.md) — STT/AI 통합 회의 기록
|
||||
- [견적 시스템](../quotes/README.md) — SAM 견적 관리 (BOM, 10단계 로직)
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-06
|
||||
362
features/quality-management/README.md
Normal file
362
features/quality-management/README.md
Normal file
@@ -0,0 +1,362 @@
|
||||
# 품질관리 시스템
|
||||
|
||||
> **작성일**: 2026-03-09
|
||||
> **상태**: 운영 중
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
SAM 품질관리 시스템은 **방화문 제품검사 → 실적신고 → 건기원 제출**의 전체 흐름을 자동화한다.
|
||||
건축자재 품질관리 법규(건설기술진흥법)에 따른 제품검사 수행 및 분기별 실적신고를 관리한다.
|
||||
|
||||
### 1.2 역할별 프로세스 플로우
|
||||
|
||||
> 스토리보드 슬라이드 3 기준
|
||||
|
||||
```
|
||||
매출거래처 견적/수주 담당자 생산 담당자 출고 담당자 품질 담당자
|
||||
─────────── ────────────── ────────── ────────── ──────────
|
||||
주문 견적 작성 생산 부서 할당 출고 대기 제품검사 신청
|
||||
(전화/카톡/메일) │ │ │ (거래처 요청)
|
||||
│ 수주 전환?──No──→ 재고 소요량 출고 완료 │
|
||||
│ │ Yes 충분? 검사원 일정 관리
|
||||
주문 확인 및 수주서 작성 │ │
|
||||
내역 확정 (견적서 선택) 원자재 투입 체크 검사원 현장 방문
|
||||
│ │ 및 검사 진행
|
||||
생산지시 생성 공정 작업 진행 │
|
||||
│ │ 합격? ──No──→
|
||||
재고 소요량 중간검사 │ Yes
|
||||
충분?──No──→ │ 실적 신고 관리
|
||||
│ 생산 완료 │
|
||||
(자재 담당자 품질 인증 심사
|
||||
입고 등록
|
||||
수입검사)
|
||||
|
||||
* 분할 수주/생산/출고는 1차 이후 반복 가능
|
||||
```
|
||||
|
||||
### 1.3 핵심 흐름 (시스템)
|
||||
|
||||
```
|
||||
품질관리서 생성 → 수주 연결 → 개소별 검사 → 검사완료
|
||||
↓
|
||||
실적신고 자동생성 (분기별)
|
||||
↓
|
||||
필수정보 확인 → 확정 → 건기원 신고
|
||||
↓
|
||||
품질인정심사
|
||||
(기준/매뉴얼 + 로트추적)
|
||||
```
|
||||
|
||||
### 1.4 메뉴 구조
|
||||
|
||||
| 메뉴 | URL | 설명 | 상태 |
|
||||
|------|-----|------|------|
|
||||
| 제품검사관리 | `/quality/inspections` | 품질관리서 목록/생성/상세 | 운영 중 |
|
||||
| 실적신고관리 | `/quality/performance-reports` | 분기별 실적신고 관리 | 운영 중 |
|
||||
| 품질인정심사 | `/quality/qms` | 기준/매뉴얼 심사 + 로트 추적 심사 | 개발 예정 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 제품검사 (QualityDocument)
|
||||
|
||||
> 상세 문서: [inspection-management.md](./inspection-management.md)
|
||||
|
||||
### 2.1 품질관리서 생성
|
||||
|
||||
- **채번**: `KD-QD-YYYYMM-0001` (자동)
|
||||
- **필수 입력**: 현장명, 접수일, 검사자
|
||||
- **선택 입력**: 수주처(client_id), 관련자 정보(options JSON)
|
||||
|
||||
### 2.2 수주 연결
|
||||
|
||||
- 품질관리서에 수주(Order)를 연결하면 **개소(Location)가 자동 생성**
|
||||
- 개소 = 수주의 root node (층/부호 단위)
|
||||
- 시공규격(post_width, post_height)은 발주규격과 다를 수 있음
|
||||
|
||||
### 2.3 검사 수행
|
||||
|
||||
각 개소에 대해 **15개 검사항목** + **제품 사진**을 입력:
|
||||
|
||||
| 분류 | 검사항목 | 판정 |
|
||||
|------|---------|------|
|
||||
| 외관 | 가공, 봉제, 조립, 차연재, 하부마감 | pass/fail |
|
||||
| 기능 | 모터, 소재 | pass/fail |
|
||||
| 치수 | 가로, 세로, 가이드레일 간격, 하부마감 간격 | OK/NG |
|
||||
| 시험 | 내화, 차연, 개폐, 충격 | pass/fail |
|
||||
|
||||
### 2.4 상태 전이
|
||||
|
||||
```
|
||||
received (접수) → in_progress (검사 중) → completed (검사 완료)
|
||||
```
|
||||
|
||||
**개소별 상태 자동 판정**:
|
||||
- `pending`: 검사 데이터 없음
|
||||
- `in_progress`: 일부 항목 입력 또는 사진 미등록
|
||||
- `completed`: 15개 항목 전부 + 사진 등록
|
||||
|
||||
---
|
||||
|
||||
## 3. 생산실적신고 (PerformanceReport)
|
||||
|
||||
> 상세 문서: [performance-reports.md](./performance-reports.md)
|
||||
|
||||
### 3.1 자동 생성
|
||||
|
||||
- **트리거**: 품질관리서 검사완료(`complete()`) 시
|
||||
- **생성 기준**: 현재 연도 + 분기 (`year`, `quarter`)
|
||||
- **초기 상태**: `unconfirmed`
|
||||
|
||||
### 3.2 확정 프로세스
|
||||
|
||||
```
|
||||
unconfirmed (미확정)
|
||||
↓ confirm() — 필수정보 검증 통과 시
|
||||
confirmed (확정)
|
||||
↓ distribute() — 건기원 신고 시 (미구현)
|
||||
reported (신고완료)
|
||||
```
|
||||
|
||||
**확정 해제**: `confirmed` → `unconfirmed` (unconfirm)
|
||||
|
||||
### 3.3 필수정보 검증
|
||||
|
||||
확정 전 4가지 섹션의 필수필드가 모두 입력되어야 함:
|
||||
|
||||
| 섹션 | 필수필드 |
|
||||
|------|---------|
|
||||
| 건축공사장 | 현장명, 대지위치, 지번 |
|
||||
| 자재유통업자 | 업체명, 주소, 대표자, 전화번호 |
|
||||
| 공사시공자 | 업체명, 주소, 담당자, 연락처 |
|
||||
| 공사감리자 | 사무소명, 주소, 담당자, 연락처 |
|
||||
|
||||
### 3.4 누락체크
|
||||
|
||||
- 출고완료(배송 완료)된 수주 중 품질관리서가 미등록된 건 탐지
|
||||
- 별도 탭에서 조회 가능
|
||||
|
||||
### 3.5 건기원 실적신고 비즈니스 컨텍스트
|
||||
|
||||
**건기원(한국건설기술연구원)** 실적신고는 법적 의무:
|
||||
|
||||
- **주기**: 분기별 (1~3월, 4~6월, 7~9월, 10~12월)
|
||||
- **대상**: 해당 분기에 검사 완료된 모든 건
|
||||
- **내용**: 품질관리서 번호, LOT 번호, 현장 정보, 검사 결과
|
||||
- **제출처**: 건기원 온라인 시스템 (수기 입력 또는 엑셀 업로드)
|
||||
|
||||
**SAM 시스템 역할**:
|
||||
1. 제품검사 완료 시 실적신고 데이터 자동 수집
|
||||
2. 필수정보 누락 여부 사전 검증
|
||||
3. 확정 후 건기원 제출 양식으로 데이터 정리
|
||||
4. (향후) 건기원 시스템 연동 자동 배포
|
||||
|
||||
---
|
||||
|
||||
## 4. 데이터 구조
|
||||
|
||||
### 4.1 테이블 관계
|
||||
|
||||
```
|
||||
quality_documents (품질관리서)
|
||||
├── quality_document_orders (수주 연결, M:N)
|
||||
│ └── orders
|
||||
├── quality_document_locations (개소별 검사)
|
||||
│ ├── order_items (대표 품목)
|
||||
│ └── documents (EAV 성적서)
|
||||
└── performance_reports (실적신고, 1:1)
|
||||
```
|
||||
|
||||
### 4.2 주요 테이블
|
||||
|
||||
| 테이블 | 설명 | 주요 컬럼 |
|
||||
|--------|------|----------|
|
||||
| `quality_documents` | 품질관리서 | quality_doc_number, status, client_id, options(JSON) |
|
||||
| `quality_document_orders` | 품질-수주 연결 | quality_document_id, order_id |
|
||||
| `quality_document_locations` | 개소별 검사 | inspection_data(JSON), inspection_status, post_width/height |
|
||||
| `performance_reports` | 실적신고 | year, quarter, confirmation_status, confirmed_date |
|
||||
|
||||
### 4.3 options JSON 구조 (quality_documents)
|
||||
|
||||
```json
|
||||
{
|
||||
"construction_site": {
|
||||
"name": "현장명",
|
||||
"land_location": "대지위치",
|
||||
"lot_no": "지번"
|
||||
},
|
||||
"material_distributor": {
|
||||
"company": "업체명",
|
||||
"address": "주소",
|
||||
"ceo": "대표자",
|
||||
"tel": "전화번호"
|
||||
},
|
||||
"contractor": {
|
||||
"company": "업체명",
|
||||
"address": "주소",
|
||||
"name": "담당자",
|
||||
"phone": "연락처"
|
||||
},
|
||||
"supervisor": {
|
||||
"office": "사무소명",
|
||||
"address": "주소",
|
||||
"name": "담당자",
|
||||
"phone": "연락처"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API 엔드포인트
|
||||
|
||||
### 5.1 제품검사 (`/api/v1/quality/documents`)
|
||||
|
||||
| Method | Path | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/quality/documents` | 목록 (상태/날짜 필터) |
|
||||
| GET | `/quality/documents/stats` | 상태별 통계 |
|
||||
| GET | `/quality/documents/calendar` | 캘린더 스케줄 |
|
||||
| GET | `/quality/documents/available-orders` | 미등록 수주 조회 |
|
||||
| POST | `/quality/documents` | 생성 |
|
||||
| GET | `/quality/documents/{id}` | 상세 |
|
||||
| PUT | `/quality/documents/{id}` | 수정 |
|
||||
| DELETE | `/quality/documents/{id}` | 삭제 |
|
||||
| PATCH | `/quality/documents/{id}/complete` | 검사완료 (→ 실적신고 자동생성) |
|
||||
| POST | `/quality/documents/{id}/orders` | 수주 연결 |
|
||||
| DELETE | `/quality/documents/{id}/orders/{orderId}` | 수주 해제 |
|
||||
| POST | `/quality/documents/{id}/locations/{locId}/inspect` | 개소별 검사 저장 |
|
||||
|
||||
### 5.2 실적신고 (`/api/v1/quality/performance-reports`)
|
||||
|
||||
| Method | Path | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/quality/performance-reports` | 목록 (연도/분기/상태 필터) |
|
||||
| GET | `/quality/performance-reports/stats` | 확정/미확정 통계 |
|
||||
| GET | `/quality/performance-reports/missing` | 누락체크 |
|
||||
| PATCH | `/quality/performance-reports/confirm` | 일괄 확정 |
|
||||
| PATCH | `/quality/performance-reports/unconfirm` | 확정 해제 |
|
||||
| PATCH | `/quality/performance-reports/memo` | 메모 일괄 업데이트 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 프론트엔드 구조
|
||||
|
||||
### 6.1 페이지
|
||||
|
||||
```
|
||||
react/src/app/[locale]/(protected)/quality/
|
||||
├── page.tsx # 대시보드
|
||||
├── inspections/
|
||||
│ ├── page.tsx # 검사 목록
|
||||
│ ├── new/page.tsx # 검사 생성
|
||||
│ └── [id]/page.tsx # 검사 상세/수정
|
||||
└── performance-reports/
|
||||
└── page.tsx # 실적신고 목록
|
||||
```
|
||||
|
||||
### 6.2 컴포넌트
|
||||
|
||||
```
|
||||
react/src/components/quality/
|
||||
├── InspectionManagement/
|
||||
│ ├── InspectionList.tsx # 검사 목록
|
||||
│ ├── InspectionCreate.tsx # 검사 생성
|
||||
│ ├── InspectionDetail.tsx # 검사 상세 (수정 포함)
|
||||
│ ├── OrderSelectModal.tsx # 수주 선택 모달
|
||||
│ ├── ProductInspectionInputModal.tsx # 검사 입력 모달
|
||||
│ ├── actions.ts # Server Actions
|
||||
│ ├── types.ts # TypeScript 타입
|
||||
│ └── documents/ # 요청서/성적서 문서
|
||||
└── PerformanceReportManagement/
|
||||
├── PerformanceReportList.tsx # 실적신고 목록 (2탭)
|
||||
├── MemoModal.tsx # 메모 모달
|
||||
└── actions.ts # Server Actions
|
||||
```
|
||||
|
||||
### 6.3 실적신고 화면 기능
|
||||
|
||||
**탭 1: 분기별 실적신고**
|
||||
- 연도/분기 필터
|
||||
- 통계: 전체, 확정, 미확정, 총 개소
|
||||
- 테이블: 품질관리서번호, 작성일, 현장명, 수주처, 개소수, 필수정보 상태, 확정상태, 확정일, 메모
|
||||
- 액션: 선택 확정, 확정 해제, 메모 일괄 작성
|
||||
|
||||
**탭 2: 누락체크**
|
||||
- 출고완료 수주 중 품질관리서 미등록 건 조회
|
||||
- 빠른 누락 확인으로 법규 준수 지원
|
||||
|
||||
---
|
||||
|
||||
## 7. 품질인정심사 (QMS)
|
||||
|
||||
> 상세 문서: [quality-certification-audit.md](./quality-certification-audit.md)
|
||||
|
||||
### 7.1 개요
|
||||
|
||||
품질 인정 심사 자료를 관리하는 기능. 두 가지 심사 영역으로 구성:
|
||||
|
||||
| 심사 영역 | 설명 | 진행률 추적 |
|
||||
|----------|------|-----------|
|
||||
| 기준/매뉴얼 심사 | 품질 기준 문서 및 매뉴얼 점검표 체크 | 완료 항목 / 전체 항목 |
|
||||
| 로트 추적 심사 | 품질관리서 → 수주코드 → 개소별 제품로트 → 관련 서류 추적 확인 | 확인 개소 / 전체 개소 |
|
||||
|
||||
### 7.2 로트 추적 심사 구조
|
||||
|
||||
```
|
||||
품질관리서 목록 → 수주코드 목록 → 관련 서류
|
||||
(1단계 선택) (2단계 선택) (3단계 확인)
|
||||
```
|
||||
|
||||
- 해당 분기 실적신고 확정 건 기준
|
||||
- 수입검사, 중간검사, 납품확인서, 출고증, 제품검사 성적서, 품질관리서 등 서류 연결 확인
|
||||
|
||||
### 7.3 구현 상태
|
||||
|
||||
**개발 예정** — 현재 페이지 구조만 존재
|
||||
|
||||
---
|
||||
|
||||
## 8. 미구현 기능 요약
|
||||
|
||||
| 기능 | 상태 | 설명 |
|
||||
|------|------|------|
|
||||
| 배포(distribute) API | 미구현 | 건기원 시스템 연동 자동 배포 |
|
||||
| 확정건 엑셀 다운로드 | 미구현 | 확정된 실적 건기원 양식 엑셀 내보내기 |
|
||||
| 품질인정심사 | 미구현 | 기준/매뉴얼 심사 + 로트 추적 심사 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 스토리보드 참조
|
||||
|
||||
> **출처**: `SAM_MES_경동기업_품질관리_Storyboard_D1.9_260224`
|
||||
|
||||
| 슬라이드 | 화면 | 기능 영역 |
|
||||
|---------|------|----------|
|
||||
| 3 | 프로젝트 진행 플로우차트 | 전체 프로세스 |
|
||||
| 5~6 | 제품검사 목록 + 캘린더 | 제품검사관리 |
|
||||
| 7~9 | 제품검사 상세 | 제품검사관리 |
|
||||
| 10 | 수주 선택 팝업 | 제품검사관리 |
|
||||
| 11 | 제품검사 팝업 (검사 입력) | 제품검사관리 |
|
||||
| 12~13 | 제품검사요청서 | 문서 출력 |
|
||||
| 14~15 | 제품검사성적서 | 문서 출력 |
|
||||
| 16 | 실적신고 목록 | 실적신고관리 |
|
||||
| 17 | 메모 팝업 | 실적신고관리 |
|
||||
| 18 | 누락체크 | 실적신고관리 |
|
||||
| 19 | 품질인정심사 (기준/매뉴얼) | 품질인정심사 |
|
||||
| 20 | 품질인정심사 (로트 추적) | 품질인정심사 |
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [DB 스키마 — 생산/품질](../../system/database/production.md)
|
||||
- [API 규칙](../../dev/standards/api-rules.md)
|
||||
- [채번 규칙](../../rules/numbering-rules.md)
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-09
|
||||
317
features/quality-management/inspection-management.md
Normal file
317
features/quality-management/inspection-management.md
Normal file
@@ -0,0 +1,317 @@
|
||||
# 제품검사 관리 (Inspection Management)
|
||||
|
||||
> **작성일**: 2026-03-09
|
||||
> **상태**: 운영 중
|
||||
> **URL**: `/quality/inspections`
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
방화문 제품의 현장 출고 전 품질검사를 수행하고 검사성적서를 발행한다.
|
||||
검사 완료 시 실적신고(PerformanceReport)를 자동 생성한다.
|
||||
|
||||
### 1.2 품질관리서 구조
|
||||
|
||||
```
|
||||
품질관리서 (QualityDocument)
|
||||
├── 기본정보: 채번, 현장명, 접수일, 검사자, 수주처
|
||||
├── 관련자 정보 (options JSON):
|
||||
│ ├── 건축공사장 정보
|
||||
│ ├── 자재유통업자 정보
|
||||
│ ├── 공사시공자 정보
|
||||
│ └── 공사감리자 정보
|
||||
├── 수주 연결 (QualityDocumentOrder, 1:N)
|
||||
│ └── 개소 (QualityDocumentLocation, 1:N)
|
||||
│ ├── 시공규격 (post_width, post_height)
|
||||
│ ├── 검사 데이터 (inspection_data JSON)
|
||||
│ └── 검사성적서 (Document EAV)
|
||||
└── 실적신고 (PerformanceReport, 1:1)
|
||||
```
|
||||
|
||||
### 1.3 화면 구성
|
||||
|
||||
**목록 페이지** (슬라이드 5~6):
|
||||
- 상단: 날짜 필터 (전체/전전월/전월/금월/어제/오늘) + 검색
|
||||
- 통계 카드: 접수, 진행중, 완료 건수
|
||||
- 테이블: 품질관리서번호, 번호, 수주처, 개소, 실적신고 필수정보, 검사시기, 검사자, 상태, 작성자, 접수일
|
||||
- 하단: **캘린더 스케줄** (월간 뷰)
|
||||
|
||||
**캘린더 뷰**:
|
||||
- 월 단위 표시, 상태 필터(전체/진행중/완료)
|
||||
- 색상 구분: 완료(초록 배지), 진행중(파란 바)
|
||||
- 검사 완료 건: `홍길동 - 현장명 / 완료` 형태
|
||||
- 검사 진행 건: `홍길동 - 현장명 / 진행중` 형태 (날짜 범위 바)
|
||||
- 클릭 시 해당 제품검사 상세 화면으로 이동
|
||||
|
||||
**상세 페이지** (슬라이드 7~9):
|
||||
- 기본 정보: 품질관리서번호, 현장명, 수주처, 접수일, 담당자, 연락처, 상태, 작성자
|
||||
- 관련자 정보 4개 섹션 (실적 신고 시 필수 정보)
|
||||
- 검사 정보: 검사방문요청일, 검사시작일, 검사종료일, 검사자
|
||||
- 현장 주소: 우편번호 찾기 + 상세주소
|
||||
- 수주 설정 정보: 수주 선택 버튼 → 수주별 개소 목록 (층수/부호/수주규격/시공규격/변경사유)
|
||||
- 하단 버튼: 검사제품요청서 보기, 제품검사성적서 보기, **검사 완료**, 수정
|
||||
|
||||
---
|
||||
|
||||
## 2. 상태 관리
|
||||
|
||||
### 2.1 품질관리서 상태
|
||||
|
||||
| 상태 | 코드 | 조건 |
|
||||
|------|------|------|
|
||||
| 접수 | `received` | 생성 직후, 수주 미연결 |
|
||||
| 진행중 | `in_progress` | 수주 연결됨 또는 일부 검사 진행 |
|
||||
| 완료 | `completed` | 모든 개소 검사 완료 후 `complete()` 호출 |
|
||||
|
||||
### 2.2 개소별 검사 상태 (자동 판정)
|
||||
|
||||
| 상태 | 코드 | 판정 기준 |
|
||||
|------|------|----------|
|
||||
| 대기 | `pending` | 검사 데이터 없음 (15개 항목 0개 + 사진 없음) |
|
||||
| 진행중 | `in_progress` | 일부 항목 입력 또는 사진 미등록 |
|
||||
| 완료 | `completed` | 15개 항목 전부 입력 + 사진 1장 이상 |
|
||||
|
||||
### 2.3 상태 자동 재계산
|
||||
|
||||
개소별 검사 저장 시 → 개소 상태 자동 판정 → 품질관리서 상태 재계산:
|
||||
- 전부 `pending` → `received`
|
||||
- 하나라도 `completed` 또는 `in_progress` → `in_progress`
|
||||
- 전부 `completed` → `in_progress` (수동 `complete()` 필요)
|
||||
|
||||
---
|
||||
|
||||
## 3. 검사 항목
|
||||
|
||||
### 3.1 15개 검사 항목
|
||||
|
||||
| # | 키 | 분류 | 설명 | 판정값 |
|
||||
|---|-----|------|------|--------|
|
||||
| 1 | `appearanceProcessing` | 외관 | 가공 상태 | pass/fail |
|
||||
| 2 | `appearanceSewing` | 외관 | 봉제 상태 | pass/fail |
|
||||
| 3 | `appearanceAssembly` | 외관 | 조립 상태 | pass/fail |
|
||||
| 4 | `appearanceSmokeBarrier` | 외관 | 차연재 상태 | pass/fail |
|
||||
| 5 | `appearanceBottomFinish` | 외관 | 하부마감 상태 | pass/fail |
|
||||
| 6 | `motor` | 기능 | 모터 작동 | pass/fail |
|
||||
| 7 | `material` | 기능 | 소재 적합성 | pass/fail |
|
||||
| 8 | `lengthJudgment` | 치수 | 가로 치수 | OK/NG |
|
||||
| 9 | `heightJudgment` | 치수 | 세로 치수 | OK/NG |
|
||||
| 10 | `guideRailGap` | 치수 | 가이드레일 간격 | OK/NG |
|
||||
| 11 | `bottomFinishGap` | 치수 | 하부마감 간격 | OK/NG |
|
||||
| 12 | `fireResistanceTest` | 시험 | 내화 시험 | pass/fail |
|
||||
| 13 | `smokeLeakageTest` | 시험 | 차연 시험 | pass/fail |
|
||||
| 14 | `openCloseTest` | 시험 | 개폐 시험 | pass/fail |
|
||||
| 15 | `impactTest` | 시험 | 충격 시험 | pass/fail |
|
||||
|
||||
### 3.2 추가 데이터
|
||||
|
||||
| 키 | 설명 | 필수 |
|
||||
|----|------|------|
|
||||
| `productImages` | 제품 사진 URL 배열 | 완료 판정에 필수 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 수주 연결
|
||||
|
||||
### 4.1 수주 선택
|
||||
|
||||
- `availableOrders()`: 해당 수주처(client_id)의 미등록 수주 조회
|
||||
- 모달에서 복수 수주 선택 가능
|
||||
|
||||
### 4.2 개소 자동생성
|
||||
|
||||
수주 연결 시 각 수주의 root node(층/부호)마다 개소(Location) 자동생성:
|
||||
|
||||
```
|
||||
수주 A (3개 root node)
|
||||
├── 1F A호 → Location 1
|
||||
├── 2F B호 → Location 2
|
||||
└── 3F C호 → Location 3
|
||||
|
||||
수주 B (2개 root node)
|
||||
├── 지하1F → Location 4
|
||||
└── 1F → Location 5
|
||||
```
|
||||
|
||||
### 4.3 개소 데이터
|
||||
|
||||
| 필드 | 설명 | 출처 |
|
||||
|------|------|------|
|
||||
| `order_item_id` | 대표 OrderItem | root node의 첫 번째 품목 |
|
||||
| `post_width` | 시공 가로 | 발주 규격에서 복사 (수정 가능) |
|
||||
| `post_height` | 시공 세로 | 발주 규격에서 복사 (수정 가능) |
|
||||
| `change_reason` | 변경 사유 | 규격 변경 시 입력 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 문서 자동생성 (EAV)
|
||||
|
||||
### 5.1 제품검사요청서 (슬라이드 12~13)
|
||||
|
||||
- **Template ID**: 66 (제품검사 요청서)
|
||||
- **트리거**: 품질관리서 생성/수정 시 `syncRequestDocument()` 호출
|
||||
- **인쇄용 페이지 형태로 구분**되어 표시 (인쇄, 공유, 닫기 버튼)
|
||||
|
||||
**문서 구성**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 제품검사요청서 │
|
||||
│ 문서번호: ABC123 | 작성일자: 2025.11.11 │
|
||||
│ │
|
||||
│ 승인라인: 작성 → 승인 → 승인 → 승인 │
|
||||
│ 홍길동 이름 이름 이름 │
|
||||
│ │
|
||||
│ ── 기본정보 ── │
|
||||
│ 수주처, 수주번호, 담당자, 연락처 │
|
||||
│ 현장명, 납품일, 총 개소, 접수일 │
|
||||
│ │
|
||||
│ ── 입력사항 (실적신고 필수 정보) ── │
|
||||
│ 건축공사장: 현장명, 대지위치, 지번 │
|
||||
│ 자재유통업자: 회사명, 회사주소, 대표자명, │
|
||||
│ 전화번호 │
|
||||
│ 공사시공자: 회사명, 회사주소, 성명, 전화번호 │
|
||||
│ 공사감리자: 사무소명, 사무소주소, 성명, │
|
||||
│ 전화번호 │
|
||||
│ │
|
||||
│ ── 검사대상 사전 고지 정보 ── │
|
||||
│ No. 층수 부호 발주규격(가로/세로) │
|
||||
│ 시공후규격(가로/세로) 변경사유 │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**주의 문구** (빨간색):
|
||||
- 발주 사이즈와 시공 완료된 사이즈가 다를 시 **실질 범위를 넣어야 한다**
|
||||
- 변경사유를 고지하여야 인정마을을 부착할 수 있다
|
||||
- 사전고지를 하지 않음으로 발생하는 문제의 귀책은 신청업체에 있다
|
||||
|
||||
### 5.2 제품검사성적서 (슬라이드 14~15)
|
||||
|
||||
- 각 개소별 `document_id`로 EAV Document 참조
|
||||
- 검사 결과(inspection_data)를 EAV 필드로 저장
|
||||
- **개소별 페이지 단위**: 1/50 형태의 페이지 네비게이션 (이전/이동/다음 버튼)
|
||||
|
||||
**문서 구성**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 제품검사성적서 │
|
||||
│ 문서번호: ABC123 | 작성일자: 2025.11.11 │
|
||||
│ │
|
||||
│ 제품명, 제품 LOT NO, 로트크기 │
|
||||
│ 제품코드, 검사일자 │
|
||||
│ 수주처, 검사자 │
|
||||
│ 현장명 │
|
||||
│ │
|
||||
│ ── 제품 사진 ── │
|
||||
│ [IMG] [IMG] │
|
||||
│ │
|
||||
│ ── 검사 항목 ── │
|
||||
│ No. 검사항목 검사기준 검사 특정값 판정│
|
||||
│ 1 외모양 │
|
||||
│ 가공상태 사용상 해로운 결함이 없을 것 │
|
||||
│ 재봉상태 내화심에 의해 견고하게 접합 │
|
||||
│ 조립상태 핸드바 견고하게 조립되어야 함 │
|
||||
│ 연기차단재 연기차단재 가이드레일 W60, │
|
||||
│ 가이드레일 W50 (분체 설치) │
|
||||
│ 하단마감재 내부 부재형상 설치 유무 │
|
||||
│ 2 모터 인정제품과 동일사양 │
|
||||
│ 3 재질 WY-SC780 인쇄상태 확인 │
|
||||
│ 4 치수 │
|
||||
│ 길이 수주 치수 ± 30mm │
|
||||
│ 높이 수주 치수 ± 30mm │
|
||||
│ 가이드레일 10 ± 5mm (측정부위 길이 100 이내)│
|
||||
│ 간격 가이드레일갑과 하단마감재 25mm 이내│
|
||||
│ 5 작동테스트 6mm 관절게이지 관통 여 150mm │
|
||||
│ 6 내화시험 25mm 관절게이지 관통 유무 │
|
||||
│ 7 차연시험 10초 이상 자속되는 화염 발생 유무 │
|
||||
│ 8 개폐시험 전도/개폐 2.5~6.5m/min 등 │
|
||||
│ │
|
||||
│ 특이사항: │
|
||||
│ 종합판정: 합격 │
|
||||
│ │
|
||||
│ [이전] [1] /50 [이동] [다음] │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. API
|
||||
|
||||
### 6.1 주요 엔드포인트
|
||||
|
||||
| Method | Path | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/quality/documents` | 목록 |
|
||||
| POST | `/quality/documents` | 생성 |
|
||||
| GET | `/quality/documents/{id}` | 상세 |
|
||||
| PUT | `/quality/documents/{id}` | 수정 (개소/규격 포함) |
|
||||
| PATCH | `/quality/documents/{id}/complete` | 검사완료 |
|
||||
| POST | `/quality/documents/{id}/orders` | 수주 연결 |
|
||||
| DELETE | `/quality/documents/{id}/orders/{orderId}` | 수주 해제 |
|
||||
| POST | `/quality/documents/{id}/locations/{locId}/inspect` | 개소별 검사 저장 |
|
||||
|
||||
### 6.2 검사 저장 요청 예시
|
||||
|
||||
```json
|
||||
POST /quality/documents/1/locations/5/inspect
|
||||
{
|
||||
"inspection_data": {
|
||||
"appearanceProcessing": "pass",
|
||||
"appearanceSewing": "pass",
|
||||
"appearanceAssembly": "pass",
|
||||
"appearanceSmokeBarrier": "pass",
|
||||
"appearanceBottomFinish": "pass",
|
||||
"motor": "pass",
|
||||
"material": "pass",
|
||||
"lengthJudgment": "OK",
|
||||
"heightJudgment": "OK",
|
||||
"guideRailGap": "OK",
|
||||
"bottomFinishGap": "OK",
|
||||
"fireResistanceTest": "pass",
|
||||
"smokeLeakageTest": "pass",
|
||||
"openCloseTest": "pass",
|
||||
"impactTest": "pass",
|
||||
"productImages": ["https://..."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 소스 파일
|
||||
|
||||
### 7.1 Backend
|
||||
|
||||
| 파일 | 역할 |
|
||||
|------|------|
|
||||
| `api/app/Models/Qualitys/QualityDocument.php` | 품질관리서 모델 |
|
||||
| `api/app/Models/Qualitys/QualityDocumentLocation.php` | 개소 모델 |
|
||||
| `api/app/Models/Qualitys/QualityDocumentOrder.php` | 수주 연결 모델 |
|
||||
| `api/app/Services/QualityDocumentService.php` | 서비스 (770줄) |
|
||||
| `api/app/Http/Controllers/Api/V1/QualityDocumentController.php` | 컨트롤러 |
|
||||
|
||||
### 7.2 Frontend
|
||||
|
||||
| 파일 | 역할 |
|
||||
|------|------|
|
||||
| `react/src/components/quality/InspectionManagement/InspectionList.tsx` | 목록 |
|
||||
| `react/src/components/quality/InspectionManagement/InspectionCreate.tsx` | 생성 |
|
||||
| `react/src/components/quality/InspectionManagement/InspectionDetail.tsx` | 상세/수정 |
|
||||
| `react/src/components/quality/InspectionManagement/OrderSelectModal.tsx` | 수주 선택 |
|
||||
| `react/src/components/quality/InspectionManagement/ProductInspectionInputModal.tsx` | 검사 입력 |
|
||||
| `react/src/components/quality/InspectionManagement/actions.ts` | Server Actions |
|
||||
| `react/src/components/quality/InspectionManagement/types.ts` | 타입 정의 |
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [품질관리 시스템 개요](./README.md)
|
||||
- [생산실적신고](./performance-reports.md)
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-09
|
||||
268
features/quality-management/performance-reports.md
Normal file
268
features/quality-management/performance-reports.md
Normal file
@@ -0,0 +1,268 @@
|
||||
# 생산실적신고 (Performance Reports)
|
||||
|
||||
> **작성일**: 2026-03-09
|
||||
> **상태**: 운영 중
|
||||
> **URL**: `/quality/performance-reports`
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
건설기술진흥법에 따라 방화문 제품검사 완료 건에 대해 **분기별 실적신고**를 관리한다.
|
||||
건기원(한국건설기술연구원) 온라인 시스템에 제출할 데이터를 자동 수집하고, 필수정보 검증 후 확정 처리한다.
|
||||
|
||||
### 1.2 비즈니스 배경
|
||||
|
||||
- **법적 의무**: 방화문은 건축자재 품질관리 대상으로, 제품검사 후 실적을 건기원에 신고해야 함
|
||||
- **신고 주기**: 분기별 (Q1: 1~3월, Q2: 4~6월, Q3: 7~9월, Q4: 10~12월)
|
||||
- **신고 내용**: 품질관리서 번호, 현장정보, LOT, 규격, 검사결과
|
||||
- **제출 방식**: 건기원 온라인 시스템 수기 입력 또는 엑셀 업로드
|
||||
|
||||
### 1.3 SAM에서의 역할
|
||||
|
||||
```
|
||||
제품검사 완료 → 실적신고 자동생성 → 필수정보 검증 → 확정 → 건기원 제출
|
||||
(SAM 자동) (SAM 자동) (담당자) (수동/향후 자동)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 상태 흐름
|
||||
|
||||
### 2.1 상태 정의
|
||||
|
||||
| 상태 | 코드 | 설명 |
|
||||
|------|------|------|
|
||||
| 미확정 | `unconfirmed` | 자동생성 직후, 필수정보 미완료 가능 |
|
||||
| 확정 | `confirmed` | 필수정보 완료 후 담당자가 확정 |
|
||||
| 신고완료 | `reported` | 건기원에 신고 완료 (미구현) |
|
||||
|
||||
### 2.2 상태 전이
|
||||
|
||||
```
|
||||
confirm()
|
||||
unconfirmed ─────────────────→ confirmed
|
||||
↑ │
|
||||
│ unconfirm() │
|
||||
└──────────────────────────────┘
|
||||
│
|
||||
distribute()
|
||||
↓
|
||||
reported (미구현)
|
||||
```
|
||||
|
||||
### 2.3 확정 조건
|
||||
|
||||
확정(`confirm`) 시 **필수정보 4개 섹션** 검증:
|
||||
|
||||
```
|
||||
✅ 건축공사장: name, land_location, lot_no
|
||||
✅ 자재유통업자: company, address, ceo, tel
|
||||
✅ 공사시공자: company, address, name, phone
|
||||
✅ 공사감리자: office, address, name, phone
|
||||
```
|
||||
|
||||
하나라도 누락되면 확정 불가 → `cannotConfirmWithMissingInfo` 에러 반환
|
||||
|
||||
---
|
||||
|
||||
## 3. 자동 생성 로직
|
||||
|
||||
### 3.1 트리거
|
||||
|
||||
`QualityDocumentService::complete()` 호출 시:
|
||||
|
||||
```php
|
||||
PerformanceReport::firstOrCreate(
|
||||
[
|
||||
'tenant_id' => $tenantId,
|
||||
'quality_document_id' => $qualityDocument->id,
|
||||
],
|
||||
[
|
||||
'year' => now()->year, // 현재 연도
|
||||
'quarter' => ceil(now()->month / 3), // 현재 분기
|
||||
'confirmation_status' => 'unconfirmed',
|
||||
'created_by' => $userId,
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
### 3.2 특징
|
||||
|
||||
- `firstOrCreate`: 동일 품질관리서에 대해 중복 생성 방지
|
||||
- 검사완료 시점의 연도/분기로 자동 배정
|
||||
- Unique 제약: `(tenant_id, quality_document_id)`
|
||||
|
||||
---
|
||||
|
||||
## 4. 화면 구성
|
||||
|
||||
### 4.1 탭 구조
|
||||
|
||||
| 탭 | 내용 |
|
||||
|----|------|
|
||||
| **분기별 실적신고** | 기본 탭. 연도/분기별 실적 목록 |
|
||||
| **누락체크** | 출고완료 수주 중 품질관리서 미등록 건 |
|
||||
|
||||
### 4.2 통계 카드
|
||||
|
||||
```
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ 전체 │ │ 확정 │ │ 미확정 │ │ 총 개소 │
|
||||
│ 12건 │ │ 8건 │ │ 4건 │ │ 48개소 │
|
||||
└──────────┘ └──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
### 4.3 테이블 컬럼 (분기별 실적신고)
|
||||
|
||||
| 컬럼 | 설명 |
|
||||
|------|------|
|
||||
| 선택 | 체크박스 (일괄 처리용) |
|
||||
| 번호 | 순번 |
|
||||
| 품질관리서번호 | KD-QD-YYYYMM-NNNN |
|
||||
| 작성일 | 품질관리서 접수일 |
|
||||
| 현장명 | 시공 현장명 |
|
||||
| 수주처 | 거래처 |
|
||||
| 개소 | 검사 개소 수 |
|
||||
| 필수정보 | "완료" 또는 "N건 누락" |
|
||||
| 확정상태 | 미확정/확정/신고완료 |
|
||||
| 확정일 | 확정 처리일 |
|
||||
| 메모 | 메모 내용 |
|
||||
|
||||
### 4.4 액션 버튼
|
||||
|
||||
| 버튼 | 조건 | 설명 |
|
||||
|------|------|------|
|
||||
| 선택 확정 | 미확정 건 선택 시 | 필수정보 완료된 건만 일괄 확정 |
|
||||
| 확정 해제 | 확정 건 선택 시 | 확정 → 미확정 되돌리기 |
|
||||
| 배포 | 확정 건 선택 시 | 건기원 신고 (미구현) |
|
||||
| 메모 | 건 선택 시 | 메모 일괄 작성 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 누락체크 (슬라이드 18)
|
||||
|
||||
### 5.1 목적
|
||||
|
||||
실적신고 기간이 지났지만 확정이 안된 목록을 확인한다.
|
||||
출고(배송)가 완료되었지만 품질관리서가 등록되지 않은 수주를 탐지한다.
|
||||
|
||||
### 5.2 누락 발생 원인
|
||||
|
||||
| 원인 | 설명 |
|
||||
|------|------|
|
||||
| 품질관리서 발행일 기준 분기 불일치 | 품질관리서가 해당 분기에 포함되어야 하나, 공사 미완료로 다음 분기로 이월 |
|
||||
| 수주 중복 등록 | 수주통 시 다른 현장명으로 등록되어 제품검사 발행 시 누락 |
|
||||
| 납품 후 미등록 | 납품 후 제품검사 등록이 누락된 경우 |
|
||||
|
||||
### 5.3 누락체크 목록 표시
|
||||
|
||||
| 컬럼 | 설명 |
|
||||
|------|------|
|
||||
| 품질관리서 번호 | 관련 품질관리서 (있는 경우) |
|
||||
| 현장명 | 수주 현장명 |
|
||||
| 수주처 | 거래처 |
|
||||
| 개소 | 수주 개소 수 |
|
||||
| 제품검사완료일 | 검사 완료 일자 |
|
||||
| 메모 | 누락 사유 메모 |
|
||||
|
||||
### 5.4 로직
|
||||
|
||||
```sql
|
||||
-- 출고완료 수주 중 quality_document_orders에 미등록된 건
|
||||
SELECT orders.*
|
||||
FROM orders
|
||||
WHERE delivery_status = 'completed'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM quality_document_orders
|
||||
WHERE order_id = orders.id
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. API
|
||||
|
||||
### 6.1 엔드포인트
|
||||
|
||||
| Method | Path | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/quality/performance-reports` | 목록 (year, quarter, status 필터) |
|
||||
| GET | `/quality/performance-reports/stats` | 확정/미확정 통계 |
|
||||
| GET | `/quality/performance-reports/missing` | 누락체크 |
|
||||
| PATCH | `/quality/performance-reports/confirm` | 일괄 확정 |
|
||||
| PATCH | `/quality/performance-reports/unconfirm` | 확정 해제 |
|
||||
| PATCH | `/quality/performance-reports/memo` | 메모 일괄 업데이트 |
|
||||
|
||||
### 6.2 요청/응답 예시
|
||||
|
||||
**목록 조회**:
|
||||
```
|
||||
GET /quality/performance-reports?year=2026&quarter=1&status=unconfirmed
|
||||
```
|
||||
|
||||
**일괄 확정**:
|
||||
```json
|
||||
PATCH /quality/performance-reports/confirm
|
||||
{
|
||||
"ids": [1, 2, 3]
|
||||
}
|
||||
```
|
||||
|
||||
**응답**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "message.performance_report.confirmed",
|
||||
"data": {
|
||||
"confirmed_count": 2,
|
||||
"skipped_count": 1,
|
||||
"skipped_ids": [3]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 소스 파일
|
||||
|
||||
### 7.1 Backend
|
||||
|
||||
| 파일 | 역할 |
|
||||
|------|------|
|
||||
| `api/app/Models/Qualitys/PerformanceReport.php` | 모델 |
|
||||
| `api/app/Services/PerformanceReportService.php` | 서비스 (280줄) |
|
||||
| `api/app/Http/Controllers/Api/V1/PerformanceReportController.php` | 컨트롤러 |
|
||||
| `api/routes/api/v1/quality.php` | 라우트 |
|
||||
|
||||
### 7.2 Frontend
|
||||
|
||||
| 파일 | 역할 |
|
||||
|------|------|
|
||||
| `react/src/app/[locale]/(protected)/quality/performance-reports/page.tsx` | 페이지 |
|
||||
| `react/src/components/quality/PerformanceReportManagement/PerformanceReportList.tsx` | 목록 컴포넌트 |
|
||||
| `react/src/components/quality/PerformanceReportManagement/MemoModal.tsx` | 메모 모달 |
|
||||
| `react/src/components/quality/PerformanceReportManagement/actions.ts` | Server Actions |
|
||||
|
||||
---
|
||||
|
||||
## 8. 미구현 기능
|
||||
|
||||
| 기능 | 설명 | 우선순위 |
|
||||
|------|------|---------|
|
||||
| 배포(distribute) API | 건기원 시스템 연동 자동 신고 | 중 |
|
||||
| 엑셀 다운로드 | 확정건 건기원 양식 엑셀 내보내기 | 높음 |
|
||||
| 분기 마감 알림 | 분기 종료 전 미확정건 알림 | 낮음 |
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [품질관리 시스템 개요](./README.md)
|
||||
- [제품검사 관리](./inspection-management.md)
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-09
|
||||
169
features/quality-management/quality-certification-audit.md
Normal file
169
features/quality-management/quality-certification-audit.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# 품질인정심사 (Quality Certification Audit)
|
||||
|
||||
> **작성일**: 2026-03-09
|
||||
> **상태**: 개발 예정
|
||||
> **URL**: `/quality/qms`
|
||||
> **스토리보드**: 슬라이드 19~20
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
품질인정심사 자료를 조회하고 관리한다. 기준/매뉴얼 심사와 로트 추적 심사 두 가지 영역으로 구성된다.
|
||||
|
||||
### 1.2 탭 구조
|
||||
|
||||
| 탭 | 설명 | 진행률 예시 |
|
||||
|----|------|-----------|
|
||||
| 기준/매뉴얼 심사 | 품질 기준 문서 및 매뉴얼 점검 | 2/15 |
|
||||
| 로트 추적 심사 | 개소별 제품로트 추적 및 서류 확인 | 7/12 |
|
||||
|
||||
**전체 심사 진행률**: 기준/매뉴얼 + 로트추적 합산 (예: 9/27)
|
||||
|
||||
---
|
||||
|
||||
## 2. 기준/매뉴얼 심사
|
||||
|
||||
### 2.1 화면 구성
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 필터: 연도, 분기(전체/1~4), 검색 │
|
||||
├──────────────────┬──────────────────────────────────┤
|
||||
│ 점검표 항목 │ 기준 문서화 │
|
||||
│ │ │
|
||||
│ ▼ 1. 제목 │ 항목명 ─────────── │
|
||||
│ ☑ 1. 항목 [완료] │ 소개 ─────────── │
|
||||
│ ☑ 2. 항목 [완료] │ │
|
||||
│ □ 3. 항목 │ 관련 기준 문서 │
|
||||
│ │ 📄 문서명 R025 2025-01-01 │
|
||||
│ ▼ 2. 제목 │ 📄 문서명 R025 2025-01-01 │
|
||||
│ ☑ 1. 항목 │ │
|
||||
│ ☑ 2. 항목 │ 📎 기준/매뉴얼 확인 │
|
||||
│ □ 3. 항목 │ │
|
||||
│ │ 문서 미리보기 영역 │
|
||||
│ 완료 체크 박스 │ (PDF 등) │
|
||||
│ □ 완료 │ 파일명: 파일명.pdf 1/1 페이지 │
|
||||
└──────────────────┴──────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 점검표 항목
|
||||
|
||||
- 계층 구조: 대분류(제목) → 세부 항목
|
||||
- 각 항목에 완료/미완료 체크
|
||||
- 항목 클릭 시 우측에 해당 항목의 기준 문서 정보 표시
|
||||
|
||||
### 2.3 기준 문서화
|
||||
|
||||
| 영역 | 설명 |
|
||||
|------|------|
|
||||
| 항목명 및 소개 | 선택한 점검표 항목의 상세 정보 |
|
||||
| 관련 기준 문서 | 해당 항목에 연결된 기준 문서 목록 (문서명, 문서번호, 날짜) |
|
||||
| 문서 미리보기 | PDF 등 첨부 문서 미리보기 |
|
||||
| 완료 체크 박스 | 기본값: 완료 해제 상태 |
|
||||
|
||||
### 2.4 기준/매뉴얼 확인 버튼
|
||||
|
||||
- 클릭 시 확인 완료 스티커로 변경
|
||||
- 전체 항목 확인 완료 시 탭 진행률 갱신
|
||||
|
||||
---
|
||||
|
||||
## 3. 로트 추적 심사
|
||||
|
||||
### 3.1 화면 구성
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────┐
|
||||
│ 필터: 연도, 분기(전체/1~4), 검색 │
|
||||
├─────────────────┬─────────────────┬───────────────────────┤
|
||||
│ 품질관리서 목록 │ 수주코드 목록 │ 관련 서류 │
|
||||
│ │ │ │
|
||||
│ KD-SS-2024- │ KD-SS-240921-19 │ 수입검사 성적서 │
|
||||
│ 2025년 3분기 │ 수주: 2024-09-24│ ┌──────────────────┐ │
|
||||
│ 현장명 │ 7개소 / 완료 │ │ 전반 수입검사 성적서│ │
|
||||
│ 인정특성, 실리카 │ │ │ 절반 수입검사 성적서│ │
|
||||
│ 수주코드 2건 │ 개소별 제품로트 │ └──────────────────┘ │
|
||||
│ 14개소 │ KD-SS-240921-19 │ │
|
||||
│ │ -01 │ 개소별 제품로트 목록 │
|
||||
│ KD-SS-2024- │ 보조 │ 수주코드번호 │
|
||||
│ 현장명 │ │ 가로, 세로 │
|
||||
│ 수주코드 2건 │ KD-SS-240921-19 │ 3건의 서류 │
|
||||
│ 7개소 │ -19 [확인] │ │
|
||||
│ │ 보조 │ 확인 서류 목록 │
|
||||
│ │ │ 수입검사, 일반전표, │
|
||||
│ │ │ 중간검사, 납품확인서, │
|
||||
│ │ │ 출고증, 제품검사, │
|
||||
│ │ │ 검사 성적서, 품질관리서│
|
||||
├─────────────────┴─────────────────┤ │
|
||||
│ 문서 정보 영역 │ 확인 버튼 │
|
||||
│ 품질: 해당 문서 열람/닫힘 토글 │ [확인] [완료] │
|
||||
└────────────────────────────────────┴───────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 3단 드릴다운 구조
|
||||
|
||||
| 단계 | 영역 | 표시 내용 |
|
||||
|------|------|---------|
|
||||
| 1단계 | 품질관리서 목록 | 해당 분기 확정된 품질관리서, 인정특성, 수주코드 건수, 개소수 |
|
||||
| 2단계 | 수주코드 목록 | 선택한 품질관리서의 수주코드, 수주일, 개소수, 완료 상태 |
|
||||
| 3단계 | 관련 서류 | 해당 수주코드의 개소별 제품로트, 확인 서류 목록 |
|
||||
|
||||
### 3.3 품질관리서 목록 (1단계)
|
||||
|
||||
- 해당 분기로 실적신고 확정된 품질관리서
|
||||
- 표시: 품질관리서 번호, 해당 분기, 현장명, 인정특성, 수주코드 건수, 개소수
|
||||
- 클릭 시 2단계(수주코드 목록) 표시
|
||||
|
||||
### 3.4 수주코드 목록 (2단계)
|
||||
|
||||
- 해당 품질관리서에 연결된 수주코드 목록
|
||||
- 품질관리서는 제품검사 시 수주코드 연결
|
||||
- 표시: 수주코드 번호, 개소 수, 수주일, 현장, 개소수, 개소별 제품로트 확인 여부
|
||||
- 클릭 시 5단계 영역에 해당 수주코드의 관련 서류 표시
|
||||
|
||||
### 3.5 관련 서류 (3단계)
|
||||
|
||||
개소별 제품로트에 연결된 서류:
|
||||
|
||||
| 서류 종류 | 설명 |
|
||||
|---------|------|
|
||||
| 수입검사 성적서 | 원자재 수입검사 결과 |
|
||||
| 일반전표 | 회계 전표 |
|
||||
| 중간검사 성적서 | 공정 중간검사 |
|
||||
| 납품확인서 | 납품 확인 |
|
||||
| 출고증 | 출고 기록 |
|
||||
| 제품검사 성적서 | 완제품 검사 |
|
||||
| 품질관리서 | 품질관리서 원본 |
|
||||
|
||||
### 3.6 확인 프로세스
|
||||
|
||||
1. 품질관리서 선택 → 수주코드 선택 → 관련 서류 조회
|
||||
2. 각 서류를 열람하고 **확인** 버튼 클릭
|
||||
3. 모든 서류 확인 완료 시 해당 로트 **완료** 처리
|
||||
4. 전체 로트 완료 시 로트 추적 심사 진행률 갱신
|
||||
|
||||
---
|
||||
|
||||
## 4. 구현 상태
|
||||
|
||||
| 기능 | 상태 | 비고 |
|
||||
|------|------|------|
|
||||
| 기준/매뉴얼 심사 점검표 | 미구현 | 페이지만 존재 |
|
||||
| 기준 문서 관리 | 미구현 | |
|
||||
| 로트 추적 심사 | 미구현 | |
|
||||
| 서류 연결/확인 | 미구현 | |
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [품질관리 시스템 개요](./README.md)
|
||||
- [제품검사 관리](./inspection-management.md)
|
||||
- [생산실적신고](./performance-reports.md)
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-09
|
||||
110
features/rd/README.md
Normal file
110
features/rd/README.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# R&D 메뉴
|
||||
|
||||
> **작성일**: 2026-03-08
|
||||
> **상태**: 운영 중
|
||||
> **프로젝트**: SAM MNG (관리자 웹)
|
||||
> **라우트 접두사**: `/rd`
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
R&D 메뉴는 SAM 플랫폼의 **연구개발 및 내부 도구** 모음이다. AI 견적, 조직도 관리, 기획디자인(스토리보드 에디터), 안전점검 등 실험적이거나 내부 운영 목적의 기능을 제공한다.
|
||||
|
||||
### 1.2 문서 구조
|
||||
|
||||
| 문서 | 설명 |
|
||||
|------|------|
|
||||
| **README.md** (이 문서) | 전체 개요, 메뉴 구조, 컨트롤러 매핑 |
|
||||
| [planning-design.md](planning-design.md) | 기획디자인 스토리보드 에디터 기술 명세 |
|
||||
| [design-insight.md](design-insight.md) | 디자인 인사이트 UI/UX 연구 도구 (100종 패턴, AI 프롬프트) |
|
||||
|
||||
### 1.3 하위 메뉴 구조
|
||||
|
||||
```
|
||||
R&D
|
||||
├── 대시보드 /rd
|
||||
├── 조직도 관리 /rd/org-chart
|
||||
├── 중대재해처벌법 점검 /rd/safety-audit
|
||||
├── AI 견적 /rd/ai-quotation
|
||||
│ ├── 목록 /rd/ai-quotation
|
||||
│ ├── 생성 /rd/ai-quotation/create
|
||||
│ ├── 상세 /rd/ai-quotation/{id}
|
||||
│ ├── 편집 /rd/ai-quotation/{id}/edit
|
||||
│ └── 문서 /rd/ai-quotation/{id}/document
|
||||
├── 기획디자인 /rd/planning-design
|
||||
└── 디자인 인사이트 /rd/design-insight
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 아키텍처
|
||||
|
||||
### 2.1 기술 스택
|
||||
|
||||
| 계층 | 기술 | 설명 |
|
||||
|------|------|------|
|
||||
| 뷰 | Blade + Alpine.js | 반응형 SPA (서버 렌더링 없음) |
|
||||
| 컨트롤러 | `RdController` | 모든 R&D 라우트 처리 |
|
||||
| 서비스 | `AiQuotationService` | AI 견적 비즈니스 로직 |
|
||||
| 모델 | `AiQuotation`, `Employee`, `Department`, `Tenant` | Multi-tenant |
|
||||
| 저장 | localStorage (기획디자인), DB (견적/조직도) | 용도별 분리 |
|
||||
|
||||
### 2.2 컨트롤러 구조
|
||||
|
||||
**파일**: `app/Http/Controllers/RdController.php`
|
||||
|
||||
| 메서드 | 라우트 | 설명 |
|
||||
|--------|--------|------|
|
||||
| `index()` | `GET /rd` | R&D 대시보드 |
|
||||
| `orgChart()` | `GET /rd/org-chart` | 조직도 관리 |
|
||||
| `orgChartAssign()` | `POST /rd/org-chart/assign` | 직원 부서 배치 |
|
||||
| `orgChartUnassign()` | `POST /rd/org-chart/unassign` | 직원 부서 해제 |
|
||||
| `orgChartReorder()` | `POST /rd/org-chart/reorder` | 직원 순서/이동 |
|
||||
| `orgChartReorderDepts()` | `POST /rd/org-chart/reorder-depts` | 부서 순서 변경 |
|
||||
| `orgChartToggleHide()` | `POST /rd/org-chart/toggle-hide` | 부서 숨기기/표시 |
|
||||
| `safetyAudit()` | `GET /rd/safety-audit` | 중대재해처벌법 점검 |
|
||||
| `quotations()` | `GET /rd/ai-quotation` | AI 견적 목록 |
|
||||
| `createQuotation()` | `GET /rd/ai-quotation/create` | AI 견적 생성 폼 |
|
||||
| `showQuotation()` | `GET /rd/ai-quotation/{id}` | AI 견적 상세 |
|
||||
| `editQuotation()` | `GET /rd/ai-quotation/{id}/edit` | AI 견적 편집 |
|
||||
| `documentQuotation()` | `GET /rd/ai-quotation/{id}/document` | AI 견적 문서 |
|
||||
| `planningDesign()` | `GET /rd/planning-design` | 기획디자인 |
|
||||
| `designInsight()` | `GET /rd/design-insight` | 디자인 인사이트 |
|
||||
|
||||
### 2.3 HTMX 전체 페이지 로드 규칙
|
||||
|
||||
모든 `/rd/*` 페이지는 Alpine.js 또는 React 컴포넌트를 사용하므로, HTMX 부분 로드 시 스크립트가 실행되지 않는다. 각 메서드에서 `HX-Request` 감지 시 `HX-Redirect`로 전체 페이지 로드를 강제한다.
|
||||
|
||||
```php
|
||||
if ($request->header('HX-Request')) {
|
||||
return response('', 200)->header('HX-Redirect', route('rd.planning-design'));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 기능별 구현 현황
|
||||
|
||||
| 기능 | 구현 방식 | 백엔드 | DB | 상태 |
|
||||
|------|----------|--------|-----|------|
|
||||
| R&D 대시보드 | Blade | AiQuotationService | ai_quotations | 운영 중 |
|
||||
| 조직도 관리 | Blade + Alpine.js | RdController (직접 쿼리) | employees, departments | 운영 중 |
|
||||
| 중대재해처벌법 점검 | Blade (정적) | 없음 | 없음 | 운영 중 |
|
||||
| AI 견적 | Blade + Alpine.js | AiQuotationService | ai_quotations | 운영 중 |
|
||||
| **기획디자인** | **Blade + Alpine.js (SPA)** | **없음 (localStorage)** | **없음** | **운영 중** |
|
||||
| **디자인 인사이트** | **Blade + Alpine.js (SPA)** | **없음 (localStorage)** | **없음** | **운영 중** |
|
||||
|
||||
---
|
||||
|
||||
## 4. 관련 문서
|
||||
|
||||
- [기획디자인 스토리보드 에디터](planning-design.md) — 블록 에디터, 서식, 인쇄, 내보내기
|
||||
- [디자인 인사이트](design-insight.md) — UI/UX 연구 도구 (100종 패턴, AI 프롬프트 복사)
|
||||
- [조직도 관리 기술문서](../../projects/org-chart/) — 조직도 시스템 상세 (별도 프로젝트 문서)
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-08
|
||||
246
features/rd/design-insight.md
Normal file
246
features/rd/design-insight.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# 디자인 인사이트 — UI/UX 연구 도구
|
||||
|
||||
> **작성일**: 2026-03-08
|
||||
> **상태**: 운영 중
|
||||
> **라우트**: `GET /rd/design-insight`
|
||||
> **뷰**: `resources/views/rd/design-insight/index.blade.php`
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
디자인 인사이트는 UI/UX 패턴을 **수집·분석·비교**하는 연구 도구이다. 외부 서비스의 UI 레퍼런스를 카드 형태로 정리하고, CSS 와이어프레임 미리보기와 AI 프롬프트 생성 기능으로 디자인 의사결정을 지원한다.
|
||||
|
||||
### 1.2 핵심 기능
|
||||
|
||||
| 기능 | 설명 |
|
||||
|------|------|
|
||||
| 카드 관리 | 레퍼런스/분석/패턴/Before-After 4종 카드 CRUD |
|
||||
| 프로젝트 관리 | 다중 프로젝트, localStorage 저장 |
|
||||
| CSS 와이어프레임 | 100종 UI 패턴의 순수 CSS 미니 와이어프레임 |
|
||||
| 프리셋 템플릿 | 인기 UI 패턴 100종 원클릭 불러오기 |
|
||||
| AI 프롬프트 복사 | 카드 정보를 AI용 구조화 프롬프트로 변환·복사 |
|
||||
| 3종 뷰 | 보드(카테고리별)/갤러리(그리드)/리스트 뷰 |
|
||||
| JSON 내보내기/가져오기 | 프로젝트 데이터 백업/복원 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 아키텍처
|
||||
|
||||
### 2.1 기술 스택
|
||||
|
||||
| 계층 | 기술 | 설명 |
|
||||
|------|------|------|
|
||||
| 뷰 | Blade + Alpine.js | 단일 파일 SPA |
|
||||
| 컨트롤러 | `RdController::designInsight()` | HX-Redirect 패턴 |
|
||||
| 저장 | localStorage | `di_projects`, `di_current` 키 사용 |
|
||||
| 백엔드 | 없음 | 서버 API 호출 없이 클라이언트 단독 동작 |
|
||||
| 스타일 | 커스텀 CSS 변수 | `--di-*` 접두사 (Tailwind 미사용) |
|
||||
|
||||
### 2.2 데이터 구조
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "di_1709000000_abc123",
|
||||
"title": "프로젝트명",
|
||||
"cards": [
|
||||
{
|
||||
"id": "di_1709000001_def456",
|
||||
"type": "pattern",
|
||||
"title": "KPI 대시보드",
|
||||
"category": "dashboard",
|
||||
"rating": 5,
|
||||
"tags": ["대시보드", "KPI", "통계"],
|
||||
"memo": "레퍼런스 설명",
|
||||
"guidelines": "디자인 가이드라인",
|
||||
"usedIn": ["Stripe", "Shopify"],
|
||||
"components": [
|
||||
{ "name": "KPI 요약 카드", "required": true },
|
||||
{ "name": "필터 영역", "required": false }
|
||||
],
|
||||
"image": null,
|
||||
"createdAt": "2026-03-08T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"createdAt": "2026-03-08T00:00:00.000Z",
|
||||
"updatedAt": "2026-03-08T00:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 카드 유형 (4종)
|
||||
|
||||
| 코드 | 라벨 | 용도 |
|
||||
|------|------|------|
|
||||
| `reference` | 레퍼런스 | 외부 서비스 UI 스크린샷 수집 |
|
||||
| `analysis` | 분석 | CRAP 원칙 등 UX 분석 (8가지 디자인 원칙 평가) |
|
||||
| `pattern` | 패턴 | 재사용 가능한 UI 패턴 정의 |
|
||||
| `comparison` | Before/After | 개선 전후 비교 (이미지 2장) |
|
||||
|
||||
---
|
||||
|
||||
## 4. 카테고리 (8종)
|
||||
|
||||
| 코드 | 라벨 | 아이콘 |
|
||||
|------|------|--------|
|
||||
| `dashboard` | 대시보드 | 📊 |
|
||||
| `list` | 목록 | 📋 |
|
||||
| `form` | 상세/폼 | 📝 |
|
||||
| `modal` | 모달/팝업 | 💬 |
|
||||
| `navigation` | 네비게이션 | 🧭 |
|
||||
| `auth` | 로그인 | 🔐 |
|
||||
| `report` | 보고서 | 📄 |
|
||||
| `etc` | 기타 | 📎 |
|
||||
|
||||
---
|
||||
|
||||
## 5. CSS 와이어프레임 시스템
|
||||
|
||||
### 5.1 동작 원리
|
||||
|
||||
`getWireframe(card)` 함수가 카드의 `title`과 `tags`를 키워드 매칭하여 해당 패턴에 맞는 순수 CSS/HTML 미니 와이어프레임을 반환한다.
|
||||
|
||||
```javascript
|
||||
getWireframe(card) {
|
||||
const t = (card.title || '').toLowerCase();
|
||||
const tags = (card.tags || []).join(' ').toLowerCase();
|
||||
const key = t + ' ' + tags;
|
||||
|
||||
if (key.includes('kpi') || key.includes('대시보드') && key.includes('통계')) return `...`;
|
||||
// ... 100종 패턴 매칭
|
||||
return `기본 와이어프레임 (매칭 안 됨)`;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 프리셋 100종 분포
|
||||
|
||||
| 카테고리 | 패턴 수 | 대표 패턴 |
|
||||
|---------|---------|----------|
|
||||
| 대시보드 | 10 | KPI, 실시간 모니터링, 게이지/미터, 히트맵, 퍼널 |
|
||||
| 목록 | 10 | 데이터 테이블, 칸반 보드, 마스터-디테일, 피벗 테이블 |
|
||||
| 상세/폼 | 16 | 프로필, 설정, 위지윅 에디터, 멀티스텝 폼, 태그 입력 |
|
||||
| 모달/팝업 | 10 | 확인 다이얼로그, 라이트박스, 바텀시트, 컨텍스트 메뉴 |
|
||||
| 네비게이션 | 10 | 사이드바, 탭, 브레드크럼, FAB, 앵커 스크롤 |
|
||||
| 로그인 | 8 | 로그인 폼, 회원가입, 비밀번호 재설정, RBAC, API 키 |
|
||||
| 보고서 | 9 | 인쇄용 보고서, 간트 차트, 조직도, 워터폴, 리포트 빌더 |
|
||||
| 기타 | 27 | 댓글, 에러 페이지, FAQ, 캐러셀, 파일 매니저 등 |
|
||||
|
||||
---
|
||||
|
||||
## 6. AI 프롬프트 복사 기능
|
||||
|
||||
### 6.1 목적
|
||||
|
||||
카드에 정리된 UI 패턴 정보를 **AI가 이해할 수 있는 구조화된 마크다운 프롬프트**로 변환하여 클립보드에 복사한다. 복사한 프롬프트를 AI(Claude, ChatGPT 등)에 붙여넣으면 해당 스타일로 코드를 생성할 수 있다.
|
||||
|
||||
### 6.2 UI 위치
|
||||
|
||||
카드 상세 모달 상단, **편집** 버튼 왼쪽에 보라색 `✨ AI 프롬프트` 버튼으로 배치.
|
||||
|
||||
### 6.3 프롬프트 구조
|
||||
|
||||
`copyAiPrompt(card)` 함수가 카드 데이터를 다음 구조로 변환한다:
|
||||
|
||||
```markdown
|
||||
## UI 패턴 구현 요청
|
||||
|
||||
아래 UI/UX 패턴 레퍼런스를 참고하여, 동일한 스타일과 구조로 코드를 작성해 주세요.
|
||||
|
||||
---
|
||||
|
||||
### 패턴 정보
|
||||
- **패턴명**: {title}
|
||||
- **카테고리**: {category label}
|
||||
- **완성도 평점**: ★★★☆☆ ({rating}/5)
|
||||
- **키워드**: {tags}
|
||||
|
||||
### 레퍼런스 설명
|
||||
{memo}
|
||||
|
||||
### 실제 사용처 (벤치마킹 대상)
|
||||
- {usedIn[0]}
|
||||
- {usedIn[1]}
|
||||
|
||||
### 필수 구성 요소
|
||||
|
||||
**필수 (반드시 포함)**:
|
||||
- ✅ {required component}
|
||||
|
||||
**선택 (권장)**:
|
||||
- ○ {optional component}
|
||||
|
||||
### 디자인 가이드라인
|
||||
{guidelines}
|
||||
|
||||
### 개선 제안
|
||||
{suggestion}
|
||||
|
||||
### 기대 효과
|
||||
{effect}
|
||||
|
||||
---
|
||||
|
||||
### 구현 요구사항
|
||||
|
||||
1. **기술 스택**: HTML + Tailwind CSS (또는 프로젝트에 맞는 프레임워크)
|
||||
2. **반응형**: 모바일/태블릿/데스크톱 대응
|
||||
3. **접근성**: 시맨틱 태그, ARIA 라벨, 키보드 네비게이션
|
||||
4. **인터랙션**: hover, focus, active 상태 포함
|
||||
5. **위 구성 요소를 모두 포함**하되, 실제 서비스처럼 자연스러운 더미 데이터 사용
|
||||
6. **위 가이드라인을 충실히 반영**하여 UX 완성도를 높일 것
|
||||
```
|
||||
|
||||
### 6.4 포함 필드 매핑
|
||||
|
||||
| 카드 필드 | 프롬프트 섹션 | 조건 |
|
||||
|----------|-------------|------|
|
||||
| `title` | 패턴 정보 > 패턴명 | 항상 |
|
||||
| `category` | 패턴 정보 > 카테고리 | 항상 (라벨로 변환) |
|
||||
| `rating` | 패턴 정보 > 평점 | 항상 (별점으로 변환) |
|
||||
| `tags` | 패턴 정보 > 키워드 | 태그가 있을 때만 |
|
||||
| `memo` | 레퍼런스 설명 | 값이 있을 때만 |
|
||||
| `usedIn` | 실제 사용처 | 배열이 비어있지 않을 때만 |
|
||||
| `components` | 필수 구성 요소 | 배열이 비어있지 않을 때만 |
|
||||
| `guidelines` | 디자인 가이드라인 | 값이 있을 때만 |
|
||||
| `suggestion` | 개선 제안 | 값이 있을 때만 |
|
||||
| `effect` | 기대 효과 | 값이 있을 때만 |
|
||||
| `principles` | UX 원칙 | `type === 'analysis'`일 때만 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 뷰 모드 (3종)
|
||||
|
||||
| 모드 | 설명 | 정렬 |
|
||||
|------|------|------|
|
||||
| `board` | 카테고리별 컬럼 그룹핑 | 카테고리 → 생성순 |
|
||||
| `gallery` | 그리드 갤러리 (와이어프레임 강조) | 필터 순 |
|
||||
| `list` | 테이블형 리스트 | 필터 순 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 파일 구조
|
||||
|
||||
```
|
||||
resources/views/rd/design-insight/
|
||||
└── index.blade.php # 단일 파일 SPA (~6,200줄)
|
||||
├── <style> # 커스텀 CSS (~700줄)
|
||||
├── HTML 템플릿 # Toolbar, 사이드바, 카드, 모달 (~1,300줄)
|
||||
├── Alpine.js x-data # 상태 관리, CRUD, 필터 (~2,000줄)
|
||||
├── getWireframe() # CSS 와이어프레임 100종 (~2,000줄)
|
||||
└── loadPresetTemplates() # 프리셋 100종 데이터 (~1,200줄)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [R&D 메뉴 개요](README.md) — 전체 R&D 메뉴 구조
|
||||
- [기획디자인 스토리보드 에디터](planning-design.md) — 유사한 단일 파일 SPA 패턴
|
||||
- [디자인 인사이트 기획서](../../plans/design-insight-menu-plan.md) — 초기 기획 문서
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-08
|
||||
366
features/rd/planning-design.md
Normal file
366
features/rd/planning-design.md
Normal file
@@ -0,0 +1,366 @@
|
||||
# 기획디자인 — 스토리보드 에디터
|
||||
|
||||
> **작성일**: 2026-03-08
|
||||
> **상태**: 운영 중
|
||||
> **경로**: `/rd/planning-design`
|
||||
> **뷰 파일**: `resources/views/rd/planning-design/index.blade.php`
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 목적
|
||||
|
||||
ERP 화면 기획서(스토리보드)를 **브라우저 내에서 직접 설계**하는 Notion/Figma 스타일의 블록 에디터. 별도 소프트웨어 없이 화면 와이어프레임, Description, 메뉴 트리를 작성하고 HTML 내보내기 및 인쇄까지 지원한다.
|
||||
|
||||
### 1.2 핵심 특징
|
||||
|
||||
| 항목 | 설명 |
|
||||
|------|------|
|
||||
| **프레임워크** | Alpine.js 단일 파일 SPA (서버 API 없음) |
|
||||
| **저장 방식** | localStorage (`pc_projects` 키) |
|
||||
| **블록 에디터** | 자유 배치 캔버스 (absolute positioning) |
|
||||
| **서식 시스템** | 블록별 글자색/배경색/크기/굵기/정렬/z-index |
|
||||
| **내보내기** | HTML 파일 다운로드 + 좌표 기반 인쇄 (A4 Landscape) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 화면 구조
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 프로젝트 툴바 (프로젝트명, 저장, 내보내기, 인쇄) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ 블록 툴바 (H1, H2, 텍스트, 테이블, 콜아웃, ... 번호마커) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ 문서 정보 바 (단위업무명, 버전, 페이지 네비) │
|
||||
├────┬─────────────────────────────────────────────────────┤
|
||||
│ │ 페이지 헤더 (경로, 화면명, 화면ID) │
|
||||
│ 메 │ ┌─────────────────────────────────────────────────┐ │
|
||||
│ 뉴 │ │ 캔버스 (자유 배치 블록 영역) │ │
|
||||
│ 트 │ │ ┌──────┐ ┌────────────┐ ┌──────┐ │ │
|
||||
│ 리 │ │ │ H1 │ │ 테이블 │ │ 01 │ │ │
|
||||
│ │ │ └──────┘ └────────────┘ └──────┘ │ │
|
||||
│ ◀▶ │ │ │ │
|
||||
│ 리 │ └─────────────────────────────────────────────────┘ │
|
||||
│ 사 ├─────────────────────────────────────────────────────┤
|
||||
│ 이 │ Description 패널 (기능 설명 목록, 번호 뱃지 D&D) │
|
||||
│ 저 │ 01 로그인 후 3초 내 전사 현황 표시 │
|
||||
│ │ 02 실시간 수주 데이터 연동 │
|
||||
├────┴─────────────────────────────────────────────────────┤
|
||||
│ [플로팅 서식 툴바] B I ☰ A □ 13px ▲▼ ↺ │
|
||||
│ [우클릭 컨텍스트 메뉴] 복제/잘라내기/삭제/색상/정렬/레이어 │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.1 리사이저
|
||||
|
||||
| 경계 | 방향 | 범위 |
|
||||
|------|------|------|
|
||||
| 메뉴 ↔ 캔버스 | 좌우 (col-resize) | 80px ~ 400px |
|
||||
| 캔버스 ↔ Description | 상하 (row-resize) | 60px ~ 500px |
|
||||
|
||||
---
|
||||
|
||||
## 3. 데이터 구조
|
||||
|
||||
### 3.1 프로젝트 (localStorage: `pc_projects`)
|
||||
|
||||
```json
|
||||
{
|
||||
"sb": {
|
||||
"docInfo": {
|
||||
"projectName": "SAM ERP",
|
||||
"unitTask": "품질관리",
|
||||
"version": "D1.0"
|
||||
},
|
||||
"menuTree": [
|
||||
{ "name": "대시보드", "children": [] },
|
||||
{ "name": "품질관리", "children": [
|
||||
{ "name": "제품검사관리" }
|
||||
]}
|
||||
],
|
||||
"pages": [ /* Page[] */ ],
|
||||
"currentPageIndex": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 페이지 (Page)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "sp_1709856000000_a1b2",
|
||||
"path": "품질관리 > 제품검사관리",
|
||||
"screenName": "제품검사 목록",
|
||||
"screenId": "QM-001",
|
||||
"blocks": [ /* Block[] */ ],
|
||||
"descriptions": [
|
||||
{ "text": "검색 조건 입력 후 조회 버튼 클릭" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 블록 (Block)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "blk_1709856000000_x3y",
|
||||
"type": "text",
|
||||
"content": "텍스트 내용",
|
||||
"x": 16,
|
||||
"y": 100,
|
||||
"w": 340,
|
||||
"h": 50,
|
||||
"style": {
|
||||
"fontColor": "#ef4444",
|
||||
"bgColor": "#fef2f2",
|
||||
"fontSize": 14,
|
||||
"bold": true,
|
||||
"italic": false,
|
||||
"textAlign": "center",
|
||||
"zIndex": 11
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 블록 유형
|
||||
|
||||
| type | 설명 | 기본 크기 (W x H) | 고유 속성 |
|
||||
|------|------|-------------------|----------|
|
||||
| `heading` | 제목 (H1) | 400 x 40 | `content` |
|
||||
| `heading2` | 소제목 (H2) | 350 x 36 | `content` |
|
||||
| `text` | 텍스트 | 340 x 50 | `content` |
|
||||
| `divider` | 구분선 | 400 x 20 | — |
|
||||
| `callout` | 콜아웃 박스 | 240 x 60 | `content`, `icon` |
|
||||
| `code` | 코드 블록 | 400 x 80 | `content` |
|
||||
| `table` | 테이블 | 500 x 140 | `cols[]`, `rows[][]` |
|
||||
| `button` | 버튼 모형 | 240 x 50 | `content`, `color` |
|
||||
| `input` | 입력필드 모형 | 240 x 70 | `label`, `placeholder` |
|
||||
| `select` | 셀렉트 모형 | 240 x 70 | `label`, `placeholder` |
|
||||
| `card` | 카드 모형 | 300 x 90 | `title`, `content` |
|
||||
| `badges` | 뱃지 모음 | 350 x 50 | `items[{text, color, textColor}]` |
|
||||
| `todo` | 체크리스트 | 300 x 80 | `items[{text, checked}]` |
|
||||
| `image` | 이미지 | 400 x 200 | `src` (base64 Data URL) |
|
||||
| `marker` | 번호 마커 | 32 x 32 | `content` (01, 02, ...) |
|
||||
|
||||
### 3.5 블록 스타일 (style 객체)
|
||||
|
||||
| 속성 | 타입 | 기본값 | 설명 |
|
||||
|------|------|--------|------|
|
||||
| `fontColor` | string | — | 글자색 (CSS color) |
|
||||
| `bgColor` | string | — | 배경색 (CSS color) |
|
||||
| `fontSize` | number | 13 | 글자 크기 (px) |
|
||||
| `bold` | boolean | false | 굵게 |
|
||||
| `italic` | boolean | false | 기울임 |
|
||||
| `textAlign` | string | `'left'` | 정렬 (`left`, `center`, `right`) |
|
||||
| `zIndex` | number | 10 | 레이어 순서 (높을수록 앞) |
|
||||
|
||||
---
|
||||
|
||||
## 4. 기능 상세
|
||||
|
||||
### 4.1 블록 조작
|
||||
|
||||
| 기능 | 조작 방법 |
|
||||
|------|----------|
|
||||
| 블록 추가 | 상단 블록 툴바에서 유형 클릭 |
|
||||
| 블록 선택 | 클릭 (단일), 올가미 드래그 (다중) |
|
||||
| 블록 이동 | 드래그 앤 드롭 (단일/다중) |
|
||||
| 블록 리사이즈 | 선택 후 우측/하단/우하단 핸들 드래그 |
|
||||
| 블록 편집 | 더블클릭 → contenteditable 활성화 |
|
||||
| 이미지 업로드 | 이미지 블록 더블클릭 → 파일 선택 (드래그 충돌 방지) |
|
||||
| 블록 복제 | 우상단 ⧉ 버튼 또는 Ctrl+C → Ctrl+V |
|
||||
| 블록 삭제 | 우상단 × 버튼 또는 Delete 키 |
|
||||
| 블록 잘라내기 | Ctrl+X |
|
||||
|
||||
### 4.2 다중 선택 (올가미)
|
||||
|
||||
| 단계 | 동작 |
|
||||
|------|------|
|
||||
| 1 | 빈 캔버스 영역에서 마우스 드래그 시작 |
|
||||
| 2 | 보라색 점선 사각형(lasso rect) 표시 |
|
||||
| 3 | 마우스 놓으면 사각형과 겹치는 블록 전부 선택 (주황 테두리) |
|
||||
| 4 | 선택된 블록 그룹을 드래그/복사/삭제 가능 |
|
||||
| Ctrl+A | 전체 블록 선택 |
|
||||
|
||||
### 4.3 서식 설정
|
||||
|
||||
#### 플로팅 서식 툴바 (블록 선택 시 위에 나타남)
|
||||
|
||||
```
|
||||
[ B ] [ I ] | [ ☰ ] [ ≡ ] [ ≡ ] | [ A▾ ] [ □▾ ] | [ 13px▾ ] | [ ▲ ] [ ▼ ] | [ ↺ ]
|
||||
굵게 기울임 좌 가운데 우 글자색 배경색 크기 앞으로 뒤로 초기화
|
||||
```
|
||||
|
||||
- 글자색 / 배경색: 클릭 시 12색 팔레트 드롭다운
|
||||
- 글자 크기: 10px ~ 24px 선택 드롭다운
|
||||
- 앞으로/뒤로: z-index 증감 (레이어 순서)
|
||||
|
||||
#### 우클릭 컨텍스트 메뉴
|
||||
|
||||
블록에서 마우스 오른쪽 버튼 클릭 시 표시:
|
||||
|
||||
| 메뉴 항목 | 단축키 | 설명 |
|
||||
|----------|--------|------|
|
||||
| 복제 | Ctrl+C → V | 블록 복사 + 즉시 붙여넣기 |
|
||||
| 잘라내기 | Ctrl+X | 클립보드에 복사 후 삭제 |
|
||||
| 삭제 | Del | 블록 삭제 |
|
||||
| 글자색 ▸ | — | 12색 팔레트 서브메뉴 |
|
||||
| 배경색 ▸ | — | 12색 팔레트 서브메뉴 |
|
||||
| 왼쪽/가운데/오른쪽 정렬 | — | 텍스트 정렬 |
|
||||
| 앞으로 가져오기 | — | z-index +1 |
|
||||
| 뒤로 보내기 | — | z-index -1 |
|
||||
| 굵게 / 기울임 | — | 토글 |
|
||||
| 서식 초기화 | — | 모든 style 속성 제거 |
|
||||
|
||||
### 4.4 키보드 단축키
|
||||
|
||||
| 단축키 | 기능 |
|
||||
|--------|------|
|
||||
| `Ctrl+Z` | 실행 취소 (Undo) |
|
||||
| `Ctrl+Y` | 다시 실행 (Redo) |
|
||||
| `Ctrl+C` | 블록 복사 (단일/다중) |
|
||||
| `Ctrl+V` | 블록 붙여넣기 |
|
||||
| `Ctrl+X` | 블록 잘라내기 |
|
||||
| `Ctrl+A` | 전체 블록 선택 |
|
||||
| `Ctrl+S` | 프로젝트 저장 |
|
||||
| `Delete` / `Backspace` | 선택된 블록 삭제 |
|
||||
|
||||
### 4.5 번호 마커 (Description 연동)
|
||||
|
||||
| 입력 방식 | 설명 |
|
||||
|----------|------|
|
||||
| 블록 툴바 입력 | 번호 입력 후 "번호" 버튼 클릭 → 캔버스에 마커 추가 (자동 증가) |
|
||||
| Description 드래그 | Description 번호 뱃지를 캔버스로 드래그 앤 드롭 |
|
||||
|
||||
### 4.6 페이지 관리
|
||||
|
||||
| 기능 | 설명 |
|
||||
|------|------|
|
||||
| 페이지 추가 | + 버튼으로 새 페이지 생성 |
|
||||
| 페이지 복사 | 현재 페이지 통째로 복제 (블록 ID 재생성) |
|
||||
| 페이지 삭제 | 마지막 1페이지는 삭제 불가 |
|
||||
| 페이지 이동 | ◀ ▶ 버튼으로 전환 |
|
||||
|
||||
### 4.7 Undo/Redo
|
||||
|
||||
- 최대 50단계 히스토리 유지
|
||||
- 블록 추가/삭제/이동/리사이즈/서식 변경 모두 스냅샷 저장
|
||||
- 히스토리 분기: 중간 상태에서 새 작업 시 이후 히스토리 폐기
|
||||
|
||||
### 4.8 작업 영역 극대화
|
||||
|
||||
| 기능 | 설명 |
|
||||
|------|------|
|
||||
| 사이드바 접기 | 좌측 패널 탭 바의 `<<` 버튼 → 메뉴트리 패널 숨김 |
|
||||
| 사이드바 펼치기 | 좌측 가장자리 `>` 버튼 → 메뉴트리 패널 복원 |
|
||||
| Description 접기 | Description 영역 상단 토글 바 클릭 → 패널 숨김 |
|
||||
| 캔버스 폭 자동 확장 | 사이드바 접힘 시 페이지 폭 1100px → 1400px |
|
||||
| 패딩 축소 | sb-editor padding 24px → 12px |
|
||||
|
||||
### 4.9 템플릿 시스템
|
||||
|
||||
| 유형 | 설명 |
|
||||
|------|------|
|
||||
| 프리셋 | 검색+목록, 상세 폼, CRUD, 대시보드 카드 등 기본 레이아웃 |
|
||||
| 커스텀 | 현재 페이지 블록을 템플릿으로 저장 (localStorage: `sb_custom_templates`) |
|
||||
|
||||
---
|
||||
|
||||
## 5. 내보내기 & 인쇄
|
||||
|
||||
### 5.1 HTML 내보내기 (`sbExportHtml`)
|
||||
|
||||
- 모든 페이지를 단일 HTML 파일로 출력
|
||||
- 블록은 **좌표 기반 absolute positioning**으로 배치 (WYSIWYG)
|
||||
- 블록 스타일(글자색, 배경색, 크기 등) 반영
|
||||
- `@media print` CSS 포함 → 브라우저 인쇄 지원
|
||||
|
||||
### 5.2 인쇄 미리보기 (`sbPrintPreview`)
|
||||
|
||||
- 새 창에서 인쇄 미리보기 HTML 생성
|
||||
- A4 Landscape, 8mm 마진
|
||||
- `window.print()` 자동 호출
|
||||
- 페이지별 `page-break-after: always`
|
||||
|
||||
---
|
||||
|
||||
## 6. CSS 스타일 상속 구조
|
||||
|
||||
블록 컨테이너(`.sb-block`)에 인라인 스타일로 서식을 적용하고, 자식 요소에 `inherit` 규칙을 적용하여 상속한다.
|
||||
|
||||
```css
|
||||
/* 부모에 color가 설정된 경우 자식에게 상속 */
|
||||
.sb-block[style*="color"] .sb-blk-text,
|
||||
.sb-block[style*="color"] .sb-blk-heading { color: inherit; }
|
||||
|
||||
/* font-size, font-weight, font-style, text-align 동일 패턴 */
|
||||
```
|
||||
|
||||
> **배경**: 자식 요소(`.sb-blk-text`, `.sb-blk-heading` 등)에 하드코딩된 `color: #334155` 등이 있어, 단순히 부모에 `color`를 설정하면 CSS 우선순위에 의해 무시된다. `[style*="color"]` attribute selector로 부모에 인라인 스타일이 존재할 때만 `inherit`를 활성화한다.
|
||||
|
||||
---
|
||||
|
||||
## 7. 기술적 주의사항
|
||||
|
||||
### 7.1 저장 용량
|
||||
|
||||
- localStorage 제한: 브라우저별 약 5~10MB
|
||||
- 이미지를 base64 Data URL로 저장하므로 다수의 이미지 사용 시 용량 초과 가능
|
||||
- 향후 서버 저장(DB) 전환 검토 필요
|
||||
|
||||
### 7.2 Alpine.js 반응성
|
||||
|
||||
- 블록 데이터는 Alpine.js 반응형 객체로 관리
|
||||
- `style` 속성은 `sbEnsureStyle(blk)`로 객체 초기화 후 속성 설정
|
||||
- 배열 조작(`splice`, `push`)은 Alpine.js가 자동 감지
|
||||
|
||||
### 7.3 좌표 시스템
|
||||
|
||||
| 항목 | 단위 | 기준 |
|
||||
|------|------|------|
|
||||
| `blk.x`, `blk.y` | px | 캔버스 좌상단 (0,0) |
|
||||
| `blk.w`, `blk.h` | px | 블록 폭/높이 |
|
||||
| 드래그 계산 | clientX/Y + scroll offset | 뷰포트 → 캔버스 좌표 변환 |
|
||||
| 올가미 | 캔버스 내부 좌표 | scroll 보정 포함 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 파일 구조
|
||||
|
||||
```
|
||||
mng/
|
||||
├── app/Http/Controllers/
|
||||
│ └── RdController.php ← planningDesign() 메서드 (L308)
|
||||
└── resources/views/rd/planning-design/
|
||||
└── index.blade.php ← 전체 CSS + HTML + Alpine.js (~4,430줄)
|
||||
```
|
||||
|
||||
> **단일 파일 구조**: 이 기능은 서버 API가 없으며, 모든 CSS/HTML/JS가 `index.blade.php` 하나에 포함된다. Alpine.js `x-data` 객체 내에 모든 상태와 메서드가 정의되어 있다.
|
||||
|
||||
---
|
||||
|
||||
## 9. 향후 확장 가능성
|
||||
|
||||
| 기능 | 설명 | 우선순위 |
|
||||
|------|------|---------|
|
||||
| 스냅/그리드 정렬 | 블록 간 자석 가이드라인 | 높음 |
|
||||
| 그룹핑 | 여러 블록을 하나의 그룹으로 묶기 | 중간 |
|
||||
| 레이어 패널 | z-index 순서를 시각적으로 관리 | 중간 |
|
||||
| DB 저장 | localStorage → DB 전환 (협업 지원) | 높음 |
|
||||
| PDF 내보내기 | 직접 PDF 생성 | 낮음 |
|
||||
| 리치 텍스트 | 블록 내 부분 텍스트 서식 (인라인 B/I/색상) | 중간 |
|
||||
| 스냅샷/버전 관리 | 명시적 버전 저장 및 비교 | 낮음 |
|
||||
| 이미지 드래그 업로드 | 외부 이미지를 캔버스로 드래그 앤 드롭 | 낮음 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 관련 문서
|
||||
|
||||
- [R&D 메뉴 개요](README.md) — R&D 전체 메뉴 구조
|
||||
- [MNG 구조](../../system/mng-structure.md) — MNG 관리자 패널 전체 구조
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-08
|
||||
244
features/rd/sound-logo-studio.md
Normal file
244
features/rd/sound-logo-studio.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# 사운드 로고 스튜디오
|
||||
|
||||
> **작성일**: 2026-03-08
|
||||
> **상태**: 운영 중
|
||||
> **라우트**: `/rd/sound-logo`
|
||||
> **뷰**: `resources/views/rd/sound-logo/index.blade.php`
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
사운드 로고 스튜디오는 기업 시그니처 사운드(사운드 로고)를 제작하는 올인원 도구이다. Web Audio API 기반 시퀀서, Gemini AI 어시스트, TTS 음성 오버레이, Lyria RealTime BGM 생성을 하나의 SPA에서 통합 제공한다.
|
||||
|
||||
**핵심 기능:**
|
||||
- 시퀀서 기반 사운드 로고 수동/프리셋 제작
|
||||
- Gemini AI가 프롬프트 기반으로 음표 시퀀스 자동 설계
|
||||
- Gemini TTS로 나레이션 음성 생성 (여성/남성/아이, 30종 음성, 속도 조절)
|
||||
- Lyria RealTime WebSocket으로 AI 배경음악 실시간 생성
|
||||
- 시퀀서/BGM 상호 배타적 재생 + TTS 공통 합성
|
||||
- WAV 내보내기
|
||||
|
||||
---
|
||||
|
||||
## 2. 아키텍처
|
||||
|
||||
### 2.1 3레이어 오디오 구조
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Layer 1: 사운드 로고 (시퀀서 또는 AI BGM — 상호 배타적) │
|
||||
│ ├── A) 시퀀서 (수동 편집 / 프리셋 / AI 생성) │
|
||||
│ └── B) AI 배경음악 (Lyria RealTime) │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Layer 2: 음성 TTS (Gemini) ─── 양쪽 모드 공통 │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ DynamicsCompressor (클리핑 방지) → AudioContext.dest │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **시퀀서 모드**: 수동/프리셋/AI 생성 음표 + TTS 합성. BGM 제외.
|
||||
- **BGM 모드**: AI 배경음악 + TTS 합성. 시퀀서 음표 제외.
|
||||
- 판단 기준: `bgmBuffer` 존재 여부
|
||||
|
||||
### 2.2 기술 스택
|
||||
|
||||
| 계층 | 기술 | 설명 |
|
||||
|------|------|------|
|
||||
| 프론트엔드 | Blade + Alpine.js | 단일 SPA |
|
||||
| 오디오 엔진 | Web Audio API | OscillatorNode, BufferSourceNode, DynamicsCompressorNode |
|
||||
| AI 시퀀서 | Gemini 2.5 Flash | 프롬프트 → JSON 음표 시퀀스 |
|
||||
| TTS | `gemini-2.5-flash-preview-tts` | 30종 음성, Director's Note 스타일 제어 |
|
||||
| BGM | Lyria RealTime (WebSocket) | `models/lyria-realtime-exp`, 48kHz 스테레오 PCM |
|
||||
| 저장 | 없음 (클라이언트 전용) | WAV 내보내기로 결과물 보존 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 시퀀서 (Layer 1-A)
|
||||
|
||||
### 3.1 입력 모드
|
||||
|
||||
| 모드 | 설명 |
|
||||
|------|------|
|
||||
| **수동** | 음표 그리드에서 직접 추가/삭제/편집 |
|
||||
| **프리셋** | 사전 정의된 사운드 패턴 선택 (기업 시그널, 알림음 등) |
|
||||
| **AI 생성** | 프롬프트 입력 → Gemini가 음표 시퀀스 JSON 반환 |
|
||||
|
||||
### 3.2 음표 데이터 구조
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "note | chord | rest",
|
||||
"note": "C5",
|
||||
"chord": ["C4", "E4", "G4"],
|
||||
"duration": 0.2,
|
||||
"velocity": 0.8
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 신디사이저 설정
|
||||
|
||||
| 파라미터 | 범위 | 설명 |
|
||||
|---------|------|------|
|
||||
| `synth` | sine, triangle, square, sawtooth | 파형 |
|
||||
| `volume` | 0~1 | 마스터 볼륨 |
|
||||
| `adsr.attack` | 1~500ms | 어택 |
|
||||
| `adsr.decay` | 10~1000ms | 디케이 |
|
||||
| `adsr.sustain` | 0~1.0 | 서스테인 |
|
||||
| `adsr.release` | 10~3000ms | 릴리즈 |
|
||||
| `reverb` | 0~1 | 리버브 양 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 음성 오버레이 — TTS (Layer 2)
|
||||
|
||||
### 4.1 음성 카테고리
|
||||
|
||||
| 카테고리 | 음성 수 | 주요 음성 |
|
||||
|---------|---------|----------|
|
||||
| **여성** | 9종 | Kore(단정), Aoede(산뜻), Leda(따뜻), Zephyr(차분) 등 |
|
||||
| **남성** | 9종 | Puck(밝음), Charon(깊음), Orus(안정), Fenrir(무게) 등 |
|
||||
| **아이** | 5종 | Leda 기반(여아), Puck 기반(남아) 등 |
|
||||
|
||||
### 4.2 속도 조절
|
||||
|
||||
| 단계 | Director's Note |
|
||||
|------|----------------|
|
||||
| 1 (매우 느림) | "아주 천천히 또박또박 말해주세요." |
|
||||
| 2 (느림) | "조금 느린 속도로 말해주세요." |
|
||||
| 3 (보통) | (지시문 없음) |
|
||||
| 4 (빠름) | "조금 빠른 속도로 말해주세요." |
|
||||
| 5 (매우 빠름) | "아주 빠른 속도로 말해주세요." |
|
||||
|
||||
### 4.3 오디오 형식
|
||||
|
||||
- 출력: `audio/L16;rate=24000` (16-bit PCM, 24kHz, 모노, little-endian)
|
||||
- 디코딩: `DataView.getInt16(offset, true)` → Float32 변환
|
||||
|
||||
---
|
||||
|
||||
## 5. AI 배경음악 — Lyria (Layer 1-B)
|
||||
|
||||
### 5.1 WebSocket 프로토콜
|
||||
|
||||
```
|
||||
브라우저 ──WebSocket──→ Google Lyria RealTime API
|
||||
(wss://generativelanguage.googleapis.com/ws/...BidiGenerateMusic)
|
||||
```
|
||||
|
||||
서버는 API 키만 전달(`GET /lyria-config`), 실제 WebSocket 통신은 브라우저에서 직접 수행한다.
|
||||
|
||||
### 5.2 메시지 흐름
|
||||
|
||||
```
|
||||
1. setup → { setup: { model: "models/lyria-realtime-exp" } }
|
||||
2. (수신) ← { setupComplete: {} }
|
||||
3. 프롬프트 → { clientContent: { weightedPrompts: [...] } }
|
||||
4. 설정 → { musicGenerationConfig: { bpm, density, brightness, scale, temperature } }
|
||||
5. 재생 → { playbackControl: "PLAY" }
|
||||
6. (수신 반복) ← { serverContent: { audioChunks: [{ data: "base64..." }] } }
|
||||
7. 정지 → { playbackControl: "STOP" }
|
||||
8. (종료) ← WebSocket close
|
||||
```
|
||||
|
||||
### 5.3 BGM 파라미터
|
||||
|
||||
| 파라미터 | 범위 | 설명 |
|
||||
|---------|------|------|
|
||||
| `bgmPrompt` | 텍스트 | 음악 분위기 설명 |
|
||||
| `bgmBpm` | 60~180 | BPM |
|
||||
| `bgmDensity` | 0~100 | 밀도 (0~1 변환) |
|
||||
| `bgmBrightness` | 0~100 | 밝기 (0~1 변환) |
|
||||
| `bgmScale` | C_MAJOR 등 | 음계 |
|
||||
| `bgmDuration` | 5~60초 | 생성 길이 |
|
||||
|
||||
### 5.4 오디오 형식
|
||||
|
||||
- 출력: 16-bit PCM, 48kHz, 스테레오, little-endian
|
||||
- WAV 헤더 감지 시 `decodeAudioData` fallback
|
||||
|
||||
---
|
||||
|
||||
## 6. API 엔드포인트
|
||||
|
||||
### 6.1 사운드 로고 (RdController)
|
||||
|
||||
| HTTP | URI | 메서드 | 설명 |
|
||||
|------|-----|--------|------|
|
||||
| GET | `/rd/sound-logo` | `soundLogo()` | 스튜디오 페이지 |
|
||||
| POST | `/rd/sound-logo/generate` | `soundLogoGenerate()` | AI 음표 시퀀스 생성 (Gemini) |
|
||||
| POST | `/rd/sound-logo/tts` | `soundLogoTts()` | TTS 음성 생성 (Gemini TTS) |
|
||||
| GET | `/rd/sound-logo/lyria-config` | `soundLogoLyriaConfig()` | Lyria WebSocket 접속 설정 반환 |
|
||||
|
||||
### 6.2 CM송/나레이션 (CmSongController)
|
||||
|
||||
| HTTP | URI | 메서드 | 설명 |
|
||||
|------|-----|--------|------|
|
||||
| GET | `/rd/cm-song` | `index()` | 나레이션 목록 |
|
||||
| GET | `/rd/cm-song/create` | `create()` | 나레이션 제작 |
|
||||
| POST | `/rd/cm-song` | `store()` | 나레이션 저장 |
|
||||
| GET | `/rd/cm-song/{id}` | `show()` | 나레이션 상세 |
|
||||
| DELETE | `/rd/cm-song/{id}` | `destroy()` | 나레이션 삭제 |
|
||||
| GET | `/rd/cm-song/{id}/download` | `download()` | 음성 파일 다운로드 |
|
||||
| POST | `/rd/cm-song/generate-lyrics` | `generateLyrics()` | AI 가사 생성 (Gemini) |
|
||||
| POST | `/rd/cm-song/generate-audio` | `generateAudio()` | TTS 음성 생성 |
|
||||
|
||||
### 6.3 CM송 데이터 모델
|
||||
|
||||
| 모델 | 테이블 | 설명 |
|
||||
|------|--------|------|
|
||||
| `CmSong` | `cm_songs` | 나레이션 (회사명, 업종, 가사, 음성파일) |
|
||||
|
||||
**저장 경로**: `tenant` 디스크 → `cm-songs/{tenant_id}/{filename}.wav`
|
||||
|
||||
---
|
||||
|
||||
## 7. 오디오 엔진 상세
|
||||
|
||||
### 7.1 마스터 출력 체인
|
||||
|
||||
```
|
||||
각 소스 (Oscillator / BufferSource)
|
||||
→ GainNode (개별 볼륨)
|
||||
→ DynamicsCompressorNode (마스터 리미터)
|
||||
→ AudioContext.destination
|
||||
```
|
||||
|
||||
**컴프레서 설정:**
|
||||
- threshold: -6dB
|
||||
- knee: 10dB
|
||||
- ratio: 12:1
|
||||
- attack: 3ms
|
||||
- release: 150ms
|
||||
|
||||
### 7.2 WAV 내보내기
|
||||
|
||||
`OfflineAudioContext`로 오프라인 렌더링 후 `bufferToWav()` 변환.
|
||||
- 샘플레이트: 44,100Hz
|
||||
- 채널: 2 (스테레오)
|
||||
- 비트: 16-bit PCM
|
||||
- 오프라인 컨텍스트에도 동일한 DynamicsCompressor 적용
|
||||
|
||||
---
|
||||
|
||||
## 8. 관련 파일
|
||||
|
||||
| 파일 | 설명 |
|
||||
|------|------|
|
||||
| `resources/views/rd/sound-logo/index.blade.php` | SPA 뷰 (Alpine.js, ~2100줄) |
|
||||
| `app/Http/Controllers/RdController.php` | 사운드 로고 API (4 메서드) |
|
||||
| `app/Http/Controllers/Rd/CmSongController.php` | CM송/나레이션 CRUD (8 메서드) |
|
||||
| `app/Models/Rd/CmSong.php` | CM송 모델 |
|
||||
| `app/Helpers/AiTokenHelper.php` | Gemini 토큰 사용량 추적 |
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [R&D 메뉴 개요](README.md) — R&D 전체 메뉴 구조
|
||||
- [AI 분석 리포트](../ai/README.md) — Gemini API 활용 패턴 참고
|
||||
- [사운드 로고 생성기 기획서](../../plans/sound-logo-generator-plan.md) — 초기 기획
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-08
|
||||
96
frontend/_index.md
Normal file
96
frontend/_index.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# SAM ERP Frontend Documentation
|
||||
|
||||
> **프로젝트**: SAM ERP Next.js 프론트엔드
|
||||
> **최종 갱신**: 2026-03-09
|
||||
> **현재 문서 버전**: v1
|
||||
|
||||
---
|
||||
|
||||
## 문서 구조
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── _index.md ← 현재 문서 (목록 + 버전 관리)
|
||||
├── v1/ ← 현재 활성 버전
|
||||
│ ├── 01 ~ 09 ← 프론트엔드 아키텍처/가이드
|
||||
│ └── 10 ← API 연동 스펙
|
||||
└── api-specs/ ← (레거시, v1/10으로 이관됨)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 문서 목록 및 버전 현황
|
||||
|
||||
| # | 문서 | 버전 | 최종 수정 | 담당 | 대상 | 설명 |
|
||||
|---|------|------|----------|------|------|------|
|
||||
| 01 | [architecture](v1/01-architecture.md) | 1.0.0 | 2026-03-09 | Frontend | 전체 | 프로젝트 구조, 기술 스택, 디렉토리 설계 |
|
||||
| 02 | [api-pattern](v1/02-api-pattern.md) | 1.0.0 | 2026-03-09 | Frontend | FE/BE | API 통신 패턴 (프록시, Server Action, buildApiUrl) |
|
||||
| 03 | [component-design](v1/03-component-design.md) | 1.0.0 | 2026-03-09 | Frontend | FE/기획 | 컴포넌트 계층 (atoms → templates), 페이지 유형 |
|
||||
| 04 | [common-components](v1/04-common-components.md) | 1.0.0 | 2026-03-09 | Frontend | FE | 공통 컴포넌트 사용법 (UniversalListPage 등) |
|
||||
| 05 | [form-pattern](v1/05-form-pattern.md) | 1.0.0 | 2026-03-09 | Frontend | FE | 폼 패턴 (Zod, FormField, react-hook-form) |
|
||||
| 06 | [styling-guide](v1/06-styling-guide.md) | 1.0.0 | 2026-03-09 | Frontend | FE/디자인 | CSS 규칙 (Tailwind, shadcn/ui, 색상 시스템) |
|
||||
| 07 | [auth-flow](v1/07-auth-flow.md) | 1.0.0 | 2026-03-09 | Frontend | FE/BE | 인증 흐름 (HttpOnly cookie, 토큰 갱신) |
|
||||
| 08 | [dashboard-system](v1/08-dashboard-system.md) | 1.0.0 | 2026-03-09 | Frontend | FE/BE | CEO 대시보드 아키텍처 (invalidation, hooks) |
|
||||
| 09 | [conventions](v1/09-conventions.md) | 1.0.0 | 2026-03-09 | Frontend | FE | 네이밍, import, 파일 배치, Git 규칙 |
|
||||
| 10 | [document-api-integration](v1/10-document-api-integration.md) | 1.0.0 | 2026-02-05 | API Team | FE/BE | 문서 관리 API 연동 (검사 성적서 resolve/upsert) |
|
||||
|
||||
### 대상 범례
|
||||
- **FE**: 프론트엔드 개발자
|
||||
- **BE**: 백엔드 개발자
|
||||
- **기획**: 기획자/PM
|
||||
- **디자인**: 디자이너
|
||||
- **전체**: 모든 역할
|
||||
|
||||
---
|
||||
|
||||
## 버전 변경 이력
|
||||
|
||||
### v1 (2026-03-09 ~)
|
||||
|
||||
| 날짜 | 문서 | 변경 | 버전 |
|
||||
|------|------|------|------|
|
||||
| 2026-03-09 | 01~09 | 초기 작성 | 1.0.0 |
|
||||
| 2026-02-05 | 10 | 문서 API 연동 가이드 작성 (api-specs에서 이관) | 1.0.0 |
|
||||
|
||||
---
|
||||
|
||||
## 버전 관리 규칙
|
||||
|
||||
### 문서 버전 (Semantic Versioning)
|
||||
|
||||
```
|
||||
MAJOR.MINOR.PATCH
|
||||
|
||||
MAJOR: 문서 구조 변경, 기존 내용 대폭 수정
|
||||
MINOR: 새로운 섹션 추가, 기존 내용 보완
|
||||
PATCH: 오탈자, 코드 예시 수정, 사소한 수정
|
||||
```
|
||||
|
||||
### 업데이트 절차
|
||||
|
||||
1. 해당 문서 내용 수정
|
||||
2. 문서 상단 `버전`과 `최종 수정` 날짜 갱신
|
||||
3. 이 `_index.md`의 문서 목록 테이블 버전/날짜 갱신
|
||||
4. 변경 이력 테이블에 행 추가
|
||||
|
||||
### 새 문서 추가 시
|
||||
|
||||
1. `v1/` 폴더에 `{번호}-{주제}.md` 형식으로 생성
|
||||
2. 문서 상단에 버전/날짜/대상 헤더 포함
|
||||
3. `_index.md` 문서 목록 테이블에 행 추가
|
||||
|
||||
---
|
||||
|
||||
## 빠른 참고
|
||||
|
||||
| 할 일 | 읽을 문서 |
|
||||
|-------|----------|
|
||||
| 프로젝트 전체 구조 이해 | 01-architecture |
|
||||
| API 호출 방법 알기 | 02-api-pattern |
|
||||
| 새 리스트 페이지 만들기 | 03-component-design → 04-common-components |
|
||||
| 새 폼 페이지 만들기 | 05-form-pattern |
|
||||
| 디자인/스타일 규칙 확인 | 06-styling-guide |
|
||||
| 인증 동작 이해 | 07-auth-flow |
|
||||
| 대시보드 연동 작업 | 08-dashboard-system |
|
||||
| 코딩 컨벤션 확인 | 09-conventions |
|
||||
| 문서 관리 API 연동 | 10-document-api-integration |
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user