'in_progress']); $result = []; foreach ($stocktakes as $stocktake) { $location = $stocktake->getLocation(); $result[] = [ 'id' => $stocktake->id, 'stocktakeNumber' => $stocktake->stocktakeNumber, 'title' => $stocktake->title, 'locationName' => $location ? $location->title : 'Unbekannt', 'totalScannedItems' => $stocktake->totalScannedItems, 'startedAt' => $stocktake->startedAt ? date('d.m.Y H:i', $stocktake->startedAt) : null, ]; } self::returnJson(['success' => true, 'stocktakes' => $result]); } public function getStocktakeAction() { $id = intval($this->request->id); if (!$id) { self::returnJson(['success' => false, 'message' => 'Keine Inventur-ID angegeben']); return; } $stocktake = WarehouseStocktakeModel::get($id); if (!$stocktake) { self::returnJson(['success' => false, 'message' => 'Inventur nicht gefunden']); return; } $location = $stocktake->getLocation(); self::returnJson([ 'success' => true, 'stocktake' => [ 'id' => $stocktake->id, 'stocktakeNumber' => $stocktake->stocktakeNumber, 'title' => $stocktake->title, 'status' => $stocktake->status, 'locationId' => $stocktake->warehouseLocationId, 'locationName' => $location ? $location->title : 'Unbekannt', 'totalScannedItems' => $stocktake->totalScannedItems, 'startedAt' => $stocktake->startedAt ? date('d.m.Y H:i', $stocktake->startedAt) : null, ] ]); } public function getArticleAction() { $code = $this->request->code; if (!$code) { self::returnJson(['success' => false, 'message' => 'Kein Code angegeben']); return; } $articleId = null; if (preg_match('/^(?:WA|WH):(\d+):/', $code, $matches)) { $articleId = intval($matches[1]); } else { $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; } $category = WarehouseCategory::get($article->category_id); self::returnJson([ 'success' => true, 'article' => [ 'id' => $article->id, 'articleNumber' => $article->articleNumber, 'title' => $article->title, 'description' => $article->description ?? '', 'unit' => $article->unit ?? 'Stk.', 'categoryName' => $category ? $category->name : '', ] ]); } public function searchArticlesAction() { $query = $this->request->query ?? ''; $categoryId = intval($this->request->categoryId ?? 0); $db = $this->db(); $conditions = ["(isEndOfLife IS NULL OR isEndOfLife = 0)"]; if ($query && strlen($query) >= 2) { $escapedQuery = $db->escape($query); $conditions[] = "(articleNumber LIKE '%{$escapedQuery}%' OR title LIKE '%{$escapedQuery}%' OR description LIKE '%{$escapedQuery}%')"; } if ($categoryId > 0) { $conditions[] = "category_id = {$categoryId}"; } if (count($conditions) === 1 && !$categoryId) { self::returnJson(['success' => true, 'articles' => []]); return; } $whereClause = implode(' AND ', $conditions); $result = $db->query("SELECT id, articleNumber, title, unit, category_id FROM WarehouseArticle WHERE {$whereClause} ORDER BY title ASC LIMIT 50"); $articles = []; while ($row = $result->fetch_assoc()) { $articles[] = [ 'id' => intval($row['id']), 'articleNumber' => $row['articleNumber'], 'title' => $row['title'], 'unit' => $row['unit'] ?? 'Stk.', 'categoryId' => intval($row['category_id'] ?? 0), ]; } self::returnJson(['success' => true, 'articles' => $articles]); } public function getCategoriesAction() { $db = $this->db(); $res = $db->query("SELECT id, name FROM WarehouseCategory ORDER BY name ASC"); $categories = []; while ($row = $res->fetch_assoc()) { $categories[] = [ 'id' => intval($row['id']), 'name' => $row['name'], ]; } self::returnJson(['success' => true, 'categories' => $categories]); } public function checkAlreadyScannedAction() { $stocktakeId = intval($this->request->stocktakeId); $articleId = intval($this->request->articleId); if (!$stocktakeId || !$articleId) { self::returnJson(['success' => false, 'message' => 'Fehlende Parameter']); return; } $existing = WarehouseStocktakeItemModel::getFirst([ 'stocktakeId' => $stocktakeId, 'articleId' => $articleId, 'overwrittenById' => null ]); if ($existing) { $db = $this->db(); $scannedByResult = $db->query("SELECT name FROM Worker WHERE id = {$existing->scannedBy}"); $scannedByRow = $scannedByResult->fetch_assoc(); self::returnJson([ 'success' => true, 'alreadyScanned' => true, 'existingItem' => [ 'id' => $existing->id, 'countedQuantity' => $existing->countedQuantity, 'scannedAt' => $existing->scannedAt ? date('d.m.Y H:i', $existing->scannedAt) : null, 'scannedBy' => $scannedByRow ? $scannedByRow['name'] : 'Unbekannt', ] ]); } else { self::returnJson(['success' => true, 'alreadyScanned' => false]); } } public function submitScanAction() { $postData = $this->getPostData(); $stocktakeId = intval($postData['stocktakeId'] ?? 0); $articleId = intval($postData['articleId'] ?? 0); $quantity = floatval($postData['quantity'] ?? 0); $rack = $postData['rack'] ?? null; $shelf = $postData['shelf'] ?? null; $note = $postData['note'] ?? null; $overwrite = boolval($postData['overwrite'] ?? false); $overwriteItemId = intval($postData['overwriteItemId'] ?? 0); if (!$stocktakeId || !$articleId) { self::returnJson(['success' => false, 'message' => 'Fehlende Parameter']); return; } if ($quantity <= 0) { self::returnJson(['success' => false, 'message' => 'Menge muss größer als 0 sein']); return; } $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; } $article = WarehouseArticleModel::get($articleId); if (!$article) { self::returnJson(['success' => false, 'message' => 'Artikel nicht gefunden']); return; } $db = $this->db(); if ($overwrite && $overwriteItemId) { $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->user->id}, {$this->user->id}, " . time() . ")"); $itemId = $db->insert_id; $db->query("UPDATE WarehouseStocktakeItem SET overwrittenById = {$itemId} WHERE id = {$overwriteItemId}"); $finalQuantity = $quantity; WarehouseStocktakeLogModel::log($stocktakeId, 'overwritten', $itemId, [ 'articleId' => $articleId, 'articleNumber' => $article->articleNumber, 'articleTitle' => $article->title, 'quantity' => $quantity, 'overwrittenItemId' => $overwriteItemId, ]); $stocktake->updateProgress(); self::returnJson([ 'success' => true, 'message' => "'{$article->title}' überschrieben ({$quantity} {$article->unit})", 'item' => [ 'id' => $itemId, 'articleId' => $articleId, 'articleNumber' => $article->articleNumber, 'articleTitle' => $article->title, 'countedQuantity' => $finalQuantity, 'unit' => $article->unit ?? 'Stk.', 'rack' => $rack, 'shelf' => $shelf, 'isOverwrite' => true, ] ]); return; } $existing = WarehouseStocktakeItemModel::getFirst([ 'stocktakeId' => $stocktakeId, 'articleId' => $articleId, 'overwrittenById' => null ]); if ($existing) { $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->user->id} WHERE id = {$existing->id}"); $itemId = $existing->id; $finalQuantity = $newQuantity; $isUpdate = true; } else { $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->user->id}, {$this->user->id}, " . time() . ")"); $itemId = $db->insert_id; $finalQuantity = $quantity; $isUpdate = false; } $stocktake->updateProgress(); WarehouseStocktakeLogModel::log($stocktakeId, 'scanned', $itemId, [ 'articleId' => $articleId, 'articleNumber' => $article->articleNumber, 'articleTitle' => $article->title, 'quantity' => $quantity, 'totalQuantity' => $finalQuantity, 'rack' => $rack, 'shelf' => $shelf, 'isUpdate' => $isUpdate, ]); self::returnJson([ 'success' => true, 'message' => $isUpdate ? "Menge für '{$article->title}' erhöht auf {$finalQuantity}" : "'{$article->title}' hinzugefügt ({$quantity} {$article->unit})", 'item' => [ 'id' => $itemId, 'articleId' => $articleId, 'articleNumber' => $article->articleNumber, 'articleTitle' => $article->title, 'countedQuantity' => $finalQuantity, 'unit' => $article->unit ?? 'Stk.', 'rack' => $rack, 'shelf' => $shelf, 'isUpdate' => $isUpdate, ] ]); } public function getMyScansAction() { $stocktakeId = intval($this->request->stocktakeId); if (!$stocktakeId) { self::returnJson(['success' => false, 'message' => 'Keine Inventur-ID angegeben']); return; } $db = $this->db(); $result = $db->query("SELECT si.*, wa.articleNumber, wa.title as articleTitle, wa.unit FROM WarehouseStocktakeItem si JOIN WarehouseArticle wa ON wa.id = si.articleId WHERE si.stocktakeId = {$stocktakeId} AND si.scannedBy = {$this->user->id} ORDER BY si.scannedAt DESC LIMIT 50"); $items = []; while ($row = $result->fetch_assoc()) { $items[] = [ 'id' => intval($row['id']), 'articleId' => intval($row['articleId']), 'articleNumber' => $row['articleNumber'], 'articleTitle' => $row['articleTitle'], 'countedQuantity' => floatval($row['countedQuantity']), 'unit' => $row['unit'] ?? 'Stk.', 'rack' => $row['rack'], 'shelf' => $row['shelf'], 'scannedAt' => $row['scannedAt'] ? date('H:i', $row['scannedAt']) : null, ]; } self::returnJson(['success' => true, 'items' => $items]); } public function getProgressAction() { $stocktakeId = intval($this->request->stocktakeId); if (!$stocktakeId) { self::returnJson(['success' => false, 'message' => 'Keine Inventur-ID angegeben']); return; } $stocktake = WarehouseStocktakeModel::get($stocktakeId); if (!$stocktake) { self::returnJson(['success' => false, 'message' => 'Inventur nicht gefunden']); return; } $db = $this->db(); $totalResult = $db->query("SELECT COUNT(*) as count FROM WarehouseStocktakeItem WHERE stocktakeId = {$stocktakeId}"); $totalRow = $totalResult->fetch_assoc(); $totalScanned = intval($totalRow['count']); $myResult = $db->query("SELECT COUNT(*) as count FROM WarehouseStocktakeItem WHERE stocktakeId = {$stocktakeId} AND scannedBy = {$this->user->id}"); $myRow = $myResult->fetch_assoc(); $myScanned = intval($myRow['count']); self::returnJson([ 'success' => true, 'progress' => [ 'totalScanned' => $totalScanned, 'myScanned' => $myScanned, 'status' => $stocktake->status, ] ]); } }