feat(WEB): 생산/검사 기능 대폭 확장 및 작업자화면 검사입력 추가
생산관리: - 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>
This commit is contained in:
@@ -44,6 +44,7 @@ interface StockDetailData {
|
||||
unit: string;
|
||||
calculatedQty: number;
|
||||
safetyStock: number;
|
||||
wipStatus: 'active' | 'inactive';
|
||||
useStatus: 'active' | 'inactive';
|
||||
}
|
||||
|
||||
@@ -57,7 +58,7 @@ export function StockStatusDetail({ id }: StockStatusDetailProps) {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// 폼 데이터 (수정 모드용)
|
||||
// 폼 데이터 (수정 모드용) - wipStatus는 읽기 전용이므로 제외
|
||||
const [formData, setFormData] = useState<{
|
||||
safetyStock: number;
|
||||
useStatus: 'active' | 'inactive';
|
||||
@@ -90,6 +91,7 @@ export function StockStatusDetail({ id }: StockStatusDetailProps) {
|
||||
unit: data.unit,
|
||||
calculatedQty: data.currentStock, // 재고량
|
||||
safetyStock: data.safetyStock,
|
||||
wipStatus: 'active', // 재공품 상태 (기본값: 사용)
|
||||
useStatus: data.status === null ? 'active' : 'active', // 기본값
|
||||
};
|
||||
setDetail(detailData);
|
||||
@@ -201,8 +203,9 @@ export function StockStatusDetail({ id }: StockStatusDetailProps) {
|
||||
{renderReadOnlyField('안전재고', detail.safetyStock)}
|
||||
</div>
|
||||
|
||||
{/* Row 3: 상태 */}
|
||||
{/* Row 3: 재공품, 상태 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
{renderReadOnlyField('재공품', USE_STATUS_LABELS[detail.wipStatus])}
|
||||
{renderReadOnlyField('상태', USE_STATUS_LABELS[detail.useStatus])}
|
||||
</div>
|
||||
</div>
|
||||
@@ -252,8 +255,11 @@ export function StockStatusDetail({ id }: StockStatusDetailProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 3: 상태 (수정 가능) */}
|
||||
{/* Row 3: 재공품 (읽기 전용), 상태 (수정 가능) */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
{/* 재공품 (읽기 전용) */}
|
||||
{renderReadOnlyField('재공품', USE_STATUS_LABELS[detail.wipStatus], true)}
|
||||
{/* 상태 (수정 가능) */}
|
||||
<div>
|
||||
<Label htmlFor="useStatus" className="text-sm text-muted-foreground">
|
||||
상태
|
||||
|
||||
@@ -60,6 +60,7 @@ export function StockStatusList() {
|
||||
// ===== 검색 및 필터 상태 =====
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [filterValues, setFilterValues] = useState<Record<string, string | string[]>>({
|
||||
wipStatus: 'all',
|
||||
useStatus: 'all',
|
||||
});
|
||||
|
||||
@@ -134,6 +135,7 @@ export function StockStatusList() {
|
||||
{ header: '단위', key: 'unit' },
|
||||
{ header: '재고량', key: 'calculatedQty' },
|
||||
{ header: '안전재고', key: 'safetyStock' },
|
||||
{ header: '재공품', key: 'wipQty' },
|
||||
{ header: '상태', key: 'useStatus', transform: (value) => USE_STATUS_LABELS[value as 'active' | 'inactive'] || '-' },
|
||||
];
|
||||
|
||||
@@ -156,6 +158,7 @@ export function StockStatusList() {
|
||||
actualQty: hasStock ? (parseFloat(String(stock?.actual_qty ?? stock?.stock_qty)) || 0) : 0,
|
||||
stockQty: hasStock ? (parseFloat(String(stock?.stock_qty)) || 0) : 0,
|
||||
safetyStock: hasStock ? (parseFloat(String(stock?.safety_stock)) || 0) : 0,
|
||||
wipQty: hasStock ? (parseFloat(String(stock?.wip_qty)) || 0) : 0,
|
||||
lotCount: hasStock ? (Number(stock?.lot_count) || 0) : 0,
|
||||
lotDaysElapsed: hasStock ? (Number(stock?.days_elapsed) || 0) : 0,
|
||||
status: hasStock ? (stock?.status as StockStatusType | null) : null,
|
||||
@@ -194,14 +197,23 @@ export function StockStatusList() {
|
||||
},
|
||||
];
|
||||
|
||||
// ===== 필터 설정 (전체/사용/미사용) =====
|
||||
// ===== 필터 설정 (재공품, 상태) =====
|
||||
// 참고: IntegratedListTemplateV2에서 자동으로 '전체' 옵션을 추가하므로 options에서 제외
|
||||
const filterConfig: FilterFieldConfig[] = [
|
||||
{
|
||||
key: 'wipStatus',
|
||||
label: '재공품',
|
||||
type: 'single',
|
||||
options: [
|
||||
{ value: 'active', label: '사용' },
|
||||
{ value: 'inactive', label: '미사용' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'useStatus',
|
||||
label: '상태',
|
||||
type: 'single',
|
||||
options: [
|
||||
{ value: 'all', label: '전체' },
|
||||
{ value: 'active', label: '사용' },
|
||||
{ value: 'inactive', label: '미사용' },
|
||||
],
|
||||
@@ -219,6 +231,7 @@ export function StockStatusList() {
|
||||
{ key: 'unit', label: '단위', className: 'w-[60px] text-center' },
|
||||
{ key: 'calculatedQty', label: '재고량', className: 'w-[80px] text-center' },
|
||||
{ key: 'safetyStock', label: '안전재고', className: 'w-[80px] text-center' },
|
||||
{ key: 'wipQty', label: '재공품', className: 'w-[80px] text-center' },
|
||||
{ key: 'useStatus', label: '상태', className: 'w-[80px] text-center' },
|
||||
];
|
||||
|
||||
@@ -250,6 +263,7 @@ export function StockStatusList() {
|
||||
<TableCell className="text-center">{item.unit}</TableCell>
|
||||
<TableCell className="text-center">{item.calculatedQty}</TableCell>
|
||||
<TableCell className="text-center">{item.safetyStock}</TableCell>
|
||||
<TableCell className="text-center">{item.wipQty}</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<span className={item.useStatus === 'inactive' ? 'text-gray-400' : ''}>
|
||||
{USE_STATUS_LABELS[item.useStatus]}
|
||||
@@ -296,6 +310,7 @@ export function StockStatusList() {
|
||||
<InfoField label="단위" value={item.unit} />
|
||||
<InfoField label="재고량" value={`${item.calculatedQty}`} />
|
||||
<InfoField label="안전재고" value={`${item.safetyStock}`} />
|
||||
<InfoField label="재공품" value={`${item.wipQty}`} />
|
||||
</div>
|
||||
}
|
||||
actions={
|
||||
@@ -361,6 +376,13 @@ export function StockStatusList() {
|
||||
customFilterFn: (items, fv) => {
|
||||
if (!items || items.length === 0) return items;
|
||||
return items.filter((item) => {
|
||||
// 재공품 필터 (사용: wipQty > 0, 미사용: wipQty === 0)
|
||||
const wipStatusVal = fv.wipStatus as string;
|
||||
if (wipStatusVal && wipStatusVal !== 'all') {
|
||||
if (wipStatusVal === 'active' && item.wipQty === 0) return false;
|
||||
if (wipStatusVal === 'inactive' && item.wipQty > 0) return false;
|
||||
}
|
||||
// 상태 필터
|
||||
const useStatusVal = fv.useStatus as string;
|
||||
if (useStatusVal && useStatusVal !== 'all' && item.useStatus !== useStatusVal) {
|
||||
return false;
|
||||
|
||||
@@ -137,6 +137,7 @@ function transformApiToListItem(data: ItemApiData): StockItem {
|
||||
actualQty: hasStock ? (parseFloat(String((stock as unknown as Record<string, unknown>).actual_qty ?? stock.stock_qty)) || 0) : 0,
|
||||
stockQty: hasStock ? (parseFloat(String(stock.stock_qty)) || 0) : 0,
|
||||
safetyStock: hasStock ? (parseFloat(String(stock.safety_stock)) || 0) : 0,
|
||||
wipQty: hasStock ? (parseFloat(String((stock as unknown as Record<string, unknown>).wip_qty)) || 0) : 0,
|
||||
lotCount: hasStock ? (stock.lot_count || 0) : 0,
|
||||
lotDaysElapsed: hasStock ? (stock.days_elapsed || 0) : 0,
|
||||
status: hasStock ? stock.status : null,
|
||||
|
||||
@@ -51,6 +51,7 @@ const rawMaterialItems: StockItem[] = [
|
||||
actualQty: 500,
|
||||
stockQty: 500,
|
||||
safetyStock: 100,
|
||||
wipQty: 30,
|
||||
lotCount: 3,
|
||||
lotDaysElapsed: 21,
|
||||
status: 'normal',
|
||||
@@ -70,6 +71,7 @@ const rawMaterialItems: StockItem[] = [
|
||||
actualQty: 350,
|
||||
stockQty: 350,
|
||||
safetyStock: 80,
|
||||
wipQty: 20,
|
||||
lotCount: 2,
|
||||
lotDaysElapsed: 15,
|
||||
status: 'normal',
|
||||
@@ -89,6 +91,7 @@ const rawMaterialItems: StockItem[] = [
|
||||
actualQty: 280,
|
||||
stockQty: 280,
|
||||
safetyStock: 70,
|
||||
wipQty: 15,
|
||||
lotCount: 2,
|
||||
lotDaysElapsed: 18,
|
||||
status: 'normal',
|
||||
@@ -108,6 +111,7 @@ const rawMaterialItems: StockItem[] = [
|
||||
actualQty: 420,
|
||||
stockQty: 420,
|
||||
safetyStock: 90,
|
||||
wipQty: 25,
|
||||
lotCount: 4,
|
||||
lotDaysElapsed: 12,
|
||||
status: 'normal',
|
||||
@@ -139,6 +143,7 @@ const bentPartItems: StockItem[] = Array.from({ length: 41 }, (_, i) => {
|
||||
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),
|
||||
@@ -172,6 +177,7 @@ const purchasedPartItems: StockItem[] = [
|
||||
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),
|
||||
@@ -202,6 +208,7 @@ const purchasedPartItems: StockItem[] = [
|
||||
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),
|
||||
@@ -234,6 +241,7 @@ const purchasedPartItems: StockItem[] = [
|
||||
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),
|
||||
@@ -264,6 +272,7 @@ const purchasedPartItems: StockItem[] = [
|
||||
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),
|
||||
@@ -292,6 +301,7 @@ const purchasedPartItems: StockItem[] = [
|
||||
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),
|
||||
@@ -322,6 +332,7 @@ const purchasedPartItems: StockItem[] = [
|
||||
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),
|
||||
@@ -346,6 +357,7 @@ const subMaterialItems: StockItem[] = [
|
||||
actualQty: 5000,
|
||||
stockQty: 5000,
|
||||
safetyStock: 1000,
|
||||
wipQty: 100,
|
||||
lotCount: 3,
|
||||
lotDaysElapsed: 28,
|
||||
status: 'normal',
|
||||
@@ -365,6 +377,7 @@ const subMaterialItems: StockItem[] = [
|
||||
actualQty: 120,
|
||||
stockQty: 120,
|
||||
safetyStock: 30,
|
||||
wipQty: 10,
|
||||
lotCount: 1,
|
||||
lotDaysElapsed: 5,
|
||||
status: 'normal',
|
||||
@@ -384,6 +397,7 @@ const subMaterialItems: StockItem[] = [
|
||||
actualQty: 800,
|
||||
stockQty: 800,
|
||||
safetyStock: 200,
|
||||
wipQty: 50,
|
||||
lotCount: 2,
|
||||
lotDaysElapsed: 12,
|
||||
status: 'normal',
|
||||
@@ -403,6 +417,7 @@ const subMaterialItems: StockItem[] = [
|
||||
actualQty: 200,
|
||||
stockQty: 200,
|
||||
safetyStock: 50,
|
||||
wipQty: 15,
|
||||
lotCount: 5,
|
||||
lotDaysElapsed: 37,
|
||||
status: 'normal',
|
||||
@@ -422,6 +437,7 @@ const subMaterialItems: StockItem[] = [
|
||||
actualQty: 150,
|
||||
stockQty: 150,
|
||||
safetyStock: 40,
|
||||
wipQty: 8,
|
||||
lotCount: 2,
|
||||
lotDaysElapsed: 10,
|
||||
status: 'normal',
|
||||
@@ -441,6 +457,7 @@ const subMaterialItems: StockItem[] = [
|
||||
actualQty: 3000,
|
||||
stockQty: 3000,
|
||||
safetyStock: 500,
|
||||
wipQty: 200,
|
||||
lotCount: 4,
|
||||
lotDaysElapsed: 8,
|
||||
status: 'normal',
|
||||
@@ -460,6 +477,7 @@ const subMaterialItems: StockItem[] = [
|
||||
actualQty: 2500,
|
||||
stockQty: 2500,
|
||||
safetyStock: 400,
|
||||
wipQty: 150,
|
||||
lotCount: 3,
|
||||
lotDaysElapsed: 15,
|
||||
status: 'normal',
|
||||
@@ -483,6 +501,7 @@ const consumableItems: StockItem[] = [
|
||||
actualQty: 200,
|
||||
stockQty: 200,
|
||||
safetyStock: 50,
|
||||
wipQty: 20,
|
||||
lotCount: 2,
|
||||
lotDaysElapsed: 8,
|
||||
status: 'normal',
|
||||
@@ -502,6 +521,7 @@ const consumableItems: StockItem[] = [
|
||||
actualQty: 350,
|
||||
stockQty: 350,
|
||||
safetyStock: 80,
|
||||
wipQty: 30,
|
||||
lotCount: 3,
|
||||
lotDaysElapsed: 5,
|
||||
status: 'normal',
|
||||
|
||||
@@ -64,6 +64,7 @@ export interface StockItem {
|
||||
actualQty: number; // 실제 재고량 (Stock.actual_qty)
|
||||
stockQty: number; // Stock.stock_qty (없으면 0)
|
||||
safetyStock: number; // Stock.safety_stock (없으면 0)
|
||||
wipQty: number; // 재공품 수량 (Stock.wip_qty, 없으면 0)
|
||||
lotCount: number; // Stock.lot_count (없으면 0)
|
||||
lotDaysElapsed: number; // Stock.days_elapsed (없으면 0)
|
||||
status: StockStatusType | null; // Stock.status (없으면 null)
|
||||
|
||||
Reference in New Issue
Block a user