Re-apply mhkim work lost in jskim-node conflict resolution
Backend:
- outboundController.ts: restore sales_order_id/shipment_plan_id/item_info_id columns in outbound_mng INSERT; restore getProductionResults endpoint
- popProductionController.ts: restore transactionPackagingService import (ensureLoadingInstance/insertPackagingRows); restore material auto-input + inventory_stock deduction before WIP trigger; restore autoCompleteProcess/savePackaging/getProcessPackaging endpoints
- workInstructionController.ts: restore getProcessResults function for production-result right panel
- workInstructionRoutes.ts: restore GET /:wiId/process-results route
Frontend:
- COMPANY_7/production/work-instruction/page.tsx: restore Lock icon, WorkRow detailId/locked fields, items mapping detailId, locked column UI with lock icon and disabled cells
- COMPANY_8/10/16/28/29/production/work-instruction/page.tsx: restore SelectedItem baseQty/splitMode fields, calcBatchCount/splitQty helpers, expandedItems batch split logic in finalizeRegistration payload (keeps jskim infos field)
- COMPANY_8/logistics/inbound-outbound/page.tsx: restore autoFilter:true (company scope) for user_info writer lookup — replaces jskim autoFilter:{enabled:false} which violated multitenancy policy
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,31 @@ interface SelectedItem {
|
||||
equipmentIds?: string[];
|
||||
workTeams?: string[];
|
||||
workers?: string[];
|
||||
// 기준수(BOM 0레벨 base_qty) / 배치수(자동) / 배분(균등|순차)
|
||||
baseQty?: number | null;
|
||||
splitMode?: "even" | "sequential";
|
||||
}
|
||||
|
||||
// 배치수 산출: baseQty>0 && qty>baseQty면 ceil(qty/baseQty), 아니면 1
|
||||
function calcBatchCount(qty: number, baseQty: number | null | undefined): number {
|
||||
const b = Number(baseQty || 0);
|
||||
if (!Number.isFinite(b) || b <= 0) return 1;
|
||||
if (!Number.isFinite(qty) || qty <= 0) return 1;
|
||||
return qty > b ? Math.ceil(qty / b) : 1;
|
||||
}
|
||||
|
||||
// 분할 산출: batchCount<=1이면 [qty], 아니면 mode 따라 분할
|
||||
function splitQty(qty: number, baseQty: number, batchCount: number, mode: "even" | "sequential"): number[] {
|
||||
if (batchCount <= 1) return [qty];
|
||||
if (mode === "sequential") {
|
||||
const head = Array(batchCount - 1).fill(baseQty);
|
||||
const tail = qty - baseQty * (batchCount - 1);
|
||||
return [...head, tail];
|
||||
}
|
||||
// even: 앞은 floor, 마지막이 잔여 흡수
|
||||
const base = Math.floor(qty / batchCount);
|
||||
const remainder = qty - base * (batchCount - 1);
|
||||
return [...Array(batchCount - 1).fill(base), remainder];
|
||||
}
|
||||
|
||||
// 공용 다중선택 Popover 컴포넌트 (설비/작업조/작업자에 재사용)
|
||||
@@ -368,14 +393,27 @@ export default function WorkInstructionPage() {
|
||||
const headerEquipment = first?.equipmentIds?.[0] || "";
|
||||
const headerWorkTeam = first?.workTeams?.[0] || "";
|
||||
const headerWorker = first?.workers?.[0] || "";
|
||||
// 배치수≥2인 품목은 splitMode에 따라 N건으로 펼친다.
|
||||
const expandedItems: Array<typeof confirmItems[number] & { _qty: number }> = [];
|
||||
for (const i of confirmItems) {
|
||||
const qty = Number(i.qty || 0);
|
||||
const baseQty = Number(i.baseQty || 0);
|
||||
const batchCount = calcBatchCount(qty, i.baseQty);
|
||||
if (batchCount > 1 && baseQty > 0) {
|
||||
const parts = splitQty(qty, baseQty, batchCount, i.splitMode || "even");
|
||||
for (const p of parts) expandedItems.push({ ...i, _qty: p });
|
||||
} else {
|
||||
expandedItems.push({ ...i, _qty: qty });
|
||||
}
|
||||
}
|
||||
const payload = {
|
||||
status: confirmStatus,
|
||||
startDate: headerStart, endDate: headerEnd,
|
||||
equipmentId: headerEquipment, workTeam: headerWorkTeam, worker: headerWorker,
|
||||
routing: confirmRouting || null,
|
||||
infos: confirmInfos.map((s) => s.trim()).filter(Boolean),
|
||||
items: confirmItems.map(i => ({
|
||||
itemNumber: i.itemCode, itemCode: i.itemCode, qty: String(i.qty), remark: i.remark,
|
||||
items: expandedItems.map(i => ({
|
||||
itemNumber: i.itemCode, itemCode: i.itemCode, qty: String(i._qty), remark: i.remark,
|
||||
sourceTable: i.sourceTable, sourceId: String(i.sourceId), partCode: i.itemCode,
|
||||
routing: i.routing || null,
|
||||
// 품목별 일정/설비/작업조/작업자 (옵션 A — 다중값 쉼표 구분)
|
||||
|
||||
@@ -66,6 +66,31 @@ interface SelectedItem {
|
||||
equipmentIds?: string[];
|
||||
workTeams?: string[];
|
||||
workers?: string[];
|
||||
// 기준수(BOM 0레벨 base_qty) / 배치수(자동) / 배분(균등|순차)
|
||||
baseQty?: number | null;
|
||||
splitMode?: "even" | "sequential";
|
||||
}
|
||||
|
||||
// 배치수 산출: baseQty>0 && qty>baseQty면 ceil(qty/baseQty), 아니면 1
|
||||
function calcBatchCount(qty: number, baseQty: number | null | undefined): number {
|
||||
const b = Number(baseQty || 0);
|
||||
if (!Number.isFinite(b) || b <= 0) return 1;
|
||||
if (!Number.isFinite(qty) || qty <= 0) return 1;
|
||||
return qty > b ? Math.ceil(qty / b) : 1;
|
||||
}
|
||||
|
||||
// 분할 산출: batchCount<=1이면 [qty], 아니면 mode 따라 분할
|
||||
function splitQty(qty: number, baseQty: number, batchCount: number, mode: "even" | "sequential"): number[] {
|
||||
if (batchCount <= 1) return [qty];
|
||||
if (mode === "sequential") {
|
||||
const head = Array(batchCount - 1).fill(baseQty);
|
||||
const tail = qty - baseQty * (batchCount - 1);
|
||||
return [...head, tail];
|
||||
}
|
||||
// even: 앞은 floor, 마지막이 잔여 흡수
|
||||
const base = Math.floor(qty / batchCount);
|
||||
const remainder = qty - base * (batchCount - 1);
|
||||
return [...Array(batchCount - 1).fill(base), remainder];
|
||||
}
|
||||
|
||||
// 공용 다중선택 Popover 컴포넌트 (설비/작업조/작업자에 재사용)
|
||||
@@ -372,14 +397,27 @@ export default function WorkInstructionPage() {
|
||||
const headerEquipment = first?.equipmentIds?.[0] || "";
|
||||
const headerWorkTeam = first?.workTeams?.[0] || "";
|
||||
const headerWorker = first?.workers?.[0] || "";
|
||||
// 배치수≥2인 품목은 splitMode에 따라 N건으로 펼친다.
|
||||
const expandedItems: Array<typeof confirmItems[number] & { _qty: number }> = [];
|
||||
for (const i of confirmItems) {
|
||||
const qty = Number(i.qty || 0);
|
||||
const baseQty = Number(i.baseQty || 0);
|
||||
const batchCount = calcBatchCount(qty, i.baseQty);
|
||||
if (batchCount > 1 && baseQty > 0) {
|
||||
const parts = splitQty(qty, baseQty, batchCount, i.splitMode || "even");
|
||||
for (const p of parts) expandedItems.push({ ...i, _qty: p });
|
||||
} else {
|
||||
expandedItems.push({ ...i, _qty: qty });
|
||||
}
|
||||
}
|
||||
const payload = {
|
||||
status: confirmStatus,
|
||||
startDate: headerStart, endDate: headerEnd,
|
||||
equipmentId: headerEquipment, workTeam: headerWorkTeam, worker: headerWorker,
|
||||
routing: confirmRouting || null,
|
||||
infos: confirmInfos.map((s) => s.trim()).filter(Boolean),
|
||||
items: confirmItems.map(i => ({
|
||||
itemNumber: i.itemCode, itemCode: i.itemCode, qty: String(i.qty), remark: i.remark,
|
||||
items: expandedItems.map(i => ({
|
||||
itemNumber: i.itemCode, itemCode: i.itemCode, qty: String(i._qty), remark: i.remark,
|
||||
sourceTable: i.sourceTable, sourceId: String(i.sourceId), partCode: i.itemCode,
|
||||
routing: i.routing || null,
|
||||
// 품목별 일정/설비/작업조/작업자 (옵션 A — 다중값 쉼표 구분)
|
||||
|
||||
@@ -66,6 +66,31 @@ interface SelectedItem {
|
||||
equipmentIds?: string[];
|
||||
workTeams?: string[];
|
||||
workers?: string[];
|
||||
// 기준수(BOM 0레벨 base_qty) / 배치수(자동) / 배분(균등|순차)
|
||||
baseQty?: number | null;
|
||||
splitMode?: "even" | "sequential";
|
||||
}
|
||||
|
||||
// 배치수 산출: baseQty>0 && qty>baseQty면 ceil(qty/baseQty), 아니면 1
|
||||
function calcBatchCount(qty: number, baseQty: number | null | undefined): number {
|
||||
const b = Number(baseQty || 0);
|
||||
if (!Number.isFinite(b) || b <= 0) return 1;
|
||||
if (!Number.isFinite(qty) || qty <= 0) return 1;
|
||||
return qty > b ? Math.ceil(qty / b) : 1;
|
||||
}
|
||||
|
||||
// 분할 산출: batchCount<=1이면 [qty], 아니면 mode 따라 분할
|
||||
function splitQty(qty: number, baseQty: number, batchCount: number, mode: "even" | "sequential"): number[] {
|
||||
if (batchCount <= 1) return [qty];
|
||||
if (mode === "sequential") {
|
||||
const head = Array(batchCount - 1).fill(baseQty);
|
||||
const tail = qty - baseQty * (batchCount - 1);
|
||||
return [...head, tail];
|
||||
}
|
||||
// even: 앞은 floor, 마지막이 잔여 흡수
|
||||
const base = Math.floor(qty / batchCount);
|
||||
const remainder = qty - base * (batchCount - 1);
|
||||
return [...Array(batchCount - 1).fill(base), remainder];
|
||||
}
|
||||
|
||||
// 공용 다중선택 Popover 컴포넌트 (설비/작업조/작업자에 재사용)
|
||||
@@ -368,14 +393,27 @@ export default function WorkInstructionPage() {
|
||||
const headerEquipment = first?.equipmentIds?.[0] || "";
|
||||
const headerWorkTeam = first?.workTeams?.[0] || "";
|
||||
const headerWorker = first?.workers?.[0] || "";
|
||||
// 배치수≥2인 품목은 splitMode에 따라 N건으로 펼친다.
|
||||
const expandedItems: Array<typeof confirmItems[number] & { _qty: number }> = [];
|
||||
for (const i of confirmItems) {
|
||||
const qty = Number(i.qty || 0);
|
||||
const baseQty = Number(i.baseQty || 0);
|
||||
const batchCount = calcBatchCount(qty, i.baseQty);
|
||||
if (batchCount > 1 && baseQty > 0) {
|
||||
const parts = splitQty(qty, baseQty, batchCount, i.splitMode || "even");
|
||||
for (const p of parts) expandedItems.push({ ...i, _qty: p });
|
||||
} else {
|
||||
expandedItems.push({ ...i, _qty: qty });
|
||||
}
|
||||
}
|
||||
const payload = {
|
||||
status: confirmStatus,
|
||||
startDate: headerStart, endDate: headerEnd,
|
||||
equipmentId: headerEquipment, workTeam: headerWorkTeam, worker: headerWorker,
|
||||
routing: confirmRouting || null,
|
||||
infos: confirmInfos.map((s) => s.trim()).filter(Boolean),
|
||||
items: confirmItems.map(i => ({
|
||||
itemNumber: i.itemCode, itemCode: i.itemCode, qty: String(i.qty), remark: i.remark,
|
||||
items: expandedItems.map(i => ({
|
||||
itemNumber: i.itemCode, itemCode: i.itemCode, qty: String(i._qty), remark: i.remark,
|
||||
sourceTable: i.sourceTable, sourceId: String(i.sourceId), partCode: i.itemCode,
|
||||
routing: i.routing || null,
|
||||
// 품목별 일정/설비/작업조/작업자 (옵션 A — 다중값 쉼표 구분)
|
||||
|
||||
@@ -66,6 +66,31 @@ interface SelectedItem {
|
||||
equipmentIds?: string[];
|
||||
workTeams?: string[];
|
||||
workers?: string[];
|
||||
// 기준수(BOM 0레벨 base_qty) / 배치수(자동) / 배분(균등|순차)
|
||||
baseQty?: number | null;
|
||||
splitMode?: "even" | "sequential";
|
||||
}
|
||||
|
||||
// 배치수 산출: baseQty>0 && qty>baseQty면 ceil(qty/baseQty), 아니면 1
|
||||
function calcBatchCount(qty: number, baseQty: number | null | undefined): number {
|
||||
const b = Number(baseQty || 0);
|
||||
if (!Number.isFinite(b) || b <= 0) return 1;
|
||||
if (!Number.isFinite(qty) || qty <= 0) return 1;
|
||||
return qty > b ? Math.ceil(qty / b) : 1;
|
||||
}
|
||||
|
||||
// 분할 산출: batchCount<=1이면 [qty], 아니면 mode 따라 분할
|
||||
function splitQty(qty: number, baseQty: number, batchCount: number, mode: "even" | "sequential"): number[] {
|
||||
if (batchCount <= 1) return [qty];
|
||||
if (mode === "sequential") {
|
||||
const head = Array(batchCount - 1).fill(baseQty);
|
||||
const tail = qty - baseQty * (batchCount - 1);
|
||||
return [...head, tail];
|
||||
}
|
||||
// even: 앞은 floor, 마지막이 잔여 흡수
|
||||
const base = Math.floor(qty / batchCount);
|
||||
const remainder = qty - base * (batchCount - 1);
|
||||
return [...Array(batchCount - 1).fill(base), remainder];
|
||||
}
|
||||
|
||||
// 공용 다중선택 Popover 컴포넌트 (설비/작업조/작업자에 재사용)
|
||||
@@ -368,14 +393,27 @@ export default function WorkInstructionPage() {
|
||||
const headerEquipment = first?.equipmentIds?.[0] || "";
|
||||
const headerWorkTeam = first?.workTeams?.[0] || "";
|
||||
const headerWorker = first?.workers?.[0] || "";
|
||||
// 배치수≥2인 품목은 splitMode에 따라 N건으로 펼친다.
|
||||
const expandedItems: Array<typeof confirmItems[number] & { _qty: number }> = [];
|
||||
for (const i of confirmItems) {
|
||||
const qty = Number(i.qty || 0);
|
||||
const baseQty = Number(i.baseQty || 0);
|
||||
const batchCount = calcBatchCount(qty, i.baseQty);
|
||||
if (batchCount > 1 && baseQty > 0) {
|
||||
const parts = splitQty(qty, baseQty, batchCount, i.splitMode || "even");
|
||||
for (const p of parts) expandedItems.push({ ...i, _qty: p });
|
||||
} else {
|
||||
expandedItems.push({ ...i, _qty: qty });
|
||||
}
|
||||
}
|
||||
const payload = {
|
||||
status: confirmStatus,
|
||||
startDate: headerStart, endDate: headerEnd,
|
||||
equipmentId: headerEquipment, workTeam: headerWorkTeam, worker: headerWorker,
|
||||
routing: confirmRouting || null,
|
||||
infos: confirmInfos.map((s) => s.trim()).filter(Boolean),
|
||||
items: confirmItems.map(i => ({
|
||||
itemNumber: i.itemCode, itemCode: i.itemCode, qty: String(i.qty), remark: i.remark,
|
||||
items: expandedItems.map(i => ({
|
||||
itemNumber: i.itemCode, itemCode: i.itemCode, qty: String(i._qty), remark: i.remark,
|
||||
sourceTable: i.sourceTable, sourceId: String(i.sourceId), partCode: i.itemCode,
|
||||
routing: i.routing || null,
|
||||
// 품목별 일정/설비/작업조/작업자 (옵션 A — 다중값 쉼표 구분)
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
ClipboardCheck,
|
||||
Inbox,
|
||||
Settings2,
|
||||
Lock,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
@@ -131,6 +132,9 @@ interface SelectedItem {
|
||||
batchUse?: "Y" | "N";
|
||||
// 미사용 품목의 사용자 수동 배치수 (기본 1 = 분할 없음)
|
||||
manualBatch?: number;
|
||||
// 수정 모드: 기존 detail row 식별 + 잠금 상태
|
||||
detailId?: string;
|
||||
locked?: boolean;
|
||||
}
|
||||
|
||||
// ── BOM 자재 매핑 행 (TASK:ERP-node-090, 트리화) ──
|
||||
@@ -1291,6 +1295,8 @@ export default function WorkInstructionPage() {
|
||||
infos: editInfos.map((s) => s.trim()).filter(Boolean),
|
||||
routing: editRouting || null,
|
||||
items: editItems.map((i) => ({
|
||||
// detailId 있으면 백엔드가 UPDATE, 없으면 INSERT 분류
|
||||
detailId: i.detailId || undefined,
|
||||
itemNumber: i.itemCode,
|
||||
itemCode: i.itemCode,
|
||||
qty: String(i.qty),
|
||||
@@ -2689,23 +2695,27 @@ export default function WorkInstructionPage() {
|
||||
editItems.map((item, idx) => {
|
||||
const editItemKey = makeItemKey(item.itemCode, item.sourceTable, item.sourceId);
|
||||
const editMatExpanded = editExpandedItems.has(editItemKey);
|
||||
const rowBg = item.locked ? "bg-amber-50/60" : "bg-background";
|
||||
return (
|
||||
<React.Fragment key={idx}>
|
||||
<TableRow className="bg-background">
|
||||
<TableCell className="bg-background sticky left-0 z-20 text-center text-[13px]">
|
||||
{idx + 1}
|
||||
<TableRow className={rowBg}>
|
||||
<TableCell className={`${rowBg} sticky left-0 z-20 text-center text-[13px]`}>
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
{idx + 1}
|
||||
{item.locked && <Lock className="w-3 h-3 text-amber-600" aria-label="생산접수됨" />}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="bg-background sticky left-[50px] z-20 text-[13px] font-medium">
|
||||
<TableCell className={`${rowBg} sticky left-[50px] z-20 text-[13px] font-medium`}>
|
||||
{item.itemCode}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className="bg-background sticky left-[180px] z-20 max-w-[180px] truncate text-sm"
|
||||
className={`${rowBg} sticky left-[180px] z-20 max-w-[180px] truncate text-sm`}
|
||||
title={item.itemName}
|
||||
>
|
||||
{item.itemName || "-"}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className="bg-background sticky left-[360px] z-20 truncate border-r text-[13px] shadow-[1px_0_0_0_hsl(var(--border))]"
|
||||
className={`${rowBg} sticky left-[360px] z-20 truncate border-r text-[13px] shadow-[1px_0_0_0_hsl(var(--border))]`}
|
||||
title={item.spec}
|
||||
>
|
||||
{item.spec || "-"}
|
||||
@@ -2741,6 +2751,7 @@ export default function WorkInstructionPage() {
|
||||
type="number"
|
||||
className="ml-auto h-9 w-full min-w-[100px] text-sm"
|
||||
value={item.qty}
|
||||
disabled={item.locked}
|
||||
onChange={(e) =>
|
||||
setEditItems((prev) =>
|
||||
prev.map((it, i) => (i === idx ? { ...it, qty: Number(e.target.value) } : it)),
|
||||
@@ -2751,6 +2762,7 @@ export default function WorkInstructionPage() {
|
||||
<TableCell>
|
||||
<Select
|
||||
value={nv(item.routing || "")}
|
||||
disabled={item.locked}
|
||||
onValueChange={(v) => {
|
||||
const val = fromNv(v);
|
||||
setEditItems((prev) =>
|
||||
@@ -2758,7 +2770,7 @@ export default function WorkInstructionPage() {
|
||||
);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-9 text-sm">
|
||||
<SelectTrigger className="h-9 text-sm" disabled={item.locked}>
|
||||
<SelectValue placeholder="라우팅" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -2887,6 +2899,8 @@ export default function WorkInstructionPage() {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6"
|
||||
disabled={item.locked}
|
||||
title={item.locked ? "이미 생산접수된 row는 삭제할 수 없습니다" : "삭제"}
|
||||
onClick={() => setEditItems((prev) => prev.filter((_, i) => i !== idx))}
|
||||
>
|
||||
<X className="text-destructive h-3 w-3" />
|
||||
|
||||
@@ -216,12 +216,9 @@ export default function InboundOutboundPage() {
|
||||
try {
|
||||
const userRes = await apiClient.post(`/table-management/tables/user_info/data`, {
|
||||
page: 1,
|
||||
size: 0,
|
||||
autoFilter: { enabled: false }, // 회사 스코프 해제 (슈퍼관리자 포함)
|
||||
dataFilter: {
|
||||
enabled: true,
|
||||
filters: [{ columnName: "user_id", operator: "in", value: writerIds }],
|
||||
},
|
||||
size: writerIds.length + 10,
|
||||
dataFilter: { enabled: true, filters: [{ columnName: "user_id", operator: "in", value: writerIds }] },
|
||||
autoFilter: true,
|
||||
});
|
||||
const users = userRes.data?.data?.data || userRes.data?.data?.rows || [];
|
||||
const uMap: Record<string, string> = {};
|
||||
|
||||
@@ -66,6 +66,31 @@ interface SelectedItem {
|
||||
equipmentIds?: string[];
|
||||
workTeams?: string[];
|
||||
workers?: string[];
|
||||
// 기준수(BOM 0레벨 base_qty) / 배치수(자동) / 배분(균등|순차)
|
||||
baseQty?: number | null;
|
||||
splitMode?: "even" | "sequential";
|
||||
}
|
||||
|
||||
// 배치수 산출: baseQty>0 && qty>baseQty면 ceil(qty/baseQty), 아니면 1
|
||||
function calcBatchCount(qty: number, baseQty: number | null | undefined): number {
|
||||
const b = Number(baseQty || 0);
|
||||
if (!Number.isFinite(b) || b <= 0) return 1;
|
||||
if (!Number.isFinite(qty) || qty <= 0) return 1;
|
||||
return qty > b ? Math.ceil(qty / b) : 1;
|
||||
}
|
||||
|
||||
// 분할 산출: batchCount<=1이면 [qty], 아니면 mode 따라 분할
|
||||
function splitQty(qty: number, baseQty: number, batchCount: number, mode: "even" | "sequential"): number[] {
|
||||
if (batchCount <= 1) return [qty];
|
||||
if (mode === "sequential") {
|
||||
const head = Array(batchCount - 1).fill(baseQty);
|
||||
const tail = qty - baseQty * (batchCount - 1);
|
||||
return [...head, tail];
|
||||
}
|
||||
// even: 앞은 floor, 마지막이 잔여 흡수
|
||||
const base = Math.floor(qty / batchCount);
|
||||
const remainder = qty - base * (batchCount - 1);
|
||||
return [...Array(batchCount - 1).fill(base), remainder];
|
||||
}
|
||||
|
||||
// 공용 다중선택 Popover 컴포넌트 (설비/작업조/작업자에 재사용)
|
||||
@@ -368,14 +393,27 @@ export default function WorkInstructionPage() {
|
||||
const headerEquipment = first?.equipmentIds?.[0] || "";
|
||||
const headerWorkTeam = first?.workTeams?.[0] || "";
|
||||
const headerWorker = first?.workers?.[0] || "";
|
||||
// 배치수≥2인 품목은 splitMode에 따라 N건으로 펼친다.
|
||||
const expandedItems: Array<typeof confirmItems[number] & { _qty: number }> = [];
|
||||
for (const i of confirmItems) {
|
||||
const qty = Number(i.qty || 0);
|
||||
const baseQty = Number(i.baseQty || 0);
|
||||
const batchCount = calcBatchCount(qty, i.baseQty);
|
||||
if (batchCount > 1 && baseQty > 0) {
|
||||
const parts = splitQty(qty, baseQty, batchCount, i.splitMode || "even");
|
||||
for (const p of parts) expandedItems.push({ ...i, _qty: p });
|
||||
} else {
|
||||
expandedItems.push({ ...i, _qty: qty });
|
||||
}
|
||||
}
|
||||
const payload = {
|
||||
status: confirmStatus,
|
||||
startDate: headerStart, endDate: headerEnd,
|
||||
equipmentId: headerEquipment, workTeam: headerWorkTeam, worker: headerWorker,
|
||||
routing: confirmRouting || null,
|
||||
infos: confirmInfos.map((s) => s.trim()).filter(Boolean),
|
||||
items: confirmItems.map(i => ({
|
||||
itemNumber: i.itemCode, itemCode: i.itemCode, qty: String(i.qty), remark: i.remark,
|
||||
items: expandedItems.map(i => ({
|
||||
itemNumber: i.itemCode, itemCode: i.itemCode, qty: String(i._qty), remark: i.remark,
|
||||
sourceTable: i.sourceTable, sourceId: String(i.sourceId), partCode: i.itemCode,
|
||||
routing: i.routing || null,
|
||||
// 품목별 일정/설비/작업조/작업자 (옵션 A — 다중값 쉼표 구분)
|
||||
|
||||
Reference in New Issue
Block a user