From c57eef6e8dfdb8df05442485a1363d3be939c489 Mon Sep 17 00:00:00 2001 From: Luca Haid Date: Thu, 10 Oct 2024 08:49:50 +0200 Subject: [PATCH] update for warehouse --- .../WarehouseShippingNote/PDF_FOOTER.html | 43 +++ .../WarehouseShippingNote/PDF_HEADER.html | 98 ++++++ .../WarehouseShippingNote/PDF_MAIN.php | 134 ++++++++ Layout/default/menu.php | 15 +- application/Address/AddressController.php | 1 + .../WarehouseAdministration.php | 9 + .../WarehouseAdministrationController.php | 110 ++++++ .../WarehouseArticleController.php | 96 ++++-- .../WarehouseArticleModel.php | 1 - application/WarehouseArticle/import.json | 0 .../WarehouseArticlePriceTypeController.php | 52 ++- .../WarehouseArticlePriceTypeModel.php | 4 + .../WarehouseEShopOrderController.php | 1 + .../WarehouseEShopOrderItemModel.php | 3 +- .../WarehouseHistoryController.php | 6 + .../WarehouseItem/WarehouseItemController.php | 33 +- .../WarehouseItem/WarehouseItemModel.php | 4 +- .../WarehouseLocationController.php | 4 +- .../WarehouseLocationModel.php | 3 + application/WarehouseOrder/WarehouseOrder.php | 9 + .../WarehouseOrderController.php | 110 ++++++ .../WarehouseOrder/WarehouseOrderModel.php | 28 ++ .../WarehouseOrderItem/WarehouseOrderItem.php | 9 + .../WarehouseOrderItemModel.php | 16 + .../WarehouseRevenueAccount.php | 9 - .../WarehouseRevenueAccountController.php | 39 --- .../WarehouseRevenueAccountModel.php | 7 - .../WarehouseShippingNote.php | 9 + .../WarehouseShippingNoteController.php | 254 ++++++++++++++ .../WarehouseShippingNoteModel.php | 17 + .../WarehouseShippingNoteTextElement.php | 9 + ...houseShippingNoteTextElementController.php | 48 +++ .../WarehouseShippingNoteTextElementModel.php | 10 + .../20241010070000_warehouse_modify.php | 146 ++++++++ docker-compose.yml | 2 +- docker/php/Dockerfile | 7 + lib/TTCrud/TTCrud.php | 71 +++- lib/TTCrudBaseModel/TTCrudBaseModel.php | 22 +- .../mfExceptionhandler/mfExceptionhandler.php | 3 + lib/mvcfronk/mfRouter/mfRouter.php | 3 +- .../WarehouseAdministration.js | 56 +++ .../WarehouseArticle/WarehouseArticle.js | 92 ++++- .../WarehouseArticlePriceType.js | 14 + .../WarehouseDistributor.js | 1 + .../js/pages/WarehouseEShop/WarehouseEShop.js | 44 --- .../WarehouseEShopOrder.js | 1 - .../WarehouseHistory/WarehouseHistoryModal.js | 94 +++++ .../js/pages/WarehouseItem/WarehouseItem.js | 8 +- .../WarehouseLocation/WarehouseLocation.js | 1 + .../js/pages/WarehouseOrder/WarehouseOrder.js | 70 ++++ .../WarehouseShippingNote.js | 321 ++++++++++++++++++ .../vue/tt-components/tt-autocomplete.js | 184 +++++----- public/plugins/vue/tt-components/tt-modal.js | 106 +++--- public/plugins/vue/tt-components/tt-select.js | 4 +- .../vue/tt-components/tt-table-crud.js | 98 ++++-- public/plugins/vue/tt-components/tt-table.js | 162 +++++---- 56 files changed, 2250 insertions(+), 451 deletions(-) create mode 100644 Layout/default/WarehouseShippingNote/PDF_FOOTER.html create mode 100644 Layout/default/WarehouseShippingNote/PDF_HEADER.html create mode 100644 Layout/default/WarehouseShippingNote/PDF_MAIN.php create mode 100644 application/WarehouseAdministration/WarehouseAdministration.php create mode 100644 application/WarehouseAdministration/WarehouseAdministrationController.php create mode 100644 application/WarehouseArticle/import.json create mode 100644 application/WarehouseOrder/WarehouseOrder.php create mode 100644 application/WarehouseOrder/WarehouseOrderController.php create mode 100644 application/WarehouseOrder/WarehouseOrderModel.php create mode 100644 application/WarehouseOrderItem/WarehouseOrderItem.php create mode 100644 application/WarehouseOrderItem/WarehouseOrderItemModel.php delete mode 100644 application/WarehouseRevenueAccount/WarehouseRevenueAccount.php delete mode 100644 application/WarehouseRevenueAccount/WarehouseRevenueAccountController.php delete mode 100644 application/WarehouseRevenueAccount/WarehouseRevenueAccountModel.php create mode 100644 application/WarehouseShippingNote/WarehouseShippingNote.php create mode 100644 application/WarehouseShippingNote/WarehouseShippingNoteController.php create mode 100644 application/WarehouseShippingNote/WarehouseShippingNoteModel.php create mode 100644 application/WarehouseShippingNoteTextElement/WarehouseShippingNoteTextElement.php create mode 100644 application/WarehouseShippingNoteTextElement/WarehouseShippingNoteTextElementController.php create mode 100644 application/WarehouseShippingNoteTextElement/WarehouseShippingNoteTextElementModel.php create mode 100644 db/migrations/20241010070000_warehouse_modify.php create mode 100644 public/js/pages/WarehouseAdministration/WarehouseAdministration.js create mode 100644 public/js/pages/WarehouseArticlePriceType/WarehouseArticlePriceType.js create mode 100644 public/js/pages/WarehouseOrder/WarehouseOrder.js create mode 100644 public/js/pages/WarehouseShippingNote/WarehouseShippingNote.js diff --git a/Layout/default/WarehouseShippingNote/PDF_FOOTER.html b/Layout/default/WarehouseShippingNote/PDF_FOOTER.html new file mode 100644 index 000000000..0a984b7d6 --- /dev/null +++ b/Layout/default/WarehouseShippingNote/PDF_FOOTER.html @@ -0,0 +1,43 @@ + + + + Xinon Rechnung + + + + + + +
+
+ XINON GmbH | Fladnitz 150 | 8322 Studenzen
+ Tel.: +43 3115 40800 | E-Mail: office@xinon.at
+ UID: ATU68711968 | FN: 416556h | LG: Feldbach
+ IBAN: {{ bank_iban }} | BIC: {{ bank_bic }}
+
+ +
Seite von
+ +
+ + diff --git a/Layout/default/WarehouseShippingNote/PDF_HEADER.html b/Layout/default/WarehouseShippingNote/PDF_HEADER.html new file mode 100644 index 000000000..418a44096 --- /dev/null +++ b/Layout/default/WarehouseShippingNote/PDF_HEADER.html @@ -0,0 +1,98 @@ + + + + XINON Shipping Note Header + + + + + +
+ +
+ Xinon Logo +
+ + + + + + +
+
{{ addressLine_1 }}
+
{{ addressLine_2 }}
+
{{ addressLine_3 }}
+
{{ addressLine_4 }}
+
{{ addressLine_5 }}
+
+ + + + + +
+ + + + + + + + + + + + + +
Kundennummer:{{ customerNumber }}
LS-Nummer:{{ shippingNoteNumber }}
LS-Datum:{{ shippingNoteDate }}
+
+
+ + +
+ + + diff --git a/Layout/default/WarehouseShippingNote/PDF_MAIN.php b/Layout/default/WarehouseShippingNote/PDF_MAIN.php new file mode 100644 index 000000000..aa2be60ab --- /dev/null +++ b/Layout/default/WarehouseShippingNote/PDF_MAIN.php @@ -0,0 +1,134 @@ +setReturnValue(['filename' => $shippingNote->id . ".pdf"]); +?> + + + + + Lieferschein + + + + + + + + + +
+ +

Ihr XINON Lieferschein vom create)?>

