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 }}
{{ searchQuery.length < 2 ? 'Mindestens 2 Zeichen eingeben' : 'Keine Artikel gefunden' }}
{{ article.title }}
{{ article.articleNumber }}
{{ scan.articleTitle }}
{{ scan.articleNumber }}
{{ scan.countedQuantity }} {{ scan.unit }}
{{ scan.scannedAt }}
`
};