From 4108eb99c9183e5111a131a1b1ff7ed36f55e3b4 Mon Sep 17 00:00:00 2001 From: Luca Haid Date: Thu, 24 Apr 2025 13:39:26 +0200 Subject: [PATCH] new update --- .../WarehouseArticleController.php | 24 +- .../WarehouseArticleModel.php | 2 +- .../WarehouseArticlePriceController.php | 4 + .../WarehouseCategory/WarehouseCategory.php | 23 + .../WarehouseCategoryController.php | 30 + .../WarehouseOfferController.php | 67 +- .../WarehouseOfferTemplateModel.php | 2 + .../WarehouseShippingNoteController.php | 3 - .../WarehouseShippingNoteTextElement.php | 9 - ...houseShippingNoteTextElementController.php | 48 -- .../WarehouseShippingNoteTextElementModel.php | 10 - .../20250424100000_warehouse_modify_20.php | 176 +++++ lib/TTCrud/TTCrud.php | 4 + .../WarehouseArticle/WarehouseArticle.css | 39 +- .../WarehouseArticle/WarehouseArticle.js | 216 ++++-- .../WarehouseHistory/WarehouseHistoryModal.js | 5 +- .../js/pages/WarehouseOffer/WarehouseOffer.js | 20 +- .../js/pages/WarehouseOrder/WarehouseOrder.js | 1 - .../WarehouseShippingNote.js | 632 +++++++++++++----- .../WarehouseShippingNoteModal.js | 9 +- .../vue/tt-components/tt-autocomplete.js | 1 + .../vue/tt-components/tt-position-manager.js | 4 + 22 files changed, 992 insertions(+), 337 deletions(-) create mode 100644 application/WarehouseCategory/WarehouseCategory.php create mode 100644 application/WarehouseCategory/WarehouseCategoryController.php delete mode 100644 application/WarehouseShippingNoteTextElement/WarehouseShippingNoteTextElement.php delete mode 100644 application/WarehouseShippingNoteTextElement/WarehouseShippingNoteTextElementController.php delete mode 100644 application/WarehouseShippingNoteTextElement/WarehouseShippingNoteTextElementModel.php create mode 100644 db/migrations/20250424100000_warehouse_modify_20.php diff --git a/application/WarehouseArticle/WarehouseArticleController.php b/application/WarehouseArticle/WarehouseArticleController.php index 3180a1f02..38fbc9704 100644 --- a/application/WarehouseArticle/WarehouseArticleController.php +++ b/application/WarehouseArticle/WarehouseArticleController.php @@ -10,22 +10,20 @@ class WarehouseArticleController extends TTCrud { ['key' => 'title', 'text' => 'Titel', 'required' => true, 'table' => ['priority' => 9]], ['key' => 'articleNumber', 'text' => 'Nr.', 'required' => true], ['key' => 'description', 'text' => 'Beschreibung', 'required' => true], - ['key' => 'category', 'text' => 'Kategorie', 'required' => true], - ['key' => 'unit', 'text' => 'Einheit', 'required' => true,'table' => false], // Boolean value - ['key' => 'revenueAccount', 'text' => 'Erlöskonto', 'required' => true,'modal' => - ['type' => 'select', 'items' => [['value' => 0, 'text' => 'Dienstleistungen'], ['value' => 1, 'text' => 'Handelswaren']] - ], 'table' => false], // Boolean value + ['key' => 'category_id', 'text' => 'Kategorie', 'required' => true, 'modal' => ['type' => 'select', 'items' => []], 'table' => ['filter' => 'select']], + ['key' => 'unit', 'text' => 'Einheit', 'required' => true,'table' => false], + ['key' => 'revenueAccount', 'text' => 'Erlöskonto', 'required' => true,'modal' => ['type' => 'select', 'items' => [['value' => 0, 'text' => 'Dienstleistungen'], ['value' => 1, 'text' => 'Handelswaren']]], 'table' => false], ['key' => 'cheapestPurchasePrice', 'text' => 'Einkauf', 'modal' => false, 'table' => ['class' => 'text-center', 'suffix' => ' €']], ['key' => 'cheapestSellPrice', 'text' => 'Verkauf', 'modal' => false, 'table' => ['class' => 'text-center', 'suffix' => ' €']], - ['key' => 'warningAmount', 'text' => 'Warnmenge', 'required' => true,'modal' => ['type' => 'number'], 'table' => false], // Stock/inventory related - ['key' => 'criticalAmount', 'text' => 'Kritische Menge', 'required' => true,'modal' => ['type' => 'number'], 'table' => false], // Stock/inventory related - ['key' => 'isSerialDocumentation', 'text' => 'Seriennummern', 'required' => false,'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value - ['key' => 'isEShop', 'text' => 'Ist E-Shop', 'required' => false,'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value - ['key' => 'isEShopHide', 'text' => 'E-Shop Versteckt', 'required' => false,'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value + ['key' => 'warningAmount', 'text' => 'Warnmenge', 'required' => true,'modal' => ['type' => 'number'], 'table' => false], + ['key' => 'criticalAmount', 'text' => 'Kritische Menge', 'required' => true,'modal' => ['type' => 'number'], 'table' => false], + ['key' => 'isSerialDocumentation', 'text' => 'Seriennummern', 'required' => false,'modal' => ['type' => 'checkbox'], 'table' => false], + ['key' => 'isEShop', 'text' => 'Ist E-Shop', 'required' => false,'modal' => ['type' => 'checkbox'], 'table' => false], + ['key' => 'isEShopHide', 'text' => 'E-Shop Versteckt', 'required' => false,'modal' => ['type' => 'checkbox'], 'table' => false], ['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center', 'priority' => 8]] ]; - protected array $autocompleteColumns = ['articleNumber', 'title', 'description', 'category']; + protected array $autocompleteColumns = ['articleNumber', 'title', 'description']; protected array $additionalActions = [['key' => 'openHistory','title' => 'Historie','class' => 'fas fa-history text-secondary']]; // @formatter:on @@ -33,8 +31,12 @@ class WarehouseArticleController extends TTCrud { protected array $additionalJSVariables = ['WAREHOUSE_ADMIN' => true]; protected function prepareCrudConfig() { + $categories = array_map(fn($category) => ['value' => $category->id, 'text' => $category->name], WarehouseCategory::getAll()); + $this->columns[array_search('category_id', array_column($this->columns, 'key'))]['modal']['items'] = $categories; + if ($this->user->can('WarehouseAdmin')) return; + array_walk($this->columns, fn(&$col) => in_array($col['key'], ['actions', 'cheapestPurchasePrice', 'warningAmount', 'criticalAmount']) && $col['table'] = false); $this->createText = false; diff --git a/application/WarehouseArticle/WarehouseArticleModel.php b/application/WarehouseArticle/WarehouseArticleModel.php index 549e5bc08..12d651b76 100644 --- a/application/WarehouseArticle/WarehouseArticleModel.php +++ b/application/WarehouseArticle/WarehouseArticleModel.php @@ -5,7 +5,7 @@ class WarehouseArticleModel extends TTCrudBaseModel { public string $title; public string $articleNumber; public string $description; - public string $category; + public ?int $category_id; public ?float $cheapestPurchasePrice; public ?string $cheapestSellPrice; public int $warningAmount; diff --git a/application/WarehouseArticlePrice/WarehouseArticlePriceController.php b/application/WarehouseArticlePrice/WarehouseArticlePriceController.php index 5f27f25fa..a0862f347 100644 --- a/application/WarehouseArticlePrice/WarehouseArticlePriceController.php +++ b/application/WarehouseArticlePrice/WarehouseArticlePriceController.php @@ -95,4 +95,8 @@ class WarehouseArticlePriceController extends TTCrud { WarehouseArticleController::updateSellPrices($postData['articleId']); } + public function afterDelete($postData) { + WarehouseArticleController::updateSellPrices($postData['articleId']); + } + } \ No newline at end of file diff --git a/application/WarehouseCategory/WarehouseCategory.php b/application/WarehouseCategory/WarehouseCategory.php new file mode 100644 index 000000000..77703bb1b --- /dev/null +++ b/application/WarehouseCategory/WarehouseCategory.php @@ -0,0 +1,23 @@ + 'title', 'text' => 'Titel', 'required' => true,], + ['key' => 'description', 'text' => 'Beschreibung', 'required' => true], + ['key' => 'create', 'text' => 'Erstellt am', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']], + ['key' => 'create_by', 'text' => 'Erstellt von', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']], + ['key' => 'edit', 'text' => 'Bearbeitet am', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']], + ['key' => 'edit_by', 'text' => 'Bearbeitet von', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']], + ['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center', 'priority' => 10]], + ]; + // @formatter:on + + protected array $additionalActions = [['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary']]; + + protected function beforeUpdate($postData): bool { + (new WarehouseHistoryController)->create($postData, $this->mod); + return true; + } + + protected function getHistoryAction() { + self::returnJson((new WarehouseHistoryController)->getHistory($this->request->id, $this->mod, $this->columns)); + } +} \ No newline at end of file diff --git a/application/WarehouseOffer/WarehouseOfferController.php b/application/WarehouseOffer/WarehouseOfferController.php index 25eb11961..497d28c96 100644 --- a/application/WarehouseOffer/WarehouseOfferController.php +++ b/application/WarehouseOffer/WarehouseOfferController.php @@ -23,15 +23,16 @@ class WarehouseOfferController extends TTCrud { 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']], ]; - + protected array $additionalJSVariables = ['WAREHOUSE_ADMIN' => true]; protected array $permissionCheck = ['WarehouseAdmin']; - protected array $additionalActions = [['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary']]; + protected array $additionalActions = [['key' => 'openpdf', 'title' => 'PDF öffnen', 'class' => 'fas fa-file-pdf', 'color' => 'primary']]; protected function prepareCrudConfig(): void { $editorColumnIndex = array_search('editor', array_column($this->columns, 'key')); $this->columns[$editorColumnIndex]['modal']['items'] = array_map(function ($user) { return ['value' => intval($user->id), 'text' => $user->name]; }, UserModel::search(['employee' => true])); + if (!$this->user->can('WarehouseAdmin')) $this->additionalJSVariables['WAREHOUSE_ADMIN'] = false; } protected function beforeCreate(): bool { @@ -52,6 +53,7 @@ class WarehouseOfferController extends TTCrud { } protected function createTemplateAction() { + if (!$this->user->can('WarehouseAdmin')) self::sendError("Keine Berechtigung"); $_POST = json_decode(file_get_contents('php://input'), true); $templateId = WarehouseOfferTemplateModel::create([ @@ -67,7 +69,68 @@ class WarehouseOfferController extends TTCrud { self::returnJson(['success' => true, 'id' => $templateId]); } + protected function deleteTemplateAction() { + if (!$this->user->can('WarehouseAdmin')) self::sendError("Keine Berechtigung"); + WarehouseOfferTemplateModel::delete($this->request->id); + self::returnJson(['success' => true]); + } + protected function getTemplatesAction() { self::returnJson(WarehouseOfferTemplateModel::getAll()); } + + public function createPDFAction($returnFilename = false, $idOverride = null) { + //display errors + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + error_reporting(E_ALL); + + $id = $idOverride ?? $this->request->id; + if (strlen($id) < 1) self::sendError('ID fehlt'); + + $offer = WarehouseOfferModel::get($id); + if (!$offer->id) self::sendError('Angebot nicht gefunden'); + + $positions = json_decode($offer->positions, true); + $entries = []; + + foreach ($positions as $position) { + if (!isset($position['article'])) continue; + $article = WarehouseArticleModel::get($position['article']); + $position['articleText'] = WarehouseArticleModel::get($position['article'])->title; + $position['articleDescription'] = $article->description === $article->title ? "" : $article->description; + $position['articleUnit'] = $position['unit'] ?? $article->unit ?? 'Stk.'; + + if (isset($position['_group'])) { + $entries[$position['_group']][] = $position; + } else { + $entries[''][] = $position; + } + } + + $pdf_vars = [ + "offer" => $offer, + "entries" => $entries, + "bank_iban" => TT_INVOICE_BANK_IBAN, + "bank_bic" => TT_INVOICE_BANK_BIC, + "bank_bank" => TT_INVOICE_BANK_BANK, + "bank_owner" => TT_INVOICE_BANK_OWNER + ]; + + $headerHtml = file_get_contents(BASEDIR . "/Layout/default/WarehouseOffer/PDF_HEADER.html"); + $headerHtml = str_replace("{{ addressLine_header }}", +// {{ addressLine_1 }} {{ addressLine_2 }} {{ addressLine_3 }} {{ addressLine_4 }} {{ externalReference }} + $headerHtml = str_replace("{{ addressLine_1 }}", $offer->customerName, $headerHtml)); + $headerHtml = str_replace("{{ addressLine_2 }}", $offer->customerStreet, $headerHtml); + $headerHtml = str_replace("{{ addressLine_3 }}", $offer->customerZip . ' ' . $offer->customerCity, $headerHtml); + $headerHtml = str_replace("{{ addressLine_5 }}", $offer->customerVAT, $headerHtml); + $headerHtml = str_replace("{{ externalReference }}", $offer->customerReference, $headerHtml); + + + + + exit; + } + + } diff --git a/application/WarehouseOfferTemplate/WarehouseOfferTemplateModel.php b/application/WarehouseOfferTemplate/WarehouseOfferTemplateModel.php index 20465c360..ad63b36da 100644 --- a/application/WarehouseOfferTemplate/WarehouseOfferTemplateModel.php +++ b/application/WarehouseOfferTemplate/WarehouseOfferTemplateModel.php @@ -5,6 +5,7 @@ * * Represents a warehouse offer template with key details. * + * @property int $id Unique identifier for the template * @property string $templateName Name of the template * @property string $positions Details about positions in the offer * @property float $totalDiscount Total discount applied to the offer @@ -15,6 +16,7 @@ */ class WarehouseOfferTemplateModel extends TTCrudBaseModel { + public int $id; public string $templateName; public string $positions; public float $totalDiscount; diff --git a/application/WarehouseShippingNote/WarehouseShippingNoteController.php b/application/WarehouseShippingNote/WarehouseShippingNoteController.php index 2ad3902d4..2a134a6f6 100644 --- a/application/WarehouseShippingNote/WarehouseShippingNoteController.php +++ b/application/WarehouseShippingNote/WarehouseShippingNoteController.php @@ -529,9 +529,6 @@ class WarehouseShippingNoteController extends TTCrud { return $distance; } - $fromData = geocode($from); - $toData = geocode($to); - $distance = route($from, $to); $roundedDistanceKm = round($distance / 1000, 0); diff --git a/application/WarehouseShippingNoteTextElement/WarehouseShippingNoteTextElement.php b/application/WarehouseShippingNoteTextElement/WarehouseShippingNoteTextElement.php deleted file mode 100644 index 093fcc42d..000000000 --- a/application/WarehouseShippingNoteTextElement/WarehouseShippingNoteTextElement.php +++ /dev/null @@ -1,9 +0,0 @@ - 'title', 'text' => 'Titel', 'required' => true], - ['key' => 'content', 'text' => 'Text', 'required' => true, 'modal' => []], - ['key' => 'create', 'text' => 'Erstellt', 'required' => false, 'modal' => false, 'table' => ['filter' => 'datetime', 'class' => 'text-nowrap']], - ['key' => 'createBy', 'text' => 'Erstellt von', 'required' => false, 'modal' => [ - 'type' => 'select', 'items' => [],'visible' => false], ], - ['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center', 'priority' => 10]], - ]; - // @formatter:on - - protected array $infoMessages = ['create' => 'Lieferschein Textelement wurde erstellt', - 'update' => 'Lieferschein Textelement wurde aktualisiert', - 'delete' => 'Lieferschein Textelement wurde gelöscht', - 'noChanges' => 'Keine Änderungen']; - - protected function prepareCrudConfig() { - // add all users to createBy column - $this->columns[array_search('createBy', array_column($this->columns, 'key'))]['modal']['items'] = array_map(function ($user) { - return ['value' => $user->id, 'text' => $user->name]; - }, UserModel::getAll()); - } - - protected function beforeUpdate($postData): bool { - (new WarehouseHistoryController)->create($postData, $this->mod); - return true; - } - - protected function getHistoryAction() { - $history = WarehouseHistoryModel::getByRowId($this->request->id, $this->mod); - - $history = array_map(function ($item) { - $item = (array) $item; - - $item['columnHeader'] = $this->columns[array_search($item['key'], array_column($this->columns, 'key'))]['text']; - return $item; - }, $history); - - self::returnJson($history); - } - -} \ No newline at end of file diff --git a/application/WarehouseShippingNoteTextElement/WarehouseShippingNoteTextElementModel.php b/application/WarehouseShippingNoteTextElement/WarehouseShippingNoteTextElementModel.php deleted file mode 100644 index c95c53237..000000000 --- a/application/WarehouseShippingNoteTextElement/WarehouseShippingNoteTextElementModel.php +++ /dev/null @@ -1,10 +0,0 @@ -getEnvironment() == "thetool") { + + // --- Start: Original WarehouseShippingNoteTextElement logic --- + $WarehouseShippingNoteTextElementTable = $this->table("WarehouseShippingNoteTextElement"); + if ($WarehouseShippingNoteTextElementTable->exists()) { + $WarehouseShippingNoteTextElementTable->drop()->save(); + } + + $WarehouseShippingNoteTextElementTable = $this->table("WarehouseShippingNoteTextElement", ['id' => 'id', 'signed' => false]); + if (!$WarehouseShippingNoteTextElementTable->exists()) { + $WarehouseShippingNoteTextElementTable + ->addColumn("title", "string", ["limit" => 255]) + ->addColumn("content", "text") + // Use Literal::from('CURRENT_TIMESTAMP') or similar if your DB supports it for default timestamps + // Using a fixed default like this might not be ideal + ->addColumn("create", "integer", ["default" => 1728541890]) + ->addColumn("createBy", "integer", ["default" => 1]) + ->create(); // Use create() instead of save() for new tables + } + // --- End: Original WarehouseShippingNoteTextElement logic --- + + + // --- Start: New WarehouseArticle Category Migration Logic --- + + // 1. Populate WarehouseCategory with distinct categories from WarehouseArticle + // Using execute() for complex INSERT with SELECT, TRIM, UNIX_TIMESTAMP, and ON DUPLICATE KEY UPDATE + // Assumes WarehouseCategory table exists with columns: name, description, create, create_by + $populateCategorySql = <<execute($populateCategorySql); + + // Get the WarehouseArticle table object + $warehouseArticleTable = $this->table('WarehouseArticle'); + + // 2. Add the new category_id column to WarehouseArticle + $warehouseArticleTable + ->addColumn('category_id', 'integer', [ + 'null' => true, // Allow NULL values + 'signed' => false, // Typically IDs are unsigned + 'default' => null, + 'after' => 'description', // Position the column + 'comment' => 'References the WarehouseCategory table ID' + ]) + ->update(); // Apply the column addition + + // 3. Update WarehouseArticle.category_id based on the old category name + // Using execute() for the UPDATE with JOIN and COLLATE + // *** IMPORTANT: Adjust 'utf8mb4_unicode_ci' if your columns use a different collation *** + $updateCategoryIdSql = <<execute($updateCategoryIdSql); + + // 4. Drop the old category column and its index + // Phinx automatically handles index removal when removing the column if it was defined via Phinx. + // However, the original index 'category' might have been created manually. + // Best practice: Explicitly remove the index first if unsure. + if ($warehouseArticleTable->hasIndex('category')) { + $warehouseArticleTable->removeIndexByName('category')->update(); + } + // Now remove the column itself + $warehouseArticleTable->removeColumn('category')->update(); + + // 5. Add an index to the new category_id column + // Check if index already exists before adding + if (!$warehouseArticleTable->hasIndex('category_id')) { + $warehouseArticleTable->addIndex('category_id', ['name' => 'idx_category_id'])->update(); + } + + // --- End: New WarehouseArticle Category Migration Logic --- + } + } + + public function down(): void { + // Only run this migration in the 'thetool' environment + if ($this->getEnvironment() == "thetool") { + + // --- Start: Reversal of WarehouseArticle Category Migration Logic --- + + $warehouseArticleTable = $this->table('WarehouseArticle'); + + // 1. Remove the index from category_id + if ($warehouseArticleTable->hasIndexByName('idx_category_id')) { + $warehouseArticleTable->removeIndexByName('idx_category_id')->update(); + } + + // 2. Add the old 'category' column back + // Ensure the definition matches the original state as closely as possible. + $warehouseArticleTable + ->addColumn('category', 'string', [ + 'limit' => 255, + 'null' => false, // Assuming it was NOT NULL originally based on CREATE TABLE statement + 'collation' => 'utf8mb4_unicode_ci', // Match original collation + 'encoding' => 'utf8mb4', + 'after' => 'description' // Place it back roughly where it was + ]) + ->update(); + + // 3. Add the index back to the 'category' column + // Check if index already exists before adding + if (!$warehouseArticleTable->hasIndex('category')) { + $warehouseArticleTable->addIndex('category')->update(); + } + + // 4. Populate the old 'category' column based on 'category_id' + // Using execute() for the UPDATE with JOIN + // *** IMPORTANT: Adjust 'utf8mb4_unicode_ci' if your columns use a different collation *** + $repopulateCategorySql = <<execute($repopulateCategorySql); + + // 5. Remove the 'category_id' column + $warehouseArticleTable->removeColumn('category_id')->update(); + + // --- End: Reversal of WarehouseArticle Category Migration Logic --- + + + // --- Start: Original Down Logic (potentially destructive/incomplete) --- + // Note: This part only recreates WarehouseShippingNoteTextElement and drops WarehouseCategory. + // It does NOT fully revert the state before the *entire* 'up' method ran if WarehouseCategory existed before. + // Consider if this is the desired behavior. + + $WarehouseShippingNoteTextElementTable = $this->table("WarehouseShippingNoteTextElement", ['id' => 'id', 'signed' => false]); + // This check seems redundant if the goal is to ensure it exists after 'down' + // if (!$WarehouseShippingNoteTextElementTable->exists()) { + // Drop if exists, then recreate to ensure clean state matching original 'down' intent + if ($WarehouseShippingNoteTextElementTable->exists()) { + $WarehouseShippingNoteTextElementTable->drop()->save(); + } + $WarehouseShippingNoteTextElementTable = $this->table("WarehouseShippingNoteTextElement", ['id' => 'id', 'signed' => false]); // Re-initialize after drop + $WarehouseShippingNoteTextElementTable + ->addColumn("title", "string", ["limit" => 255]) + ->addColumn("content", "text") + ->addColumn("create", "integer", ["default" => 1728541890]) + ->addColumn("createBy", "integer", ["default" => 1]) + ->create(); // Use create() + // } + + $WarehouseCategoryTable = $this->table("WarehouseCategory", ['id' => 'id', 'signed' => false]); + if ($WarehouseCategoryTable->exists()) { + // This drops the category table entirely, including data potentially added outside this migration + $WarehouseCategoryTable->drop()->save(); + } + // --- End: Original Down Logic --- + } + } +} diff --git a/lib/TTCrud/TTCrud.php b/lib/TTCrud/TTCrud.php index 928d8f3b0..61e84108e 100644 --- a/lib/TTCrud/TTCrud.php +++ b/lib/TTCrud/TTCrud.php @@ -295,6 +295,10 @@ class TTCrud extends mfBaseController { }, $data)); } + protected function getAllAction() { + self::returnJson($this->model::getAll($this->postData['filters'] ?? [])); + } + protected function getByIdAction() { $id = $_GET['id'] ?? null; if (!$id || !is_numeric($id)) { diff --git a/public/js/pages/WarehouseArticle/WarehouseArticle.css b/public/js/pages/WarehouseArticle/WarehouseArticle.css index 4e6b9cf1e..764bec90a 100644 --- a/public/js/pages/WarehouseArticle/WarehouseArticle.css +++ b/public/js/pages/WarehouseArticle/WarehouseArticle.css @@ -1,5 +1,38 @@ -.warehouse-article-prices > div { +/* style.css */ +.warehouse-article-prices > div, +.warehouse-article-distributor > div { display: grid; - grid-template-columns: repeat(4, minmax(120px, 1fr)) 72px; - gap: 10px; + grid-template-columns: repeat(3, minmax(150px, 1fr)) 120px; /* Defines 4 columns */ + gap: 12px; /* Use gap for spacing */ + margin-bottom: 8px; /* Add small gap between rows */ +} + +.warehouse-article-prices .form-group, +.warehouse-article-distributor .form-group { + margin-bottom: 0; /* Keep inputs tight in grid cells */ +} + +@media (max-width: 992px) { + .warehouse-article-prices > div, + .warehouse-article-distributor > div { + display: block; /* Stack items vertically */ + border: 1px solid #eee; /* Lighter border */ + padding: 10px; + margin-bottom: 10px; + } + + .warehouse-article-prices > div > *, + .warehouse-article-distributor > div > * { + display: block; /* Ensure children are block */ + margin-bottom: 10px !important; /* Add space between stacked items */ + } + .warehouse-article-prices > div > div:last-child, /* Target button container */ + .warehouse-article-distributor > div > div:last-child { + text-align: right; /* Keep buttons aligned right */ + margin-bottom: 0 !important; /* Remove extra margin below buttons */ + } + .warehouse-article-prices > div > div:last-child > *, /* Target buttons inside */ + .warehouse-article-distributor > div > div:last-child > * { + margin-left: 5px; /* Add small space between buttons */ + } } \ No newline at end of file diff --git a/public/js/pages/WarehouseArticle/WarehouseArticle.js b/public/js/pages/WarehouseArticle/WarehouseArticle.js index d281f118b..521f4b4a8 100644 --- a/public/js/pages/WarehouseArticle/WarehouseArticle.js +++ b/public/js/pages/WarehouseArticle/WarehouseArticle.js @@ -1,97 +1,175 @@ +async function handleApiResponse(responsePromise) { + const res = await responsePromise; + if (res.data.success === false) return window.notify('error', `Fehler: ${res.data.errors.join(', ')}`); + window.notify('success', res.data.message || 'Erfolgreich'); +} + Vue.component('warehouse-article-prices', { props: {id: {type: Number, required: true}}, template: ` -
- - -

Artikelpreise

- -
-
-
- - {{ index }} -
- - - -
- - -
- + +

Artikelpreise überschreiben

+
+
+
+ {{ typeTitle }} +
+ + +
+ +
- - -
`, - data: () => ({ - window, - articlePrices: [], - }), +
+ + `, + data: () => ({window, articlePrices: {}}), async mounted() { - const [res1, res2] = await Promise.all([ - axios.post(window['TT_CONFIG']['BASE_PATH'] + '/WarehouseArticlePrice/get', {filters: {articleId: this.id}}), - axios.post(window['TT_CONFIG']['BASE_PATH'] + '/WarehouseArticlePriceType/get') - ]); - const prices = {}; - res2.data.rows.forEach(t => prices[t.title] = { - isRobot: true, - articlePriceTypeId: t.id, - priceMultiplier: t.defaultPriceFactor, - priceOverride: null - }); - res1.data.rows.forEach(p => { - const t = res2.data.rows.find(t => t.id === p.articlePriceTypeId); - if (t) prices[t.title] = { - isRobot: false, - articlePriceTypeId: p.articlePriceTypeId, - priceMultiplier: p.priceMultiplier || t.defaultPriceFactor, - priceOverride: p.priceOverride + await this.fetchArticlePrices(); + }, + methods: { + async fetchArticlePrices() { + const [pricesRes, typesRes] = await Promise.all([ + axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePrice/get`, {filters: {articleId: this.id}}), + axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePriceType/get`) + ]); + const prices = {}; + typesRes.data.rows.forEach(type => prices[type.title] = { + isRobot: true, + articlePriceTypeId: type.id, + priceMultiplier: type.defaultPriceFactor, + priceOverride: null + }); + pricesRes.data.rows.forEach(pData => { + const type = typesRes.data.rows.find(t => t.id === pData.articlePriceTypeId); + if (type) prices[type.title] = { + id: pData.id, + isRobot: false, + articlePriceTypeId: pData.articlePriceTypeId, + priceMultiplier: pData.priceMultiplier, + priceOverride: pData.priceOverride + }; + }); + this.articlePrices = prices; + }, + async savePrice(price) { + const payload = { + articleId: this.id, + articlePriceTypeId: price.articlePriceTypeId, + priceMultiplier: price.priceMultiplier, + priceOverride: price.priceOverride }; - }); - this.articlePrices = prices; + if (price.isRobot) { + await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePrice/create`, payload)); + await this.fetchArticlePrices(); + } else { + await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePrice/update`, {id: price.id, ...payload})); + await this.fetchArticlePrices(); + } + }, + async deletePrice(price) { + const payload = {id: price.id, articleId: this.id, articlePriceTypeId: price.articlePriceTypeId} + await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePrice/delete`, payload)); + await this.fetchArticlePrices(); + } } -}) +}); -Vue.component('warehouse-article', { - //language=Vue +// warehouse-article-distributor.vue.js +Vue.component('warehouse-article-distributor', { + props: {id: {type: Number, required: true}}, template: ` +

Lieferanten für diesen Artikel

+ +
+
+ + + +
+ + +
+
+
+
+ `, + data: () => ({window, articleDistributors: [], newDistributorId: null}), + async mounted() { + await this.fetchArticleDistributors(); + }, + methods: { + async fetchArticleDistributors() { + const res = await axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticleDistributor/get`, {filters: {articleId: this.id}}); + this.articleDistributors = res.data.rows; + }, + async saveDistributor(distributor) { + await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticleDistributor/${distributor.id ? 'update' : 'create'}`, distributor)); + await this.fetchArticleDistributors(); + }, + async deleteDistributor(distributor) { + await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticleDistributor/delete`, distributor)); + await this.fetchArticleDistributors(); + } + }, + watch: { + newDistributorId(newId) { + if (newId) { + if (!this.articleDistributors.some(d => d.distributorId === newId)) { + this.articleDistributors.push({ + articleId: this.id, + distributorId: newId, + externalArticleNumber: null, + purchasePrice: null + }); + } + this.$nextTick(() => { + this.newDistributorId = null; + }); + } + } + } +}); - - +// warehouse-article.vue.js +Vue.component('warehouse-article', { + template: ` + +