Files
thetool/application/WarehouseStocktakeItem/WarehouseStocktakeItemController.php

196 lines
7.3 KiB
PHP

<?php
class WarehouseStocktakeItemController extends TTCrud {
protected string $headerTitle = 'Inventur-Artikel';
protected string $createText = 'Artikel hinzufügen';
protected array $columns = [
['key' => 'articleId', 'text' => 'Artikel', 'required' => true,
'modal' => ['type' => 'autocomplete', 'apiUrl' => '/WarehouseArticle/autocomplete'],
'table' => ['priority' => 10]],
['key' => 'countedQuantity', 'text' => 'Menge', 'required' => true,
'modal' => ['type' => 'number'],
'table' => ['priority' => 9]],
['key' => 'rack', 'text' => 'Regal', 'required' => false,
'modal' => ['type' => 'text'],
'table' => ['priority' => 8]],
['key' => 'shelf', 'text' => 'Fach', 'required' => false,
'modal' => ['type' => 'text'],
'table' => ['priority' => 7]],
['key' => 'note', 'text' => 'Notiz', 'required' => false,
'modal' => ['type' => 'textarea'],
'table' => ['priority' => 6]],
['key' => 'scannedAt', 'text' => 'Gescannt am', 'required' => false,
'modal' => false,
'table' => ['priority' => 5]],
['key' => 'actions', 'text' => 'Aktionen', 'required' => false,
'modal' => false,
'table' => ['filter' => false, 'sortable' => false]],
];
protected array $permissionCheck = ['WarehouseUser'];
protected function formatRow($row) {
// Format article
if ($row['articleId']) {
$article = WarehouseArticleModel::get($row['articleId']);
$row['articleId'] = $article ? "[{$article->articleNumber}] {$article->title}" : 'Unbekannt';
}
// Format scannedAt
if ($row['scannedAt']) {
$row['scannedAt'] = date('d.m.Y H:i', $row['scannedAt']);
} else {
$row['scannedAt'] = '-';
}
return $row;
}
/**
* Add item via scan (used by PWA)
*/
protected function scanItemAction() {
$stocktakeId = intval($this->request->stocktakeId);
$articleId = intval($this->request->articleId);
$quantity = floatval($this->request->quantity);
$rack = $this->request->rack ?? null;
$shelf = $this->request->shelf ?? null;
$note = $this->request->note ?? null;
if (!$stocktakeId || !$articleId) {
self::returnJson(['success' => false, 'message' => 'Fehlende Parameter']);
return;
}
// Verify stocktake exists and is in progress
$stocktake = WarehouseStocktakeModel::get($stocktakeId);
if (!$stocktake) {
self::returnJson(['success' => false, 'message' => 'Inventur nicht gefunden']);
return;
}
if ($stocktake->status !== 'in_progress') {
self::returnJson(['success' => false, 'message' => 'Inventur ist nicht aktiv']);
return;
}
// Verify article exists
$article = WarehouseArticleModel::get($articleId);
if (!$article) {
self::returnJson(['success' => false, 'message' => 'Artikel nicht gefunden']);
return;
}
// Check if this article was already scanned in this stocktake
$existing = WarehouseStocktakeItemModel::getFirst([
'stocktakeId' => $stocktakeId,
'articleId' => $articleId
]);
$db = FronkDB::singleton();
if ($existing) {
// Update existing entry - add to quantity
$newQuantity = $existing->countedQuantity + $quantity;
$db->query("UPDATE WarehouseStocktakeItem SET
countedQuantity = {$newQuantity},
rack = " . ($rack ? "'{$db->escape($rack)}'" : "rack") . ",
shelf = " . ($shelf ? "'{$db->escape($shelf)}'" : "shelf") . ",
scannedAt = " . time() . ",
scannedBy = {$this->me->id}
WHERE id = {$existing->id}");
$itemId = $existing->id;
$message = "Artikel aktualisiert: {$article->title} (Neue Menge: {$newQuantity})";
} else {
// Create new entry
$db->query("INSERT INTO WarehouseStocktakeItem
(stocktakeId, articleId, countedQuantity, rack, shelf, note, scannedAt, scannedBy, createBy, `create`)
VALUES ({$stocktakeId}, {$articleId}, {$quantity},
" . ($rack ? "'{$db->escape($rack)}'" : "NULL") . ",
" . ($shelf ? "'{$db->escape($shelf)}'" : "NULL") . ",
" . ($note ? "'{$db->escape($note)}'" : "NULL") . ",
" . time() . ", {$this->me->id}, {$this->me->id}, " . time() . ")");
$itemId = $db->insert_id;
$message = "Artikel hinzugefügt: {$article->title} (Menge: {$quantity})";
}
// Update stocktake progress
$stocktake->updateProgress();
// Log the scan
WarehouseStocktakeLogModel::log($stocktakeId, 'scanned', $itemId, [
'articleId' => $articleId,
'articleNumber' => $article->articleNumber,
'articleTitle' => $article->title,
'quantity' => $quantity,
'rack' => $rack,
'shelf' => $shelf,
]);
self::returnJson([
'success' => true,
'message' => $message,
'item' => [
'id' => $itemId,
'articleId' => $articleId,
'articleNumber' => $article->articleNumber,
'articleTitle' => $article->title,
'countedQuantity' => $existing ? ($existing->countedQuantity + $quantity) : $quantity,
'rack' => $rack,
'shelf' => $shelf,
],
'totalScanned' => $stocktake->totalScannedItems + 1,
]);
}
/**
* Get article info by QR code or article number
*/
protected function getArticleByCodeAction() {
$code = $this->request->code;
if (!$code) {
self::returnJson(['success' => false, 'message' => 'Kein Code angegeben']);
return;
}
// Try to parse QR code format: WA:articleId:articleNumber (Warehouse Article)
// Also accept WH: for backwards compatibility
$articleId = null;
if (preg_match('/^(?:WA|WH):(\d+):/', $code, $matches)) {
$articleId = intval($matches[1]);
} else {
// Try to find by article number
$article = WarehouseArticleModel::getFirst(['articleNumber' => $code]);
if ($article) {
$articleId = $article->id;
}
}
if (!$articleId) {
self::returnJson(['success' => false, 'message' => 'Artikel nicht gefunden']);
return;
}
$article = WarehouseArticleModel::get($articleId);
if (!$article) {
self::returnJson(['success' => false, 'message' => 'Artikel nicht gefunden']);
return;
}
self::returnJson([
'success' => true,
'article' => [
'id' => $article->id,
'articleNumber' => $article->articleNumber,
'title' => $article->title,
'description' => $article->description ?? '',
'unit' => $article->unit ?? 'Stk.',
]
]);
}
}