Feature/add e shop

This commit is contained in:
Luca Haid
2024-07-24 13:25:49 +00:00
parent 1c8f1acf2a
commit 6c79a9302f
30 changed files with 588 additions and 53 deletions

View File

@@ -225,6 +225,31 @@
</div>
</div>
</div>
<h4 class="card-title mb-3 mt-3">Lager</h4>
<div class="row">
<div class="col-4">
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" name="can[WarehouseAdmin]" id="can_warehouse_admin" value="1" <?=($user && $user->can("WarehouseAdmin")) ? "checked='checked'" : ""?> />
<label for="can_warehouse_admin" class="form-check-label">Lager-Admin</label>
</div>
</div>
<div class="col-4">
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" name="can[WarehouseUser]" id="can_warehouse_user" value="1" <?=($user && $user->can("WarehouseUser")) ? "checked='checked'" : ""?> />
<label for="can_warehouse_user" class="form-check-label">Lager-User</label>
</div>
</div>
<div class="col-4">
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" name="can[WarehouseEShop]" id="can_warehouse_e_shop" value="1" <?=($user && $user->can("WarehouseEShop")) ? "checked='checked'" : ""?> />
<label for="can_warehouse_e_shop" class="form-check-label">Energie Steiermark Shop</label>
</div>
</div>
</div>
<h4 class="card-title mb-3 mt-3">Zusatzberechtigungen</h4>

View File

@@ -130,23 +130,28 @@
</li>
<?php endif; ?>
<?php if($me->is(["Admin"])&& isset($_GET['warehouse'])): ?>
<?php if($me->can(["WarehouseAdmin", "WarehouseUser", "WarehouseEShop"])): ?>
<li class="has-submenu">
<a href="#">
<i class="fa-solid fa-warehouse"></i>Lager <div class="arrow-down"></div>
<?php if ($me->can("WarehouseEShop") && !($me->can("WarehouseAdmin") || $me->can("WarehouseUser"))): ?>
<i class="fas fa-fw fa-shopping-cart"></i>E-Shop<div class="arrow-down"></div>
<?php elseif ($me->can("WarehouseAdmin") || $me->can("WarehouseUser")): ?>
<i class="fas fa-fw fa-warehouse"></i>Lager<div class="arrow-down"></div>
<?php endif; ?>
</a>
<ul class="submenu">
<!-- create links for WarehouseArticle, WarehouseDistributor, WarehouseLocation, WarehouseItem, WarehouseOrderRecommendation -->
<?php if($me->isAdmin() || $me->can("WarehouseArticle")): ?><li><a href="<?=self::getUrl("WarehouseArticle")?>?warehouse"><i class="far fa-fw fa-box text-info"></i> Artikel</a></li><?php endif; ?>
<?php if($me->isAdmin() || $me->can("WarehouseDistributor")): ?><li><a href="<?=self::getUrl("WarehouseDistributor")?>?warehouse"><i class="far fa-fw fa-truck text-info"></i> Lieferanten</a></li><?php endif; ?>
<?php if($me->isAdmin() || $me->can("WarehouseArticlePriceType")): ?><li><a href="<?=self::getUrl("WarehouseArticlePriceType")?>?warehouse"><i class="far fa-fw fa-money-bill-wave text-info"></i> Preis Typen</a></li><?php endif; ?>
<?php if($me->isAdmin() || $me->can("WarehouseLocation")): ?><li><a href="<?=self::getUrl("WarehouseLocation")?>?warehouse"><i class="far fa-fw fa-map-marker-alt text-info"></i> Lagerorte</a></li><?php endif; ?>
<?php if($me->isAdmin() || $me->can("WarehouseItem")): ?><li><a href="<?=self::getUrl("WarehouseItem")?>?warehouse"><i class="far fa-fw fa-boxes text-info"></i> Lagerbestand</a></li><?php endif; ?>
<?php if($me->isAdmin() || $me->can("WarehouseOrderRecommendation")): ?><li><a href="<?=self::getUrl("WarehouseOrderRecommendation")?>?warehouse"><i class="far fa-fw fa-box-full text-info"></i> Bestellvorschläge</a></li><?php endif; ?>
<?php if($me->isAdmin() || $me->can("WarehouseEShop")): ?><li><a href="<?=self::getUrl("WarehouseEShop")?>?warehouse"><i class="far fa-fw fa-shopping-cart text-info"></i> E-Shop</a></li><?php endif; ?>
<?php if($me->isAdmin() || $me->can("WarehouseEShopOrder")): ?><li><a href="<?=self::getUrl("WarehouseEShopOrder")?>?warehouse"><i class="far fa-fw fa-shopping-basket text-info"></i> E-Shop Bestellungen</a></li><?php endif; ?>
<?php if($me->isAdmin() || $me->can("WarehouseOrder")): ?><li><a href="<?=self::getUrl("WarehouseOrder")?>?warehouse"><i class="far fa-fw fa-shopping-bag text-info"></i> Bestellungen</a></li><?php endif; ?>
<?php if($me->isAdmin() || $me->can("WarehouseShippingNote")): ?><li><a href="<?=self::getUrl("WarehouseShippingNote")?>?warehouse"><i class="far fa-fw fa-shipping-fast text-info"></i> Lieferscheine</a></li><?php endif; ?>
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseArticle")?>"><i class="far fa-fw fa-box text-info"></i> Artikel</a></li><?php endif; ?>
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseArticlePacket")?>"><i class="far fa-fw fa-box text-info"></i> Artikel-Pakete (EStmk)</a></li><?php endif; ?>
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseDistributor")?>"><i class="far fa-fw fa-truck text-info"></i> Lieferanten</a></li><?php endif; ?>
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseArticlePriceType")?>"><i class="far fa-fw fa-money-bill-wave text-info"></i> Preis Typen</a></li><?php endif; ?>
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseLocation")?>"><i class="far fa-fw fa-map-marker-alt text-info"></i> Lagerorte</a></li><?php endif; ?>
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseItem")?>"><i class="far fa-fw fa-boxes text-info"></i> Lagerbestand</a></li><?php endif; ?>
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseOrderRecommendation")?>"><i class="far fa-fw fa-box-full text-info"></i> Bestellvorschläge</a></li><?php endif; ?>
<?php if($me->can("WarehouseEShop")): ?><li><a href="<?=self::getUrl("WarehouseEShop")?>"><i class="far fa-fw fa-shopping-cart text-info"></i> E-Shop</a></li><?php endif; ?>
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseEShopOrder")?>"><i class="far fa-fw fa-shopping-basket text-info"></i> E-Shop Bestellungen</a></li><?php endif; ?>
<?php if($me->can("WarehouseAdmins")): ?><li><a href="<?=self::getUrl("WarehouseOrder")?>"><i class="far fa-fw fa-shopping-bag text-info"></i> Bestellungen</a></li><?php endif; ?>
<?php if($me->can("WarehouseAdmins")): ?><li><a href="<?=self::getUrl("WarehouseShippingNote")?>"><i class="far fa-fw fa-shipping-fast text-info"></i> Lieferscheine</a></li><?php endif; ?>
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseRevenueAccount")?>"><i class="far fa-fw fa-money-bill-wave text-info"></i> Erlöskontos</a></li><?php endif; ?>
</ul>
</li>
<?php endif; ?>

