From a4df764a4987cc97c7752f32b5a8bd569f076f8d Mon Sep 17 00:00:00 2001 From: Luca Haid Date: Thu, 6 Mar 2025 10:29:48 +0100 Subject: [PATCH] Updated WarehouseOrder and WarehouseOrderRequest --- application/File/FileController.php | 15 + application/User/UserController.php | 6 + application/WarehouseLog/WarehouseLog.php | 9 + .../WarehouseLog/WarehouseLogModel.php | 12 + .../WarehouseOrderController.php | 136 +++++- .../WarehouseOrder/WarehouseOrderModel.php | 2 +- .../WarehouseOrderRequestController.php | 230 ++++------ .../WarehouseOrderRequestModel.php | 19 +- .../WarehouseProjectController.php | 57 ++- .../20250306100000_warehouse_modify_13.php | 61 +++ docker/php/Dockerfile | 2 +- lib/TTCrud/TTCrud.php | 19 + lib/mvcfronk/mfUpload/mfUpload.php | 1 - lib/mvcfronk/mfUpload/mfUpload_TmpFile.php | 8 + .../pages/WarehouseOrder/WarehouseOrder.css | 77 ++++ .../js/pages/WarehouseOrder/WarehouseOrder.js | 354 ++++++++++++--- .../WarehouseOrderRequest.css | 49 +++ .../WarehouseOrderRequest.js | 330 +++++++++----- .../WarehouseProject/WarehouseProject.js | 154 +------ .../vue/tt-components/tt-autocomplete.js | 2 + .../vue/tt-components/tt-position-manager.js | 414 +++++++++--------- 21 files changed, 1236 insertions(+), 721 deletions(-) create mode 100644 application/WarehouseLog/WarehouseLog.php create mode 100644 application/WarehouseLog/WarehouseLogModel.php create mode 100644 db/migrations/20250306100000_warehouse_modify_13.php create mode 100644 public/js/pages/WarehouseOrderRequest/WarehouseOrderRequest.css diff --git a/application/File/FileController.php b/application/File/FileController.php index 819eede11..b587624b4 100644 --- a/application/File/FileController.php +++ b/application/File/FileController.php @@ -73,4 +73,19 @@ class FileController extends mfBaseController { return true; } + protected function getByIdAction() { + $file = new File($this->request->id); + + if (!$file->id) { + http_response_code(404); + self::returnJson(["error" => "File not found"]); + return; + } + + self::returnJson([ + "id" => $file->id, + "filename" => $file->orig_filename + ]); + } + } \ No newline at end of file diff --git a/application/User/UserController.php b/application/User/UserController.php index 8b7a1a2fb..2b2fcf5dc 100644 --- a/application/User/UserController.php +++ b/application/User/UserController.php @@ -442,4 +442,10 @@ class UserController extends mfBaseController return ["valid_to" => null]; } + + protected function getByIdAction() { + $id = $this->request->id; + $user = new User($id); + $this->returnJson($user->toArray()); + } } diff --git a/application/WarehouseLog/WarehouseLog.php b/application/WarehouseLog/WarehouseLog.php new file mode 100644 index 000000000..2c872a07e --- /dev/null +++ b/application/WarehouseLog/WarehouseLog.php @@ -0,0 +1,9 @@ + 'editor', 'text' => 'Bearbeiter', 'required' => true, 'modal' => ['type' => 'select'], 'table' => ['filter' => 'select']], ['key' => 'note', 'text' => 'Notiz', 'required' => false, 'modal' => false, 'table' => false], ['key' => 'sum', 'text' => 'Summe', 'required' => false, 'modal' => false, 'table' => ['class' => 'text-right']], - ['key' => 'status', 'text' => 'Status', 'required' => false, 'modal' => ['type' => 'select', 'items' => []], 'table' => ['filter' => 'select']], + ['key' => 'status', 'text' => 'Status', 'required' => false, 'modal' => ['type' => 'select', 'items' => [ + ['value' => 'new', 'text' => 'Neu'], + ['value' => 'accepted', 'text' => 'Akzeptiert'], + ['value' => 'ordered', 'text' => 'Bestellt'], + ['value' => 'sent', 'text' => 'Versendet'], + ['value' => 'partiallyDelivered', 'text' => 'Teilweise geliefert'], + ['value' => 'fullyDelivered', 'text' => 'Geliefert'], + ['value' => 'cancelled', 'text' => 'Storniert'], + ]], 'table' => ['filter' => 'select']], ['key' => 'positions', 'text' => 'Positionen', 'required' => true, 'modal' => false, 'table' => false], ['key' => 'extReference', 'text' => 'Externe Referenz', 'required' => false, 'modal' => false], ['key' => 'createBy', 'text' => 'Erstellt von', 'required' => true, 'modal' => ['type' => 'select'], 'table' => ['filter' => 'select']], @@ -40,21 +48,17 @@ class WarehouseOrderController extends TTCrud { return ['value' => intval($user->id), 'text' => $user->name]; }, UserModel::search(['employee' => true])); - $statusIndex = array_search('status', array_column($this->columns, 'key')); - $this->columns[$statusIndex]['modal']['items'] = [ - ['value' => 'new', 'text' => 'Neu'], - ['value' => 'accepted', 'text' => 'Akzeptiert'], - ['value' => 'ordered', 'text' => 'Bestellt'], - ['value' => 'sent', 'text' => 'Versendet'], - ['value' => 'partiallyDelivered', 'text' => 'Teilweise geliefert'], - ['value' => 'fullyDelivered', 'text' => 'Geliefert'], - ['value' => 'cancelled', 'text' => 'Storniert'], - ]; - $distributorIndex = array_search('distributorId', array_column($this->columns, 'key')); $this->columns[$distributorIndex]['modal']['items'] = array_map(function ($distributor) { return ['value' => intval($distributor->id), 'text' => $distributor->name]; }, WarehouseDistributorModel::getAll()); + + $this->additionalActions[] = [ + 'key' => 'changeStatus', + 'title' => 'Status ändern', + 'class' => 'fas fa-exchange-alt', + 'color' => 'warning', + ]; } protected function beforeCreate(): bool { @@ -116,8 +120,7 @@ class WarehouseOrderController extends TTCrud { $headerHtml = file_get_contents(BASEDIR . "/Layout/default/WarehouseOrder/PDF_HEADER.html"); $headerHtml = str_replace("{{ basedir }}", BASEDIR, $headerHtml); - $headerHtml = str_replace("{{ externalReference }}", count($order['extReference']) > 0 ? "Ext. Ref.: ". $order['extReference'] : "", $headerHtml); - + $headerHtml = str_replace("{{ externalReference }}", !empty($order['extReference']) && count($order['extReference']) > 0 ? "Ext. Ref.: ". $order['extReference'] : "", $headerHtml); $headerHtml = str_replace("{{ addressLine_header }}", $shouldGenerateEnglisch ? "Supplier" : "Lieferant", $headerHtml); $headerHtml = str_replace("{{ addressLine_1 }}", WarehouseDistributorModel::get($distributorId)->name, $headerHtml); $headerHtml = str_replace("{{ addressLine_2 }}", WarehouseDistributorModel::get($distributorId)->address, $headerHtml); @@ -165,5 +168,110 @@ class WarehouseOrderController extends TTCrud { } + protected function getLogAction() { + $orderId = $this->request->orderId; + if (empty($orderId)) { + self::returnJson(['error' => 'Order ID is required']); + return; + } + + $logs = WarehouseLogModel::getAll(['table' => 'WarehouseOrder','rowId' => $orderId], ['timestamp' => 'DESC']); + self::returnJson($logs); + } + + protected function createNewLogAction() { + $postData = json_decode(file_get_contents('php://input'), true); + + if (empty($postData['orderId']) || empty($postData['status'])) { + self::returnJson(['error' => 'Order ID and Status are required']); + return; + } + + $log = [ + "table" => "WarehouseOrder", + "rowId" => intval($postData['orderId']), + "type" => $postData['status'] === 'noChanges' ? 'noChanges' : 'statusChange', + "fileIds" => $postData['fileIds'] ?? null, + "message" => $postData['note'] ?? null, + "createBy" => intval($this->user->id), + "create" => time() + ]; + + try { + $order = WarehouseOrderModel::get($log['orderId']); + if ($postData['status'] !== 'noChanges') { + $oldStatusText = array_values(array_filter($this->columns, fn($c) => $c['key'] === 'status'))[0]['modal']['items'][array_search($order->status, array_column(array_values(array_filter($this->columns, fn($c) => $c['key'] === 'status'))[0]['modal']['items'], 'value'))]['text']; + $newStatusText = array_values(array_filter($this->columns, fn($c) => $c['key'] === 'status'))[0]['modal']['items'][array_search($postData['status'], array_column(array_values(array_filter($this->columns, fn($c) => $c['key'] === 'status'))[0]['modal']['items'], 'value'))]['text']; + $log['message'] = 'Status wurde geändert von ' . $oldStatusText . ' auf ' . $newStatusText . ($log['message'] ? ': ' . $log['message'] : ''); + $order->status = $postData['status']; + $order = (array) $order; + WarehouseOrderModel::update($order); + } + + WarehouseLogModel::create($log); + self::returnJson(['success' => 'Log entry created']); + } catch (Exception $e) { + self::returnJson(['error' => 'Error creating log entry']); + } + } + + protected function uploadFileAction() { + if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) { + self::returnJson(['error' => 'No file uploaded or upload error occurred']); + return; + } + + $_FILES = ['WarehouseOrder' => $_FILES['file']]; + + try { + $file = mfUpload::handleFormUpload("WarehouseOrder", false, "/WarehouseOrder"); + + // Return the file ID + self::returnJson(['success' => true, 'fileId' => $file->id]); + } catch (Exception $ex) { + self::returnJson(['error' => 'Error uploading file: ' . $ex->getMessage()]); + } + } + + + protected function getLogByIdAction() { + $orderId = $this->request->id; + if (empty($orderId)) { + self::returnJson(['error' => 'Order ID is required']); + return; + } + + $log = WarehouseLogModel::getAll(['table' => 'WarehouseOrder', 'rowId' => $orderId]); + self::returnJson($log); + } + + + protected function afterUpdate($postData) { + $this->updateOrderRequestLinkedOrderIds($postData['id']); + } + + protected function afterCreate($postData) { + $this->updateOrderRequestLinkedOrderIds($postData['id']); + } + protected function updateOrderRequestLinkedOrderIds($id) { + $order = (array) WarehouseOrderModel::get($id); + foreach (json_decode($order['positions'], true) as $position) { + if (!empty($position['linkedOrderRequestId'])) { + $warehouseOrderRequest = (array) WarehouseOrderRequestModel::get($position['linkedOrderRequestId']); + if (is_null($warehouseOrderRequest['linkedOrderIds'])) { + $warehouseOrderRequest['linkedOrderIds'] = [$id]; + WarehouseOrderRequestModel::update($warehouseOrderRequest); + } else { + if (!in_array($id, $warehouseOrderRequest['linkedOrderIds'])) { + $warehouseOrderRequest['linkedOrderIds'][] = $id; + WarehouseOrderRequestModel::update($warehouseOrderRequest); + } + } + + } + } + + } + } \ No newline at end of file diff --git a/application/WarehouseOrder/WarehouseOrderModel.php b/application/WarehouseOrder/WarehouseOrderModel.php index 3d7cbf339..9c66e75ca 100644 --- a/application/WarehouseOrder/WarehouseOrderModel.php +++ b/application/WarehouseOrder/WarehouseOrderModel.php @@ -1,5 +1,4 @@ 'id', 'text' => 'ID', 'modal' => false, 'table' => false], - ['key' => 'ware', - 'text' => 'Ware', - 'required' => true, - 'type' => 'autocomplete', - 'table' => ['class' => 'text-nowrap', 'filter' => 'autocomplete'], - 'modal' => [ - 'apiUrl' => 'WarehouseArticle/autocomplete', - 'type' => 'autocomplete', - 'returnText' => true]], - ['key' => 'anzahl', 'text' => 'Anzahl', 'required' => true, 'type' => 'number'], - ['key' => 'verwendungszweck', 'text' => 'Verwendungszweck', 'required' => true], - ['key' => 'create', 'text' => 'Beauftragt am', 'required' => true, 'modal' => false, 'table' => ['filter' => 'datepicker']], - ['key' => 'createBy', - 'text' => 'Beauftragt von', - 'required' => true, - 'table' => ['filter' => 'select'], - 'modal' => ['visible' => false, 'type' => 'select', 'items' => []]], - ['key' => 'distributorId', - 'text' => 'Lieferant', - 'required' => false, - 'type' => 'autocomplete', - 'table' => ['class' => 'text-nowrap', 'filter' => 'autocomplete'], - 'modal' => [ - 'apiUrl' => 'WarehouseDistributor/autocomplete', - 'type' => 'autocomplete']], - ['key' => 'order', 'text' => 'Bestellt am', 'required' => false, 'type' => 'datepicker', 'table' => ['filter' => 'datepicker']], - ['key' => 'orderBy', 'text' => 'Bestellt von', 'required' => false, 'table' => ['filter' => 'select'], 'modal' => ['type' => 'select', 'items' => []]], - ['key' => 'takeOver', 'text' => 'Übernommen am', 'required' => false, 'type' => 'datepicker', 'table' => ['filter' => 'datepicker']], - ['key' => 'takeOverBy', - 'text' => 'Übernommen von', - 'required' => false, - 'table' => ['filter' => 'select'], - 'modal' => ['type' => 'select', 'items' => []]], - ['key' => 'warehouseLocation', 'text' => 'Lagerort', 'required' => false, 'type' => 'varchar'], - ['key' => 'canceled', - 'text' => 'Storniert', - 'required' => false, - 'modal' => ['visible' => false, 'type' => 'select', 'items' => [['value' => 0, 'text' => 'Nein'], ['value' => 1, 'text' => 'Ja']]], - 'table' => ['filter' => 'select']], - ['key' => 'note', 'text' => 'Notiz', 'required' => false, 'type' => 'textarea'], - ['key' => 'actions', - 'text' => 'Aktionen', - 'required' => false, - 'modal' => false, - 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']], + ['key' => 'purpose', 'text' => 'Verwendungszweck', 'required' => true], + ['key' => 'positions', 'text' => 'Positionen', 'required' => true, 'modal' => ['type' => 'positions-manager', 'config' => [ + 'header' => 'Positionen', + 'fields' => [ + 'articleId' => [ + 'apiUrl' => '/WarehouseArticle/autoComplete', + 'type' => 'autocomplete', + 'emitDisplayValue' => true, + 'customFieldReference' => 'WarehouseArticle', + 'label' => 'Artikel', + ], + 'amount' => ['type' => 'input', 'label' => 'Menge', 'inputType' => 'number'], + 'purpose' => ['type' => 'input', 'label' => 'Zweck'], + ], + 'validateFormOptions' => [ + ['key' => 'articleId', 'message' => 'Bitte füllen Sie den Artikel aus'], + ['key' => 'amount', 'message' => 'Bitte füllen Sie die Menge aus'], + ['key' => 'purpose', 'message' => 'Bitte füllen Sie den Zweck aus'], + ], + ]], 'table' => false], + ['key' => 'linkedOrderIds', 'text' => 'Verlinkte Bestellung', 'modal' => false], + ['key' => 'createBy', 'text' => 'Erstellt von', 'required' => true, 'modal' => ['visible' => false, 'type' => 'select'], 'table' => ['filter' => 'select']], + ['key' => 'create', 'text' => 'Erstellt am', 'required' => true, 'modal' => false], + ['key' => 'cancelled', 'text' => 'Storniert', 'modal' => ['visible' => false, 'type' => 'icon-select', 'items' => [ + ['value' => 0, 'text' => 'Bestellwunsch nicht storniert', 'icon' => 'fa-regular fa-circle-check text-success'], + ['value' => 1, 'text' => 'Bestellwunsch storniert', 'icon' => 'fa-regular fa-circle-xmark text-danger']]], 'table' => ['filter' => 'iconSelect'] + ], + ['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']], ]; + //@formatter:on protected array $permissionCheck = ['WarehouseUser']; - protected array $additionalActions = [['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary']]; - - protected array $infoMessages = ['create' => 'Bestellwunsch wurde erstellt.', - 'update' => 'Bestellwunsch wurde aktualisiert', - 'delete' => 'Bestellwunsch wurde gelöscht', - 'noChanges' => 'Keine Änderungen',]; - - protected array $additionalJSVariables = ['BASE_URL' => '/WarehouseOrderRequest', 'WAREHOUSE_ADMIN' => true]; + protected array $additionalActions = [ + ['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary'], + ['key' => 'createLog', 'title' => 'Log-Eintrag erstellen', 'class' => 'fas fa-plus text-primary'], + ]; protected function prepareCrudConfig() { - // Fill Users in createBy column - $userArray = array_map(function ($user) { - return ['value' => intval($user->id), 'text' => $user->name]; - }, UserModel::search(['employee' => true])); - $createByColumn = array_search('createBy', array_column($this->columns, 'key')); - $this->columns[$createByColumn]['modal']['items'] = $userArray; - $orderByColumn = array_search('orderBy', array_column($this->columns, 'key')); - $this->columns[$orderByColumn]['modal']['items'] = $userArray; - $takeOverByColumn = array_search('takeOverBy', array_column($this->columns, 'key')); - $this->columns[$takeOverByColumn]['modal']['items'] = $userArray; - - // if this user can WarehouseAdmin is false then set modal false to warehouselocation, takeOverBy, takeOver, orderBy, order - if (!$this->user->can(["WarehouseAdmin"])) { - $warehouselocationColumn = array_search('warehouseLocation', array_column($this->columns, 'key')); - $this->columns[$warehouselocationColumn]['modal']['visible'] = false; - $takeOverByColumn = array_search('takeOverBy', array_column($this->columns, 'key')); - $this->columns[$takeOverByColumn]['modal']['visible'] = false; - $takeOverColumn = array_search('takeOver', array_column($this->columns, 'key')); - $this->columns[$takeOverColumn]['modal']['visible'] = false; - $orderByColumn = array_search('orderBy', array_column($this->columns, 'key')); - $this->columns[$orderByColumn]['modal']['visible'] = false; - $orderColumn = array_search('order', array_column($this->columns, 'key')); - $this->columns[$orderColumn]['modal']['visible'] = false; - } - - $this->additionalJSVariables['user_id'] = $this->user->id; - if (!$this->user->can('WarehouseAdmin')) { - $this->additionalJSVariables['WAREHOUSE_ADMIN'] = false; - } - } - - protected function customAutoCompleteWare($value) { - if (!is_numeric($value)) return ['id' => $value, 'title' => $value]; - - $article = WarehouseArticleModel::get(intval($value)); - return ['id' => $article->id, 'title' => $article->title]; + $this->additionalJSVariables = [ + 'user_id' => $this->user->id, + 'BASE_URL' => '/WarehouseOrderRequest', + 'WAREHOUSE_ADMIN' => $this->user->can('WarehouseAdmin') + ]; } protected function beforeUpdate($postData): bool { @@ -110,62 +59,73 @@ class WarehouseOrderRequestController extends TTCrud { } protected function afterCreate($postData): void { - if ($_SERVER['HTTP_HOST'] == 'localhost') return; - - if (is_numeric($postData['ware'])) { - $article = WarehouseArticleModel::get(intval($postData['ware'])); - $postData['ware'] = $article->title; - } + if ($_SERVER['HTTP_HOST'] === 'localhost') return; + die("TODO we need this to work with new positions manager"); $email = new Emailnotification(); + $postData['ware'] = is_numeric($postData['ware']) ? WarehouseArticleModel::get((int) $postData['ware'])->title : $postData['ware']; $paddedId = str_pad($postData['id'], 5, '0', STR_PAD_LEFT); - $email->setSubject("TheTool: Neue Interne Bestellung #$paddedId"); - $body = "Hallo,\n\nes wurde eine neue interne Bestellung erstellt.\n\n"; - $body .= "Bestellnummer: #$paddedId\n"; - $body .= "Ware: " . $postData['ware'] . "\n"; - $body .= "Anzahl: " . $postData['anzahl'] . "\n"; - $body .= "Verwendungszweck: " . $postData['verwendungszweck'] . "\n"; - $body .= "Beauftragt von: " . $this->user->name . "\n"; - $body .= "Beauftragt am: " . date('d.m.Y H:i') . "\n"; - $body .= "Notiz: " . $postData['note'] . "\n\n"; + $email->setSubject("TheTool: Neue Interne Bestellung #$paddedId") + ->setBody(<<setBody($body); - $email->setFrom(TT_OUTGOING_EMAIL_2FA, TT_OUTGOING_EMAIL_2FA); - $email->setTo("einkauf@xinon.at", "Einkauf"); - $email->send(); +es wurde eine neue interne Bestellung erstellt. + +Bestellnummer: #$paddedId +Ware: {$postData['ware']} +Anzahl: {$postData['anzahl']} +Verwendungszweck: {$postData['verwendungszweck']} +Beauftragt von: {$this->user->name} +Beauftragt am: {date('d.m.Y H:i')} +Notiz: {$postData['note']} + +BODY + ) + ->setFrom(TT_OUTGOING_EMAIL_2FA, TT_OUTGOING_EMAIL_2FA) + ->setTo("einkauf@xinon.at", "Einkauf") + ->send(); } protected function cancelAction() { - $id = $this->request->id; - $cancel = $this->request->cancel; + $id = filter_var($this->request->id, FILTER_VALIDATE_INT); + $cancel = filter_var($this->request->cancel, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0, 'max_range' => 1]]); - if (!is_numeric($id) || !is_numeric($cancel)) { - self::returnJson(['error' => 'Invalid request']); + if (!$id || $cancel === false) self::returnJson(['error' => 'Ungültige Anfrage']); + if (!(WarehouseOrderRequestModel::get($id))) self::returnJson(['error' => 'Bestellwunsch nicht gefunden']); + + WarehouseOrderRequestModel::update(['id' => $id, 'canceled' => $cancel]); + self::returnJson(['success' => true]); + } + + protected function createNewLogAction() { + $postData = json_decode(file_get_contents('php://input'), true); + + if (empty($postData['orderRequestId']) || empty($postData['note'])) { + self::returnJson(['error' => 'Order Request ID is required']); + return; } - $order = (array) WarehouseOrderRequestModel::get($id); + WarehouseLogModel::create([ + "table" => "WarehouseOrderRequest", + "rowId" => intval($postData['orderRequestId']), + "type" => 'noChanges', + "message" => $postData['note'], + "createBy" => intval($this->user->id), + "create" => time() + ]); + self::returnJson(['success' => 'Log entry created']); + } - if (empty($order)) { - self::returnJson(['error' => 'Order not found']); + protected function getLogByIdAction() { + $orderRequestId = $this->request->orderRequestId; + if (empty($orderRequestId)) { + self::returnJson(['error' => 'Order ID is required']); + return; } - // $cancel is either 0 for uncancelling or 1 for cancelling - - if ($cancel == 1) { - $order['canceled'] = 1; - } else { - $order['canceled'] = 0; - } - - $order['id'] = $id; - - if (!WarehouseOrderRequestModel::update($order)) { - self::returnJson(['error' => 'Error updating order']); - } - - self::returnJson(['success' => true, 'message' => 'Order updated']); - + $log = WarehouseLogModel::getAll(['table' => 'WarehouseOrderRequest', 'rowId' => $orderRequestId]); + self::returnJson($log); } protected function getHistoryAction() { diff --git a/application/WarehouseOrderRequest/WarehouseOrderRequestModel.php b/application/WarehouseOrderRequest/WarehouseOrderRequestModel.php index 880ab484a..63f93a823 100644 --- a/application/WarehouseOrderRequest/WarehouseOrderRequestModel.php +++ b/application/WarehouseOrderRequest/WarehouseOrderRequestModel.php @@ -2,19 +2,12 @@ class WarehouseOrderRequestModel extends TTCrudBaseModel { public int $id; - public int $anzahl; - public string $ware; - public string $verwendungszweck; - public string $create; - public int $createBy; - public ?int $distributorId; - public ?string $order; - public ?int $orderBy; - public ?string $takeOver; - public ?int $takeOverBy; - public ?string $warehouseLocation; + public string $purpose; + public string $positions; public ?string $note; - - public ?int $canceled; + public ?string $linkedOrderIds; + public ?int $cancelled; + public int $create; + public int $createBy; } diff --git a/application/WarehouseProject/WarehouseProjectController.php b/application/WarehouseProject/WarehouseProjectController.php index 9d8e77541..7da44ce9d 100644 --- a/application/WarehouseProject/WarehouseProjectController.php +++ b/application/WarehouseProject/WarehouseProjectController.php @@ -3,34 +3,45 @@ class WarehouseProjectController extends TTCrud { protected string $headerTitle = 'Projekte'; protected string $createText = 'Neues Projekt erstellen'; + protected string $singleText = 'Projekt'; //@formatter:off protected array $columns = [ - ['key' => 'title', 'text' => 'Titel', 'required' => true, 'table' => ['class' => 'text-nowrap', 'priority' => 9]], - ['key' => 'description', 'text' => 'Beschreibung', 'required' => true, 'table' => ['class' => 'text-nowrap']], - ['key' => 'createBy', 'text' => 'Erstellt von', 'required' => true, 'type' => 'select', 'table' => ['class' => 'text-nowrap', 'filter' => 'select'], 'modal' => ['items' => [], 'type' => 'select']], - ['key' => 'create', 'text' => 'Erstellt am', 'required' => true, 'table' => ['filter' => 'date', 'class' => 'text-center']], - ['key' => 'address', 'text' => 'Adresse', 'required' => true, 'type' => 'autocomplete', 'table' => ['class' => 'text-nowrap', 'filter' => false], 'modal' => ['apiUrl' => '/Address/api?do=findAddress', 'items' => '/Address/api?do=findAddress', 'type' => 'autocomplete']], - ['key' => 'status', 'text' => 'Status', 'required' => true, 'table' => ['filter' => 'select'], 'modal' => [ 'type' => 'select', 'items' => [ ['value' => 'erstellt', 'text' => 'Erstellt'], ['value' => 'in_bearbeitung', 'text' => 'In Bearbeitung'], ['value' => 'erledigt', 'text' => 'Erledigt'], ['value' => 'verrechnet', 'text' => 'Verrechnet']]]] - ]; + ['key' => 'title', 'text' => 'Titel', 'required' => true], + ['key' => 'description', 'text' => 'Projektbeschreibung', 'modal' => ['type' => 'textarea']], + ['key' => 'startDate', 'text' => 'Startdatum', 'required' => true, 'modal' => ['type' => 'datepicker']], + ['key' => 'endDate', 'text' => 'Enddatum', 'required' => true, 'modal' => ['type' => 'datepicker']], + ['key' => 'positions', 'text' => 'Positionen', 'required' => true, 'modal' => ['type' => 'positions-manager', 'config' => [ + 'header' => 'Positionen', + 'fields' => [ + 'articleId' => ['apiUrl' => '/WarehouseArticle/autoComplete','type' => 'autocomplete','customFieldReference' => 'WarehouseArticle','label' => 'Artikel'], + 'amount' => ['type' => 'input', 'label' => 'Menge', 'inputType' => 'number'], + 'purpose' => ['type' => 'input', 'label' => 'Zweck'], + ], + 'validateFormOptions' => [ + ['key' => 'articleId', 'message' => 'Bitte füllen Sie den Artikel aus'], + ['key' => 'amount', 'message' => 'Bitte füllen Sie die Menge aus'], + ['key' => 'purpose', 'message' => 'Bitte füllen Sie den Zweck aus'], + ], + ]], 'table' => false], + ['key' => 'linkedOrderIds', 'text' => 'Verlinkte Bestellung', 'modal' => false], +// + ['key' => 'assignedPersons', 'text' => 'Zugewiesene Personen', 'modal' => ['type' => 'positions-manager', 'config' => [ + 'header' => 'Zugewiesene Personen', + 'fields' => [ + 'userId' => ['apiUrl' => '/WarehouseShippingNote/userAutoComplete','type' => 'autocomplete','label' => 'Person','customFieldReference' => 'User'] + ], + 'validateFormOptions' => [ + ['key' => 'userId', 'message' => 'Bitte füllen Sie die Person aus'], + ], + ]], 'table' => false], - protected array $additionalActions = [ - ]; - - protected array $infoMessages = [ - 'create' => 'Projekt wurde erstellt', - 'update' => 'Projekt wurde aktualisiert', - 'delete' => 'Projekt wurde gelöscht', - 'noChanges' => 'Keine Änderungen', + ['key' => 'storageLocation', 'text' => 'Lagerort', 'modal' => ['type' => 'input']], + ['key' => 'note', 'text' => 'Notiz', 'modal' => ['type' => 'textarea']], + ['key' => 'createBy', 'text' => 'Erstellt von', 'required' => true, 'modal' => ['visible' => false, 'type' => 'select'], 'table' => ['filter' => 'select']], + ['key' => 'create', 'text' => 'Erstellt', 'required' => true, 'modal' => false], + ['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']], ]; //@formatter:on - - public function prepareCrudConfig() { - $users = array_map(function($user) { - return ['value' => $user->id, 'text' => $user->name]; - }, UserModel::search(['employee' => true])); - - $this->columns[1]['modal']['items'] = $users; - } } \ No newline at end of file diff --git a/db/migrations/20250306100000_warehouse_modify_13.php b/db/migrations/20250306100000_warehouse_modify_13.php new file mode 100644 index 000000000..bf8144842 --- /dev/null +++ b/db/migrations/20250306100000_warehouse_modify_13.php @@ -0,0 +1,61 @@ +getEnvironment() == "thetool") { + // Add status column to WarehouseOrder + $table = $this->table("WarehouseOrder"); + $table->addColumn("status", "enum", [ + 'values' => ['new','accepted','ordered','sent','partiallyDelivered','fullyDelivered','cancelled'], + 'null' => true, + 'default' => null + ]) + ->save(); + + // Recreate WarehouseOrderRequest + if ($this->hasTable('WarehouseOrderRequest')) { + $this->table('WarehouseOrderRequest')->drop()->save(); + } + + $orderRequest = $this->table('WarehouseOrderRequest', ['id' => 'id', 'signed' => false]); + $orderRequest->addColumn('purpose', 'text') + ->addColumn('positions', 'text') + ->addColumn('note', 'text', ['null' => true]) + ->addColumn('linkedOrderIds', 'text', ['null' => true]) + ->addColumn('cancelled', 'integer', ['default' => 0]) + ->addColumn('create', 'integer') + ->addColumn('createBy', 'integer') + ->create(); + + // Create WarehouseLog if not exists + if (!$this->hasTable('WarehouseLog')) { + $log = $this->table('WarehouseLog', ['id' => 'id', 'signed' => false]); + $log->addColumn('table', 'string', ['limit' => 255]) + ->addColumn('rowId', 'integer') + ->addColumn('type', 'enum', ['values' => ['noChanges','statusChange']]) + ->addColumn('fileIds', 'text', ['null' => true]) + ->addColumn('message', 'string', ['limit' => 255]) + ->addColumn('create', 'integer') + ->addColumn('createBy', 'integer') + ->addIndex(['rowId'], ['name' => 'orderId']) + ->addIndex(['createBy'], ['name' => 'createBy']) + ->create(); + } + } + } + + public function down(): void { + if ($this->getEnvironment() == "thetool") { + // Remove status column + $table = $this->table("WarehouseOrder"); + $table->removeColumn("status") + ->save(); + + // Drop new tables + $this->table('WarehouseOrderRequest')->drop()->save(); + $this->table('WarehouseLog')->drop()->save(); + } + } +} diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 603c42c77..156d739c7 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -13,7 +13,7 @@ RUN apt install wget libfontenc1 xfonts-75dpi xfonts-base xfonts-encodings xfont # Install apache2 and PHP and PHP modules RUN apt update && \ - apt install -y apache2 curl cron unzip php8.2 php8.2-imap php8.2-curl php8.2-cli php8.2-mysqli php8.2-gd php8.2-zip php8.2-dom php8.2-mbstring && \ + apt install -y poppler-utils apache2 curl cron unzip php8.2 php8.2-imap php8.2-curl php8.2-cli php8.2-mysqli php8.2-gd php8.2-zip php8.2-dom php8.2-mbstring && \ curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer && \ apt clean && \ rm -rf /var/lib/apt/lists/* diff --git a/lib/TTCrud/TTCrud.php b/lib/TTCrud/TTCrud.php index daca7bbcc..9a20d34d8 100644 --- a/lib/TTCrud/TTCrud.php +++ b/lib/TTCrud/TTCrud.php @@ -37,6 +37,7 @@ class TTCrud extends mfBaseController { $this->model = new $modelName(); $this->postData = json_decode(file_get_contents('php://input'), true); $this->checkArray = $this->getCheckArray(); + $this->infoMessages = $this->getInfoMessages(); } /** @@ -293,6 +294,24 @@ class TTCrud extends mfBaseController { if (method_exists($this, 'getByIdParse') && !isset($_GET['disableParse'])) $data = $this->getByIdParse($data); self::returnJson($data); } + + private function getInfoMessages(): array { + if (isset($this->infoMessages) && is_array($this->infoMessages)) { + return $this->infoMessages; + } + + if (isset($this->singleText) && is_string($this->singleText)) { + return ['create' => $this->singleText . ' wurde erstellt.', + 'update' => $this->singleText . ' wurde aktualisiert', + 'delete' => $this->singleText . ' wurde gelöscht', + 'noChanges' => 'Keine Änderungen']; + } + + return ['create' => 'Eintrag wurde erstellt.', + 'update' => 'Eintrag wurde aktualisiert', + 'delete' => 'Eintrag wurde gelöscht', + 'noChanges' => 'Keine Änderungen']; + } } ?> diff --git a/lib/mvcfronk/mfUpload/mfUpload.php b/lib/mvcfronk/mfUpload/mfUpload.php index 0dc0930fc..128d7f624 100644 --- a/lib/mvcfronk/mfUpload/mfUpload.php +++ b/lib/mvcfronk/mfUpload/mfUpload.php @@ -149,7 +149,6 @@ class mfUpload { throw new Exception ("Not enough data to build savepath!",605); } } - if(!$this->upload->move_upload($this->savepath."/".$this->filename)) { throw new Exception ("Unable to move temp file: ".$this->upload->errormessage,605); } diff --git a/lib/mvcfronk/mfUpload/mfUpload_TmpFile.php b/lib/mvcfronk/mfUpload/mfUpload_TmpFile.php index 2efe4fc27..9fb29c407 100644 --- a/lib/mvcfronk/mfUpload/mfUpload_TmpFile.php +++ b/lib/mvcfronk/mfUpload/mfUpload_TmpFile.php @@ -50,6 +50,14 @@ class mfUpload_TmpFile { public function move_upload($path) { if($path && $this->tmp_name) { + // check if all directories exist needed for the path + $dir = dirname($path); + if(!is_dir($dir)) { + if(!mkdir($dir, 0777, true)) { + $this->errormessage = "Cannot create directory $dir."; + return false; + } + } if(move_uploaded_file($this->tmp_name, $path)) { return true; } else { diff --git a/public/js/pages/WarehouseOrder/WarehouseOrder.css b/public/js/pages/WarehouseOrder/WarehouseOrder.css index 9b3746d82..36dd1f290 100644 --- a/public/js/pages/WarehouseOrder/WarehouseOrder.css +++ b/public/js/pages/WarehouseOrder/WarehouseOrder.css @@ -26,6 +26,83 @@ } } +.grid-container { + display: grid; + grid-template-columns: 2fr 0.5fr 0.5fr 1fr 2fr 0.5fr; + grid-gap: 10px; +} +.grid-container.header { + margin-top: 24px; +} + +.upload-success-alert { + background-color: #d4edda; + border: 1px solid #c3e6cb; + border-radius: 8px; + padding: 15px; + margin-bottom: 20px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); +} + +.alert-header { + display: flex; + align-items: center; + margin-bottom: 10px; + font-size: 18px; + color: #155724; +} + +.alert-header i { + margin-right: 10px; + font-size: 24px; +} + +.file-list { + list-style-type: none; + padding: 0; + margin: 0; +} + +.file-item { + display: flex; + align-items: center; + background-color: #ffffff; + border-radius: 4px; + padding: 10px; + margin-bottom: 8px; + transition: background-color 0.3s ease; +} + +.file-item:hover { + background-color: #f8f9fa; +} + +.file-item i { + margin-right: 10px; + color: #6c757d; +} + +.file-name { + flex-grow: 1; + font-size: 14px; +} + +.remove-btn { + background-color: #dc3545; + color: #ffffff; + border: none; + border-radius: 4px; + padding: 5px 10px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.remove-btn:hover { + background-color: #c82333; +} + + + /* Expanded Row Styling */ .order-summary { diff --git a/public/js/pages/WarehouseOrder/WarehouseOrder.js b/public/js/pages/WarehouseOrder/WarehouseOrder.js index c274ae0ca..af9c9651e 100644 --- a/public/js/pages/WarehouseOrder/WarehouseOrder.js +++ b/public/js/pages/WarehouseOrder/WarehouseOrder.js @@ -1,3 +1,174 @@ +Vue.component('change-status-modal', { + props: { + orderId: {type: Number, required: true}, + type: {type: String, default: 'accept'} + }, + data() { + return { + order: null, + newStatus: 'noChanges', + note: '', + file: null, + uploadedFiles: [] + }; + }, + async mounted() { + const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getById`, {params: {id: this.orderId}}); + // if order.status is canceled emit close event and window.notify('error', 'Bestellung wurde storniert') + if (response.data.status === 'cancelled') { + this.$emit('close'); + window.notify('error', 'Bestellung wurde storniert'); + } + this.order = response.data; + + + }, + computed: { + availableStatuses() { + switch (this.order.status) { + case 'new': + return [ + {value: 'noChanges', text: 'Keine Änderungen'}, + {value: 'accepted', text: 'Akzeptiert'}, + {value: 'cancelled', text: 'Storniert'}, + ]; + case 'accepted': + return [ + {value: 'noChanges', text: 'Keine Änderungen'}, + {value: 'ordered', text: 'Bestellt'}, + {value: 'cancelled', text: 'Storniert'}, + ]; + case 'ordered': + return [ + {value: 'noChanges', text: 'Keine Änderungen'}, + {value: 'sent', text: 'Versendet'}, + {value: 'cancelled', text: 'Storniert'}, + ]; + case 'sent': + return [ + {value: 'noChanges', text: 'Keine Änderungen'}, + {value: 'partiallyDelivered', text: 'Teilweise geliefert'}, + {value: 'fullyDelivered', text: 'Geliefert'}, + {value: 'cancelled', text: 'Storniert'}, + ]; + case 'partiallyDelivered': + return [ + {value: 'noChanges', text: 'Keine Änderungen'}, + {value: 'fullyDelivered', text: 'Geliefert'}, + {value: 'cancelled', text: 'Storniert'}, + ]; + case 'fullyDelivered': + return [ + {value: 'noChanges', text: 'Keine Änderungen'}, + {value: 'cancelled', text: 'Storniert'}, + ]; + } + } + }, + methods: { + async handleFileUpload(event) { + const files = event.target.files; + if (!files.length) return; + + for (let i = 0; i < files.length; i++) { + const file = files[i]; + const formData = new FormData(); + formData.append('file', file); + + try { + const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/uploadFile`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + if (response.data.success) { + this.uploadedFiles.push({ + id: response.data.fileId, + name: file.name + }); + window.notify('success', `File "${file.name}" uploaded successfully`); + } else { + window.notify('error', `File "${file.name}" upload failed: ${response.data.error || 'Unknown error'}`); + } + } catch (error) { + window.notify('error', `Error uploading file "${file.name}"`); + } + } + + // Clear the file input + event.target.value = ''; + }, + removeFile: index => this.uploadedFiles.splice(index, 1), + async submit() { + const fileIds = this.uploadedFiles.map(file => file.id); + const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/createNewLogAction`, { + orderId: this.order.id, + status: this.newStatus, + note: this.note, + fileIds: JSON.stringify(fileIds) + }); + + if (response.data.success) { + this.$emit('close'); + window.notify('success', response.data.message ?? 'Status erfolgreich geändert'); + } else { + window.notify('error', + response.data.errors ? Object.values(response.data.errors).join('
') : response.data.message || 'Ein Fehler ist aufgetreten'); + } + } + }, + template: ` + + + + + + ` +}); + Vue.component('warehouse-order-modal', { props: { id: {type: [String, Number], required: true}, @@ -6,6 +177,7 @@ Vue.component('warehouse-order-modal', { template: ` @@ -94,13 +266,31 @@ Vue.component('warehouse-order-modal', { } }, async mounted() { - if (this.id === 'create') return; + if (this.id !== 'create') { + const {data} = await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrder/getById?disableParse`, {params: {id: this.id}}); + this.order = {...data, positions: JSON.parse(data.positions)}; + return; + } - console.log(this.id); + const orderRequest = JSON.parse(localStorage.getItem('WarehouseOrder_create')); + if (!orderRequest) return; - const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getById?disableParse`, {params: {id: this.id}}); - response.data.positions = JSON.parse(response.data.positions); - this.order = response.data; + const positions = JSON.parse(orderRequest.positions); + this.order.positions = await Promise.all(positions.map(async p => { + const distributor = (await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrder/getArticleDistributorData`, + {params: {articleId: p.articleId}})).data[0]; + return { + article: p.articleId, + amount: p.amount, + buyPrice: distributor.purchasePrice, + distributorId: distributor.id, + distributorArticleNumber: distributor.externalArticleNumber, + verwendung: `${p.purpose} [Bestellwunsch: #${orderRequest.id}]`, + linkedOrderRequestId: orderRequest.id + }; + })); + + localStorage.removeItem('WarehouseOrder_create'); }, methods: { async submit() { @@ -131,6 +321,14 @@ Vue.component('warehouse-order-modal', { response.data.errors ? Object.values(response.data.errors).join('
') : response.data.message || 'Ein Fehler ist aufgetreten'); } }, + async deleteOrder() { + if (!window.confirm('Bestellung wirklich löschen?')) return; + const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/delete`, {id: this.id}); + if (response.data.success) { + this.$emit('close'); + window.notify('success', response.data.message || 'Bestellung erfolgreich gelöscht'); + } else window.notify('error', response.data.message || 'Ein Fehler ist aufgetreten'); + }, async fetchDistributors(article) { const url = `${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getArticleDistributorData`; const params = typeof article === 'string' ? {allDistributor: true} : {articleId: article}; @@ -152,96 +350,130 @@ Vue.component('warehouse-order-modal', { } }, }, + watch: { + 'order.positions': { + handler(newPositions) { + if (this.id !== 'create' && new Set(newPositions.map(p => p.distributorId)).size > 1) { + window.notify('error', 'Eine bestehende Bestellung kann nur Positionen vom gleichen Lieferanten enthalten.'); + this.order.positions = newPositions.filter(p => p.distributorId === this.order.distributorId); + } + }, + deep: true + } + } + , }); +Vue.component('tt-file', { + props: ['id'], + data: () => ({file: null}), + async mounted() { + const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/File/getById`, {params: {id: this.id}}); + this.file = response.data; + }, + template: ` +
+ {{ file.filename }} + +
+ ` +}) + + Vue.component('warehouse-order-detail', { - //language=Vue template: ` - - - `, - props: { - id: {type: [String, Number], required: true} - }, - data() { - return { - window: window, - order: {}, - loading: true - } - }, + props: ['id'], + data: () => ({order: {}, orderLog: null, loading: true}), async mounted() { - const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getById`, {params: {id: this.id}}); - this.order = response.data; + const [orderResponse, logResponse] = await Promise.all([ + axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrder/getById`, {params: {id: this.id}}), + axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrder/getLogById`, {params: {id: this.id}}) + ]); + this.order = orderResponse.data; + this.orderLog = logResponse.data; this.loading = false; }, + methods: { + formatDate: date => window.moment(date * 1000).format('DD.MM.YYYY HH:mm'), + getUserName: id => window.TT_CONFIG.CRUD_CONFIG.columns.find(col => col.key === 'createBy')?.modal.items.find(u => u.value === id)?.text + } }); Vue.component('warehouse-order', { template: ` - - + + + - - + `, - data() { - return { - window: window, - orderModalId: null, - } + data: () => ({ + orderModalId: null, + changeStatusModalId: null + }), + mounted() { + if (JSON.parse(localStorage.getItem('WarehouseOrder_create'))) this.orderModalId = 'create'; }, methods: { - closeOrderModal() { + closeModal() { this.orderModalId = null; + this.changeStatusModalId = null; this.$refs.table.$refs.table.refreshTable(); }, - calculateSum(positions) { - return positions.reduce((sum, position) => sum + position.amount * position.buyPrice, 0); - } + calculateSum: positions => positions.reduce((sum, {amount, buyPrice}) => sum + amount * buyPrice, 0), + openPDF: order => window.open(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/createPDF?id=${order.id}`) } }); diff --git a/public/js/pages/WarehouseOrderRequest/WarehouseOrderRequest.css b/public/js/pages/WarehouseOrderRequest/WarehouseOrderRequest.css new file mode 100644 index 000000000..fdbdae59d --- /dev/null +++ b/public/js/pages/WarehouseOrderRequest/WarehouseOrderRequest.css @@ -0,0 +1,49 @@ +.WarehouseOrderRequestDetailTable { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + max-width: 500px; + font-family: Arial, sans-serif; + background-color: #f8f9fa; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); + padding: 20px; +} + +.WarehouseOrderRequestDetailTable > div { + padding: 12px 15px; + text-align: left; + font-weight: bold; + font-size: 14px; + color: #ffffff; + background-color: #4a90e2; +} + +.WarehouseOrderRequestDetailTable > div:nth-child(3n+1), +.WarehouseOrderRequestDetailTable > div:nth-child(3n+2), +.WarehouseOrderRequestDetailTable > div:nth-child(3n+3) { + background-color: #2980b9; +} + +.WarehouseOrderRequestDetailTable > div:nth-child(n+4) { + background-color: #ffffff; + color: #333333; + font-weight: normal; + border-bottom: 1px solid #e0e0e0; +} + +.WarehouseOrderRequestDetailTable > div:nth-child(n+4):nth-child(6n+4), +.WarehouseOrderRequestDetailTable > div:nth-child(n+4):nth-child(6n+5), +.WarehouseOrderRequestDetailTable > div:nth-child(n+4):nth-child(6n+6) { + background-color: #f2f2f2; +} + +.WarehouseOrderRequestDetailTable > div:last-child, +.WarehouseOrderRequestDetailTable > div:nth-last-child(2), +.WarehouseOrderRequestDetailTable > div:nth-last-child(3) { + border-bottom: none; +} + +.modal-content { + min-height: unset !important; +} \ No newline at end of file diff --git a/public/js/pages/WarehouseOrderRequest/WarehouseOrderRequest.js b/public/js/pages/WarehouseOrderRequest/WarehouseOrderRequest.js index fcf618429..65710a654 100644 --- a/public/js/pages/WarehouseOrderRequest/WarehouseOrderRequest.js +++ b/public/js/pages/WarehouseOrderRequest/WarehouseOrderRequest.js @@ -1,127 +1,247 @@ -window.localStorage.setItem('tt-table-WarehouseOrderRequest', JSON.stringify({ - filters: { - takeOverBy: null, - canceled: 0 - } -})); - window.TT_CONFIG["CRUD_CONFIG"]["additionalActions"] = [ ...window.TT_CONFIG["CRUD_CONFIG"]["additionalActions"], { - "key": "cancel", - "title": "Bestellwunsch stornieren", - "class": "fas fa-times text-danger", - "condition": (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.canceled === 0, + key: "cancelRequest", + title: "Bestellwunsch stornieren", + class: "fas fa-times text-danger", + condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.cancelled === 0, }, { - "key": "uncancel", - "title": "Bestellwunsch wiederherstellen", - "class": "fas fa-check text-success", - "condition": (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.canceled === 1, + key: "uncancelRequest", + title: "Bestellwunsch wiederherstellen", + class: "fas fa-check text-success", + condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.cancelled === 1, }, + { + key: "createOrder", + title: "Bestellung erstellen", + class: "fas fa-plus text-success", + condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' + && row.cancelled === 0 && (!row.linkedOrderIds || row.linkedOrderIds.length === 0) + && JSON.parse(row.positions).filter(position => position.articleId_text).length === 0, + } ] +Vue.component('add-log-modal', { + props: { + orderRequestId: {type: Number, required: true}, + type: {type: String, default: 'accept'} + }, + data() { + return { + orderRequest: null, + note: '', + }; + }, + async mounted() { + const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrderRequest/getById`, {params: {id: this.orderRequestId}}); + this.orderRequest = response.data; + + if (this.orderRequest.cancelled === 1) { + this.$emit('close'); + window.notify('error', 'Bestellwunsch wurde storniert'); + } + }, + methods: { + async submit() { + const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrderRequest/createNewLogAction`, { + orderRequestId: this.orderRequestId, + note: this.note, + }); + + if (response.data.success) { + this.$emit('close'); + window.notify('success', 'Log-Eintrag erstellt'); + } else { + window.notify('error', + response.data.errors ? Object.values(response.data.errors).join('
') : response.data.message || 'Ein Fehler ist aufgetreten'); + } + } + }, + template: ` + + + + + + ` +}) + +Vue.component('order-request-log', { + props: {orderRequestId: {type: Number, required: true}}, + data: () => ({ + logs: [] + }), + async mounted() { + const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrderRequest/getLogById`, {params: {orderRequestId: this.orderRequestId}}); + this.logs = response.data; + + const response2 = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrderRequest/getById`, {params: {id: this.orderRequestId}}); + // check if linkedOrderIds is set and if set length > 0 and if so, get the linked orders logs + // and add them to the logs array and sort them by create date + + // if response2.data.linkedOrderIds is a string try to parse it + if (typeof response2.data.linkedOrderIds === 'string') { + try { + response2.data.linkedOrderIds = JSON.parse(response2.data.linkedOrderIds); + } catch {} + } + + if (response2.data.linkedOrderIds && response2.data.linkedOrderIds.length > 0) { + const linkedOrdersLogs = await Promise.all( + response2.data.linkedOrderIds.map(async (id) => { + const res1 = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getById`, {params: {id}}); + const res2 = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getLogById`, {params: {id}}); + + return res2.data.map(log => { + log.message = `${res1.data.orderNumber} - ${log.message}`; + return log; + }) + }) + ); + this.logs = this.logs.concat(...linkedOrdersLogs).sort((a, b) => b.create - a.create); + } + + + }, + methods: { + formatDate: date => window.moment(date * 1000).format('DD.MM.YYYY HH:mm'), + getUserName: id => window.TT_CONFIG.CRUD_CONFIG.columns.find(col => col.key === 'createBy')?.modal.items.find(u => u.value === id)?.text + }, + //language=Vue + template: ` +
+ +
+ ` +}) + + +Vue.component('linked-order-status', { + props: ['linkedOrders'], + data: () => ({ + orders: [], + statusTranslations: { + new: 'Neu', + accepted: 'Akzeptiert', + ordered: 'Bestellt', + sent: 'Versendet', + partiallyDelivered: 'Teilweise geliefert', + fullyDelivered: 'Geliefert', + cancelled: 'Storniert', + } + }), + async mounted() { + this.orders = await Promise.all( + JSON.parse(this.linkedOrders).map(id => axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrder/getById?id=${id}`).then(response => response.data)) + ); + }, + //language=Vue + template: ` +
+ {{ order.orderNumber }} - {{ statusTranslations[order.status] }} +
` +}); + +Vue.component('warehouse-order-request-detail', { + props: { + positions: { + type: Array, + required: true + } + }, + //language=Vue + template: ` +
+
+
ARTIKEL
+
MENGE
+
ZWECK
+ +
+
+ ` +}); + + Vue.component('warehouse-order-request', { //language=Vue template: ` - - - -