import { createModuleApi, debounce } from '/mobile/shared/api.js'; const inventurApi = createModuleApi('Lager/Inventur'); export default { name: 'Scanner', emits: ['close', 'toast'], props: { stocktake: { type: Object, required: true }, user: { type: Object, required: true } }, setup(props, { emit }) { const { ref, onMounted, onUnmounted, nextTick, computed } = Vue; const currentTab = ref('scan'); const isLoading = ref(false); const scanner = ref(null); const isScannerActive = ref(false); const scannerError = ref(''); const scannedArticle = ref(null); const quantity = ref('1'); const rack = ref(''); const shelf = ref(''); const searchQuery = ref(''); const searchResults = ref([]); const categories = ref([]); const selectedCategory = ref(0); const isSearching = ref(false); const recentScans = ref([]); const isLoadingHistory = ref(false); const alreadyScannedWarning = ref(null); const showKeypad = ref(false); const canSubmit = computed(() => { return scannedArticle.value && parseFloat(quantity.value) > 0 && !isLoading.value; }); const startScanner = async () => { scannerError.value = ''; try { scanner.value = new Html5Qrcode('qr-reader'); await scanner.value.start( { facingMode: 'environment' }, { fps: 10, qrbox: { width: 250, height: 250 }, aspectRatio: 1.0 }, onScanSuccess, () => {} ); isScannerActive.value = true; } catch (err) { scannerError.value = 'Kamera konnte nicht gestartet werden. Bitte Berechtigungen prüfen.'; } }; const stopScanner = async () => { if (scanner.value && isScannerActive.value) { try { await scanner.value.stop(); } catch (e) {} isScannerActive.value = false; } }; const onScanSuccess = async (decodedText) => { await stopScanner(); await lookupArticle(decodedText); }; const lookupArticle = async (code) => { isLoading.value = true; alreadyScannedWarning.value = null; try { const result = await inventurApi.get(`getArticle?code=${encodeURIComponent(code)}`); if (result.success) { scannedArticle.value = result.article; const checkResult = await inventurApi.get( `checkAlreadyScanned?stocktakeId=${props.stocktake.id}&articleId=${result.article.id}` ); if (checkResult.success && checkResult.alreadyScanned) { alreadyScannedWarning.value = checkResult.existingItem; } quantity.value = '1'; } else { emit('toast', result.message || 'Artikel nicht gefunden', 'error'); await startScanner(); } } catch (e) { emit('toast', 'Fehler beim Laden des Artikels', 'error'); await startScanner(); } finally { isLoading.value = false; } }; const submitScan = async (overwrite = false) => { if (!canSubmit.value) return; isLoading.value = true; try { const payload = { stocktakeId: props.stocktake.id, articleId: scannedArticle.value.id, quantity: parseFloat(quantity.value), rack: rack.value || null, shelf: shelf.value || null, overwrite: overwrite, overwriteItemId: overwrite && alreadyScannedWarning.value ? alreadyScannedWarning.value.id : 0 }; const result = await inventurApi.post('submitScan', payload); if (result.success) { navigator.vibrate?.([100]); emit('toast', result.message, 'success'); scannedArticle.value = null; quantity.value = '1'; rack.value = ''; shelf.value = ''; alreadyScannedWarning.value = null; await startScanner(); } else { emit('toast', result.message || 'Fehler beim Speichern', 'error'); } } catch (e) { emit('toast', 'Netzwerkfehler', 'error'); } finally { isLoading.value = false; } }; const loadCategories = async () => { const result = await inventurApi.get('getCategories'); if (result.success) categories.value = result.categories; }; const doSearch = async () => { if (searchQuery.value.length < 2 && !selectedCategory.value) { searchResults.value = []; return; } isSearching.value = true; try { const params = new URLSearchParams(); if (searchQuery.value) params.set('query', searchQuery.value); if (selectedCategory.value) params.set('categoryId', selectedCategory.value); const result = await inventurApi.get(`searchArticles?${params}`); if (result.success) searchResults.value = result.articles; } catch (e) {} finally { isSearching.value = false; } }; const searchArticles = debounce(doSearch, 300); const selectSearchResult = async (article) => { await stopScanner(); scannedArticle.value = article; quantity.value = '1'; currentTab.value = 'scan'; const checkResult = await inventurApi.get( `checkAlreadyScanned?stocktakeId=${props.stocktake.id}&articleId=${article.id}` ); if (checkResult.success && checkResult.alreadyScanned) { alreadyScannedWarning.value = checkResult.existingItem; } }; const loadHistory = async () => { isLoadingHistory.value = true; try { const result = await inventurApi.get(`getMyScans?stocktakeId=${props.stocktake.id}`); if (result.success) recentScans.value = result.items; } catch (e) {} finally { isLoadingHistory.value = false; } }; const appendDigit = (digit) => { if (digit === '.' && quantity.value.includes('.')) return; if (quantity.value === '0' && digit !== '.') { quantity.value = digit; } else { quantity.value += digit; } }; const deleteDigit = () => { quantity.value = quantity.value.length > 1 ? quantity.value.slice(0, -1) : '0'; }; const clearQuantity = () => { quantity.value = '0'; }; const handleClose = async () => { await stopScanner(); emit('close'); }; const switchTab = async (tab) => { currentTab.value = tab; if (tab === 'scan' && !scannedArticle.value) { await nextTick(); await startScanner(); } else if (tab === 'search') { await stopScanner(); await loadCategories(); } else if (tab === 'history') { await stopScanner(); await loadHistory(); } }; const cancelScan = async () => { scannedArticle.value = null; alreadyScannedWarning.value = null; quantity.value = '1'; await startScanner(); }; onMounted(async () => { await startScanner(); }); onUnmounted(async () => { await stopScanner(); }); return { currentTab, isLoading, isScannerActive, scannerError, scannedArticle, quantity, rack, shelf, searchQuery, searchResults, categories, selectedCategory, isSearching, recentScans, isLoadingHistory, alreadyScannedWarning, showKeypad, canSubmit, startScanner, stopScanner, submitScan, searchArticles, selectSearchResult, loadHistory, appendDigit, deleteDigit, clearQuantity, handleClose, switchTab, cancelScan }; }, template: `
{{ stocktake.title || 'Inventur #' + stocktake.stocktakeNumber }}

{{ scannerError }}

QR-Code scannen oder Artikel suchen

Bereits gescannt

Menge: {{ alreadyScannedWarning.countedQuantity }}
Von: {{ alreadyScannedWarning.scannedBy }}
Am: {{ alreadyScannedWarning.scannedAt }}

{{ scannedArticle.title }}

Art.-Nr.: {{ scannedArticle.articleNumber }}

Kategorie: {{ scannedArticle.categoryName }}

{{ quantity }}

{{ searchQuery.length < 2 ? 'Mindestens 2 Zeichen eingeben' : 'Keine Artikel gefunden' }}

{{ article.title }}

{{ article.articleNumber }}

Noch keine Scans

{{ scan.articleTitle }}

{{ scan.articleNumber }}

{{ scan.countedQuantity }} {{ scan.unit }}

{{ scan.scannedAt }}

{{ quantity }}
` };