diff --git a/application/WarehouseArticle/WarehouseArticleController.php b/application/WarehouseArticle/WarehouseArticleController.php index fd3af534c..cfee60901 100644 --- a/application/WarehouseArticle/WarehouseArticleController.php +++ b/application/WarehouseArticle/WarehouseArticleController.php @@ -56,12 +56,16 @@ class WarehouseArticleController extends TTCrud { protected function beforeCreate() { if (!in_array($this->user->id, [2, 5, 6, 145, 14])) self::sendError("Sie haben keine Berechtigung, Artikel zu erstellen."); + + $this->validateArticleNumber($_POST); return true; } protected function beforeUpdate($postData): bool { if (!in_array($this->user->id, [2, 5, 6, 145, 14])) self::sendError("Sie haben keine Berechtigung, Artikel zu bearbeiten."); + + $this->validateArticleNumber($postData, $postData['id'] ?? null); (new WarehouseHistoryController)->create($postData, $this->mod); return true; } @@ -84,6 +88,38 @@ class WarehouseArticleController extends TTCrud { self::updateSellPrices($postData['id']); } + /** + * Validate article number for duplicates and correct category prefix + */ + private function validateArticleNumber(array $postData, ?int $excludeId = null): void { + $articleNumber = $postData['articleNumber'] ?? ''; + $categoryId = $postData['category_id'] ?? null; + + if (empty($articleNumber)) { + self::sendError("Artikelnummer ist erforderlich."); + } + + // Check for duplicate article number + $existingArticles = WarehouseArticleModel::getAll(['articleNumber' => $articleNumber]); + foreach ($existingArticles as $existing) { + if ($excludeId === null || $existing->id != $excludeId) { + self::sendError("Artikelnummer '{$articleNumber}' existiert bereits (Artikel ID: {$existing->id})."); + } + } + + // Validate category prefix + if ($categoryId) { + $category = WarehouseCategory::get($categoryId); + if ($category && $category->articleNumberPrefix) { + $expectedPrefix = $category->articleNumberPrefix; + $articlePrefix = substr($articleNumber, 0, strlen($expectedPrefix)); + if ($articlePrefix !== $expectedPrefix) { + self::sendError("Artikelnummer muss mit dem Kategorie-Prefix '{$expectedPrefix}' beginnen."); + } + } + } + } + public static function updateSellPrices(int $id): void { // Added return type hint $a = WarehouseArticleModel::get($id); if (!$a instanceof WarehouseArticleModel) throw new Exception("Invalid article type"); diff --git a/public/js/pages/WarehouseArticle/WarehouseArticle.css b/public/js/pages/WarehouseArticle/WarehouseArticle.css index e61cde973..706bbb0ff 100644 --- a/public/js/pages/WarehouseArticle/WarehouseArticle.css +++ b/public/js/pages/WarehouseArticle/WarehouseArticle.css @@ -13,6 +13,24 @@ background-color: #f8d7da !important; } +/* Last Edited Row Highlighting */ +.last-edited-row { + background-color: #fff3cd !important; + animation: highlight-fade 5s ease-out forwards; +} + +@keyframes highlight-fade { + 0% { + background-color: #fff3cd; + } + 70% { + background-color: #fff3cd; + } + 100% { + background-color: transparent; + } +} + /* * Modal Layout */ diff --git a/public/js/pages/WarehouseArticle/WarehouseArticle.js b/public/js/pages/WarehouseArticle/WarehouseArticle.js index 38572c325..362a3943b 100644 --- a/public/js/pages/WarehouseArticle/WarehouseArticle.js +++ b/public/js/pages/WarehouseArticle/WarehouseArticle.js @@ -1,5 +1,13 @@ +// Track last edited article for highlighting +window.TT_CONFIG.lastEditedArticleId = null; + window.TT_CONFIG.CRUD_CONFIG.customRowClass = (row) => { - if (row.isEndOfLife) return 'end-of-life'; + const classes = []; + if (row.isEndOfLife) classes.push('end-of-life'); + if (window.TT_CONFIG.lastEditedArticleId && row.id == window.TT_CONFIG.lastEditedArticleId) { + classes.push('last-edited-row'); + } + return classes.join(' '); } async function handleApiResponse(responsePromise) { @@ -14,15 +22,19 @@ async function handleApiResponse(responsePromise) { } Vue.component('warehouse-article-prices', { - props: {id: {type: Number, required: true}}, + props: { + id: {type: Number, required: true}, + cheapestPurchasePrice: {type: Number, default: null} + }, template: `