195 lines
7.2 KiB
PHP
195 lines
7.2 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: WH:articleId:articleNumber
|
|
$articleId = null;
|
|
if (preg_match('/^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.',
|
|
]
|
|
]);
|
|
}
|
|
}
|