Files
QuantEngineByItz/src/frontend/src/views/WmsInboundPickingView.vue
T
kjh2064 4695de8783
Validators (Pushes and Pull Requests) / UI & Storage Validation (push) Failing after 14s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (push) Failing after 23s
Validators (Pushes and Pull Requests) / Database & Schema Validation (push) Successful in 9s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (push) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (push) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (push) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (push) Has been skipped
Validators (Pushes and Pull Requests) / Security & Secrets (push) Successful in 10s
Validators (Pushes and Pull Requests) / CI Workflow Lint (push) Failing after 10s
Validators (Pushes and Pull Requests) / Notify PR Results (push) Has been skipped
Frontend CI Pipeline / ci-frontend-8-steps (push) Failing after 3m3s
fix(frontend): resolve all vue-tsc -b production build errors and optimize enterprise input components
2026-07-26 15:00:32 +09:00

141 lines
5.6 KiB
Vue

<!-- WmsInboundPickingView.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import BaseStatusBadge from '../components/primitives/BaseStatusBadge.vue';
import BaseButton from '../components/primitives/BaseButton.vue';
import BarcodeInput from '../components/domain-fields/BarcodeInput.vue';
import LotField from '../components/domain-fields/LotField.vue';
import QuantityField from '../components/domain-fields/QuantityField.vue';
import { parseGS1Barcode, type OfflineCommand, type BarcodeScanResult } from '../modules/wms/domain/offlineCommand';
const isOnline = ref(true);
// WMS Scan & Picking State
const lastScanResult = ref<BarcodeScanResult | null>(null);
const scannedItemCode = ref('PROD-WMS-801');
const lotNumber = ref('LOT-20260726-A');
const pickQty = ref('15');
const totalPickedCount = ref(150);
// Offline Command Queue State (WMS-04)
const offlineQueue = ref<OfflineCommand[]>([]);
const handleBarcodeScanned = (input: BarcodeScanResult | string) => {
const result = typeof input === 'string' ? parseGS1Barcode(input) : input;
lastScanResult.value = result;
if (result.isValid && result.itemCode) {
scannedItemCode.value = result.itemCode;
if (result.lotNumber) lotNumber.value = result.lotNumber;
}
};
const handleConfirmPick = () => {
const cmdId = `CMD-${Date.now()}`;
const newCmd: OfflineCommand = {
commandId: cmdId,
idempotencyKey: `IDEM-${cmdId}`,
actionType: 'PICK',
payload: { itemCode: scannedItemCode.value, lot: lotNumber.value, qty: pickQty.value },
status: isOnline.value ? 'completed' : 'queued',
createdAt: new Date().toISOString(),
retryCount: 0
};
if (!isOnline.value) {
offlineQueue.value.unshift(newCmd);
} else {
totalPickedCount.value += Number(pickQty.value);
}
};
const syncOfflineQueue = () => {
offlineQueue.value.forEach(cmd => {
cmd.status = 'completed';
cmd.syncedAt = new Date().toISOString();
totalPickedCount.value += Number(cmd.payload.qty);
});
setTimeout(() => {
offlineQueue.value = [];
}, 600);
};
</script>
<template>
<div class="wms-picking-container flex flex-col gap-4 p-6 bg-slate-100 min-h-full select-none text-left">
<!-- Header with Network Status Toggle -->
<div class="flex justify-between items-center bg-white p-4 rounded-lg shadow-sm border border-slate-200">
<div>
<div class="flex items-center gap-2">
<BaseStatusBadge variant="info" label="Phase 6" />
<h1 class="text-xl font-bold text-slate-800">WMS 현장 입고·피킹 파일럿</h1>
<BaseStatusBadge
:variant="isOnline ? 'success' : 'warning'"
:label="isOnline ? 'ONLINE' : 'OFFLINE MODE'"
/>
</div>
<p class="text-xs text-slate-500 mt-1">WMS-01 ~ 04: Touch Density (44px), 바코드 &lt;100ms 파싱, FEFO 검증, OfflineCommand 오프라인 </p>
</div>
<div class="flex gap-2">
<BaseButton
:variant="isOnline ? 'outline' : 'secondary'"
density="touch"
@click="isOnline = !isOnline"
>
{{ isOnline ? '📡 온라인 상태' : '📶 오프라인 모드 전환' }}
</BaseButton>
<BaseButton
v-if="!isOnline && offlineQueue.length > 0"
variant="primary"
density="touch"
@click="syncOfflineQueue"
>
🔄 오프라인 동기화 ({{ offlineQueue.length }})
</BaseButton>
</div>
</div>
<!-- Scanner & Picking Main Action (Touch Friendly min-h 44px) -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Left: Scanner Section -->
<div class="bg-white p-6 rounded-lg shadow-sm border border-slate-200 flex flex-col gap-4">
<h2 class="text-base font-bold text-slate-800">1. 스캐너 바코드 입력 (<100ms 판정)</h2>
<BarcodeInput @scan="handleBarcodeScanned" />
<div v-if="lastScanResult" class="p-3 bg-blue-50 border border-blue-200 rounded text-xs flex flex-col gap-1">
<div class="font-bold text-blue-800">최근 스캔 파싱 결과 (속도: {{ lastScanResult.parseTimeMs }}ms)</div>
<div>원본: {{ lastScanResult.rawBarcode }}</div>
<div>추출 품목: {{ lastScanResult.itemCode }}</div>
<div>추출 로트: {{ lastScanResult.lotNumber }}</div>
</div>
</div>
<!-- Right: Touch Picking Action Form -->
<div class="bg-white p-6 rounded-lg shadow-sm border border-slate-200 flex flex-col gap-4">
<h2 class="text-base font-bold text-slate-800">2. 피킹 작업 확정 (누적: {{ totalPickedCount }} EA)</h2>
<div class="flex flex-col gap-3">
<LotField v-model="lotNumber" />
<QuantityField v-model="pickQty" />
</div>
<div class="pt-2">
<BaseButton variant="primary" density="touch" class="w-full text-lg" @click="handleConfirmPick">
{{ isOnline ? '✅ 피킹 작업 확정' : '💾 오프라인 적재 (OfflineCommand)' }}
</BaseButton>
</div>
</div>
</div>
<!-- Offline Queue Log Table -->
<div v-if="offlineQueue.length > 0" class="bg-white p-4 rounded-lg shadow-sm border border-slate-200">
<h3 class="text-xs font-bold text-amber-700 mb-2">대기 중인 오프라인 명령 (IndexedDB / LocalStorage)</h3>
<ul class="divide-y text-xs">
<li v-for="cmd in offlineQueue" :key="cmd.commandId" class="py-2 flex justify-between">
<span>[{{ cmd.actionType }}] {{ cmd.payload.itemCode }} ({{ cmd.payload.qty }} EA)</span>
<span class="font-mono text-amber-600 font-semibold">{{ cmd.status }}</span>
</li>
</ul>
</div>
</div>
</template>