Merge branch 'feature/update-warehouse' into 'master'

update for warehouse

See merge request fronk/thetool!654
This commit is contained in:
Luca Haid
2024-10-10 06:52:01 +00:00
56 changed files with 2250 additions and 451 deletions
@@ -534,6 +534,7 @@ class AddressController extends mfBaseController {
];
$results[] = $result;
$this->returnJson($results);
die();
}
}
@@ -0,0 +1,9 @@
<?php
/**
* @property mixed|null $name
*/
class WarehouseAdministration extends mfBaseModel
{
}
@@ -0,0 +1,110 @@
<?php
class WarehouseAdministrationController extends mfBaseController {
private User $me;
protected function init(): void {
$me = new User();
$me->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();
}
}
@@ -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);
}
}
@@ -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;
@@ -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() {
@@ -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;
}
@@ -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();
}
@@ -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;
}
@@ -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'],
@@ -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));
}
@@ -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;
}
@@ -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']],
];
@@ -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;
}
@@ -0,0 +1,9 @@
<?php
/**
* @property mixed|null $name
*/
class WarehouseOrder extends mfBaseModel
{
}
@@ -0,0 +1,110 @@
<?php
//TODO: enable switching distributors in the order preview
class WarehouseOrderController extends TTCrud {
protected string $headerTitle = 'Lieferantenbestellungen';
protected bool $createText = false;
protected array $columns = [
['key' => '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));
}
}
@@ -0,0 +1,28 @@
<?php
//TODO: fix phpdoc
/**
* @property int $id
* @property 'new'|'accepted'|'sent'|'done' $status
* @property 'singleAddress'|'multipleAddresses' $deliveryMode
* @property string $deliveryAddressName
* @property string $deliveryAddressLine
* @property string $deliveryAddressPLZ
* @property string $deliveryAddressCity
* @property int $create
* @property int $createBy
*/
// id, distributorId, intRef, extRef, status, trackingNumber, create, createBy
class WarehouseOrderModel extends TTCrudBaseModel {
public int $id;
public int $distributorId;
public ?string $intRef;
public ?string $extRef;
public float $sum;
public string $status;
public ?string $trackingNumber;
public int $create;
public int $createBy;
}
@@ -0,0 +1,9 @@
<?php
/**
* @property mixed|null $name
*/
class WarehouseOrderItem extends mfBaseModel
{
}
@@ -0,0 +1,16 @@
<?php
/**
* @property int $id
* @property int $orderId
* @property int $articleId
* @property int $quantity
* @property int $price
*/
class WarehouseOrderItemModel extends TTCrudBaseModel {
public int $id;
public int $orderId;
public int $articleId;
public int $quantity;
public float $price;
}
@@ -1,9 +0,0 @@
<?php
/**
* @property mixed|null $name
*/
class WarehouseRevenueAccount extends mfBaseModel
{
}
@@ -1,39 +0,0 @@
<?php
class WarehouseRevenueAccountController extends TTCrud {
protected string $headerTitle = 'Erlöskontos';
protected string $createText = 'Erlöskonto erstellen';
// @formatter:off
protected array $columns = [
['key' => '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);
}
}
@@ -1,7 +0,0 @@
<?php
class WarehouseRevenueAccountModel extends TTCrudBaseModel {
public int $id;
public int $revenueAccountNumber;
public string $title;
}
@@ -0,0 +1,9 @@
<?php
/**
* @property mixed|null $name
*/
class WarehouseShippingNote extends mfBaseModel
{
}
@@ -0,0 +1,254 @@
<?php
class WarehouseShippingNoteController extends TTCrud {
protected string $headerTitle = 'Lieferscheine';
protected bool $createText = false;
protected array $columns = [['key' => '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);
}
}
@@ -0,0 +1,17 @@
<?php
class WarehouseShippingNoteModel extends TTCrudBaseModel {
public int $id;
public int $billingAddressId;
public string $deliveryAddressName;
public string $deliveryAddressLine;
public string $deliveryAddressPLZ;
public string $deliveryAddressCity;
public string $status; // 'new'|'accepted'|'invoiced'
public string $positions;
public string $textElements;
public int $create;
public int $createBy;
}
@@ -0,0 +1,9 @@
<?php
/**
* @property mixed|null $name
*/
class WarehouseShippingNoteTextElement extends mfBaseModel
{
}
@@ -0,0 +1,48 @@
<?php
class WarehouseShippingNoteTextElementController extends TTCrud {
protected string $headerTitle = 'Lieferschein Textelemente';
protected string $createText = 'Lieferschein Textelement erstellen';
// @formatter:off
protected array $columns = [
['key' => '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);
}
}
@@ -0,0 +1,10 @@
<?php
class WarehouseShippingNoteTextElementModel extends TTCrudBaseModel {
public int $id;
public string $title;
public string $content;
public int $create;
public int $createBy;
}