diff --git a/.cursor/agents/pipeline-common-rules.md b/.cursor/agents/pipeline-common-rules.md index 5baa0e97..57049ce6 100644 --- a/.cursor/agents/pipeline-common-rules.md +++ b/.cursor/agents/pipeline-common-rules.md @@ -12,13 +12,14 @@ - **대상**: 시스템 설정, 사용자 관리, 결재 관리, 코드 관리 등 - **특징**: 하드코딩된 UI, 관리자만 접근 -### 사용자 메뉴 (User/Screen) +### 사용자 메뉴 (User/Screen) - 절대 하드코딩 금지!!! - **구현 방식**: 로우코드 기반 (DB에 JSON으로 화면 구성 저장) -- **데이터 저장**: `screen_layouts` 테이블에 JSON 형식 보관 +- **데이터 저장**: `screen_layouts_v2` 테이블에 V2 JSON 형식 보관 - **화면 디자이너**: 스크린 디자이너로 드래그앤드롭 구성 - **V2 컴포넌트**: `frontend/lib/registry/components/v2-*` 디렉토리 -- **대상**: 일반 업무 화면, BOM, 문서 관리 등 +- **대상**: 일반 업무 화면, BOM, 문서 관리, 포장/적재, 금형 관리 등 - **특징**: 코드 수정 없이 화면 구성 변경 가능 +- **절대 금지**: React `.tsx` 페이지 파일로 직접 UI를 하드코딩하는 것! ### 판단 기준 @@ -26,8 +27,88 @@ |------|-------------|-------------| | 누가 쓰나? | 시스템 관리자 | 일반 사용자 | | 화면 구조 고정? | 고정 (코드) | 유동적 (JSON) | -| URL 패턴 | `/admin/*` | 스크린 디자이너 경유 | -| 메뉴 등록 | `menu_info` INSERT 필수 | 스크린 레이아웃 등록 | +| URL 패턴 | `/admin/*` | `/screen/{screen_code}` | +| 메뉴 등록 | `menu_info` INSERT | `screen_definitions` + `menu_info` INSERT | +| 프론트엔드 코드 | `frontend/app/(main)/admin/` 하위에 page.tsx 작성 | **코드 작성 금지!** DB에 스크린 정의만 등록 | + +### 사용자 메뉴 구현 방법 (반드시 이 방식으로!) + +**절대 규칙: 사용자 메뉴는 React 페이지(.tsx)를 직접 만들지 않는다!** +이미 `/screen/[screenCode]/page.tsx` → `/screens/[screenId]/page.tsx` 렌더링 시스템이 존재한다. +새 화면이 필요하면 DB에 등록만 하면 자동으로 렌더링된다. + +#### Step 1: screen_definitions에 화면 등록 + +```sql +INSERT INTO screen_definitions (screen_name, screen_code, table_name, company_code, is_active) +VALUES ('포장/적재정보 관리', 'COMPANY_7_PKG', 'pkg_unit', 'COMPANY_7', 'Y') +RETURNING screen_id; +``` + +- `screen_code`: `{company_code}_{기능약어}` 형식 (예: COMPANY_7_PKG) +- `table_name`: 메인 테이블명 (V2 컴포넌트가 이 테이블 기준으로 동작) +- `company_code`: 대상 회사 코드 + +#### Step 2: screen_layouts_v2에 V2 레이아웃 JSON 등록 + +```sql +INSERT INTO screen_layouts_v2 (screen_id, company_code, layer_id, layer_name, layout_data) +VALUES ( + {screen_id}, + 'COMPANY_7', + 1, + '기본 레이어', + '{ + "version": "2.0", + "components": [ + { + "id": "comp_split_1", + "url": "@/lib/registry/components/v2-split-panel-layout", + "position": {"x": 0, "y": 0}, + "size": {"width": 1200, "height": 800}, + "displayOrder": 0, + "overrides": { + "leftTitle": "포장단위 목록", + "rightTitle": "상세 정보", + "splitRatio": 40, + "leftTableName": "pkg_unit", + "rightTableName": "pkg_unit", + "tabs": [ + {"id": "basic", "label": "기본정보"}, + {"id": "items", "label": "매칭품목"} + ] + } + } + ] + }'::jsonb +); +``` + +- V2 컴포넌트 목록: v2-split-panel-layout, v2-table-list, v2-table-search-widget, v2-repeater, v2-button-primary, v2-tabs-widget 등 +- 상세 컴포넌트 가이드: `.cursor/rules/component-development-guide.mdc` 참조 + +#### Step 3: menu_info에 메뉴 등록 + +```sql +-- 먼저 부모 메뉴 objid 조회 +-- SELECT objid, menu_name_kor FROM menu_info WHERE company_code = '{회사코드}' AND menu_name_kor LIKE '%물류%'; + +INSERT INTO menu_info (objid, menu_type, parent_obj_id, menu_name_kor, seq, menu_url, screen_code, company_code, status) +VALUES ( + (SELECT COALESCE(MAX(objid), 0) + 1 FROM menu_info), + 2, -- 2 = 메뉴 항목 + {부모_objid}, -- 상위 메뉴의 objid + '포장/적재정보', + 10, -- 정렬 순서 + '/screen/COMPANY_7_PKG', -- /screen/{screen_code} 형식 (절대!) + 'COMPANY_7_PKG', -- screen_definitions.screen_code와 일치 + 'COMPANY_7', + 'Y' +); +``` + +**핵심**: `menu_url`은 반드시 `/screen/{screen_code}` 형식이어야 한다! +프론트엔드가 이 URL을 받아 `screen_definitions`에서 screen_id를 찾고, `screen_layouts_v2`에서 레이아웃을 로드한다. ## 2. 관리자 메뉴 등록 (코드 구현 후 필수!) @@ -35,11 +116,14 @@ ```sql -- 예시: 결재 템플릿 관리 메뉴 등록 -INSERT INTO menu_info (menu_id, menu_name, url, parent_id, menu_type, sort_order, is_active, company_code) -VALUES ('approvalTemplate', '결재 템플릿', '/admin/approvalTemplate', 'approval', 'ADMIN', 40, 'Y', '대상회사코드'); +INSERT INTO menu_info (objid, menu_type, parent_obj_id, menu_name_kor, seq, menu_url, company_code, status) +VALUES ( + (SELECT COALESCE(MAX(objid), 0) + 1 FROM menu_info), + 2, {부모_objid}, '결재 템플릿', 40, '/admin/approvalTemplate', '대상회사코드', 'Y' +); ``` -- 기존 메뉴 구조를 먼저 조회해서 parent_id, sort_order 등을 맞춰라 +- 기존 메뉴 구조를 먼저 조회해서 parent_obj_id, seq 등을 맞춰라 - company_code 별로 등록이 필요할 수 있다 - menu_auth_group 권한 매핑도 필요하면 추가 @@ -63,15 +147,25 @@ VALUES ('approvalTemplate', '결재 템플릿', '/admin/approvalTemplate', 'appr 기능 하나를 "완성"이라고 말하려면 아래를 전부 충족해야 한다: +### 공통 - [ ] DB: 마이그레이션 작성 + 실행 완료 - [ ] DB: company_code 컬럼 + 인덱스 존재 -- [ ] BE: API 엔드포인트 구현 + 라우트 등록 +- [ ] BE: API 엔드포인트 구현 + 라우트 등록 (app.ts에 import + use 추가!) - [ ] BE: company_code 필터링 적용 -- [ ] FE: API 클라이언트 함수 작성 (lib/api/) -- [ ] FE: 화면 컴포넌트 구현 -- [ ] **메뉴 등록**: 관리자 메뉴면 menu_info INSERT, 사용자 메뉴면 스크린 레이아웃 등록 - [ ] 빌드 통과: 백엔드 tsc + 프론트엔드 tsc +### 관리자 메뉴인 경우 +- [ ] FE: `frontend/app/(main)/admin/{기능}/page.tsx` 작성 +- [ ] FE: API 클라이언트 함수 작성 (lib/api/) +- [ ] DB: `menu_info` INSERT (menu_url = `/admin/{기능}`) + +### 사용자 메뉴인 경우 (코드 작성 금지!) +- [ ] DB: `screen_definitions` INSERT (screen_code, table_name, company_code) +- [ ] DB: `screen_layouts_v2` INSERT (V2 레이아웃 JSON) +- [ ] DB: `menu_info` INSERT (menu_url = `/screen/{screen_code}`) +- [ ] BE: 필요한 경우 전용 API 작성 (범용 table-management API로 커버 안 되는 경우만) +- [ ] FE: .tsx 페이지 파일 만들지 않음 (이미 `/screens/[screenId]/page.tsx`가 렌더링) + ## 6. 절대 하지 말 것 1. 페이지 파일만 만들고 메뉴 등록 안 하기 (미완성!) @@ -80,3 +174,9 @@ VALUES ('approvalTemplate', '결재 템플릿', '/admin/approvalTemplate', 'appr 4. 하드코딩 색상/URL/사용자ID 사용 5. Card 안에 Card 중첩 (중첩 박스 금지) 6. 백엔드 재실행하기 (nodemon이 자동 재시작) +7. **사용자 메뉴를 React 하드코딩(.tsx)으로 만들기 (절대 금지!!!)** + - `frontend/app/(main)/production/`, `frontend/app/(main)/warehouse/` 등에 page.tsx 파일을 만들어서 직접 UI를 코딩하는 것은 절대 금지 + - 사용자 메뉴는 반드시 `screen_definitions` + `screen_layouts_v2` + `menu_info` DB 등록 방식으로 구현 + - 이미 `/screen/[screenCode]` → `/screens/[screenId]` 렌더링 시스템이 존재함 + - 백엔드 API(controller/routes)와 프론트엔드 API 클라이언트(lib/api/)는 필요하면 코드로 작성 가능 + - 하지만 프론트엔드 화면 UI 자체는 DB의 V2 레이아웃 JSON으로만 구성 diff --git a/.cursor/agents/pipeline-frontend.md b/.cursor/agents/pipeline-frontend.md index 0eef5611..223b5b38 100644 --- a/.cursor/agents/pipeline-frontend.md +++ b/.cursor/agents/pipeline-frontend.md @@ -49,6 +49,35 @@ export async function getYourData(id: number) { } ``` +# CRITICAL: 사용자 메뉴 화면은 코드로 만들지 않는다!!! + +**이 프로젝트는 로우코드 스크린 디자이너 시스템을 사용한다.** +사용자 업무 화면(포장관리, 금형관리, BOM, 문서관리 등)은 절대 React 페이지(.tsx)로 직접 UI를 하드코딩하지 않는다! + +## 금지 패턴 (절대 하지 말 것) +``` +frontend/app/(main)/production/packaging/page.tsx ← 이런 파일 만들지 마라! +frontend/app/(main)/warehouse/something/page.tsx ← 이런 파일 만들지 마라! +``` + +## 올바른 패턴 +사용자 화면은 DB에 등록만 하면 자동으로 렌더링된다: +1. `screen_definitions` 테이블에 화면 등록 (screen_code, table_name 등) +2. `screen_layouts_v2` 테이블에 V2 레이아웃 JSON 등록 (v2-split-panel-layout, v2-table-list 등) +3. `menu_info` 테이블에 메뉴 등록 (menu_url = `/screen/{screen_code}`) + +이미 존재하는 렌더링 시스템: +- `/screen/[screenCode]/page.tsx` → screenCode를 screenId로 변환 +- `/screens/[screenId]/page.tsx` → screen_layouts_v2에서 V2 레이아웃 로드 → DynamicComponentRenderer로 렌더링 + +## 프론트엔드 에이전트가 할 수 있는 것 +- `frontend/lib/api/` 하위에 API 클라이언트 함수 작성 (백엔드와 통신) +- V2 컴포넌트 자체를 수정/신규 개발 (`frontend/lib/registry/components/v2-*/`) +- 관리자 메뉴(`/admin/*`)는 React 페이지 코딩 가능 + +## 프론트엔드 에이전트가 할 수 없는 것 +- 사용자 메뉴 화면을 React 페이지로 직접 코딩하는 것 + # Your Domain - frontend/components/ - frontend/app/ diff --git a/.cursor/agents/pipeline-ui.md b/.cursor/agents/pipeline-ui.md index 44cf2daa..05d3359e 100644 --- a/.cursor/agents/pipeline-ui.md +++ b/.cursor/agents/pipeline-ui.md @@ -39,9 +39,23 @@ FORBIDDEN: bg-gray-50, text-blue-500, bg-white, text-black - Use cn() for conditional classes - Use lucide-react for ALL icons +# CRITICAL: 사용자 메뉴 화면은 코드로 만들지 않는다!!! + +사용자 업무 화면(포장관리, 금형관리, BOM 등)의 UI는 DB의 `screen_layouts_v2` 테이블에 V2 레이아웃 JSON으로 정의된다. +React 페이지(.tsx)로 직접 UI를 하드코딩하는 것은 절대 금지! + +UI 에이전트가 할 수 있는 것: +- V2 컴포넌트 자체의 스타일/UX 개선 (`frontend/lib/registry/components/v2-*/`) +- 관리자 메뉴(`/admin/*`) 페이지의 UI 개선 +- 공통 UI 컴포넌트(`frontend/components/ui/`) 스타일 개선 + +UI 에이전트가 할 수 없는 것: +- 사용자 메뉴 화면을 React 페이지로 직접 코딩 + # Your Domain - frontend/components/ (UI components) -- frontend/app/ (pages) +- frontend/app/ (pages - 관리자 메뉴만) +- frontend/lib/registry/components/v2-*/ (V2 컴포넌트) # Output Rules 1. TypeScript strict mode diff --git a/.cursorrules b/.cursorrules index e34d4050..0019badc 100644 --- a/.cursorrules +++ b/.cursorrules @@ -1541,3 +1541,38 @@ const query = ` - 기본 5개 컬럼 누락 금지 - 메타데이터 테이블 미등록 금지 +--- + +## 화면 개발 방식 필수 규칙 (사용자 메뉴 vs 관리자 메뉴) + +**상세 가이드**: [pipeline-common-rules.md](.cursor/agents/pipeline-common-rules.md) + +### 핵심 원칙 (절대 위반 금지) + +1. **사용자 업무 화면은 React 코드(.tsx)로 직접 만들지 않는다!** + - 포장관리, 금형관리, BOM, 입출고, 품질 등 일반 업무 화면 + - DB에 `screen_definitions` + `screen_layouts_v2` + `menu_info` 등록으로 구현 + - 이미 `/screen/[screenCode]` → `/screens/[screenId]` 렌더링 시스템이 존재 + - V2 컴포넌트(v2-split-panel-layout, v2-table-list, v2-repeater 등)로 레이아웃 구성 + +2. **관리자 메뉴만 React 코드로 작성 가능** + - 사용자 관리, 권한 관리, 시스템 설정 등 + - `frontend/app/(main)/admin/{기능}/page.tsx`에 작성 + - `menu_info` 테이블에 메뉴 등록 필수 + +### 사용자 메뉴 구현 순서 + +``` +1. DB 테이블 생성 (비즈니스 데이터용) +2. screen_definitions INSERT (screen_code, table_name) +3. screen_layouts_v2 INSERT (V2 레이아웃 JSON) +4. menu_info INSERT (menu_url = '/screen/{screen_code}') +5. 필요하면 백엔드 전용 API 추가 (범용 API로 안 되는 경우만) +``` + +### 금지 사항 + +- `frontend/app/(main)/production/*/page.tsx` 같은 사용자 화면 하드코딩 금지 +- `frontend/app/(main)/warehouse/*/page.tsx` 같은 사용자 화면 하드코딩 금지 +- 사용자 메뉴의 UI를 React 컴포넌트로 직접 구현하는 것 금지 + diff --git a/backend-node/src/app.ts b/backend-node/src/app.ts index 914f608c..f45a88cd 100644 --- a/backend-node/src/app.ts +++ b/backend-node/src/app.ts @@ -131,6 +131,7 @@ import taxInvoiceRoutes from "./routes/taxInvoiceRoutes"; // 세금계산서 관 import cascadingRelationRoutes from "./routes/cascadingRelationRoutes"; // 연쇄 드롭다운 관계 관리 import cascadingAutoFillRoutes from "./routes/cascadingAutoFillRoutes"; // 자동 입력 관리 import cascadingConditionRoutes from "./routes/cascadingConditionRoutes"; // 조건부 연쇄 관리 +import packagingRoutes from "./routes/packagingRoutes"; // 포장/적재정보 관리 import cascadingMutualExclusionRoutes from "./routes/cascadingMutualExclusionRoutes"; // 상호 배제 관리 import cascadingHierarchyRoutes from "./routes/cascadingHierarchyRoutes"; // 다단계 계층 관리 import categoryValueCascadingRoutes from "./routes/categoryValueCascadingRoutes"; // 카테고리 값 연쇄관계 @@ -321,6 +322,7 @@ app.use("/api/tax-invoice", taxInvoiceRoutes); // 세금계산서 관리 app.use("/api/cascading-relations", cascadingRelationRoutes); // 연쇄 드롭다운 관계 관리 app.use("/api/cascading-auto-fill", cascadingAutoFillRoutes); // 자동 입력 관리 app.use("/api/cascading-conditions", cascadingConditionRoutes); // 조건부 연쇄 관리 +app.use("/api/packaging", packagingRoutes); // 포장/적재정보 관리 app.use("/api/cascading-exclusions", cascadingMutualExclusionRoutes); // 상호 배제 관리 app.use("/api/cascading-hierarchy", cascadingHierarchyRoutes); // 다단계 계층 관리 app.use("/api/category-value-cascading", categoryValueCascadingRoutes); // 카테고리 값 연쇄관계 diff --git a/backend-node/src/controllers/entitySearchController.ts b/backend-node/src/controllers/entitySearchController.ts index c0c4c36d..fa70de66 100644 --- a/backend-node/src/controllers/entitySearchController.ts +++ b/backend-node/src/controllers/entitySearchController.ts @@ -266,7 +266,6 @@ export async function getDistinctColumnValues(req: AuthenticatedRequest, res: Re logger.info("컬럼 DISTINCT 값 조회 성공", { tableName, columnName, - columnInputType: columnInputType || "none", labelColumn: effectiveLabelColumn, companyCode, hasFilters: !!filtersParam,