View File

@@ -261,10 +261,9 @@ class User extends mfBaseModel {
if(!is_array($what)) {
$what = [$what];
}
//ob_end_clean();var_dump($what, $this->permissions);exit;
foreach($what as $w) {
$perm = ucfirst(strtolower($w));
$perm = ucfirst(($w));
if(is_object($this->permissions) && property_exists($this->permissions->data, "can$perm")) {
if($this->permissions->{"can$perm"} === "true") {
return true;

View File

@@ -235,6 +235,9 @@ class UserController extends mfBaseController
$user->permissions->canBilling = "false";
$user->permissions->canFibu = "false";
$user->permissions->canStatistics = "false";
$user->permissions->canWarehouseAdmin = "false";
$user->permissions->canWarehouseEShop = "false";
$user->permissions->canWarehouseUser = "false";
if($r->get("can") && is_array($r->can)) {
foreach($r->can as $key => $can) {

View File

@@ -9,11 +9,15 @@ class WarehouseArticleController extends TTCrud {
['key' => 'title', 'text' => 'Titel', 'required' => true, 'table' => ['priority' => 9]],
['key' => 'description', 'text' => 'Beschreibung', 'required' => true, 'table' => false],
['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' => 'number'], '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', 'modal' => ['type' => 'number'], 'table' => ['class' => 'text-center']], // Stock/inventory related
['key' => 'criticalAmount', 'text' => 'Kritische Menge', 'modal' => ['type' => 'number'], 'table' => ['class' => 'text-center']], // Stock/inventory related
['key' => 'isEShop', 'text' => 'Ist E-Shop', 'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value
['key' => 'warningAmount', 'text' => 'Warnmenge', 'required' => true,'modal' => ['type' => 'number'], 'table' => ['class' => 'text-center']], // Stock/inventory related
['key' => 'criticalAmount', 'text' => 'Kritische Menge', 'required' => true,'modal' => ['type' => 'number'], 'table' => ['class' => 'text-center']], // Stock/inventory related
['key' => 'isSerialDocumentation', 'text' => 'Seriennummern', 'required' => true,'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value
['key' => 'isEShop', 'text' => 'Ist E-Shop', 'required' => true,'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value
['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center', 'priority' => 8]]
];
@@ -43,6 +47,7 @@ class WarehouseArticleController extends TTCrud {
$cheapestPurchasePrice = WarehouseArticleDistributorModel::getAll(['articleId' => $articleId], 1, 0,
['key' => 'purchasePrice', 'order' => 'ASC'])[0]->purchasePrice;
$article = WarehouseArticleModel::get($articleId);
if (!$article instanceof WarehouseArticleModel) {
@@ -53,13 +58,59 @@ class WarehouseArticleController extends TTCrud {
// $cheapestSellPrice = $article->sellPriceOverride ?? $article->sellPriceMultiplier * $cheapestPurchasePrice;
if ($article->cheapestPurchasePrice != $cheapestPurchasePrice) {
WarehouseArticleModel::update([...get_object_vars($article), // Unpack properties into an array
'cheapestPurchasePrice' => $cheapestPurchasePrice]);
try {
WarehouseArticleModel::update([...get_object_vars($article), // Unpack properties into an array
'cheapestPurchasePrice' => $cheapestPurchasePrice]);
} catch (Exception $e) {
print_r($article);
echo PHP_EOL;
die("WarehouseArticleController::updateCheapestPurchasePrice: " . $e->getMessage());
}
}
//TODO: start update cheapestSellPrice for each PriceType
$priceTypes = WarehouseArticlePriceTypeModel::getAll();
$articlePriceTypes = WarehouseArticlePriceModel::getAll(['articleId' => $articleId]);
//priceType has id, title
$cheapestSellPrices = [];
foreach ($priceTypes as $priceType) {
// check if priceType exists in articlePriceTypes else use $article->defaultSellMultiplier for price type
$articlePriceType = array_filter($articlePriceTypes, function ($articlePriceType) use ($priceType) {
return $articlePriceType->articlePriceTypeId == $priceType->id;
});
if (empty($articlePriceType)) {
$sellPrice = $article->defaultSellMultiplier * $cheapestPurchasePrice;
} else {
$articlePriceType = $articlePriceType[0];
if ($articlePriceType->priceOverride) {
$sellPrice = $articlePriceType->priceOverride;
} else {
$sellPrice = $articlePriceType->priceMultiplier * $cheapestPurchasePrice;
}
}
$cheapestSellPrices[] = [
'title' => $priceType->title,
'price' => $sellPrice,
];
}
// save cheapestSellPrices column on article
$article->cheapestSellPrice = json_encode($cheapestSellPrices);
WarehouseArticleModel::update([...get_object_vars($article)]);
}
protected function afterCreate($postData) {
self::updateCheapestPurchasePrice($postData['id']);
protected function afterCreate() {
$last5Articles = WarehouseArticleModel::getAll([], 5, 0, ['key' => 'id', 'order' => 'DESC']);
foreach ($last5Articles as $article) {
self::updateCheapestPurchasePrice($article->id);
}
}
protected function updateAllCheapestPurchasePricesAction() {

View File

@@ -6,10 +6,14 @@ class WarehouseArticleModel extends TTCrudBaseModel {
public string $description;
public string $category;
public ?float $cheapestPurchasePrice;
public ?float $cheapestSellPrice;
public ?string $cheapestSellPrice;
public int $warningAmount;
public int $criticalAmount;
public int $isEShop;
public float $defaultSellMultiplier;
public string $unit;
public int $isSerialDocumentation;
public int $revenueAccount;
}

View File

@@ -20,8 +20,8 @@ class WarehouseArticleDistributorController extends TTCrud {
'delete' => 'Lieferanteintrag wurde gelöscht',
'noChanges' => 'Keine Änderungen',];
protected function checkExistingThresholdEntry($postData): bool {
$count = WarehouseLocationThresholdOverrideModel::count(['articleId' => $postData['articleId'],
protected function checkExistingDistributorEntry($postData): bool {
$count = WarehouseArticleDistributorModel::count(['articleId' => $postData['articleId'],
'distributorId' => $postData['distributorId']]);
if ($count > 0) {
@@ -34,7 +34,7 @@ class WarehouseArticleDistributorController extends TTCrud {
}
protected function beforeCreate($postData): bool {
return $this->checkExistingThresholdEntry($postData);
return $this->checkExistingDistributorEntry($postData);
}
protected function afterCreate($postData) {
@@ -42,7 +42,7 @@ class WarehouseArticleDistributorController extends TTCrud {
}
protected function beforeUpdate($postData): bool {
$existing = $this->checkExistingThresholdEntry($postData);
$existing = $this->checkExistingDistributorEntry($postData);
if (!$existing) {
return false;

View File

@@ -0,0 +1,9 @@
<?php
/**
* @property mixed|null $name
*/
class WarehouseArticlePacket extends mfBaseModel
{
}

View File

@@ -0,0 +1,94 @@
<?php
class WarehouseArticlePacketController extends TTCrud {
protected string $headerTitle = 'Artikel-Pakete';
protected string $createText = 'Artikel-Paket erstellen';
// @formatter:off
protected array $columns = [
['key' => 'title', 'text' => 'Titel', 'required' => true],
['key' => 'description', 'text' => 'Beschreibung', 'required' => true],
['key' => 'category', 'text' => 'Kategorie', 'required' => true],
['key' => 'overrideSellPrice', 'text' => 'Überschriebener Verkaufspreis', 'required' => false, 'modal' => ['type' => 'number'], 'table' => false],
['key' => 'calculatedSellPrice', 'text' => 'Verkaufspreis', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false]],
['key' => 'subItems', 'text' => 'Unterartikel', 'required' => true],
['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center', 'priority' => 10]],
];
// @formatter:on
protected array $infoMessages = ['create' => 'Artikel-Paket wurde erstellt',
'update' => 'Artikel-Paket wurde aktualisiert',
'delete' => 'Artikel-Paket wurde gelöscht',
'noChanges' => 'Keine Änderungen'];
protected function prepareCrudConfig() {
$articles = array_map(function ($article) {
return ['value' => $article->id, 'text' => $article->title];
}, WarehouseArticleModel::getAll(
['isEShop' => 1],
));
$this->columns[5]['modal']['items'] = $articles;
}
//TODO: make this so it does not update all packets at the same time
protected function updatePacketPricesAction() {
$packets = WarehouseArticlePacketModel::getAll();
$articles = WarehouseArticleModel::getAll(['isEShop' => 1]);
// packet has $calculatedSellPrice for this but when overrideSellPrice is set, it should be used
foreach ($packets as $packet) {
if ($packet->overrideSellPrice) {
$calculatedSellPrice = $packet->overrideSellPrice;
} else {
$subItems = json_decode($packet->subItems);
$calculatedSellPrice = 0;
foreach ($subItems as $subItem) {
$article = WarehouseArticleModel::get($subItem->id);
$cheapestSellPrices = json_decode($article->cheapestSellPrice);
// find in array cheapestSellPrices by title === 'Energie Steiermark' and get the price
$articlePrice = array_values(array_filter($cheapestSellPrices, function ($cheapestSellPrice) {
return $cheapestSellPrice->title === 'Energie Steiermark';
}))[0]->price;
$calculatedSellPrice += $subItem->amount * $articlePrice;
}
}
WarehouseArticlePacketModel::update([...get_object_vars($packet), // Unpack properties into an array
'calculatedSellPrice' => $calculatedSellPrice]);
}
return true;
}
protected function afterUpdate(): bool {
return $this->updatePacketPricesAction();
}
protected function afterCreate(): bool {
return $this->updatePacketPricesAction();
}
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);
}
}

View File

@@ -0,0 +1,11 @@
<?php
class WarehouseArticlePacketModel extends TTCrudBaseModel {
public int $id;
public string $title;
public string $description;
public string $category;
public ?float $overrideSellPrice;
public ?float $calculatedSellPrice;
public string $subItems;
}

View File

@@ -23,6 +23,14 @@ class WarehouseArticlePriceController extends TTCrud {
}
protected function validate($postData): bool {
// if either priceOverride or priceMultiplier is empty set it to null
if (isset($postData['priceOverride']) && $postData['priceOverride'] === '') {
$postData['priceOverride'] = null;
}
if (isset($postData['priceMultiplier']) && $postData['priceMultiplier'] === '') {
$postData['priceMultiplier'] = null;
}
// check if postData priceOverride or priceMultiplier is set but only one of them
if (isset($postData['priceOverride']) && isset($postData['priceMultiplier'])) {
self::returnJson(['success' => false,

View File

@@ -51,6 +51,15 @@ class WarehouseArticlePriceTypeController extends TTCrud {
return true;
}
protected function afterUpdate($postData) {
WarehouseArticleController::updateCheapestPurchasePrice($postData['articleId']);
}
protected function afterCreate($postData) {
//TODO: fix this
WarehouseArticleController::updateCheapestPurchasePrice($postData['articleId']);
}
protected function getHistoryAction() {
$history = WarehouseHistoryModel::getByRowId($this->request->id, $this->mod);

View File

@@ -5,8 +5,9 @@ class WarehouseEShopController extends TTCrud {
protected bool $createText = false;
protected array $columns = [
['key' => 'title', 'text' => 'Titel'],
['key' => 'category', 'text' => 'Kategorie'],
['key' => 'title', 'text' => 'Artikel'],
['key' => 'category', 'text' => 'Kategorie', 'table' => false],
['key' => 'price', 'text' => 'Preis', 'table' => ['filter' => false,'sortable' => false,'class' => 'text-right']],
['key' => 'amount', 'text' => 'Menge', 'table' => ['filter' => false,'sortable' => false,'class' => 'p-0 width-80']],
['key' => 'add', 'text' => 'Hinzufügen', 'table' => ['filter' => false,'sortable' => false, 'class' => 'width-120 text-center']]
];
@@ -18,6 +19,10 @@ class WarehouseEShopController extends TTCrud {
'noChanges' => 'Keine Änderungen',
];
public function permissionCheck(): bool {
return $this->user->can(["WarehouseEShop"]);
}
public function getAction() {
$filter = $this->postData['filters'] ?? [];
$order = $this->postData['order'] ?? ['key' => null, 'order' => 'ASC'];
@@ -30,6 +35,10 @@ class WarehouseEShopController extends TTCrud {
$filteredAvailable = WarehouseArticleModel::count($filter);
$totalRows = WarehouseArticleModel::count(['isEShop' => 1]);
$packetRows = WarehouseArticlePacketModel::getAll();
$rows = [...$rows, ...$packetRows];
self::returnJson(["rows" => $rows,
"pagination" => ["page" => $page,
"total_pages" => ceil($filteredAvailable / $perPage),

View File

@@ -9,7 +9,7 @@ class WarehouseEShopOrderController extends TTCrud {
['key' => 'status', 'text' => 'Status', 'required' => true],
['key' => 'deliveryMode', 'text' => 'Liefermodus', 'required' => true, 'modal' => ['type' => 'select', 'items' => [
['value' => 'singleAddress', 'text' => 'Einzelne Adresse'],
['value' => 'multipleAddresses', 'text' => 'Mehrere Adressen'],
// ['value' => 'multipleAddresses', 'text' => 'Mehrere Adressen'],
]]],
['key' => 'deliveryAddressName', 'text' => 'Lieferadresse Name', 'required' => true, 'table' => false],
['key' => 'deliveryAddressLine', 'text' => 'Lieferadresse', 'required' => true, 'required_length' => 4],
@@ -31,6 +31,10 @@ class WarehouseEShopOrderController extends TTCrud {
'noChanges' => 'Keine Änderungen',
];
public function permissionCheck(): bool {
return $this->user->can(["WarehouseEShop"]);
}
protected function prepareCrudConfig() {
$users = array_map(function($user) {
return ['value' => intval($user->id), 'text' => $user->name];
@@ -67,11 +71,24 @@ class WarehouseEShopOrderController extends TTCrud {
// now create WarehouseEShopOrderItems for each item in the shopping cart
foreach ($shoppingCart as $item) {
WarehouseEShopOrderItemModel::create([
'orderId' => $id,
'articleId' => $item['itemId'],
'quantity' => intval($item['amount']),
]);
// itemId can either be P-[PACKETID] or I-[ARTICLEID]
// parse this and either fill articleId or articlePacketId for warehouseEShopOrderItem
if (strpos($item['itemId'], 'P-') === 0) {
WarehouseEShopOrderItemModel::create([
'orderId' => $id,
'articlePacketId' => intval(substr($item['itemId'], 2)),
'quantity' => intval($item['amount']),
]);
} else if (strpos($item['itemId'], 'I-') === 0) {
WarehouseEShopOrderItemModel::create([
'orderId' => $id,
'articleId' => intval(substr($item['itemId'], 2)),
'quantity' => intval($item['amount']),
]);
} else {
self::returnJson(['success' => false, 'message' => 'Invalid item id']);
die();
}
}
self::returnJson(['success' => true,

View File

@@ -9,6 +9,7 @@
class WarehouseEShopOrderItemModel extends TTCrudBaseModel {
public int $id;
public int $orderId;
public int $articleId;
public ?int $articleId;
public ?int $articlePacketId;
public int $quantity;
}

View File

@@ -0,0 +1,9 @@
<?php
/**
* @property mixed|null $name
*/
class WarehouseRevenueAccount extends mfBaseModel
{
}

View File

@@ -0,0 +1,39 @@
<?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);
}
}

View File

@@ -0,0 +1,7 @@
<?php
class WarehouseRevenueAccountModel extends TTCrudBaseModel {
public int $id;
public int $revenueAccountNumber;
public string $title;
}

View File

@@ -0,0 +1,34 @@
<?php /** @noinspection ALL */
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class WorkerPermissionAddcanWarehouse extends AbstractMigration {
public function up(): void {
if ($this->getEnvironment() == "thetool") {
$table = $this->table("WorkerPermission");
$table->addColumn("canWarehouseAdmin", "enum", ["null" => false, "values" => 'false,true', "default" => "false", "after" => "canSuperexpert"]);
$table->addColumn("canWarehouseUser", "enum", ["null" => false, "values" => 'false,true', "default" => "false", "after" => "canSuperexpert"]);
$table->addColumn("canWarehouseEShop", "enum", ["null" => false, "values" => 'false,true', "default" => "false", "after" => "canSuperexpert"]);
$table->update();
}
if ($this->getEnvironment() == "addressdb") {
}
}
public function down(): void {
if ($this->getEnvironment() == "thetool") {
$table = $this->table("WorkerPermission");
$table->removeColumn("canWarehouseAdmin");
$table->removeColumn("canWarehouseUser");
$table->removeColumn("canWarehouseEShop");
$table->save();
}
if ($this->getEnvironment() == "addressdb") {
}
}
}

View File

@@ -0,0 +1,62 @@
<?php /** @noinspection ALL */
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class UpdateWarehouseTables extends AbstractMigration {
public function up(): void {
if ($this->getEnvironment() == "thetool") {
$WarehouseArticle = $this->table("WarehouseArticle");
$WarehouseArticle->changeColumn("cheapestSellPrice", "text", ["null" => false]);
$WarehouseArticle->addColumn("defaultSellMultiplier", "float", ["null" => false, "default" => 1]);
$WarehouseArticle->addColumn("unit", "string", ["null" => false]);
$WarehouseArticle->addColumn("isSerialDocumentation", "integer", ["null" => false]);
$WarehouseArticle->addColumn("revenueAccount", "integer", ["null" => false]);
$WarehouseArticle->update();
$WarehouseArticlePacket = $this->table("WarehouseArticlePacket", ["signed" => true]);
$WarehouseArticlePacket->addColumn("title", "string", ["null" => false]);
$WarehouseArticlePacket->addColumn("description", "text", ["null" => false]);
$WarehouseArticlePacket->addColumn("category", "string", ["null" => false]);
$WarehouseArticlePacket->addColumn("overrideSellPrice", "float", ["null" => true]);
$WarehouseArticlePacket->addColumn("calculatedSellPrice", "float", ["null" => true]);
$WarehouseArticlePacket->addColumn("subItems", "text", ["null" => false]);
$WarehouseArticlePacket->create();
$WarehouseEShopOrderItem = $this->table("WarehouseEShopOrderItem");
$WarehouseEShopOrderItem->changeColumn("articleId", "integer", ["null" => true]);
$WarehouseEShopOrderItem->addColumn("articlePacketId", "integer", ["null" => true]);
$WarehouseEShopOrderItem->update();
$WarehouseRevenueAccount = $this->table("WarehouseRevenueAccount", ["signed" => true]);
$WarehouseRevenueAccount->addColumn("revenueAccountNumber", "integer", ["null" => false]);
$WarehouseRevenueAccount->addColumn("title", "string", ["null" => false]);
$WarehouseRevenueAccount->create();
}
}
public function down(): void {
if ($this->getEnvironment() == "thetool") {
$WarehouseArticle = $this->table("WarehouseArticle");
$WarehouseArticle->changeColumn("cheapestSellPrice", "float", ["null" => false]);
$WarehouseArticle->removeColumn("defaultSellMultiplier");
$WarehouseArticle->removeColumn("unit");
$WarehouseArticle->removeColumn("isSerialDocumentation");
$WarehouseArticle->removeColumn("revenueAccount");
$WarehouseArticle->update();
$WarehouseArticlePacket = $this->table("WarehouseArticlePacket");
$WarehouseArticlePacket->drop()->save();
$WarehouseEShopOrderItem = $this->table("WarehouseEShopOrderItem");
$WarehouseEShopOrderItem->changeColumn("articleId", "integer", ["null" => false]);
$WarehouseEShopOrderItem->removeColumn("articlePacketId");
$WarehouseEShopOrderItem->update();
$WarehouseRevenueAccount = $this->table("WarehouseRevenueAccount");
$WarehouseRevenueAccount->drop()->save();
}
}
}

View File

@@ -61,6 +61,11 @@ class Helper {
$value = $data[$key] ?? null;
$title = $rules['title'] ?? $key;
//TODO: fix this, skip arrays for now
if (is_array($value)) {
continue;
}
// Apply default values for missing rules
$rules = array_merge([

View File

@@ -23,7 +23,12 @@ class TTCrud extends mfBaseController {
$this->user = $me;
$this->layout()->set('me', $me);
if (!$me->is(["Admin"])) {
if (method_exists($this, 'permissionCheck')) {
$allowed = $this->permissionCheck();
if (!$allowed) {
$this->redirect("Dashboard");
}
} else if (!$me->is(["Admin"])) {
$this->redirect("Dashboard");
}

View File

@@ -68,12 +68,17 @@ class TTCrudBaseModel {
}
public static function get($id): TTCrudBaseModel {
public static function get($id, $die= false): TTCrudBaseModel {
$FronkDB = FronkDB::singleton();
$db = $FronkDB->link;
$id = $db->real_escape_string($id);
$table = self::getTable();
$sql = "SELECT * FROM `$table` WHERE `id` = $id";
if($die) {
die($sql);
}
$result = $db->query($sql);
// as TTCRudBaseModel is abstract, we need to get the class name of the child class
$class = get_called_class();
@@ -148,6 +153,13 @@ class TTCrudBaseModel {
if ($field === "id") {
continue;
}
// TODO: make this cleaner
if ($value === "" && (new ReflectionProperty(get_called_class(), $field))->getType()->getName() === "float") {
$value = null;
}
if ($value === "" && (new ReflectionProperty(get_called_class(), $field))->getType()->getName() === "int") {
$value = null;
}
$values[] = $value === null ? "`$field` = NULL" : "`$field` = '" . $db->real_escape_string($value) . "'";
}

View File

@@ -256,6 +256,12 @@ Vue.component('warehouse-article', {
@editPricesEntries="priceModal = true; priceModalId = $event.id"
@editThresholdEntries="thresholdModal = true; thresholdModalId = $event.id">
<template v-slot:cheapestsellprice="{ row }">
<template v-for="price in JSON.parse(row.cheapestSellPrice)">
<span v-if="price">{{price.title}}: {{(price.price)}} €</span><br>
</template>
</template>
<template v-slot:cheapestPurchasePrice="{ row }">
<span>{{(row.cheapestPurchasePrice * row.sellPriceMultiplier).toFixed(2)}} €</span>
</template>

View File

@@ -0,0 +1,90 @@
Vue.component('WarehouseArticlePacket', {
//language=Vue
template: `
<tt-card>
<tt-table-crud @openHistory="historyModal = true; historyModalId = $event.id" ref="WarehouseArticlePacketCrud">
<template v-slot:subitems="{row}">
<template v-for="item in JSON.parse(row.subItems)">
<span v-if="articles.find(article => article.value === item.id)">
{{item.amount + 'x '}}{{articles.find(article => article.value === item.id).text}}<br>
</span>
<span v-else></span>
</template>
</template>
<template v-slot:subitems-modal="{crudModalData}">
<!-- TODO: add autocomplete and list with items, each removable, just simple quick-->
<div style="display: grid; grid-template-columns: 1fr 1fr auto;grid-gap: 8px">
<tt-autocomplete v-model="subItemsAutocomplete"
:items="articles"
ref="subItemsAutocomplete"
label="Subitems" sm/>
<tt-input v-model="newSubItemAmount" label="Menge" sm/>
<button type="button" class="btn btn-primary" @click="addSubItem" style="max-height: 32px; align-self: center">
<i class="fas fa-plus"></i>
</button>
</div>
<ul class="list-group" v-if="typeof subItems !== 'undefined' && subItems.length > 0" :key="updateCounter">
<template v-for="item in subItems">
<li class="list-group-item d-flex justify-content-between align-items-center">
Menge {{item.amount}} | Artikel {{articles.find(article => article.value === item.id).text}}
<button type="button" class="btn btn-danger btn-sm" @click="removeSubItem(item)">
<i class="fas fa-trash"></i>
</button>
</li>
</template>
</ul>
<span v-else>Keine Artikel hinzugefügt</span>
</template>
</tt-table-crud>
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
</tt-card>
`, data() {
return {
window: window,
historyModal: false,
historyModalId: null,
subItemsAutocomplete: '',
articles: [],
updateCounter: 0,
newSubItemAmount: 1,
subItems: []
}
}, beforeMount() {
this.articles = window['TT_CONFIG']['CRUD_CONFIG'].columns.find(column => column.key === 'subItems').modal.items;
}, methods: {
updateCrudModalData() {
const ref = this.$refs.WarehouseArticlePacketCrud;
ref.$set(ref.crudModalData, 'subItems', JSON.stringify(this.subItems));
this.updateCounter++;
}, removeSubItem(item) {
this.subItems = this.subItems.filter(subItem => subItem !== item);
this.updateCrudModalData();
}, addSubItem() {
// only continue if id and amount are set else use window.notify('error', 'Bitte Artikel und Menge auswählen');
if (this.subItemsAutocomplete === '' || this.newSubItemAmount === '') {
window.notify('error', 'Bitte Artikel und Menge auswählen');
return;
}
this.subItems.push({id: this.subItemsAutocomplete, amount: this.newSubItemAmount});
this.updateCrudModalData();
this.$refs.subItemsAutocomplete.clear();
this.newSubItemAmount = 1;
}
}, mounted() {
this.$watch(() => {
return this.$refs.WarehouseArticlePacketCrud.crudModal
}, () => {
// check if this.$refs.WarehouseArticlePacketCrud.crudModalData.subItems is defined
if (typeof this.$refs.WarehouseArticlePacketCrud.crudModalData.subItems === 'undefined') {
this.$set(this.$refs.WarehouseArticlePacketCrud.crudModalData, 'subItems', JSON.stringify([]));
}
this.subItems = JSON.parse(this.$refs.WarehouseArticlePacketCrud.crudModalData.subItems);
})
}
})

View File

@@ -63,13 +63,18 @@ Vue.component('warehouse-e-shop', {
<tt-table-crud>
<template v-slot:price="{ row }">
<span v-if="row.hasOwnProperty('calculatedSellPrice')"> {{ row.calculatedSellPrice.toFixed(2) }} €</span>
<span v-else>{{ JSON.parse(row.cheapestSellPrice).find(price => price.title === 'Energie Steiermark').price.toFixed(2) }} €</span>
</template>
<template v-slot:amount="{ row }">
<!-- this has no padding - add a full width full height tt-input with -->
<tt-input type="number" style="width: 100%; height: 100%;margin:0 !important" v-model="itemAmounts[row.id]"/>
<tt-input type="number" style="width: 100%; height: 100%;margin:0 !important" v-model="itemAmounts[row.hasOwnProperty('calculatedSellPrice') ? 'P-' + row.id : 'I-' + row.id]" sm/>
</template>
<template v-slot:add="{ row }">
<a style="cursor: pointer;" @click="addToCart(row)">
<a style="cursor: pointer;" @click="addToCart(row, row.hasOwnProperty('calculatedSellPrice') ? 'P' : 'I')">
<i class="fas fa-shopping-cart text-primary"></i>
</a>
</template>
@@ -110,12 +115,16 @@ Vue.component('warehouse-e-shop', {
'Ein Fehler ist aufgetreten');
}
},
addToCart(row) {
addToCart(row, type) {
row = JSON.parse(JSON.stringify(row));
row.id = `${type}-${row.id}`;
if (!this.itemAmounts[row.id] || this.itemAmounts[row.id] === 0) {
window.notify('error', 'Bitte geben Sie eine Menge ein.');
return;
}
console.log(this.shoppingCart, row)
// Check if Article is already in the shopping cart
if (this.shoppingCart.some(item => item.itemId === row.id)) {
window.notify('warning', `${row.title} ist bereits im Warenkorb.`);

View File

@@ -261,7 +261,7 @@ td {
.expanded {
width: 500px; /* Expanded width */
height: 600px; /* Expanded height */
z-index: 1000;
z-index: 999999999999999;
}
.toggle-button {

View File

@@ -17,6 +17,9 @@ Vue.component('tt-autocomplete', {
:style="{'padding-right': $slots.append ? '30px' : '0'}"
/>
<slot name="append"></slot>
<button v-show="displayValue.length > 0" @click="displayValue = ''" type="button" class="btn btn-link position-absolute" style="right: -5px; top: 50%; transform: translateY(-50%);">
<i class="fas fa-times"></i>
</button>
<ul v-show="showSuggestions && displayValue.length > 0 || isLoading"
class="dropdown-menu show dropdown-shadow">
@@ -85,7 +88,6 @@ Vue.component('tt-autocomplete', {
watch: {
value(newValue) {
const selectedItem = this.displayingItems.find(item => item.value === newValue);
console.log(selectedItem);
this.displayValue = selectedItem ? selectedItem.text : '';
},
apiUrl() {
@@ -136,7 +138,7 @@ Vue.component('tt-autocomplete', {
this.fetchSuggestionsDebounceTimer = setTimeout(() => {
// Simulate the API call
setTimeout(async () => {
const response = await axios.get(`${this.apiUrl}&autocomplete=1&q=${this.displayValue}`);
const response = await axios.get(`${this.apiUrl}&autocomplete=1&q=${encodeURIComponent(this.displayValue)}`);
if (response.data?.status === 'error') {
this.displayingItems = [];
} else {
@@ -154,5 +156,9 @@ Vue.component('tt-autocomplete', {
this.displayValue = item.text;
this.showSuggestions = false;
},
clear() {
this.displayValue = '';
this.$emit('input', '');
}
},
});

View File

@@ -18,6 +18,9 @@ Vue.component('tt-checkbox', {
this.checkedValue = val;
}
},
mounted() {
this.$emit('input', this.checkedValue);
},
template: `
<div class="form-group" :class="{'row': row}">
<slot name="prepend"></slot>

View File

@@ -51,14 +51,17 @@ Vue.component('tt-table-crud', {
<template v-for="column in modalConfig.headers">
<!-- @formatter:off -->
<tt-input v-if="column.type === 'text'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
<tt-input v-if="column.type === 'number'" v-model="crudModalData[column.key]" :label="column.text" type="number" sm row/>
<tt-textarea v-if="column.type === 'textarea'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
<tt-select v-if="column.type === 'select'" v-model="crudModalData[column.key]" :label="column.text" :options="column.items" sm row/>
<tt-autocomplete v-if="column.type === 'autocomplete'" v-model="crudModalData[column.key]" :label="column.text" :items="column.items" sm row/>
<tt-date-picker v-if="column.type === 'datepicker'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
<tt-icon-select v-if="column.type === 'icon-select'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
<tt-checkbox v-if="column.type === 'checkbox'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
<!-- <slot v-if="$scopedSlots[column.key.toLowerCase() + '-modal'] && column.type !== false && 1 < 0" :name="column.key.toLowerCase() + '-modal'" slot-scope="{crudModalData}"></slot>-->
<slot :name="column.key.toLowerCase() + '-modal'" :crudModalData="crudModalData">
<tt-input v-if="column.type === 'text'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
<tt-input v-else-if="column.type === 'number'" v-model="crudModalData[column.key]" :label="column.text" type="number" sm row/>
<tt-textarea v-else-if="column.type === 'textarea'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
<tt-select v-else-if="column.type === 'select'" v-model="crudModalData[column.key]" :label="column.text" :options="column.items" sm row/>
<tt-autocomplete v-else-if="column.type === 'autocomplete'" v-model="crudModalData[column.key]" :label="column.text" :items="column.items" sm row/>
<tt-date-picker v-else-if="column.type === 'datepicker'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
<tt-icon-select v-else-if="column.type === 'icon-select'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
<tt-checkbox v-else-if="column.type === 'checkbox'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
</slot>
<!-- @formatter:on -->
</template>