refactor: [Phase 0] 공통→테넌트 모듈 의존성 해소

- InspectionReportModal/WorkLogModal/AssigneeSelectModal → document-system/modals/ dynamic import 래퍼
- ProductionOrders 타입/액션 → lib/api/production-orders/ 공유 영역 분리
- 결재(ApprovalBox), 품질(QMS), 영업(production-orders) import 경로 수정
- 하드코딩 경로 /production/work-orders → 영업 내부 경로로 변경
- dashboard-invalidation DomainKey 하드코딩 → registerDashboardDomain() 동적 레지스트리

공통 ERP에서 테넌트(생산) 직접 import 0건 달성

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-03-18 14:40:28 +09:00
parent e8fafaf5f4
commit a99c3b3908
13 changed files with 221 additions and 54 deletions

View File

@@ -0,0 +1,27 @@
'use server';
/**
* 생산지시 공유 액션 — 모듈 경계용 래퍼
*
* 원본: @/components/production/ProductionOrders/actions
* 'use server' 파일에서는 re-export 불가 → async 래퍼 함수로 위임
*/
import {
getProductionOrders as _getProductionOrders,
getProductionOrderStats as _getProductionOrderStats,
getProductionOrderDetail as _getProductionOrderDetail,
} from '@/components/production/ProductionOrders/actions';
import type { ProductionOrderListParams } from './types';
export async function getProductionOrders(params: ProductionOrderListParams) {
return _getProductionOrders(params);
}
export async function getProductionOrderStats() {
return _getProductionOrderStats();
}
export async function getProductionOrderDetail(orderId: string) {
return _getProductionOrderDetail(orderId);
}

View File

@@ -0,0 +1,22 @@
/**
* 생산지시 공유 API — 공통 ERP에서 접근하는 진입점
*
* 공통 ERP(영업 등)에서는 이 경로로 import:
* import { getProductionOrders } from '@/lib/api/production-orders';
* import type { ProductionOrder } from '@/lib/api/production-orders';
*
* 생산 모듈 내부에서는 기존 경로 유지:
* import { ... } from './actions';
* import type { ... } from './types';
*/
export { getProductionOrders, getProductionOrderStats, getProductionOrderDetail } from './actions';
export type {
ProductionStatus,
ProductionOrder,
ProductionOrderStats,
ProductionOrderDetail,
ProductionWorkOrder,
BomProcessGroup,
BomItem,
ProductionOrderListParams,
} from './types';

View File

@@ -0,0 +1,22 @@
/**
* 생산지시 공유 타입 — 모듈 경계용 re-export
*
* 원본: @/components/production/ProductionOrders/types
* 영업(공통 ERP)에서 생산(테넌트) 타입을 직접 import하지 않도록
* 공유 영역에서 re-export
*/
export type {
ProductionStatus,
ProductionOrder,
ProductionOrderStats,
ProductionOrderDetail,
ProductionWorkOrder,
BomProcessGroup,
BomItem,
ProductionOrderListParams,
ApiProductionOrder,
ApiProductionOrderDetail,
ApiProductionWorkOrder,
ApiBomProcessGroup,
ApiBomItem,
} from '@/components/production/ProductionOrders/types';

View File

