feat: COMPANY_9 수주관리 페이지 추가 및 생산계획/공정 개선
- COMPANY_9 수주관리(sales/order) 하드코딩 페이지 추가 - 생산계획 서비스 로직 정리 및 공정 컨트롤러 수정 - COMPANY_7 생산계획관리 페이지 개선 - AdminPageRenderer 기능 확장
This commit is contained in:
@@ -214,7 +214,7 @@ export async function getEquipmentList(req: AuthenticatedRequest, res: Response)
|
||||
const params = companyCode === "*" ? [] : [companyCode];
|
||||
|
||||
const result = await pool.query(
|
||||
`SELECT objid AS id, equipment_code, equipment_name FROM equipment_mng ${condition} ORDER BY equipment_code`,
|
||||
`SELECT id, equipment_code, equipment_name FROM equipment_mng ${condition} ORDER BY equipment_code`,
|
||||
params
|
||||
);
|
||||
|
||||
|
||||
@@ -401,22 +401,9 @@ export async function previewSchedule(
|
||||
const dailyCapacity = item.daily_capacity || 800;
|
||||
const itemLeadTime = item.lead_time || 0;
|
||||
|
||||
let requiredQty = item.required_qty;
|
||||
|
||||
// recalculate_unstarted 시, 삭제된 수량을 비율로 분배
|
||||
if (options.recalculate_unstarted) {
|
||||
const deletedQtyForItem = deletedSchedules
|
||||
.filter((d: any) => d.item_code === item.item_code)
|
||||
.reduce((sum: number, d: any) => sum + (parseFloat(d.plan_qty) || 0), 0);
|
||||
if (deletedQtyForItem > 0) {
|
||||
const totalRequestedForItem = items
|
||||
.filter((i) => i.item_code === item.item_code)
|
||||
.reduce((sum, i) => sum + i.required_qty, 0);
|
||||
if (totalRequestedForItem > 0) {
|
||||
requiredQty += Math.round(deletedQtyForItem * (item.required_qty / totalRequestedForItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 프론트에서 이미 전체 잔량 기준으로 계산하여 보내므로 그대로 사용
|
||||
// (recalculate_unstarted 시 기존 planned는 위에서 이미 삭제됨)
|
||||
const requiredQty = item.required_qty;
|
||||
|
||||
if (requiredQty <= 0) continue;
|
||||
|
||||
@@ -543,19 +530,9 @@ export async function generateSchedule(
|
||||
// 필요 수량 계산 (삭제된 planned 수량을 비율로 분배)
|
||||
const dailyCapacity = item.daily_capacity || 800;
|
||||
const itemLeadTime = item.lead_time || 0;
|
||||
let requiredQty = item.required_qty;
|
||||
|
||||
if (options.recalculate_unstarted) {
|
||||
const deletedQty = deletedQtyByItem.get(item.item_code) || 0;
|
||||
if (deletedQty > 0) {
|
||||
const totalRequestedForItem = items
|
||||
.filter((i) => i.item_code === item.item_code)
|
||||
.reduce((sum, i) => sum + i.required_qty, 0);
|
||||
if (totalRequestedForItem > 0) {
|
||||
requiredQty += Math.round(deletedQty * (item.required_qty / totalRequestedForItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 프론트에서 이미 전체 잔량 기준으로 계산하여 보내므로 그대로 사용
|
||||
// (recalculate_unstarted 시 기존 planned는 위에서 이미 삭제됨)
|
||||
const requiredQty = item.required_qty;
|
||||
if (requiredQty <= 0) continue;
|
||||
|
||||
// 리드타임 기반 날짜 계산: 납기일 기준으로 리드타임만큼 역산
|
||||
|
||||
@@ -409,7 +409,10 @@ export default function ProductionPlanManagementPage() {
|
||||
.filter((item) => selectedItemGroups.has(item.item_code))
|
||||
.forEach((item) => {
|
||||
const leadTime = Number(item.lead_time) || 0;
|
||||
const totalRequired = Number(item.required_plan_qty);
|
||||
// 재계산 모드: 기존 planned를 삭제 후 재생성 → 수주 잔량에서 진행중만 빼기
|
||||
const totalRequired = recalculateUnstarted
|
||||
? Number(item.total_balance_qty || 0) - Number(item.in_progress_qty || 0)
|
||||
: Number(item.required_plan_qty);
|
||||
if (totalRequired <= 0) return;
|
||||
|
||||
// 수주가 여러 건이고 납기일이 다르면 각각 분리
|
||||
@@ -460,6 +463,14 @@ export default function ProductionPlanManagementPage() {
|
||||
}
|
||||
});
|
||||
|
||||
// items가 비어있으면 사용자에게 알림
|
||||
|
||||
|
||||
if (items.length === 0) {
|
||||
toast.error("계획 수량이 있는 품목이 없습니다. 수주 잔량을 확인해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
setGenerating(true);
|
||||
try {
|
||||
const req: GenerateScheduleRequest = {
|
||||
@@ -491,7 +502,9 @@ export default function ProductionPlanManagementPage() {
|
||||
.filter((item) => selectedItemGroups.has(item.item_code))
|
||||
.forEach((item) => {
|
||||
const leadTime = Number(item.lead_time) || 0;
|
||||
const totalRequired = Number(item.required_plan_qty);
|
||||
const totalRequired = recalculateUnstarted
|
||||
? Number(item.required_plan_qty) + Number(item.existing_plan_qty || 0)
|
||||
: Number(item.required_plan_qty);
|
||||
if (totalRequired <= 0) return;
|
||||
|
||||
if (item.orders && item.orders.length > 1) {
|
||||
@@ -768,9 +781,12 @@ export default function ProductionPlanManagementPage() {
|
||||
.map((item) => ({
|
||||
item_code: item.item_code,
|
||||
item_name: item.item_name,
|
||||
required_qty: Number(item.required_plan_qty),
|
||||
required_qty: (importMode !== "new" && recalculateUnstarted)
|
||||
? Number(item.total_balance_qty || 0) - Number(item.in_progress_qty || 0)
|
||||
: Number(item.required_plan_qty),
|
||||
earliest_due_date: item.earliest_due_date || new Date().toISOString().split("T")[0],
|
||||
}));
|
||||
}))
|
||||
.filter((item) => item.required_qty > 0);
|
||||
|
||||
setGenerating(true);
|
||||
try {
|
||||
|
||||
1143
frontend/app/(main)/COMPANY_9/sales/order/page.tsx
Normal file
1143
frontend/app/(main)/COMPANY_9/sales/order/page.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -92,8 +92,32 @@ const ADMIN_PAGE_REGISTRY: Record<string, React.ComponentType<any>> = {
|
||||
"/admin/automaticMng/batchmngList": dynamic(() => import("@/app/(main)/admin/automaticMng/batchmngList/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/admin/automaticMng/crawlingList": dynamic(() => import("@/app/(main)/admin/automaticMng/crawlingList/page"), { ssr: false, loading: LoadingFallback }),
|
||||
|
||||
// 회사별 커스텀 페이지는 레지스트리에서 제거 — 회사코드 prefix로 자동 import 처리
|
||||
// (design, sales, production, logistics, equipment, outsourcing, master-data)
|
||||
// === COMPANY_7 (탑씰) ===
|
||||
"/COMPANY_7/master-data/item-info": dynamic(() => import("@/app/(main)/COMPANY_7/master-data/item-info/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/master-data/department": dynamic(() => import("@/app/(main)/COMPANY_7/master-data/department/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/sales/order": dynamic(() => import("@/app/(main)/COMPANY_7/sales/order/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/sales/customer": dynamic(() => import("@/app/(main)/COMPANY_7/sales/customer/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/sales/sales-item": dynamic(() => import("@/app/(main)/COMPANY_7/sales/sales-item/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/sales/shipping-order": dynamic(() => import("@/app/(main)/COMPANY_7/sales/shipping-order/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/sales/shipping-plan": dynamic(() => import("@/app/(main)/COMPANY_7/sales/shipping-plan/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/sales/claim": dynamic(() => import("@/app/(main)/COMPANY_7/sales/claim/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/production/process-info": dynamic(() => import("@/app/(main)/COMPANY_7/production/process-info/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/production/work-instruction": dynamic(() => import("@/app/(main)/COMPANY_7/production/work-instruction/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/production/plan-management": dynamic(() => import("@/app/(main)/COMPANY_7/production/plan-management/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/equipment/info": dynamic(() => import("@/app/(main)/COMPANY_7/equipment/info/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/logistics/material-status": dynamic(() => import("@/app/(main)/COMPANY_7/logistics/material-status/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/logistics/outbound": dynamic(() => import("@/app/(main)/COMPANY_7/logistics/outbound/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/logistics/receiving": dynamic(() => import("@/app/(main)/COMPANY_7/logistics/receiving/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/logistics/packaging": dynamic(() => import("@/app/(main)/COMPANY_7/logistics/packaging/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/outsourcing/subcontractor": dynamic(() => import("@/app/(main)/COMPANY_7/outsourcing/subcontractor/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/outsourcing/subcontractor-item": dynamic(() => import("@/app/(main)/COMPANY_7/outsourcing/subcontractor-item/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/design/project": dynamic(() => import("@/app/(main)/COMPANY_7/design/project/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/design/change-management": dynamic(() => import("@/app/(main)/COMPANY_7/design/change-management/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/design/my-work": dynamic(() => import("@/app/(main)/COMPANY_7/design/my-work/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/design/design-request": dynamic(() => import("@/app/(main)/COMPANY_7/design/design-request/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/COMPANY_7/design/task-management": dynamic(() => import("@/app/(main)/COMPANY_7/design/task-management/page"), { ssr: false, loading: LoadingFallback }),
|
||||
// === COMPANY_9 (제일그라스) ===
|
||||
"/COMPANY_9/sales/order": dynamic(() => import("@/app/(main)/COMPANY_9/sales/order/page"), { ssr: false, loading: LoadingFallback }),
|
||||
|
||||
"/admin/automaticMng/exconList": dynamic(() => import("@/app/(main)/admin/automaticMng/exconList/page"), { ssr: false, loading: LoadingFallback }),
|
||||
"/admin/automaticMng/exCallConfList": dynamic(() => import("@/app/(main)/admin/automaticMng/exCallConfList/page"), { ssr: false, loading: LoadingFallback }),
|
||||
@@ -145,6 +169,34 @@ const DYNAMIC_ADMIN_IMPORTS: Record<string, () => Promise<any>> = {
|
||||
"/admin/automaticMng/batchmngList/create": () => import("@/app/(main)/admin/automaticMng/batchmngList/create/page"),
|
||||
"/admin/systemMng/dataflow/node-editorList": () => import("@/app/(main)/admin/systemMng/dataflow/page"),
|
||||
"/admin/standards/new": () => import("@/app/(main)/admin/standards/new/page"),
|
||||
|
||||
// === 회사별 커스텀 페이지 (resolvedUrl로 매칭) ===
|
||||
// COMPANY_7 (탑씰)
|
||||
"/COMPANY_7/master-data/item-info": () => import("@/app/(main)/COMPANY_7/master-data/item-info/page"),
|
||||
"/COMPANY_7/master-data/department": () => import("@/app/(main)/COMPANY_7/master-data/department/page"),
|
||||
"/COMPANY_7/sales/order": () => import("@/app/(main)/COMPANY_7/sales/order/page"),
|
||||
"/COMPANY_7/sales/customer": () => import("@/app/(main)/COMPANY_7/sales/customer/page"),
|
||||
"/COMPANY_7/sales/sales-item": () => import("@/app/(main)/COMPANY_7/sales/sales-item/page"),
|
||||
"/COMPANY_7/sales/shipping-order": () => import("@/app/(main)/COMPANY_7/sales/shipping-order/page"),
|
||||
"/COMPANY_7/sales/shipping-plan": () => import("@/app/(main)/COMPANY_7/sales/shipping-plan/page"),
|
||||
"/COMPANY_7/sales/claim": () => import("@/app/(main)/COMPANY_7/sales/claim/page"),
|
||||
"/COMPANY_7/production/process-info": () => import("@/app/(main)/COMPANY_7/production/process-info/page"),
|
||||
"/COMPANY_7/production/work-instruction": () => import("@/app/(main)/COMPANY_7/production/work-instruction/page"),
|
||||
"/COMPANY_7/production/plan-management": () => import("@/app/(main)/COMPANY_7/production/plan-management/page"),
|
||||
"/COMPANY_7/equipment/info": () => import("@/app/(main)/COMPANY_7/equipment/info/page"),
|
||||
"/COMPANY_7/logistics/material-status": () => import("@/app/(main)/COMPANY_7/logistics/material-status/page"),
|
||||
"/COMPANY_7/logistics/outbound": () => import("@/app/(main)/COMPANY_7/logistics/outbound/page"),
|
||||
"/COMPANY_7/logistics/receiving": () => import("@/app/(main)/COMPANY_7/logistics/receiving/page"),
|
||||
"/COMPANY_7/logistics/packaging": () => import("@/app/(main)/COMPANY_7/logistics/packaging/page"),
|
||||
"/COMPANY_7/outsourcing/subcontractor": () => import("@/app/(main)/COMPANY_7/outsourcing/subcontractor/page"),
|
||||
"/COMPANY_7/outsourcing/subcontractor-item": () => import("@/app/(main)/COMPANY_7/outsourcing/subcontractor-item/page"),
|
||||
"/COMPANY_7/design/project": () => import("@/app/(main)/COMPANY_7/design/project/page"),
|
||||
"/COMPANY_7/design/change-management": () => import("@/app/(main)/COMPANY_7/design/change-management/page"),
|
||||
"/COMPANY_7/design/my-work": () => import("@/app/(main)/COMPANY_7/design/my-work/page"),
|
||||
"/COMPANY_7/design/design-request": () => import("@/app/(main)/COMPANY_7/design/design-request/page"),
|
||||
"/COMPANY_7/design/task-management": () => import("@/app/(main)/COMPANY_7/design/task-management/page"),
|
||||
// COMPANY_9 (제일그라스)
|
||||
"/COMPANY_9/sales/order": () => import("@/app/(main)/COMPANY_9/sales/order/page"),
|
||||
};
|
||||
|
||||
const DYNAMIC_ADMIN_PATTERNS: Array<{
|
||||
@@ -329,13 +381,13 @@ export function AdminPageRenderer({ url }: AdminPageRendererProps) {
|
||||
return <DashboardViewPage params={Promise.resolve({ dashboardId: dashboardMatch[1] })} />;
|
||||
}
|
||||
|
||||
// URL 직접 입력: 레지스트리 매칭 (admin 페이지 등 공통 페이지)
|
||||
// URL 직접 입력: 레지스트리 매칭 (resolvedUrl 우선, cleanUrl 폴백)
|
||||
const PageComponent = useMemo(() => {
|
||||
return ADMIN_PAGE_REGISTRY[cleanUrl] || null;
|
||||
}, [cleanUrl]);
|
||||
return ADMIN_PAGE_REGISTRY[resolvedUrl] || ADMIN_PAGE_REGISTRY[cleanUrl] || null;
|
||||
}, [resolvedUrl, cleanUrl]);
|
||||
|
||||
if (PageComponent) {
|
||||
console.log("[AdminPageRenderer] → 레지스트리 매칭:", cleanUrl);
|
||||
console.log("[AdminPageRenderer] → 레지스트리 매칭:", resolvedUrl || cleanUrl);
|
||||
return <PageComponent />;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user