feat: [process] 공정 단계에 검사범위(InspectionScope) 설정 추가
- 전수검사/샘플링/그룹 유형 선택 UI - 샘플링 시 샘플 크기(n) 입력 - options JSON으로 API 저장/복원
This commit is contained in:
@@ -30,12 +30,16 @@ import type {
|
|||||||
StepConnectionType,
|
StepConnectionType,
|
||||||
StepCompletionType,
|
StepCompletionType,
|
||||||
InspectionSetting,
|
InspectionSetting,
|
||||||
|
InspectionScope,
|
||||||
|
InspectionScopeType,
|
||||||
} from '@/types/process';
|
} from '@/types/process';
|
||||||
import {
|
import {
|
||||||
STEP_CONNECTION_TYPE_OPTIONS,
|
STEP_CONNECTION_TYPE_OPTIONS,
|
||||||
STEP_COMPLETION_TYPE_OPTIONS,
|
STEP_COMPLETION_TYPE_OPTIONS,
|
||||||
STEP_CONNECTION_TARGET_OPTIONS,
|
STEP_CONNECTION_TARGET_OPTIONS,
|
||||||
DEFAULT_INSPECTION_SETTING,
|
DEFAULT_INSPECTION_SETTING,
|
||||||
|
DEFAULT_INSPECTION_SCOPE,
|
||||||
|
INSPECTION_SCOPE_TYPE_OPTIONS,
|
||||||
} from '@/types/process';
|
} from '@/types/process';
|
||||||
import { createProcessStep, updateProcessStep } from './actions';
|
import { createProcessStep, updateProcessStep } from './actions';
|
||||||
import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types';
|
import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types';
|
||||||
@@ -108,6 +112,9 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) {
|
|||||||
const [inspectionSetting, setInspectionSetting] = useState<InspectionSetting>(
|
const [inspectionSetting, setInspectionSetting] = useState<InspectionSetting>(
|
||||||
initialData?.inspectionSetting || DEFAULT_INSPECTION_SETTING
|
initialData?.inspectionSetting || DEFAULT_INSPECTION_SETTING
|
||||||
);
|
);
|
||||||
|
const [inspectionScope, setInspectionScope] = useState<InspectionScope>(
|
||||||
|
initialData?.inspectionScope || DEFAULT_INSPECTION_SCOPE
|
||||||
|
);
|
||||||
|
|
||||||
// 모달 상태
|
// 모달 상태
|
||||||
const [isInspectionSettingOpen, setIsInspectionSettingOpen] = useState(false);
|
const [isInspectionSettingOpen, setIsInspectionSettingOpen] = useState(false);
|
||||||
@@ -137,6 +144,7 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) {
|
|||||||
connectionTarget: connectionType === '팝업' ? connectionTarget : undefined,
|
connectionTarget: connectionType === '팝업' ? connectionTarget : undefined,
|
||||||
completionType,
|
completionType,
|
||||||
inspectionSetting: isInspectionEnabled ? inspectionSetting : undefined,
|
inspectionSetting: isInspectionEnabled ? inspectionSetting : undefined,
|
||||||
|
inspectionScope: isInspectionEnabled ? inspectionScope : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -237,6 +245,52 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
{isInspectionEnabled && (
|
||||||
|
<>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>검사범위</Label>
|
||||||
|
<Select
|
||||||
|
value={inspectionScope.type}
|
||||||
|
onValueChange={(v) =>
|
||||||
|
setInspectionScope((prev) => ({
|
||||||
|
...prev,
|
||||||
|
type: v as InspectionScopeType,
|
||||||
|
...(v === 'all' ? { sampleSize: undefined, sampleBase: undefined } : {}),
|
||||||
|
...(v === 'sampling' ? { sampleSize: prev.sampleSize || 1, sampleBase: prev.sampleBase || 'order' } : {}),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{INSPECTION_SCOPE_TYPE_OPTIONS.map((opt) => (
|
||||||
|
<SelectItem key={opt.value} value={opt.value}>
|
||||||
|
{opt.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
{inspectionScope.type === 'sampling' && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>샘플 크기 (n)</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
value={inspectionScope.sampleSize ?? 1}
|
||||||
|
onChange={(e) =>
|
||||||
|
setInspectionScope((prev) => ({
|
||||||
|
...prev,
|
||||||
|
sampleSize: Math.max(1, parseInt(e.target.value) || 1),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
placeholder="검사할 개소 수"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>상태</Label>
|
<Label>상태</Label>
|
||||||
<Select value={isActive} onValueChange={setIsActive}>
|
<Select value={isActive} onValueChange={setIsActive}>
|
||||||
@@ -338,6 +392,7 @@ export function StepForm({ mode, processId, initialData }: StepFormProps) {
|
|||||||
completionType,
|
completionType,
|
||||||
initialData?.stepCode,
|
initialData?.stepCode,
|
||||||
isInspectionEnabled,
|
isInspectionEnabled,
|
||||||
|
inspectionScope,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -576,6 +576,15 @@ export async function getDocumentTemplates(): Promise<{
|
|||||||
// 공정 단계 (Process Step) API
|
// 공정 단계 (Process Step) API
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
interface ApiProcessStepOptions {
|
||||||
|
inspection_setting?: Record<string, unknown>;
|
||||||
|
inspection_scope?: {
|
||||||
|
type: string;
|
||||||
|
sample_size?: number;
|
||||||
|
sample_base?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface ApiProcessStep {
|
interface ApiProcessStep {
|
||||||
id: number;
|
id: number;
|
||||||
process_id: number;
|
process_id: number;
|
||||||
@@ -589,11 +598,13 @@ interface ApiProcessStep {
|
|||||||
connection_type: string | null;
|
connection_type: string | null;
|
||||||
connection_target: string | null;
|
connection_target: string | null;
|
||||||
completion_type: string | null;
|
completion_type: string | null;
|
||||||
|
options: ApiProcessStepOptions | null;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformStepApiToFrontend(apiStep: ApiProcessStep): ProcessStep {
|
function transformStepApiToFrontend(apiStep: ApiProcessStep): ProcessStep {
|
||||||
|
const opts = apiStep.options;
|
||||||
return {
|
return {
|
||||||
id: String(apiStep.id),
|
id: String(apiStep.id),
|
||||||
stepCode: apiStep.step_code,
|
stepCode: apiStep.step_code,
|
||||||
@@ -606,6 +617,12 @@ function transformStepApiToFrontend(apiStep: ApiProcessStep): ProcessStep {
|
|||||||
connectionType: (apiStep.connection_type as ProcessStep['connectionType']) || '없음',
|
connectionType: (apiStep.connection_type as ProcessStep['connectionType']) || '없음',
|
||||||
connectionTarget: apiStep.connection_target ?? undefined,
|
connectionTarget: apiStep.connection_target ?? undefined,
|
||||||
completionType: (apiStep.completion_type as ProcessStep['completionType']) || 'click_complete',
|
completionType: (apiStep.completion_type as ProcessStep['completionType']) || 'click_complete',
|
||||||
|
inspectionSetting: opts?.inspection_setting as ProcessStep['inspectionSetting'],
|
||||||
|
inspectionScope: opts?.inspection_scope ? {
|
||||||
|
type: opts.inspection_scope.type as 'all' | 'sampling' | 'group',
|
||||||
|
sampleSize: opts.inspection_scope.sample_size,
|
||||||
|
sampleBase: opts.inspection_scope.sample_base as 'order' | 'lot' | undefined,
|
||||||
|
} : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,6 +677,14 @@ export async function createProcessStep(
|
|||||||
connection_type: data.connectionType || null,
|
connection_type: data.connectionType || null,
|
||||||
connection_target: data.connectionTarget || null,
|
connection_target: data.connectionTarget || null,
|
||||||
completion_type: data.completionType || null,
|
completion_type: data.completionType || null,
|
||||||
|
options: (data.inspectionSetting || data.inspectionScope) ? {
|
||||||
|
inspection_setting: data.inspectionSetting || null,
|
||||||
|
inspection_scope: data.inspectionScope ? {
|
||||||
|
type: data.inspectionScope.type,
|
||||||
|
sample_size: data.inspectionScope.sampleSize,
|
||||||
|
sample_base: data.inspectionScope.sampleBase,
|
||||||
|
} : null,
|
||||||
|
} : null,
|
||||||
},
|
},
|
||||||
transform: (d: ApiProcessStep) => transformStepApiToFrontend(d),
|
transform: (d: ApiProcessStep) => transformStepApiToFrontend(d),
|
||||||
errorMessage: '공정 단계 등록에 실패했습니다.',
|
errorMessage: '공정 단계 등록에 실패했습니다.',
|
||||||
@@ -684,6 +709,16 @@ export async function updateProcessStep(
|
|||||||
if (data.connectionType !== undefined) apiData.connection_type = data.connectionType || null;
|
if (data.connectionType !== undefined) apiData.connection_type = data.connectionType || null;
|
||||||
if (data.connectionTarget !== undefined) apiData.connection_target = data.connectionTarget || null;
|
if (data.connectionTarget !== undefined) apiData.connection_target = data.connectionTarget || null;
|
||||||
if (data.completionType !== undefined) apiData.completion_type = data.completionType || null;
|
if (data.completionType !== undefined) apiData.completion_type = data.completionType || null;
|
||||||
|
if (data.inspectionSetting !== undefined || data.inspectionScope !== undefined) {
|
||||||
|
apiData.options = {
|
||||||
|
inspection_setting: data.inspectionSetting || null,
|
||||||
|
inspection_scope: data.inspectionScope ? {
|
||||||
|
type: data.inspectionScope.type,
|
||||||
|
sample_size: data.inspectionScope.sampleSize,
|
||||||
|
sample_base: data.inspectionScope.sampleBase,
|
||||||
|
} : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const result = await executeServerAction({
|
const result = await executeServerAction({
|
||||||
url: buildApiUrl(`/api/v1/processes/${processId}/steps/${stepId}`),
|
url: buildApiUrl(`/api/v1/processes/${processId}/steps/${stepId}`),
|
||||||
|
|||||||
@@ -192,6 +192,8 @@ export interface ProcessStep {
|
|||||||
completionType: StepCompletionType;
|
completionType: StepCompletionType;
|
||||||
// 검사 설정 (검사여부가 true일 때)
|
// 검사 설정 (검사여부가 true일 때)
|
||||||
inspectionSetting?: InspectionSetting;
|
inspectionSetting?: InspectionSetting;
|
||||||
|
// 검사 범위 (검사여부가 true일 때)
|
||||||
|
inspectionScope?: InspectionScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 연결 유형 옵션
|
// 연결 유형 옵션
|
||||||
@@ -295,6 +297,35 @@ export const INSPECTION_METHOD_OPTIONS: { value: InspectionMethodType; label: st
|
|||||||
{ value: '양자택일', label: '양자택일' },
|
{ value: '양자택일', label: '양자택일' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 검사 범위 (Inspection Scope) 타입 정의
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// 검사 범위 유형
|
||||||
|
export type InspectionScopeType = 'all' | 'sampling' | 'group';
|
||||||
|
|
||||||
|
// 샘플 기준
|
||||||
|
export type InspectionSampleBase = 'order' | 'lot';
|
||||||
|
|
||||||
|
// 검사 범위 설정
|
||||||
|
export interface InspectionScope {
|
||||||
|
type: InspectionScopeType; // 전수검사 | 샘플링 | 그룹
|
||||||
|
sampleSize?: number; // 샘플 크기 (n값, sampling일 때만)
|
||||||
|
sampleBase?: InspectionSampleBase; // 샘플 기준 (order | lot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 검사 범위 유형 옵션
|
||||||
|
export const INSPECTION_SCOPE_TYPE_OPTIONS: { value: InspectionScopeType; label: string; description: string }[] = [
|
||||||
|
{ value: 'all', label: '전수검사', description: '모든 개소 검사' },
|
||||||
|
{ value: 'sampling', label: '샘플링', description: '마지막 N개 개소만 검사' },
|
||||||
|
{ value: 'group', label: '그룹', description: '그룹 마지막 개소만 검사' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 기본 검사 범위
|
||||||
|
export const DEFAULT_INSPECTION_SCOPE: InspectionScope = {
|
||||||
|
type: 'all',
|
||||||
|
};
|
||||||
|
|
||||||
// 기본 검사 설정값
|
// 기본 검사 설정값
|
||||||
export const DEFAULT_INSPECTION_SETTING: InspectionSetting = {
|
export const DEFAULT_INSPECTION_SETTING: InspectionSetting = {
|
||||||
standardName: '',
|
standardName: '',
|
||||||
|
|||||||
Reference in New Issue
Block a user