@@ -2,6 +2,10 @@
* CEO 대시보드 targeted refetch 시스템
*
* CUD 발생 시 sessionStorage + CustomEvent로 대시보드 섹션별 갱신 트리거
*
* 도메인→섹션 매핑은 동적 레지스트리 패턴:
* - 공통 ERP 도메인은 여기서 직접 등록
* - 테넌트 전용 도메인은 각 모듈에서 registerDashboardDomain()으로 자기 등록
*/
// 대시보드 섹션 키 (useCEODashboard의 refetchMap과 1:1 매핑)
@@ -21,49 +25,46 @@ export type DashboardSectionKey =
| 'entertainment'
| 'welfare';
// CUD 도메인 → 영향받는 대시보드 섹션 매핑
type DomainKey =
| 'deposit'
| 'withdrawal'
| 'sales'
| 'purchase'
| 'badDebt'
| 'expectedExpense'
| 'bill'
| 'giftCertificate'
| 'journalEntry'
| 'order'
| 'stock'
| 'schedule'
| 'client'
| 'leave'
| 'approval'
| 'attendance'
| 'production'
| 'shipment'
| 'construction';
// 동적 도메인→섹션 레지스트리
const domainSectionRegistry = new Map<string, DashboardSectionKey[]>();
const DOMAIN_SECTION_MAP: Record<DomainKey, DashboardSectionKey[]> = {
deposit: ['dailyReport', 'receivable'],
withdrawal: ['dailyReport', 'monthlyExpense'],
sales: ['dailyReport', 'salesStatus', 'receivable'],
purchase: ['dailyReport', 'purchaseStatus', 'monthlyExpense'],
badDebt: ['debtCollection', 'receivable'],
expectedExpense: ['monthlyExpense'],
bill: ['dailyReport', 'receivable'],
giftCertificate: ['entertainment', 'cardManagement'],
journalEntry: ['entertainment', 'welfare', 'monthlyExpense'],
order: ['statusBoard', 'salesStatus'],
stock: ['statusBoard'],
schedule: ['statusBoard'],
client: ['statusBoard'],
leave: ['statusBoard', 'dailyAttendance'],
approval: ['statusBoard'],
attendance: ['statusBoard', 'dailyAttendance'],
production: ['statusBoard', 'dailyProduction'],
shipment: ['statusBoard', 'unshipped'],
construction: ['statusBoard', 'construction'],
};
/**
* 도메인→섹션 매핑 등록 (각 모듈에서 호출)
*
* @example
* // 생산 모듈 초기화 시
* registerDashboardDomain('production', ['statusBoard', 'dailyProduction']);
*
* // 건설 모듈 초기화 시
* registerDashboardDomain('construction', ['statusBoard', 'construction']);
*/
export function registerDashboardDomain(domain: string, sections: DashboardSectionKey[]): void {
domainSectionRegistry.set(domain, sections);
}
// ===== 공통 ERP 도메인 (테넌트 무관, 항상 등록) =====
registerDashboardDomain('deposit', ['dailyReport', 'receivable']);
registerDashboardDomain('withdrawal', ['dailyReport', 'monthlyExpense']);
registerDashboardDomain('sales', ['dailyReport', 'salesStatus', 'receivable']);
registerDashboardDomain('purchase', ['dailyReport', 'purchaseStatus', 'monthlyExpense']);
registerDashboardDomain('badDebt', ['debtCollection', 'receivable']);
registerDashboardDomain('expectedExpense', ['monthlyExpense']);
registerDashboardDomain('bill', ['dailyReport', 'receivable']);
registerDashboardDomain('giftCertificate', ['entertainment', 'cardManagement']);
registerDashboardDomain('journalEntry', ['entertainment', 'welfare', 'monthlyExpense']);
registerDashboardDomain('order', ['statusBoard', 'salesStatus']);
registerDashboardDomain('stock', ['statusBoard']);
registerDashboardDomain('schedule', ['statusBoard']);
registerDashboardDomain('client', ['statusBoard']);
registerDashboardDomain('leave', ['statusBoard', 'dailyAttendance']);
registerDashboardDomain('approval', ['statusBoard']);
registerDashboardDomain('attendance', ['statusBoard', 'dailyAttendance']);
registerDashboardDomain('shipment', ['statusBoard', 'unshipped']);
// ===== 테넌트 전용 도메인 (각 모듈에서 등록 — 현재는 하위 호환을 위해 여기서도 등록) =====
// TODO: Phase 1 완료 후 각 모듈의 초기화 코드로 이동
registerDashboardDomain('production', ['statusBoard', 'dailyProduction']);
registerDashboardDomain('construction', ['statusBoard', 'construction']);
const STORAGE_KEY = 'dashboard:stale-sections';
const EVENT_NAME = 'dashboard:invalidate';
@@ -71,8 +72,8 @@ const EVENT_NAME = 'dashboard:invalidate';
/**
* CUD 성공 후 호출 — 해당 도메인이 영향 주는 대시보드 섹션을 stale 처리
*/
export function invalidateDashboard(domain: DomainKey): void {
const sections = DOMAIN_SECTION_MAP[domain];
export function invalidateDashboard(domain: string): void {
const sections = domainSectionRegistry.get(domain);
if (!sections || sections.length === 0) return;
// 1. sessionStorage에 stale 섹션 저장 (navigation 사이 유지)