생산관리: - WipProductionModal 기능 개선 - WorkOrderDetail/Edit 확장 (+265줄) - 검사성적서 콘텐츠 5종 대폭 확장 (벤딩/벤딩WIP/스크린/슬랫/슬랫조인트바) - InspectionReportModal 기능 강화 작업자화면: - WorkerScreen 기능 대폭 확장 (+211줄) - WorkItemCard 개선 - InspectionInputModal 신규 추가 (작업자 검사입력) 공정관리: - StepForm 검사항목 설정 기능 추가 - InspectionSettingModal 신규 추가 - InspectionPreviewModal 신규 추가 - process.ts 타입 확장 (+102줄) 자재관리: - StockStatus 상세/목록/타입/목데이터 개선 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
678 lines
19 KiB
TypeScript
678 lines
19 KiB
TypeScript
/**
|
||
* 재고현황 Mock 데이터
|
||
*/
|
||
|
||
import type { StockItem, StockDetail, StockStats, FilterTab } from './types';
|
||
import { getLocalDateString } from '@/utils/date';
|
||
|
||
// 재고 상태 결정 함수
|
||
function getStockStatus(stockQty: number, safetyStock: number): 'normal' | 'low' | 'out' {
|
||
if (stockQty === 0) return 'out';
|
||
if (stockQty < safetyStock) return 'low';
|
||
return 'normal';
|
||
}
|
||
|
||
// 시드 기반 의사 난수 생성 (일관된 결과 보장)
|
||
function seededRandom(seed: number): number {
|
||
const x = Math.sin(seed) * 10000;
|
||
return x - Math.floor(x);
|
||
}
|
||
|
||
function seededInt(seed: number, min: number, max: number): number {
|
||
return Math.floor(seededRandom(seed) * (max - min + 1)) + min;
|
||
}
|
||
|
||
// 위치 코드 생성 (시드 기반)
|
||
function generateLocation(type: string, seed: number): string {
|
||
const prefixes: Record<string, string[]> = {
|
||
raw_material: ['A'],
|
||
bent_part: ['C', 'D'],
|
||
purchased_part: ['I', 'J'],
|
||
sub_material: ['A', 'B'],
|
||
consumable: ['B'],
|
||
};
|
||
const prefixList = prefixes[type] || ['A'];
|
||
const prefixIndex = seededInt(seed, 0, prefixList.length - 1);
|
||
const prefix = prefixList[prefixIndex];
|
||
return `${prefix}-${String(seededInt(seed + 1, 1, 10)).padStart(2, '0')}`;
|
||
}
|
||
|
||
// 원자재 데이터 (4개)
|
||
const rawMaterialItems: StockItem[] = [
|
||
{
|
||
id: 'rm-1',
|
||
stockNumber: 'STK-RM-001',
|
||
itemCode: 'SCR-FABRIC-WHT-03T',
|
||
itemName: '스크린원단-백색-0.3T',
|
||
itemType: 'raw_material',
|
||
specification: '-',
|
||
unit: 'm²',
|
||
calculatedQty: 500,
|
||
actualQty: 500,
|
||
stockQty: 500,
|
||
safetyStock: 100,
|
||
wipQty: 30,
|
||
lotCount: 3,
|
||
lotDaysElapsed: 21,
|
||
status: 'normal',
|
||
useStatus: 'active',
|
||
location: 'A-01',
|
||
hasStock: true,
|
||
},
|
||
{
|
||
id: 'rm-2',
|
||
stockNumber: 'STK-RM-002',
|
||
itemCode: 'SCR-FABRIC-GRY-03T',
|
||
itemName: '스크린원단-회색-0.3T',
|
||
itemType: 'raw_material',
|
||
specification: '-',
|
||
unit: 'm²',
|
||
calculatedQty: 350,
|
||
actualQty: 350,
|
||
stockQty: 350,
|
||
safetyStock: 80,
|
||
wipQty: 20,
|
||
lotCount: 2,
|
||
lotDaysElapsed: 15,
|
||
status: 'normal',
|
||
useStatus: 'active',
|
||
location: 'A-02',
|
||
hasStock: true,
|
||
},
|
||
{
|
||
id: 'rm-3',
|
||
stockNumber: 'STK-RM-003',
|
||
itemCode: 'SCR-FABRIC-BLK-03T',
|
||
itemName: '스크린원단-흑색-0.3T',
|
||
itemType: 'raw_material',
|
||
specification: '-',
|
||
unit: 'm²',
|
||
calculatedQty: 280,
|
||
actualQty: 280,
|
||
stockQty: 280,
|
||
safetyStock: 70,
|
||
wipQty: 15,
|
||
lotCount: 2,
|
||
lotDaysElapsed: 18,
|
||
status: 'normal',
|
||
useStatus: 'active',
|
||
location: 'A-03',
|
||
hasStock: true,
|
||
},
|
||
{
|
||
id: 'rm-4',
|
||
stockNumber: 'STK-RM-004',
|
||
itemCode: 'SCR-FABRIC-BEI-03T',
|
||
itemName: '스크린원단-베이지-0.3T',
|
||
itemType: 'raw_material',
|
||
specification: '-',
|
||
unit: 'm²',
|
||
calculatedQty: 420,
|
||
actualQty: 420,
|
||
stockQty: 420,
|
||
safetyStock: 90,
|
||
wipQty: 25,
|
||
lotCount: 4,
|
||
lotDaysElapsed: 12,
|
||
status: 'normal',
|
||
useStatus: 'active',
|
||
location: 'A-04',
|
||
hasStock: true,
|
||
},
|
||
];
|
||
|
||
// 절곡부품 데이터 (41개)
|
||
const bentPartItems: StockItem[] = Array.from({ length: 41 }, (_, i) => {
|
||
const types = ['브라켓', '지지대', '연결판', '고정판', '받침대', '가이드', '프레임'];
|
||
const variants = ['A', 'B', 'C', 'D', 'E', 'S', 'M', 'L', 'XL'];
|
||
const type = types[i % types.length];
|
||
const variant = variants[i % variants.length];
|
||
const seed = i * 100;
|
||
const stockQty = seededInt(seed, 50, 500);
|
||
const safetyStock = seededInt(seed + 1, 20, 100);
|
||
|
||
return {
|
||
id: `bp-${i + 1}`,
|
||
stockNumber: `STK-BP-${String(i + 1).padStart(3, '0')}`,
|
||
itemCode: `BENT-${type.toUpperCase().slice(0, 3)}-${variant}-${String(i + 1).padStart(2, '0')}`,
|
||
itemName: `${type}-${variant}형-${i + 1}`,
|
||
itemType: 'bent_part' as const,
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: stockQty,
|
||
actualQty: stockQty,
|
||
stockQty,
|
||
safetyStock,
|
||
wipQty: seededInt(seed + 5, 0, 50),
|
||
lotCount: seededInt(seed + 2, 1, 5),
|
||
lotDaysElapsed: seededInt(seed + 3, 0, 45),
|
||
status: getStockStatus(stockQty, safetyStock),
|
||
useStatus: 'active' as const,
|
||
location: generateLocation('bent_part', seed + 4),
|
||
hasStock: true,
|
||
};
|
||
});
|
||
|
||
// 구매부품 데이터 (80개)
|
||
const purchasedPartItems: StockItem[] = [
|
||
// 각파이프류 (20개)
|
||
...Array.from({ length: 20 }, (_, i) => {
|
||
const sizes = ['30×30', '40×40', '50×50', '60×60', '75×75'];
|
||
const lengths = ['3000', '4000', '5000', '6000'];
|
||
const size = sizes[i % sizes.length];
|
||
const length = lengths[i % lengths.length];
|
||
const seed = 1000 + i * 10;
|
||
const stockQty = seededInt(seed, 80, 300);
|
||
const safetyStock = seededInt(seed + 1, 30, 60);
|
||
|
||
return {
|
||
id: `pp-sqp-${i + 1}`,
|
||
stockNumber: `STK-PP-SQP-${String(i + 1).padStart(3, '0')}`,
|
||
itemCode: `SQP-${size.replace('×', '')}-${length.slice(0, 2)}`,
|
||
itemName: `각파이프 ${size} L:${length}`,
|
||
itemType: 'purchased_part' as const,
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: stockQty,
|
||
actualQty: stockQty,
|
||
stockQty,
|
||
safetyStock,
|
||
wipQty: seededInt(seed + 5, 0, 30),
|
||
lotCount: seededInt(seed + 2, 2, 5),
|
||
lotDaysElapsed: seededInt(seed + 3, 0, 40),
|
||
status: getStockStatus(stockQty, safetyStock),
|
||
useStatus: 'active' as const,
|
||
location: 'I-05',
|
||
hasStock: true,
|
||
};
|
||
}),
|
||
// 앵글류 (15개)
|
||
...Array.from({ length: 15 }, (_, i) => {
|
||
const sizes = ['50×50', '65×65', '75×75', '90×90', '100×100'];
|
||
const lengths = ['3000', '4000', '5000'];
|
||
const size = sizes[i % sizes.length];
|
||
const length = lengths[i % lengths.length];
|
||
const seed = 2000 + i * 10;
|
||
const stockQty = seededInt(seed, 60, 200);
|
||
const safetyStock = seededInt(seed + 1, 20, 50);
|
||
|
||
return {
|
||
id: `pp-ang-${i + 1}`,
|
||
stockNumber: `STK-PP-ANG-${String(i + 1).padStart(3, '0')}`,
|
||
itemCode: `ANG-${size.replace('×', '')}-${length.slice(0, 2)}`,
|
||
itemName: `앵글 ${size} L:${length}`,
|
||
itemType: 'purchased_part' as const,
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: stockQty,
|
||
actualQty: stockQty,
|
||
stockQty,
|
||
safetyStock,
|
||
wipQty: seededInt(seed + 5, 0, 25),
|
||
lotCount: seededInt(seed + 2, 2, 4),
|
||
lotDaysElapsed: seededInt(seed + 3, 0, 35),
|
||
status: getStockStatus(stockQty, safetyStock),
|
||
useStatus: 'active' as const,
|
||
location: 'I-04',
|
||
hasStock: true,
|
||
};
|
||
}),
|
||
// 전동개폐기류 (10개)
|
||
...Array.from({ length: 10 }, (_, i) => {
|
||
const weights = ['300KG', '500KG', '700KG', '1000KG'];
|
||
const voltages = ['110V', '220V', '380V'];
|
||
const types = ['유선', '무선'];
|
||
const weight = weights[i % weights.length];
|
||
const voltage = voltages[i % voltages.length];
|
||
const type = types[i % types.length];
|
||
const seed = 3000 + i * 10;
|
||
const stockQty = seededInt(seed, 10, 50);
|
||
const safetyStock = seededInt(seed + 1, 5, 15);
|
||
|
||
return {
|
||
id: `pp-motor-${i + 1}`,
|
||
stockNumber: `STK-PP-MOT-${String(i + 1).padStart(3, '0')}`,
|
||
itemCode: `MOTOR-${voltage}${weight}${type === '무선' ? '-W' : ''}`,
|
||
itemName: `전동개폐기-${voltage}${weight}${type}`,
|
||
itemType: 'purchased_part' as const,
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: stockQty,
|
||
actualQty: stockQty,
|
||
stockQty,
|
||
safetyStock,
|
||
wipQty: seededInt(seed + 5, 0, 10),
|
||
lotCount: seededInt(seed + 2, 1, 3),
|
||
lotDaysElapsed: seededInt(seed + 3, 0, 30),
|
||
status: getStockStatus(stockQty, safetyStock),
|
||
useStatus: 'active' as const,
|
||
location: 'I-01',
|
||
hasStock: true,
|
||
};
|
||
}),
|
||
// 볼트/너트류 (15개)
|
||
...Array.from({ length: 15 }, (_, i) => {
|
||
const sizes = ['M6', 'M8', 'M10', 'M12', 'M16'];
|
||
const lengths = ['20', '30', '40', '50', '60'];
|
||
const size = sizes[i % sizes.length];
|
||
const length = lengths[i % lengths.length];
|
||
const seed = 4000 + i * 10;
|
||
const stockQty = seededInt(seed, 500, 2000);
|
||
const safetyStock = seededInt(seed + 1, 100, 500);
|
||
|
||
return {
|
||
id: `pp-bolt-${i + 1}`,
|
||
stockNumber: `STK-PP-BLT-${String(i + 1).padStart(3, '0')}`,
|
||
itemCode: `BOLT-${size}-${length}`,
|
||
itemName: `볼트 ${size}×${length}mm`,
|
||
itemType: 'purchased_part' as const,
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: stockQty,
|
||
actualQty: stockQty,
|
||
stockQty,
|
||
safetyStock,
|
||
wipQty: seededInt(seed + 5, 0, 100),
|
||
lotCount: seededInt(seed + 2, 3, 6),
|
||
lotDaysElapsed: seededInt(seed + 3, 0, 25),
|
||
status: getStockStatus(stockQty, safetyStock),
|
||
useStatus: 'active' as const,
|
||
location: 'J-01',
|
||
hasStock: true,
|
||
};
|
||
}),
|
||
// 베어링류 (10개)
|
||
...Array.from({ length: 10 }, (_, i) => {
|
||
const types = ['6200', '6201', '6202', '6203', '6204', '6205', '6206', '6207', '6208', '6209'];
|
||
const type = types[i % types.length];
|
||
const seed = 5000 + i * 10;
|
||
const stockQty = seededInt(seed, 30, 150);
|
||
const safetyStock = seededInt(seed + 1, 10, 40);
|
||
|
||
return {
|
||
id: `pp-bearing-${i + 1}`,
|
||
stockNumber: `STK-PP-BRG-${String(i + 1).padStart(3, '0')}`,
|
||
itemCode: `BEARING-${type}`,
|
||
itemName: `베어링 ${type}`,
|
||
itemType: 'purchased_part' as const,
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: stockQty,
|
||
actualQty: stockQty,
|
||
stockQty,
|
||
safetyStock,
|
||
wipQty: seededInt(seed + 5, 0, 20),
|
||
lotCount: seededInt(seed + 2, 2, 4),
|
||
lotDaysElapsed: seededInt(seed + 3, 0, 20),
|
||
status: getStockStatus(stockQty, safetyStock),
|
||
useStatus: 'active' as const,
|
||
location: 'J-02',
|
||
hasStock: true,
|
||
};
|
||
}),
|
||
// 스프링류 (10개)
|
||
...Array.from({ length: 10 }, (_, i) => {
|
||
const types = ['인장', '압축', '토션'];
|
||
const sizes = ['S', 'M', 'L', 'XL'];
|
||
const type = types[i % types.length];
|
||
const size = sizes[i % sizes.length];
|
||
const seed = 6000 + i * 10;
|
||
const stockQty = seededInt(seed, 100, 400);
|
||
const safetyStock = seededInt(seed + 1, 30, 80);
|
||
|
||
return {
|
||
id: `pp-spring-${i + 1}`,
|
||
stockNumber: `STK-PP-SPR-${String(i + 1).padStart(3, '0')}`,
|
||
itemCode: `SPRING-${type.toUpperCase().slice(0, 2)}-${size}`,
|
||
itemName: `스프링-${type}-${size}`,
|
||
itemType: 'purchased_part' as const,
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: stockQty,
|
||
actualQty: stockQty,
|
||
stockQty,
|
||
safetyStock,
|
||
wipQty: seededInt(seed + 5, 0, 40),
|
||
lotCount: seededInt(seed + 2, 2, 5),
|
||
lotDaysElapsed: seededInt(seed + 3, 0, 30),
|
||
status: getStockStatus(stockQty, safetyStock),
|
||
useStatus: 'active' as const,
|
||
location: 'J-03',
|
||
hasStock: true,
|
||
};
|
||
}),
|
||
];
|
||
|
||
// 부자재 데이터 (7개)
|
||
const subMaterialItems: StockItem[] = [
|
||
{
|
||
id: 'sm-1',
|
||
stockNumber: 'STK-SM-001',
|
||
itemCode: 'SEW-WHT',
|
||
itemName: '미싱실-백색',
|
||
itemType: 'sub_material',
|
||
specification: '-',
|
||
unit: 'M',
|
||
calculatedQty: 5000,
|
||
actualQty: 5000,
|
||
stockQty: 5000,
|
||
safetyStock: 1000,
|
||
wipQty: 100,
|
||
lotCount: 3,
|
||
lotDaysElapsed: 28,
|
||
status: 'normal',
|
||
useStatus: 'active',
|
||
location: 'A-04',
|
||
hasStock: true,
|
||
},
|
||
{
|
||
id: 'sm-2',
|
||
stockNumber: 'STK-SM-002',
|
||
itemCode: 'ALU-BAR',
|
||
itemName: '하단바-알루미늄',
|
||
itemType: 'sub_material',
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: 120,
|
||
actualQty: 120,
|
||
stockQty: 120,
|
||
safetyStock: 30,
|
||
wipQty: 10,
|
||
lotCount: 1,
|
||
lotDaysElapsed: 5,
|
||
status: 'normal',
|
||
useStatus: 'active',
|
||
location: 'A-03',
|
||
hasStock: true,
|
||
},
|
||
{
|
||
id: 'sm-3',
|
||
stockNumber: 'STK-SM-003',
|
||
itemCode: 'END-CAP-STD',
|
||
itemName: '앤드락-표준',
|
||
itemType: 'sub_material',
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: 800,
|
||
actualQty: 800,
|
||
stockQty: 800,
|
||
safetyStock: 200,
|
||
wipQty: 50,
|
||
lotCount: 2,
|
||
lotDaysElapsed: 12,
|
||
status: 'normal',
|
||
useStatus: 'active',
|
||
location: 'A-02',
|
||
hasStock: true,
|
||
},
|
||
{
|
||
id: 'sm-4',
|
||
stockNumber: 'STK-SM-004',
|
||
itemCode: 'SILICON-TRANS',
|
||
itemName: '실리콘-투명',
|
||
itemType: 'sub_material',
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: 200,
|
||
actualQty: 200,
|
||
stockQty: 200,
|
||
safetyStock: 50,
|
||
wipQty: 15,
|
||
lotCount: 5,
|
||
lotDaysElapsed: 37,
|
||
status: 'normal',
|
||
useStatus: 'active',
|
||
location: 'B-03',
|
||
hasStock: true,
|
||
},
|
||
{
|
||
id: 'sm-5',
|
||
stockNumber: 'STK-SM-005',
|
||
itemCode: 'TAPE-DBL-25',
|
||
itemName: '양면테이프-25mm',
|
||
itemType: 'sub_material',
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: 150,
|
||
actualQty: 150,
|
||
stockQty: 150,
|
||
safetyStock: 40,
|
||
wipQty: 8,
|
||
lotCount: 2,
|
||
lotDaysElapsed: 10,
|
||
status: 'normal',
|
||
useStatus: 'active',
|
||
location: 'B-02',
|
||
hasStock: true,
|
||
},
|
||
{
|
||
id: 'sm-6',
|
||
stockNumber: 'STK-SM-006',
|
||
itemCode: 'RIVET-STL-4',
|
||
itemName: '리벳-스틸-4mm',
|
||
itemType: 'sub_material',
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: 3000,
|
||
actualQty: 3000,
|
||
stockQty: 3000,
|
||
safetyStock: 500,
|
||
wipQty: 200,
|
||
lotCount: 4,
|
||
lotDaysElapsed: 8,
|
||
status: 'normal',
|
||
useStatus: 'active',
|
||
location: 'B-01',
|
||
hasStock: true,
|
||
},
|
||
{
|
||
id: 'sm-7',
|
||
stockNumber: 'STK-SM-007',
|
||
itemCode: 'WASHER-M8',
|
||
itemName: '와셔-M8',
|
||
itemType: 'sub_material',
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: 2500,
|
||
actualQty: 2500,
|
||
stockQty: 2500,
|
||
safetyStock: 400,
|
||
wipQty: 150,
|
||
lotCount: 3,
|
||
lotDaysElapsed: 15,
|
||
status: 'normal',
|
||
useStatus: 'active',
|
||
location: 'B-04',
|
||
hasStock: true,
|
||
},
|
||
];
|
||
|
||
// 소모품 데이터 (2개)
|
||
const consumableItems: StockItem[] = [
|
||
{
|
||
id: 'cs-1',
|
||
stockNumber: 'STK-CS-001',
|
||
itemCode: 'PKG-BOX-L',
|
||
itemName: '포장박스-대형',
|
||
itemType: 'consumable',
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: 200,
|
||
actualQty: 200,
|
||
stockQty: 200,
|
||
safetyStock: 50,
|
||
wipQty: 20,
|
||
lotCount: 2,
|
||
lotDaysElapsed: 8,
|
||
status: 'normal',
|
||
useStatus: 'active',
|
||
location: 'B-01',
|
||
hasStock: true,
|
||
},
|
||
{
|
||
id: 'cs-2',
|
||
stockNumber: 'STK-CS-002',
|
||
itemCode: 'PKG-BOX-M',
|
||
itemName: '포장박스-중형',
|
||
itemType: 'consumable',
|
||
specification: '-',
|
||
unit: 'EA',
|
||
calculatedQty: 350,
|
||
actualQty: 350,
|
||
stockQty: 350,
|
||
safetyStock: 80,
|
||
wipQty: 30,
|
||
lotCount: 3,
|
||
lotDaysElapsed: 5,
|
||
status: 'normal',
|
||
useStatus: 'active',
|
||
location: 'B-02',
|
||
hasStock: true,
|
||
},
|
||
];
|
||
|
||
// 재고 목록 Mock 데이터 (134개)
|
||
export const mockStockItems: StockItem[] = [
|
||
...rawMaterialItems, // 4개
|
||
...bentPartItems, // 41개
|
||
...purchasedPartItems, // 80개
|
||
...subMaterialItems, // 7개
|
||
...consumableItems, // 2개
|
||
];
|
||
|
||
// ID로 아이템 찾기 헬퍼
|
||
export function findStockItemById(id: string): StockItem | undefined {
|
||
return mockStockItems.find(item => item.id === id);
|
||
}
|
||
|
||
// 재고 상세 Mock 데이터 생성 함수
|
||
export function generateStockDetail(item: StockItem): StockDetail {
|
||
const suppliers = ['포스코', '현대제철', '동국제강', '세아제강', '한국철강', '삼성물산'];
|
||
const locations = ['A-01', 'A-02', 'B-01', 'B-02', 'C-01'];
|
||
|
||
// 시드 기반으로 일관된 LOT 데이터 생성
|
||
const itemSeed = parseInt(item.id.replace(/\D/g, '') || '0', 10);
|
||
|
||
// LOT 데이터 생성
|
||
const lots = Array.from({ length: item.lotCount }, (_, i) => {
|
||
const lotSeed = itemSeed * 1000 + i * 100;
|
||
const daysAgo = seededInt(lotSeed, 5, 60);
|
||
const date = new Date('2025-12-23'); // 고정 날짜 사용
|
||
date.setDate(date.getDate() - daysAgo);
|
||
const dateStr = getLocalDateString(date);
|
||
const lotDate = dateStr.replace(/-/g, '').slice(2);
|
||
|
||
return {
|
||
id: `lot-${item.id}-${i + 1}`,
|
||
fifoOrder: i + 1,
|
||
lotNo: `${lotDate}-${String(i + 1).padStart(2, '0')}`,
|
||
receiptDate: dateStr,
|
||
daysElapsed: daysAgo,
|
||
supplier: suppliers[seededInt(lotSeed + 1, 0, suppliers.length - 1)],
|
||
poNumber: `PO-${lotDate}-${String(seededInt(lotSeed + 2, 1, 99)).padStart(2, '0')}`,
|
||
qty: Math.floor(item.stockQty / item.lotCount) + (i === 0 ? item.stockQty % item.lotCount : 0),
|
||
unit: item.unit,
|
||
location: locations[seededInt(lotSeed + 3, 0, locations.length - 1)],
|
||
status: 'available' as const,
|
||
};
|
||
});
|
||
|
||
// 카테고리 추출
|
||
const categoryMap: Record<string, string> = {
|
||
'SQP': '각파이프',
|
||
'ANG': '앵글',
|
||
'MOTOR': '전동개폐기',
|
||
'BOLT': '볼트',
|
||
'BEARING': '베어링',
|
||
'SPRING': '스프링',
|
||
'BENT': '절곡부품',
|
||
'SCR': '스크린원단',
|
||
'SEW': '미싱실',
|
||
'ALU': '알루미늄바',
|
||
'END': '앤드락',
|
||
'SILICON': '실리콘',
|
||
'TAPE': '테이프',
|
||
'RIVET': '리벳',
|
||
'WASHER': '와셔',
|
||
'PKG': '포장재',
|
||
};
|
||
|
||
const prefix = item.itemCode.split('-')[0];
|
||
const category = categoryMap[prefix] || '기타';
|
||
|
||
return {
|
||
id: item.id,
|
||
itemCode: item.itemCode,
|
||
itemName: item.itemName,
|
||
itemType: item.itemType,
|
||
category,
|
||
specification: '-',
|
||
unit: item.unit,
|
||
currentStock: item.stockQty,
|
||
safetyStock: item.safetyStock,
|
||
location: item.location,
|
||
lotCount: item.lotCount,
|
||
lastReceiptDate: lots[lots.length - 1]?.receiptDate || '2025-12-23',
|
||
status: item.status,
|
||
hasStock: item.hasStock,
|
||
lots,
|
||
};
|
||
}
|
||
|
||
// 재고 상세 Mock 데이터 (동적 생성)
|
||
export const mockStockDetails: Record<string, StockDetail> = {};
|
||
|
||
// 상세 데이터 가져오기 함수
|
||
export function getStockDetail(id: string): StockDetail | undefined {
|
||
// 캐시된 데이터가 있으면 반환
|
||
if (mockStockDetails[id]) {
|
||
return mockStockDetails[id];
|
||
}
|
||
|
||
// 없으면 생성
|
||
const item = findStockItemById(id);
|
||
if (item) {
|
||
mockStockDetails[id] = generateStockDetail(item);
|
||
return mockStockDetails[id];
|
||
}
|
||
|
||
return undefined;
|
||
}
|
||
|
||
// 통계 데이터 계산
|
||
const calculateStats = (): StockStats => {
|
||
const normalCount = mockStockItems.filter(item => item.status === 'normal').length;
|
||
const lowCount = mockStockItems.filter(item => item.status === 'low').length;
|
||
const outCount = mockStockItems.filter(item => item.status === 'out').length;
|
||
const noStockCount = mockStockItems.filter(item => !item.hasStock).length;
|
||
|
||
return {
|
||
totalItems: mockStockItems.length,
|
||
normalCount,
|
||
lowCount,
|
||
outCount,
|
||
noStockCount,
|
||
};
|
||
};
|
||
|
||
export const mockStats: StockStats = calculateStats();
|
||
|
||
// 필터 탭 데이터 계산
|
||
const calculateFilterTabs = (): FilterTab[] => {
|
||
const rawMaterialCount = mockStockItems.filter(item => item.itemType === 'raw_material').length;
|
||
const bentPartCount = mockStockItems.filter(item => item.itemType === 'bent_part').length;
|
||
const purchasedPartCount = mockStockItems.filter(item => item.itemType === 'purchased_part').length;
|
||
const subMaterialCount = mockStockItems.filter(item => item.itemType === 'sub_material').length;
|
||
const consumableCount = mockStockItems.filter(item => item.itemType === 'consumable').length;
|
||
|
||
return [
|
||
{ key: 'all', label: '전체', count: mockStockItems.length },
|
||
{ key: 'raw_material', label: '원자재', count: rawMaterialCount },
|
||
{ key: 'bent_part', label: '절곡부품', count: bentPartCount },
|
||
{ key: 'purchased_part', label: '구매부품', count: purchasedPartCount },
|
||
{ key: 'sub_material', label: '부자재', count: subMaterialCount },
|
||
{ key: 'consumable', label: '소모품', count: consumableCount },
|
||
];
|
||
};
|
||
|
||
export const mockFilterTabs: FilterTab[] = calculateFilterTabs(); |