feat: [자재투입] 개발전용 입고 강제생성 버튼 추가 (입고+재고+수입검사 자동)

This commit is contained in:
김보곤
2026-03-22 10:17:37 +09:00
parent 60e5dc81fa
commit acec5e4a24
2 changed files with 46 additions and 1 deletions

View File

@@ -34,7 +34,7 @@ import {
} from '@/components/ui/table';
import { toast } from 'sonner';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { getMaterialsForWorkOrder, registerMaterialInput, getMaterialsForItem, registerMaterialInputForItem, searchStockByCode, type MaterialForInput, type MaterialForItemInput, type StockSearchResult } from './actions';
import { getMaterialsForWorkOrder, registerMaterialInput, getMaterialsForItem, registerMaterialInputForItem, searchStockByCode, forceCreateReceiving, type MaterialForInput, type MaterialForItemInput, type StockSearchResult } from './actions';
import type { WorkOrder } from '../ProductionDashboard/types';
import type { MaterialInput } from './types';
import { formatNumber } from '@/lib/utils/amount';
@@ -103,6 +103,8 @@ export function MaterialInputModal({
setIsSearching(false);
}, []);
const [isForceCreating, setIsForceCreating] = useState(false);
// 목업 자재 데이터 (개발용)
const MOCK_MATERIALS: MaterialForInput[] = Array.from({ length: 5 }, (_, i) => ({
stockLotId: 100 + i,
@@ -346,6 +348,20 @@ export function MaterialInputModal({
}
}, [order, workOrderItemId, workOrderItemIds]);
// [개발전용] 입고 강제생성 핸들러
const handleForceReceiving = useCallback(async (itemId: number, itemCode: string) => {
setIsForceCreating(true);
const result = await forceCreateReceiving(itemId, 100);
if (result.success && result.data) {
toast.success(`${result.data.item_code} 입고 생성 완료 (LOT: ${result.data.lot_no}, ${result.data.qty}EA)`);
handleStockSearch(itemCode);
loadMaterials();
} else {
toast.error(result.error || '입고 생성 실패');
}
setIsForceCreating(false);
}, [handleStockSearch, loadMaterials]);
// 모달이 열릴 때 데이터 로드 + 선택 초기화
useEffect(() => {
if (open && order) {
@@ -823,6 +839,19 @@ export function MaterialInputModal({
) : searchQuery && (
<p className="text-[11px] text-red-500"> </p>
)}
{/* [개발전용] 입고 강제생성 버튼 */}
{group.lots[0]?.itemId && (
<Button
size="sm"
variant="outline"
className="w-full h-8 text-xs mt-1 border-orange-300 text-orange-600 hover:bg-orange-50"
disabled={isForceCreating}
onClick={() => handleForceReceiving(group.lots[0].itemId, group.materialCode || group.materialName)}
>
{isForceCreating ? <Loader2 className="h-3 w-3 animate-spin mr-1" /> : <Zap className="h-3 w-3 mr-1" />}
[DEV] ({group.materialCode || '품목'} × 100EA)
</Button>
)}
</div>
)}
</div>

View File

@@ -886,6 +886,22 @@ export interface StockSearchResult {
lots: Array<{ lotNo: string; availableQty: number; fifoOrder: number }>;
}
// ===== [개발전용] 입고 강제 생성 =====
export async function forceCreateReceiving(
itemId: number,
qty: number = 100
): Promise<{ success: boolean; data?: { receiving_number: string; lot_no: string; item_code: string; item_name: string; qty: number; available_qty: number }; error?: string }> {
const result = await executeServerAction<{
receiving_number: string; lot_no: string; item_code: string; item_name: string; qty: number; available_qty: number;
}>({
url: buildApiUrl('/api/v1/dev/force-receiving'),
method: 'POST',
body: { item_id: itemId, qty },
errorMessage: '입고 강제 생성에 실패했습니다.',
});
return { success: result.success, data: result.data, error: result.error };
}
export async function searchStockByCode(
search: string
): Promise<{ success: boolean; data: StockSearchResult[]; error?: string }> {