+ + + + + + + + + + + + + + "> + + + + + + + + + "> + + + + + + + + + + +
PositionMengeEHArtikelPreis
+ + +
+ +

+ +
+ + + + \ No newline at end of file diff --git a/Layout/default/menu.php b/Layout/default/menu.php index f4d22bff1..edcb0c6d3 100644 --- a/Layout/default/menu.php +++ b/Layout/default/menu.php @@ -142,18 +142,19 @@ diff --git a/application/Address/AddressController.php b/application/Address/AddressController.php index 2c5c3e647..60b475168 100644 --- a/application/Address/AddressController.php +++ b/application/Address/AddressController.php @@ -534,6 +534,7 @@ class AddressController extends mfBaseController { ]; $results[] = $result; $this->returnJson($results); + die(); } } diff --git a/application/WarehouseAdministration/WarehouseAdministration.php b/application/WarehouseAdministration/WarehouseAdministration.php new file mode 100644 index 000000000..292496634 --- /dev/null +++ b/application/WarehouseAdministration/WarehouseAdministration.php @@ -0,0 +1,9 @@ +loadMe(); + $this->layout()->set("me", $me); + $this->me = $me; + + if (!$this->me->isAdmin()) { + $this->redirect("dashboard"); + } + } + + protected function indexAction(): void { + $this->layout()->set('additionalJS', ['js/pages/WarehouseHistory/WarehouseHistoryModal.js']); + + Helper::renderVue($this, 'WarehouseAdministration', 'Administration-Tools', ["CREATE_URL" => $this::getUrl($this->mod . "/create"), + "TABLE_URL" => $this::getUrl($this->mod . "/get"), + "UPDATE_URL" => $this::getUrl($this->mod . "/update"), + "DELETE_URL" => $this::getUrl($this->mod . "/delete"),]); + } + + //TODO: this needs improvement as it is inefficient but it doesnt matter as it doesnt get called very often + // and also maybe we should move it to WarehouseLocationController + protected function createLocationsAction(): void { + $existingLocations = WarehouseLocationModel::getAll(); + $companyCars = TimerecordingCarModel::getAll(); + + + $wantedCarLocations = []; + foreach ($companyCars as $car) { + // check if $car->brand includes "Anhänger" or "Anhaenger", if yes then continue + if (strpos($car->brand, "Anhänger") !== false || strpos($car->brand, "Anhaenger") !== false) { + continue; + } + + $carModelParts = explode(" ", $car->model); + if (count($carModelParts) > 1) { + $wantedCarLocations[] = "{$car->number_plate} {$car->brand} {$carModelParts[0]} {$carModelParts[1]}"; + } else { + $wantedCarLocations[] = "{$car->number_plate} {$car->brand} {$carModelParts[0]}"; + } + } + + + // create a warehouse location for each wantedcar but check if $existingLocations[]->title already exists with the same title + foreach ($wantedCarLocations as $wantedCarLocation) { + $locationExists = false; + foreach ($existingLocations as $existingLocation) { + if ($existingLocation->title === $wantedCarLocation) { + $locationExists = true; + break; + } + } + + if (!$locationExists) { + $numberPlate = explode(" ", $wantedCarLocation)[0]; + $assignedTo = 1; + foreach ($companyCars as $car) { + if ($car->number_plate === $numberPlate) { + $assignedTo = $car->user_id; + break; + } + } + if ($assignedTo === null) { + $assignedTo = 6; + } + + WarehouseLocationModel::create([ + "title" => $wantedCarLocation, + "description" => "Automatisch erstellt", + "assignedTo" => $assignedTo, + "createdBy" => $this->me->id, + "create" => time() + ]); + } + } + + $existingLocations = WarehouseLocationModel::getAll(); + $users = UserModel::search(['employee' => true]); + + // now create a warehouse location for each user only if they dont already have one (for example if they have a company car) + foreach ($users as $user) { + $locationExists = false; + foreach ($existingLocations as $existingLocation) { + if (intval($existingLocation->assignedTo) === intval($user->id)) { + $locationExists = true; + break; + } + } + + if (!$locationExists) { + WarehouseLocationModel::create([ + "title" => $user->name . "'s Lagerort", + "description" => "Automatisch erstellt", + "assignedTo" => $user->id, + "createdBy" => $this->me->id, + "create" => time() + ]); + } + } + + var_dump($existingLocations); + die(); + + } +} \ No newline at end of file diff --git a/application/WarehouseArticle/WarehouseArticleController.php b/application/WarehouseArticle/WarehouseArticleController.php index 643fe6f8b..65826d967 100644 --- a/application/WarehouseArticle/WarehouseArticleController.php +++ b/application/WarehouseArticle/WarehouseArticleController.php @@ -7,11 +7,12 @@ class WarehouseArticleController extends TTCrud { // @formatter:off protected array $columns = [ ['key' => 'title', 'text' => 'Titel', 'required' => true, 'table' => ['priority' => 9]], - ['key' => 'description', 'text' => 'Beschreibung', 'required' => true, 'table' => false], + ['key' => 'description', 'text' => 'Beschreibung', 'required' => true], ['key' => 'category', 'text' => 'Kategorie', 'required' => true], ['key' => 'unit', 'text' => 'Einheit', 'required' => true,'table' => false], // Boolean value - ['key' => 'defaultSellMultiplier', 'text' => 'Standard Multiplikator','regex' => '/^[0-9]*$/' , 'required' => true,'modal' => ['type' => 'number'], 'table' => false], // Boolean value - ['key' => 'revenueAccount', 'text' => 'Erlöskonto', 'required' => true,'modal' => ['type' => 'select'], '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' => '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' => ['class' => 'text-center']], // Stock/inventory related @@ -27,6 +28,7 @@ class WarehouseArticleController extends TTCrud { ['key' => 'editDistributorEntries','title' => 'Lieferanten','class' => 'fas fa-truck text-cyan'], ['key' => 'editThresholdEntries','title' => 'Schwellenwerte','class' => 'far fa-fw fa-box-full text-orange'], ['key' => 'editPricesEntries','title' => 'Preise','class' => 'fas fa-euro-sign text-green'], + ['key' => 'addToCart','title' => 'Zur Bestellung hinzufügen','class' => 'fas fa-shopping-cart text-primary'], ]; // @formatter:on @@ -35,15 +37,6 @@ class WarehouseArticleController extends TTCrud { 'delete' => 'Artikel wurde gelöscht', 'noChanges' => 'Keine Änderungen',]; - public function prepareCrudConfig() { - $revenueAccounts = WarehouseRevenueAccountModel::getAll(); - $revenueAccounts = array_map(function ($revenueAccount) { - return ['value' => $revenueAccount->id, 'text' => $revenueAccount->title]; - }, $revenueAccounts); - - $this->columns[5]['modal']['items'] = $revenueAccounts; - } - protected function beforeUpdate($postData): bool { (new WarehouseHistoryController)->create($postData, $this->mod); return true; @@ -77,6 +70,11 @@ class WarehouseArticleController extends TTCrud { WarehouseArticleModel::update(array_merge(get_object_vars($article), ['cheapestPurchasePrice' => $cheapestPurchasePrice])); } + protected function afterCreate($postData) { + self::updateCheapestPurchasePrice($postData['id']); + self::updateSellPrices($postData['id']); + } + /** * Updates the sell prices for a given article. * @@ -96,33 +94,31 @@ class WarehouseArticleController extends TTCrud { $cheapestSellPrices = []; // Calculate sell prices for each price type, use default sell multiplier if no specific price is set foreach ($priceTypes as $priceType) { - $articlePriceType = array_filter($articlePriceTypes, function ($apt) use ($priceType) { - return $apt->articlePriceTypeId == $priceType->id; - }); + $articlePriceType = null; + foreach ($articlePriceTypes as $apt) { + if ($apt->articlePriceTypeId == $priceType->id) { + $articlePriceType = $apt; + break; + } + } - $sellPrice = $article->defaultSellMultiplier * $article->cheapestPurchasePrice; - if (!empty($articlePriceType)) { - $articlePriceType = $articlePriceType[0]; + $sellPrice = $priceType->defaultPriceFactor * $article->cheapestPurchasePrice; + if ($articlePriceType !== null) { $sellPrice = $articlePriceType->priceOverride ?: $articlePriceType->priceMultiplier * $article->cheapestPurchasePrice; } - $cheapestSellPrices[$priceType->id] = ['title' => $priceType->title, 'price' => $sellPrice]; + $cheapestSellPrices[$priceType->id] = ['title' => $priceType->title, 'price' => round($sellPrice, 2)]; } $article->cheapestSellPrice = json_encode($cheapestSellPrices); WarehouseArticleModel::update(get_object_vars($article)); } - - protected function afterCreate($postData) { - self::updateCheapestPurchasePrice($postData['id']); - self::updateSellPrices($postData['id']); - } - - protected function updatePricesAction() { + public function updatePricesAction() { foreach (WarehouseArticleModel::getAll() as $article) { self::updateCheapestPurchasePrice($article->id); self::updateSellPrices($article->id); } + self::returnJson(['success' => true, 'message' => 'Preise wurden aktualisiert']); } protected function getHistoryAction() { @@ -241,4 +237,52 @@ class WarehouseArticleController extends TTCrud { } } + + protected function prepareOrderAction() { + // inside post json it will look like + // [ + // { + // "amount": "5", + // "itemId": 441, + // "title": "RT-FB-7590AX" + // }, + // { + // "amount": "5", + // "itemId": 421, + // "title": "RT-FB-7590" + // } + //] + // get the json from the post request + // then create a array containing each order we need to make, so search through WarehouseArticleDistributorModel to get the distributorId and purchasePrice (use lowest purchasePrice) + // then get the WarehouseDistributorModel and then create a summary of the orders we need to make for each distributor + + $postData = json_decode(file_get_contents('php://input'), true); + $orders = []; + foreach ($postData as $order) { + $articleDistributors = WarehouseArticleDistributorModel::getAll(['articleId' => $order['itemId']]); + $cheapestArticleDistributor = $articleDistributors[0]; + foreach ($articleDistributors as $articleDistributor) { + if ($articleDistributor->purchasePrice < $cheapestArticleDistributor->purchasePrice) { + $cheapestArticleDistributor = $articleDistributor; + } + } + $distributor = WarehouseDistributorModel::get($cheapestArticleDistributor->distributorId); + + if (!isset($orders[$distributor->id])) { + $orders[$distributor->id] = ['distributor' => array($distributor), + 'orderAmount' => 0, + 'orders' => []]; + } + + $orders[$distributor->id]['orders'][] = ['articleId' => $order['itemId'], + 'amount' => $order['amount'], + 'sum' => $order['amount'] * $cheapestArticleDistributor->purchasePrice, + 'purchasePrice' => $cheapestArticleDistributor->purchasePrice, + 'externalArticleNumber' => $cheapestArticleDistributor->externalArticleNumber, + 'title' => $order['title'],]; + $orders[$distributor->id]['orderAmount'] += $order['amount'] * $cheapestArticleDistributor->purchasePrice; + } + + self::returnJson($orders); + } } \ No newline at end of file diff --git a/application/WarehouseArticle/WarehouseArticleModel.php b/application/WarehouseArticle/WarehouseArticleModel.php index 926920fac..2a6a14cf3 100644 --- a/application/WarehouseArticle/WarehouseArticleModel.php +++ b/application/WarehouseArticle/WarehouseArticleModel.php @@ -11,7 +11,6 @@ class WarehouseArticleModel extends TTCrudBaseModel { public int $criticalAmount; public int $isEShop; public int $isEShopHide; - public float $defaultSellMultiplier; public string $unit; public int $isSerialDocumentation; public int $revenueAccount; diff --git a/application/WarehouseArticle/import.json b/application/WarehouseArticle/import.json new file mode 100644 index 000000000..e69de29bb diff --git a/application/WarehouseArticlePriceType/WarehouseArticlePriceTypeController.php b/application/WarehouseArticlePriceType/WarehouseArticlePriceTypeController.php index e9cc254fb..2591c8b1b 100644 --- a/application/WarehouseArticlePriceType/WarehouseArticlePriceTypeController.php +++ b/application/WarehouseArticlePriceType/WarehouseArticlePriceTypeController.php @@ -7,6 +7,11 @@ class WarehouseArticlePriceTypeController extends TTCrud { // @formatter:off protected array $columns = [ ['key' => 'title', 'text' => 'Titel', 'required' => true], + ['key' => 'description', 'text' => 'Beschreibung', 'required' => false], + ['key' => 'defaultPriceFactor', 'text' => 'Standard Preisfaktor', 'required' => true, 'modal' => ['type' => 'number']], + ['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' => [], 'table' => ['class' => 'text-nowrap']], 'visible' => false], ['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center', 'priority' => 10]], ]; // @formatter:on @@ -16,47 +21,30 @@ class WarehouseArticlePriceTypeController extends TTCrud { 'delete' => 'Artikel Verkaufspreis wurde gelöscht', 'noChanges' => 'Keine Änderungen']; - protected function checkExistingDistributorEntry($postData): bool { - // if postData id exists check if there is already an entry with the same articleId and locationId if postdata id and WarehouseLocationThresholdOverrideModel id are different return false - if (isset($postData['id'])) { - $count = WarehouseArticlePriceTypeModel::count(['title' => $postData['title'], 'id' => $postData['id']]); - if ($count > 0) { - return true; - } - } else { - $count = WarehouseArticlePriceTypeModel::count(['title' => $postData['title']]); - if ($count > 0) { - self::returnJson(['success' => false, - 'message' => 'Es existiert bereits ein Preis Typ mit diesem Titel.']); - return false; - - } - } - return true; - } - - protected function beforeCreate($postData): bool { - return $this->checkExistingDistributorEntry($postData); + 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 { - $existing = $this->checkExistingDistributorEntry($postData); - - if (!$existing) { - return false; - } - (new WarehouseHistoryController)->create($postData, $this->mod); - return true; } - public function afterCreate($postData) { - WarehouseArticleController::updateSellPrices($postData['articleId']); + protected function afterUpdate($postData) { + $WarehouseArticleController = new WarehouseArticleController; + // set mod of WarehouseArticleController to WarehouseArticle + $WarehouseArticleController->mod = 'WarehouseArticle'; + $WarehouseArticleController->updatePricesAction(); } - public function afterUpdate($postData) { - WarehouseArticleController::updateSellPrices($postData['articleId']); + protected function afterCreate($postData) { + $WarehouseArticleController = new WarehouseArticleController; + // set mod of WarehouseArticleController to WarehouseArticle + $WarehouseArticleController->mod = 'WarehouseArticle'; + $WarehouseArticleController->updatePricesAction(); } protected function getHistoryAction() { diff --git a/application/WarehouseArticlePriceType/WarehouseArticlePriceTypeModel.php b/application/WarehouseArticlePriceType/WarehouseArticlePriceTypeModel.php index 7a87f539e..c58f39d23 100644 --- a/application/WarehouseArticlePriceType/WarehouseArticlePriceTypeModel.php +++ b/application/WarehouseArticlePriceType/WarehouseArticlePriceTypeModel.php @@ -3,4 +3,8 @@ class WarehouseArticlePriceTypeModel extends TTCrudBaseModel { public int $id; public string $title; + public ?string $description; + public float $defaultPriceFactor; + public int $create; + public int $createBy; } \ No newline at end of file diff --git a/application/WarehouseEShopOrder/WarehouseEShopOrderController.php b/application/WarehouseEShopOrder/WarehouseEShopOrderController.php index a138645f9..f0f21df52 100644 --- a/application/WarehouseEShopOrder/WarehouseEShopOrderController.php +++ b/application/WarehouseEShopOrder/WarehouseEShopOrderController.php @@ -153,6 +153,7 @@ class WarehouseEShopOrderController extends TTCrud { } // if it is still null, die with order id: if ($realOrderItems === null) { + continue; self::returnJson(['success' => false, 'message' => 'Bestellung mit ID ' . $order['id'] . ' hat keine Artikel. Bitte überprüfen.']); die(); } diff --git a/application/WarehouseEShopOrderItem/WarehouseEShopOrderItemModel.php b/application/WarehouseEShopOrderItem/WarehouseEShopOrderItemModel.php index 6bbf0cd56..f1bf47acc 100644 --- a/application/WarehouseEShopOrderItem/WarehouseEShopOrderItemModel.php +++ b/application/WarehouseEShopOrderItem/WarehouseEShopOrderItemModel.php @@ -4,12 +4,13 @@ * @property int $orderId * @property int $articleId * @property int $quantity + * @property int $price */ class WarehouseEShopOrderItemModel extends TTCrudBaseModel { public int $id; public int $orderId; public ?int $articleId; - public ?int $articlePacketId; public int $quantity; + public ?int $articlePacketId; } \ No newline at end of file diff --git a/application/WarehouseHistory/WarehouseHistoryController.php b/application/WarehouseHistory/WarehouseHistoryController.php index 493daf507..f5e88b80d 100644 --- a/application/WarehouseHistory/WarehouseHistoryController.php +++ b/application/WarehouseHistory/WarehouseHistoryController.php @@ -9,6 +9,12 @@ class WarehouseHistoryController { $me = new User(); $me->loadMe(); + foreach ($postData as $key => $value) { + if (is_array($value)) { + $postData[$key] = json_encode($value); + } + } + foreach (array_diff_assoc($postData, (array) $currentData) as $key => $value) { WarehouseHistoryModel::create(['table' => $mod, 'row_id' => $postData['id'], diff --git a/application/WarehouseItem/WarehouseItemController.php b/application/WarehouseItem/WarehouseItemController.php index cf1c220ce..7e5d80532 100644 --- a/application/WarehouseItem/WarehouseItemController.php +++ b/application/WarehouseItem/WarehouseItemController.php @@ -4,14 +4,25 @@ class WarehouseItemController extends TTCrud { protected string $headerTitle = 'Eintrag'; protected string $createText = 'Eintrag erstellen'; - // TODO: change articleId and warehouseLocationId to autocomplete + // TODO: check if historyController is needed + // TODO: check if apiUrl uses self::getUrl to get the correct URL // @formatter:off protected array $columns = [ - ['key' => 'articleId', 'text' => 'Artikel', 'required' => true, 'type' => 'select','table' => ['class' => 'text-nowrap'], 'modal' => ['items' => [], 'type' => 'select']], - ['key' => 'warehouseLocationId', 'text' => 'Lagerort', 'required' => true, 'type' => 'select', 'modal' => ['items' => [], 'type' => 'select']], - ['key' => 'quantity', 'text' => 'Menge', 'required' => true, 'type' => 'number'], - ['key' => 'serialNumber', 'text' => 'Seriennummer', 'required' => false], + ['key' => 'articleId', 'text' => 'Artikel', 'required' => true, 'type' => 'autocomplete','table' => ['class' => 'text-nowrap', 'filter' => 'autocomplete'],'modal' => [ + 'apiUrl' => 'WarehouseArticle/autocomplete','items' => 'WarehouseArticle/autocomplete', 'type' => 'autocomplete']], + ['key' => 'warehouseLocationId', 'text' => 'Lagerort', 'required' => true, 'type' => 'autocomplete', 'table' => ['filter' => 'autocomplete'], 'modal' => [ + 'items' => 'WarehouseLocation/autocomplete', + 'apiUrl' => 'WarehouseLocation/autocomplete', 'type' => 'autocomplete']], + ['key' => 'quantity', 'text' => 'Menge', 'required' => false, 'type' => 'number' + // quantity is only visible in modal when warehouseArticle(articleId).serial is false, add modal config here to reference warehouseArticle + , 'modal' => ['type' => 'number', + 'visible' => ['reference' => 'WarehouseArticle', 'use' => 'articleId=id', 'key' => 'isSerialDocumentation', 'value' => false] + ] + ], + ['key' => 'rack', 'text' => 'Regal', 'required' => false, 'modal' => ['type' => 'text']], + ['key' => 'shelf', 'text' => 'Fach', 'required' => false, 'modal' => ['type' => 'text'], 'table' => false], + ['key' => 'serialNumber', 'text' => 'Seriennummer', 'required' => false, 'modal' => ['type' => 'text', 'visible' => ['reference' => 'WarehouseArticle', 'use' => 'articleId=id', 'key' => 'isSerialDocumentation', 'value' => true]]], ['key' => 'note', 'text' => 'Notiz', 'required' => false], ['key' => 'actions', 'text' => 'Aktionen', 'table' => ['filter' => false], 'required' => false, 'modal' => false] ]; @@ -58,18 +69,6 @@ class WarehouseItemController extends TTCrud { return true; } - public function prepareCrudConfig() { - $articles = array_map(function($article) { - return ['value' => $article->id, 'text' => $article->title]; - }, WarehouseArticleModel::getAll()); - $this->columns[0]['modal']['items'] = $articles; - - $locations = array_map(function($location) { - return ['value' => $location->id, 'text' => $location->title]; - }, WarehouseLocationModel::getAll()); - $this->columns[1]['modal']['items'] = $locations; - } - protected function getHistoryAction() { self::returnJson((new WarehouseHistoryController)->getHistory($this->request->id, $this->mod, $this->columns)); } diff --git a/application/WarehouseItem/WarehouseItemModel.php b/application/WarehouseItem/WarehouseItemModel.php index 56eb73af7..5bff64e28 100644 --- a/application/WarehouseItem/WarehouseItemModel.php +++ b/application/WarehouseItem/WarehouseItemModel.php @@ -4,7 +4,9 @@ class WarehouseItemModel extends TTCrudBaseModel { public int $id; public int $articleId; public int $warehouseLocationId; - public int $quantity; + public ?string $rack; + public ?string $shelf; + public ?int $quantity; public ?string $serialNumber; public ?string $note; } \ No newline at end of file diff --git a/application/WarehouseLocation/WarehouseLocationController.php b/application/WarehouseLocation/WarehouseLocationController.php index af7851546..e732b3454 100644 --- a/application/WarehouseLocation/WarehouseLocationController.php +++ b/application/WarehouseLocation/WarehouseLocationController.php @@ -6,7 +6,9 @@ class WarehouseLocationController extends TTCrud { protected array $columns = [ ['key' => 'title', 'text' => 'Titel', 'required' => true], - ['key' => 'assignedTo', 'text' => 'Zugewiesen an', 'required' => true, 'modal' => ['type' => 'select', 'items' => []]], + ['key' => 'assignedTo', 'text' => 'Zugewiesen an', 'required' => true, + 'table' => ['filter' => 'select', 'items' => []], + 'modal' => ['type' => 'select', 'items' => []]], ['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']], ]; diff --git a/application/WarehouseLocation/WarehouseLocationModel.php b/application/WarehouseLocation/WarehouseLocationModel.php index 90705745d..698780440 100644 --- a/application/WarehouseLocation/WarehouseLocationModel.php +++ b/application/WarehouseLocation/WarehouseLocationModel.php @@ -3,5 +3,8 @@ class WarehouseLocationModel extends TTCrudBaseModel { public int $id; public string $title; + public string $description; public int $assignedTo; + public int $createdBy; + public int $create; } \ No newline at end of file diff --git a/application/WarehouseOrder/WarehouseOrder.php b/application/WarehouseOrder/WarehouseOrder.php new file mode 100644 index 000000000..035cdc93b --- /dev/null +++ b/application/WarehouseOrder/WarehouseOrder.php @@ -0,0 +1,9 @@ + 'id', 'text' => 'ID', 'modal' => false], + ['key' => 'distributorId', 'text' => 'Lieferant', 'required' => true, 'type' => 'autocomplete','table' => ['class' => 'text-nowrap', 'filter' => 'autocomplete'],'modal' => [ + 'apiUrl' => 'WarehouseDistributor/autocomplete','items' => 'WarehouseDistributor/autocomplete', 'type' => 'autocomplete']], + ['key' => 'extRef', 'text' => 'Externe Referenz', 'required' => false], + ['key' => 'intRef', 'text' => 'Interne Referenz', 'required' => false], + ['key' => 'status', 'text' => 'Status', 'required' => true, 'modal' => ['type' => 'select', 'items' => [ + ['value' => 'new', 'text' => 'Neu'], + ['value' => 'accepted', 'text' => 'An Lieferant übergeben'], + ['value' => 'sent', 'text' => 'Gesendet'], + ['value' => 'done', 'text' => 'Erledigt'], + ]]], + ['key' => 'trackingNumber', 'text' => 'Trackingnummer', 'required' => false], + ['key' => 'sum', 'text' => 'Summe', 'required' => true, 'modal' => false, 'table' => ['filter' => 'numberRange']], + ['key' => 'create', 'text' => 'Erstellt', 'required' => true, 'modal' => false, 'filter' => 'datetime'], + ['key' => 'createBy', 'text' => 'Erstellt von', 'required' => true, 'table' => ['filter' => 'select'], 'modal' => ['type' => 'select', 'items' => []]], + ['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']], + ]; + + protected array $additionalActions = [['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary']]; + + protected array $infoMessages = ['create' => 'Bestellung wurde erfolgreich erstellt.', + 'update' => 'Bestellung wurde aktualisiert.', + 'delete' => 'Bestellung wurde gelöscht', + 'noChanges' => 'Keine Änderungen',]; + + public function permissionCheck(): bool { + return $this->user->can(["WarehouseEShop"]); + } + + protected function prepareCrudConfig() { + // Fill Users in createBy column + $column = array_search('createBy', array_column($this->columns, 'key')); + $this->columns[$column]['modal']['items'] = array_map(function ($user) { + return ['value' => intval($user->id), 'text' => $user->name]; + }, UserModel::search()); + + } + + protected function createOrderAction() { + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + error_reporting(E_ALL); + + $json = json_decode(file_get_contents('php://input'), true); + $orders = $json; + $orderIds = []; + + foreach ($orders as $order) { + $distributor = $order['distributor'][0]; + $orderAmount = $order['orderAmount']; + $orders = $order['orders']; + + $order = [ + 'distributorId' => $distributor['id'], + 'extRef' => null, + 'status' => 'new', + 'trackingNumber' => null, + 'sum' => $orderAmount, + 'create' => time(), + 'createBy' => $this->user->id, + ]; + + $orderId = WarehouseOrderModel::create($order); + $orderIds[] = $orderId; + + foreach ($orders as $orderItem) { + $article = WarehouseArticleModel::get($orderItem['articleId']); + + WarehouseEShopOrderItemModel::create([ + 'orderId' => $orderId, + 'articleId' => $orderItem['articleId'], + 'quantity' => $orderItem['amount'], + 'price' => $article->cheapestPurchasePrice, + ]); + } + } + + self::returnJson(['success' => true, 'message' => $this->infoMessages['create'], 'ids' => $orderIds]); + } + + protected function getOrderItemsAction() { + $orderItems = WarehouseEShopOrderItemModel::getAll(['orderId' => $this->request->id]); + + // also get the article name of the order items + + foreach ($orderItems as $key => $orderItem) { + $article = WarehouseArticleModel::get($orderItem->articleId); + $orderItem->articleName = $article->title; + } + + self::returnJson($orderItems); + } + + 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/WarehouseOrder/WarehouseOrderModel.php b/application/WarehouseOrder/WarehouseOrderModel.php new file mode 100644 index 000000000..65f5d80dc --- /dev/null +++ b/application/WarehouseOrder/WarehouseOrderModel.php @@ -0,0 +1,28 @@ + 'title', 'text' => 'Titel', 'required' => true], - ['key' => 'revenueAccountNumber', 'text' => 'Erlöskonto Nummer', 'required' => true, 'modal' => ['type' => 'number']], - ['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center', 'priority' => 10]], - ]; - // @formatter:on - - protected array $infoMessages = ['create' => 'Erlöskonto wurde erstellt', - 'update' => 'Erlöskonto wurde aktualisiert', - 'delete' => 'Erlöskonto wurde gelöscht', - 'noChanges' => 'Keine Änderungen']; - - 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/WarehouseRevenueAccount/WarehouseRevenueAccountModel.php b/application/WarehouseRevenueAccount/WarehouseRevenueAccountModel.php deleted file mode 100644 index 2113d4b9f..000000000 --- a/application/WarehouseRevenueAccount/WarehouseRevenueAccountModel.php +++ /dev/null @@ -1,7 +0,0 @@ - 'id', 'text' => 'LS-Nr.', 'required' => false, 'modal' => false, 'table' => ['class' => 'text-nowrap']], + ['key' => 'billingAddressId', + 'text' => 'Rechnungsadresse', + 'required' => true, + 'type' => 'autocomplete', + 'table' => ['class' => 'text-nowrap', 'filter' => 'autocomplete'], + 'modal' => ['apiUrl' => 'Address/api?do=findAddress', 'items' => '/Address/Api?do=findAddress', 'type' => 'autocomplete']], + ['key' => 'deliveryAddressName', 'text' => 'L.-Adr. Name', 'required' => true], + ['key' => 'deliveryAddressLine', 'text' => 'L.-Adr.', 'required' => true], + ['key' => 'deliveryAddressPLZ', 'text' => 'L.-Adr. PLZ', 'required' => true], + ['key' => 'deliveryAddressCity', 'text' => 'L.-Adr. Ort', 'required' => true], + ['key' => 'status', + 'text' => 'Status', + 'required' => true, + 'table' => ['filter' => 'select'], + 'modal' => ['type' => 'select', + 'items' => [['value' => 'new', 'text' => 'Neu'], + ['value' => 'accepted', 'text' => 'Akzeptiert'], + ['value' => 'invoiced', 'text' => 'In Rechnung gestellt'],]]], + ['key' => 'positions', 'text' => 'Positionen', 'required' => true, 'table' => false, 'modal' => false], + ['key' => 'create', 'text' => 'Erstellt', 'required' => false, 'modal' => false, 'table' => ['filter' => 'date']], + ['key' => 'createBy', + 'text' => 'Erstellt von', + 'required' => true, + 'type' => 'autocomplete', + 'table' => ['class' => 'text-nowrap', 'filter' => 'select'], + 'modal' => ['items' => [], 'type' => 'select',]], + + ['key' => 'actions', + 'text' => 'Aktionen', + 'required' => false, + 'modal' => false, + 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']],]; + + protected array $additionalActions = [['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary'], + ['key' => 'print', 'title' => 'Drucken', 'class' => 'fas fa-print text-primary'], + ['key' => 'printWithPrice', 'title' => 'Drucken mit Preis', 'class' => 'fas fa-print text-success'], + ]; + + protected array $infoMessages = ['create' => 'Lieferschein wurde erstellt.', + 'update' => 'Lieferschein wurde aktualisiert', + 'delete' => 'Lieferschein wurde gelöscht', + 'noChanges' => 'Keine Änderungen vorgenommen']; + + protected function prepareCrudConfig() { + $users = array_map(function ($user) { + return ['value' => intval($user->id), 'text' => $user->name]; + }, UserModel::search()); + + $this->columns[array_search('createBy', array_column($this->columns, 'key'))]['modal']['items'] = $users; + } + + protected function beforeCreate($postData): bool { + // if postdata status is not new we return an error + if ($postData['status'] !== 'new') { + http_response_code(500); + self::returnJson(['success' => false, 'message' => 'Status muss "Neu" sein']); + die(); + } + $postData['positions'] = json_encode($postData['positions']); + return true; + } + + protected function customAutoCompleteBillingAddressId($id) { + $address = new Address($id); + if ($address->id) { + $result = ['id' => $address->id, + 'title' => str_replace("'", "\\'", str_replace(["\n", + "\r"], " ", $address->getCompanyOrName())) . " (" . $address->zip . " " . $address->city . ", " . $address->street . ")" . (($address->customer_number) ? " [" . $address->customer_number . "]" : "")]; + return $result; + } + } + + protected function beforeUpdate($postData): bool { + $postData['positions'] = json_encode($postData['positions']); + (new WarehouseHistoryController)->create($postData, $this->mod); + return true; + } + + protected function getHistoryAction() { + $historyEntries = []; + + // remove all history elements where key is positions + + foreach ((new WarehouseHistoryController)->getHistory($this->request->id, $this->mod, $this->columns) as $entry) { + if ($entry['key'] !== 'positions') { + $historyEntries[] = $entry; + } + } + +// $historyEntries = array_filter($historyEntries, function ($entry) { +// return $entry['key'] !== 'positions'; +// }); + + self::returnJson($historyEntries); + } + + protected function getArticleAddressPriceAction() { + $articleId = $this->request->articleId; + $addressId = $this->request->addressId; + + if (strlen($articleId) < 1) { + http_response_code(500); + self::returnJson(['success' => false, 'message' => 'Keine Artikel ID gefunden']); + } + + if (strlen($addressId) < 1) { + http_response_code(500); + self::returnJson(['success' => false, 'message' => 'Keine Adress ID gefunden']); + } + + //TODO: implement a select to select price category for each address + // for now we default with price with name "Verkauf" + $prices = WarehouseArticlePriceTypeModel::getAll(['title' => 'Verkauf']); + // if array is empty we return an error + if (empty($prices)) { + http_response_code(500); + self::returnJson(['success' => false, 'message' => 'Keine Preiskategorie gefunden']); + } + $priceType = $prices[0]->title; + + $article = WarehouseArticleModel::get($articleId); + $sellPrices = json_decode($article->cheapestSellPrice, true); + $sellPrice = array_search($priceType, array_column($sellPrices, 'title')); + + if (empty($sellPrice)) { + http_response_code(500); + self::returnJson(['success' => false, 'message' => 'Kein Preis gefunden']); + } + + self::returnJson(['success' => true, 'price' => $sellPrices[$sellPrice]['price']]); + } + + protected function getDeliveryAddressesAction() { + $billingAddressId = $this->request->billingAddressId; + if (strlen($billingAddressId) < 1) { + http_response_code(500); + self::returnJson(['success' => false, 'message' => 'Keine Rechnungsadresse gefunden']); + } + + $deliveryAddresses = WarehouseShippingNoteModel::getAll(['billingAddressId' => $billingAddressId]); + // TODO: maybe this should be improved as it is kinda hacky + $result = []; + foreach ($deliveryAddresses as $deliveryAddress) { + $found = false; + foreach ($result as $r) { + if ($r->deliveryAddressName == $deliveryAddress->deliveryAddressName && $r->deliveryAddressLine == $deliveryAddress->deliveryAddressLine) { + $found = true; + break; + } + } + if ($found) { + continue; + } + $result[] = $deliveryAddress; + } + + self::returnJson($result); + } + + protected function getAllTextElementsAction() { + $textElements = WarehouseShippingNoteTextElementModel::getAll(); + self::returnJson($textElements); + } + + protected function createPDFAction() { + $id = $this->request->id; + if (strlen($id) < 1) { + http_response_code(500); + self::returnJson(['success' => false, 'message' => 'Lieferschein wurde nicht gefunden']); + } + + $shippingNote = WarehouseShippingNoteModel::get($id); + $address = AddressModel::getOne($shippingNote->billingAddressId); + $positions = []; + + // loop through all positions and add articleTitle and articleDescription to each position entry + foreach (json_decode($shippingNote->positions, true) as $position) { + $article = WarehouseArticleModel::get($position['article']); + $position['articleTitle'] = $article->title; + $position['articleDescription'] = $article->description; + $position['articleUnit'] = $article->unit; + $positions[] = $position; + } + + $textElements = []; + // parse shippingNote.textElements ({"1":true,"2":true}) to array, fetch each text element and put content into array + $shippingNoteTextElements = json_decode($shippingNote->textElements, true); + foreach ($shippingNoteTextElements as $key => $value) { + if ($value) { + $textElement = WarehouseShippingNoteTextElementModel::get($key); + $textElements[] = $textElement->content; + } + } + if (empty($textElements)) { + $textElements = null; + } + + $pdf_vars = ["shippingNote" => $shippingNote, + "positions" => $positions, + "textElements" => $textElements, + "showPrices" => isset($_GET['price']) && $_GET['price'] == "true", + "bank_iban" => TT_INVOICE_BANK_IBAN, + "bank_bic" => TT_INVOICE_BANK_BIC, + "bank_bank" => TT_INVOICE_BANK_BANK, + "bank_owner" => TT_INVOICE_BANK_OWNER]; + + // Replace placeholders in header + // create shipping note in this format LS2024-X0001 + // pad number on the left side with zeros + $shippingNoteNumber = "LS" . date("Y", $shippingNote->create) . "-" . str_pad($shippingNote->id, 4, "0", STR_PAD_LEFT); + $headerHtml = file_get_contents(BASEDIR . "/Layout/default/WarehouseShippingNote/PDF_HEADER.html"); + $headerHtml = str_replace("{{ basedir }}", BASEDIR, $headerHtml); + $headerHtml = str_replace("{{ addressLine_1 }}", $shippingNote->deliveryAddressName, $headerHtml); + $headerHtml = str_replace("{{ addressLine_2 }}", $shippingNote->deliveryAddressLine, $headerHtml); + $headerHtml = str_replace("{{ addressLine_3 }}", $shippingNote->deliveryAddressPLZ . " " . $shippingNote->deliveryAddressCity, $headerHtml); + $headerHtml = str_replace("{{ addressLine_4 }}", "", $headerHtml); + $headerHtml = str_replace("{{ addressLine_5 }}", "", $headerHtml); + $headerHtml = str_replace("{{ customerNumber }}", $address->customer_number, $headerHtml); + $headerHtml = str_replace("{{ shippingNoteNumber }}", $shippingNoteNumber, $headerHtml); + $headerHtml = str_replace("{{ shippingNoteDate }}", date("d.m.Y", $shippingNote->create), $headerHtml); + + $headerFile = BASEDIR . "/var/temp/shipping-note_header-" . date("U") . "-" . rand(1000, 9999) . ".html"; + file_put_contents($headerFile, $headerHtml); + + + // Replace placeholders in header + $footerHtml = file_get_contents(BASEDIR . "/Layout/default/WarehouseShippingNote/PDF_FOOTER.html"); + $footerHtml = str_replace("{{ bank_iban }}", TT_INVOICE_BANK_IBAN_FORMATTED, $footerHtml); + $footerHtml = str_replace("{{ bank_bic }}", TT_INVOICE_BANK_BIC, $footerHtml); + $footerHtml = str_replace("{{ bank_bank }}", TT_INVOICE_BANK_BANK, $footerHtml); + $footerHtml = str_replace("{{ bank_owner }}", TT_INVOICE_BANK_OWNER, $footerHtml); + + + $footerFile = BASEDIR . "/var/temp/shipping-note_header-" . date("U") . "-" . rand(1000, 9999) . ".html"; + file_put_contents($footerFile, $footerHtml); + + + $pdf = new PdfForm("WarehouseShippingNote/PDF_MAIN", $pdf_vars); + $wkhtmltopdfArgs = "--header-html $headerFile --footer-html $footerFile"; + $filename = $pdf->render($wkhtmltopdfArgs); + + // return the pdf and die so the client sees the pdf not the filename + header('Content-Type: application/pdf'); + header('Content-Disposition: inline; filename="' . $filename . '"'); + readfile($filename); + } +} \ No newline at end of file diff --git a/application/WarehouseShippingNote/WarehouseShippingNoteModel.php b/application/WarehouseShippingNote/WarehouseShippingNoteModel.php new file mode 100644 index 000000000..8e284d797 --- /dev/null +++ b/application/WarehouseShippingNote/WarehouseShippingNoteModel.php @@ -0,0 +1,17 @@ + '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 new file mode 100644 index 000000000..c95c53237 --- /dev/null +++ b/application/WarehouseShippingNoteTextElement/WarehouseShippingNoteTextElementModel.php @@ -0,0 +1,10 @@ +getEnvironment() == "thetool") { + // Create Tables + $WarehouseShippingNoteTextElement = $this->table("WarehouseShippingNoteTextElement", ["signed" => true]); + $WarehouseShippingNoteTextElement + ->addColumn('title', 'string', ['null' => false]) + ->addColumn('content', 'text', ['null' => false]) + ->addColumn('create', 'integer', ['null' => false, 'default' => 1728541890]) + ->addColumn('createBy', 'integer', ['null' => false, 'default' => 1]) + ->create(); + + $WarehouseShippingNote = $this->table("WarehouseShippingNote", ["signed" => true]); + $WarehouseShippingNote + ->addColumn('billingAddressId', 'integer', ['null' => false]) + ->addColumn('deliveryAddressName', 'string', ['null' => false]) + ->addColumn('deliveryAddressLine', 'string', ['null' => false]) + ->addColumn('deliveryAddressPLZ', 'string', ['null' => false]) + ->addColumn('deliveryAddressCity', 'string', ['null' => false]) + ->addColumn('status', 'enum', ['values' => ['new', 'accepted', 'invoiced'], 'null' => false]) + ->addColumn('positions', 'text', ['null' => false]) + ->addColumn('textElements', 'text', ['null' => false]) + ->addColumn('create', 'integer', ['null' => false, 'default' => 1728541890]) + ->addColumn('createBy', 'integer', ['null' => false, 'default' => 1]) + ->create(); + + $WarehouseOrder = $this->table("WarehouseOrder", ["signed" => true]); + $WarehouseOrder + ->addColumn('distributorId', 'integer', ['null' => false]) + ->addColumn('intRef', 'string', ['null' => true]) + ->addColumn('extRef', 'string', ['null' => true]) + ->addColumn('status', 'enum', ['values' => ['new', 'accepted', 'sent', 'done'], 'null' => false, 'default' => 'new']) + ->addColumn('sum', 'float', ['null' => true]) + ->addColumn('trackingNumber', 'string', ['null' => true]) + ->addColumn('create', 'integer', ['null' => false, 'default' => 1728541890]) + ->addColumn('createBy', 'integer', ['null' => false, 'default' => 1]) + ->create(); + + $WarehouseOrderItem = $this->table("WarehouseOrderItem", ["signed" => true]); + $WarehouseOrderItem + ->addColumn('orderId', 'integer', ['null' => false]) + ->addColumn('articleId', 'integer', ['null' => false]) + ->addColumn('quantity', 'integer', ['null' => false]) + ->addColumn('price', 'float', ['null' => false]) + ->addForeignKey('orderId', 'WarehouseOrder', 'id') + ->addForeignKey('articleId', 'WarehouseArticle', 'id') + ->create(); + + // Delete Table + $WarehouseRevenueAccount = $this->table("WarehouseRevenueAccount"); + $WarehouseRevenueAccount->drop()->save(); + + // Modify Tables + $WarehouseLocation = $this->table("WarehouseLocation"); + $WarehouseLocation + ->addColumn('description', 'text', ['null' => true]) + ->addColumn('createBy', 'integer', ['null' => false, 'default' => 1728541890]) + ->addColumn('create', 'integer', ['null' => false, 'default' => 1]) + ->update(); + + $WarehouseItem = $this->table("WarehouseItem"); + $WarehouseItem + ->changeColumn('quantity', 'integer', ['null' => true]) + ->addColumn('rack', 'string', ['null' => true]) + ->addColumn('shelf', 'string', ['null' => true]) + ->addColumn('createBy', 'integer', ['null' => false, 'default' => 1728541890]) + ->addColumn('create', 'integer', ['null' => false, 'default' => 1]) + ->update(); + + $WarehouseArticlePriceType = $this->table("WarehouseArticlePriceType"); + $WarehouseArticlePriceType + ->addColumn('description', 'string', ['null' => true]) + ->addColumn('defaultPriceFactor', 'float', ['null' => true]) + ->addColumn('createBy', 'integer', ['null' => false, 'default' => 1728541890]) + ->addColumn('create', 'integer', ['null' => false, 'default' => 1]) + ->update(); + + // Set nullable createBy and create columns + $WarehouseLocation + ->changeColumn('createBy', 'integer', ['null' => true]) + ->changeColumn('create', 'integer', ['null' => true]) + ->save(); + + $WarehouseItem + ->changeColumn('createBy', 'integer', ['null' => true]) + ->changeColumn('create', 'integer', ['null' => true]) + ->save(); + + $WarehouseArticlePriceType + ->changeColumn('createBy', 'integer', ['null' => true]) + ->changeColumn('create', 'integer', ['null' => true]) + ->save(); + } + } + + public function down(): void + { + if ($this->getEnvironment() == "thetool") { + // Drop created tables + $this->table('WarehouseShippingNoteTextElement')->drop()->save(); + $this->table('WarehouseShippingNote')->drop()->save(); + $this->table('WarehouseOrder')->drop()->save(); + $this->table('WarehouseOrderItem')->drop()->save(); + + // Recreate deleted table + $WarehouseRevenueAccount = $this->table("WarehouseRevenueAccount"); + $WarehouseRevenueAccount + ->addColumn('revenueAccountNumber', 'integer', ['null' => false]) + ->addColumn('title', 'string', ['null' => false]) + ->create(); + + // Revert modifications + $WarehouseLocation = $this->table("WarehouseLocation"); + $WarehouseLocation + ->removeColumn('description') + ->removeColumn('createBy') + ->removeColumn('create') + ->update(); + + $WarehouseItem = $this->table("WarehouseItem"); + $WarehouseItem + ->changeColumn('quantity', 'integer', ['null' => false]) + ->removeColumn('rack') + ->removeColumn('shelf') + ->removeColumn('createBy') + ->removeColumn('create') + ->update(); + + $WarehouseArticlePriceType = $this->table("WarehouseArticlePriceType"); + $WarehouseArticlePriceType + ->removeColumn('description') + ->removeColumn('defaultPriceFactor') + ->removeColumn('createBy') + ->removeColumn('create') + ->update(); + } + } +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index af580e971..f2ae0ab7a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,7 +29,7 @@ services: adminer: image: adminer ports: - - "8080:8080" + - "8088:8080" volumes: - ./docker/adminer/php.ini:/etc/php/7.4/cli/conf.d/php.local.ini diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 73c122e5a..e97ec1a02 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -1,6 +1,13 @@ # Use Debian Bookworm as base image FROM debian:bookworm +# Install wkhtmltopdf +RUN apt update +RUN apt install wget libfontenc1 xfonts-75dpi xfonts-base xfonts-encodings xfonts-utils openssl build-essential libssl-dev libxrender-dev git-core libx11-dev libxext-dev libfontconfig1-dev libfreetype6-dev fontconfig -y +# wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.stretch_amd64.deb +# dpkg ignore + + # Install apache2 and PHP and PHP modules RUN apt update && \ apt install -y apache2 curl cron unzip php8.2 php8.2-curl php8.2-cli php8.2-mysqli php8.2-gd php8.2-zip php8.2-dom php8.2-mbstring && \ diff --git a/lib/TTCrud/TTCrud.php b/lib/TTCrud/TTCrud.php index fde700436..d6f17358c 100644 --- a/lib/TTCrud/TTCrud.php +++ b/lib/TTCrud/TTCrud.php @@ -4,6 +4,7 @@ * Class TTCrud * @property string $headerTitle * @property string $createText + * @property string|null $historyController * @property array $columns * @property array $additionalActions * @property array $infoMessages @@ -32,7 +33,7 @@ class TTCrud extends mfBaseController { $this->redirect("Dashboard"); } - $modelName = $this->mod . 'Model'; + $modelName = str_replace('Controller', 'Model', get_class($this)); $this->model = new $modelName(); $this->postData = json_decode(file_get_contents('php://input'), true); $this->checkArray = $this->getCheckArray(); @@ -88,6 +89,20 @@ class TTCrud extends mfBaseController { return $newColumn; }, $this->columns); + // check if in columns array there is a column with key "actions" and if so, we set the priority of the first column to 20 and actions to 19 + $actionsColumn = array_filter($columns, function ($column) { + return $column['key'] === 'actions'; + }); + if (count($actionsColumn) > 0) { + $columns = array_map(function ($column) { + if ($column['key'] === 'actions') { + $column['priority'] = 119; + } + return $column; + }, $columns); + $columns[0]['priority'] = 120; + } + return ['key' => $this->mod, 'tableHeader' => $this->headerTitle, 'createText' => $this->createText, @@ -105,7 +120,41 @@ class TTCrud extends mfBaseController { $filteredAvailable = $this->model::count($filter); $totalRows = $this->model::count(); + // check if any column is a autocomplete to add the text to the row + $autoCompleteColumns = array_filter($this->columns, function ($column) { + return isset($column['type']) && isset($column['modal']) && isset($column['modal']['type']) && $column['type'] === 'autocomplete' && $column['modal']['type'] === 'autocomplete'; + + }); + $autocompleteData = []; + foreach ($rows as $row) { + $row = (array) $row; + foreach ($autoCompleteColumns as $column) { + if (isset($autocompleteData[$column['key']][$row[$column['key']]])) { + continue; + } + + // if function customAutoComplete"COLUMN_KEY" is defined, we call it instead of the default + $data = null; + if (method_exists($this, 'customAutoComplete' . ucfirst($column['key']))) { + $data = $this->{'customAutoComplete' . ucfirst($column['key'])}($row[$column['key']]); + } else { + + $autoCompleteModelName = explode('/', $column['modal']['apiUrl'])[0] . 'Model'; + $autoCompleteModel = new $autoCompleteModelName(); + $data = $autoCompleteModel::get($row[$column['key']]); + + // TODO: fix that keys can be anything + if (isset($data->name) && !isset($data->title)) { + $data->title = $data->name; + } + } + + $autocompleteData[$column['key']][$row[$column['key']]] = $data; + } + } + self::returnJson(["rows" => $rows, + "autoCompleteData" => $autocompleteData, "pagination" => ["page" => $page, "total_pages" => ceil($filteredAvailable / $perPage), "per_page" => $perPage, @@ -114,6 +163,14 @@ class TTCrud extends mfBaseController { } protected function createAction() { + // if this->model has property createBy, set it to the current user id and create to current epoch time + if (property_exists($this->model, 'createBy')) { + $this->postData['createBy'] = $this->user->id; + } + if (property_exists($this->model, 'create')) { + $this->postData['create'] = time(); + } + Helper::validateArray($this->postData, $this->checkArray); if (method_exists($this, 'beforeCreate') && !$this->beforeCreate($this->postData)) { @@ -178,6 +235,18 @@ class TTCrud extends mfBaseController { return ['value' => $item->id, 'text' => $item->$textKey]; }, $data)); } + + protected function getByIdAction() { + $id = $_GET['id'] ?? null; + if (!$id || !is_numeric($id)) { + http_response_code(500); + self::returnJson(['success' => false, 'message' => 'No ID provided.']); + die(); + } + + $data = (array) $this->model::get($id); + self::returnJson($data); + } } ?> diff --git a/lib/TTCrudBaseModel/TTCrudBaseModel.php b/lib/TTCrudBaseModel/TTCrudBaseModel.php index c296fd258..3184ffa10 100644 --- a/lib/TTCrudBaseModel/TTCrudBaseModel.php +++ b/lib/TTCrudBaseModel/TTCrudBaseModel.php @@ -19,7 +19,15 @@ class TTCrudBaseModel { $sqlValues = []; foreach ($data as $field => $value) { if (!property_exists(get_called_class(), $field)) { - throw new Exception("Field $field does not exist in " . get_called_class()); + die(json_encode([ + "status" => "error", + "error" => "Field $field does not exist in " . get_called_class(), + "data" => $data + ])); + } + + if (is_array($value)) { + $value = json_encode($value); } $sqlValues[] = $value === null ? 'NULL' : "'" . $db->real_escape_string($value) . "'"; @@ -62,7 +70,12 @@ class TTCrudBaseModel { } if (!isset($data[$field])) { - throw new Exception("Required field $field is missing in data array"); + http_response_code(500); + die(json_encode([ + "status" => "error", + "error" => "Required field $field is missing in data array", + "data" => $data + ])); } } } @@ -104,6 +117,7 @@ class TTCrudBaseModel { $sql = "WHERE 1=1"; foreach ($filter as $key => $value) { if (!property_exists(get_called_class(), $key)) { + http_response_code(500); throw new Exception("Field $key does not exist in " . get_called_class()); } $sql .= Helper::generateFilterCondition($value, $key, gettype($value) === "integer"); @@ -161,6 +175,10 @@ class TTCrudBaseModel { $value = null; } + if (is_array($value)) { + $value = json_encode($value); + } + $values[] = $value === null ? "`$field` = NULL" : "`$field` = '" . $db->real_escape_string($value) . "'"; } diff --git a/lib/mvcfronk/mfExceptionhandler/mfExceptionhandler.php b/lib/mvcfronk/mfExceptionhandler/mfExceptionhandler.php index a457dc26c..0e83edf95 100644 --- a/lib/mvcfronk/mfExceptionhandler/mfExceptionhandler.php +++ b/lib/mvcfronk/mfExceptionhandler/mfExceptionhandler.php @@ -29,6 +29,9 @@ class mfExceptionhandler { public function __toString() { $str="[".$this->Time."] "; if(is_numeric($this->Code) && $this->Code > 0) { + if($this->Code == 404 || $this->Code == 500) { + http_response_code($this->Code); + } $str.="(Error code ".$this->Code.") "; } diff --git a/lib/mvcfronk/mfRouter/mfRouter.php b/lib/mvcfronk/mfRouter/mfRouter.php index e6752dfdf..01c6f6193 100644 --- a/lib/mvcfronk/mfRouter/mfRouter.php +++ b/lib/mvcfronk/mfRouter/mfRouter.php @@ -168,7 +168,8 @@ class mfRouter { } } - + + $baseurl = $baseurl ?? ""; $baseurl = preg_replace('@/$@', '', $baseurl); define("MFFANCYBASEURL",$baseurl); } diff --git a/public/js/pages/WarehouseAdministration/WarehouseAdministration.js b/public/js/pages/WarehouseAdministration/WarehouseAdministration.js new file mode 100644 index 000000000..5ebc226cf --- /dev/null +++ b/public/js/pages/WarehouseAdministration/WarehouseAdministration.js @@ -0,0 +1,56 @@ +Vue.component('warehouse-administration', { + //language=Vue + template: ` + + + +
+ + +
+
+ `, + data() { + return { + isLoading: false, + window: window, + }; + }, + methods: { + async createLocationsForAllEmployees() { + this.isLoading = true; + const response = await axios.get(window.TT_CONFIG.BASE_URL + '/createLocations'); + + if (response.data.success) { + this.window.notify('success', response.data.message || 'Lagerorte wurden erfolgreich erstellt.'); + } else { + this.window.notify('error', response.data.message || 'Fehler beim Erstellen der Lagerorte.'); + } + this.isLoading = false; + }, + async updateAllSalesPrices() { + this.isLoading = true; + const response = await axios.get(window.TT_CONFIG.BASE_PATH + '/WarehouseArticle/updatePrices'); + if (response.data.success) { + this.window.notify('success', 'Verkaufspreise wurden erfolgreich aktualisiert.'); + } else { + this.window.notify('error', 'Fehler beim Aktualisieren der Verkaufspreise.'); + } + this.isLoading = false; + }, + sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + } +}); \ No newline at end of file diff --git a/public/js/pages/WarehouseArticle/WarehouseArticle.js b/public/js/pages/WarehouseArticle/WarehouseArticle.js index 0d9b6d676..fa6c05c06 100644 --- a/public/js/pages/WarehouseArticle/WarehouseArticle.js +++ b/public/js/pages/WarehouseArticle/WarehouseArticle.js @@ -250,11 +250,14 @@ Vue.component('warehouse-article', { //language=Vue template: ` + + @editThresholdEntries="thresholdModal = true; thresholdModalId = $event.id" + @addToCart="addShoppingCartModal = true; addShoppingCartModalId = $event.id" + > + + -