Added new WarehouseOrder Module
This commit is contained in:
@@ -1,64 +1,47 @@
|
||||
<?php
|
||||
if (!isset($vueViewName)) {
|
||||
die("vueViewName is not set");
|
||||
}
|
||||
|
||||
if (!isset($mfLayoutPackage)) {
|
||||
die("mfLayoutPackage is not set");
|
||||
}
|
||||
if (!isset($vueViewName)) die("vueViewName is not set");
|
||||
if (!isset($mfLayoutPackage)) die("mfLayoutPackage is not set");
|
||||
|
||||
$additionalCSS = $additionalCSS ?? [];
|
||||
$additionalJS = $additionalJS ?? [];
|
||||
$vueViewPath = BASEDIR . "/public/js/pages/$vueViewName";
|
||||
$additionalJS = [
|
||||
"bundler.php",
|
||||
...$additionalJS,
|
||||
];
|
||||
$additionalJS = ["bundler.php", ...$additionalJS];
|
||||
|
||||
if (is_dir($vueViewPath)) {
|
||||
$files = scandir($vueViewPath);
|
||||
foreach ($files as $file) {
|
||||
if ($file === '.' || $file === '..') {
|
||||
continue;
|
||||
}
|
||||
foreach (scandir($vueViewPath) as $file) {
|
||||
if ($file === '.' || $file === '..') continue;
|
||||
|
||||
$fileExtension = pathinfo($file, PATHINFO_EXTENSION);
|
||||
if ($fileExtension === 'css') {
|
||||
$additionalCSS[] = "js/pages/$vueViewName/$file";
|
||||
} else if ($fileExtension === 'js') {
|
||||
$additionalJS[] = "js/pages/$vueViewName/$file";
|
||||
}
|
||||
if ($fileExtension === 'css') $additionalCSS[] = "js/pages/$vueViewName/$file";
|
||||
else if ($fileExtension === 'js') $additionalJS[] = "js/pages/$vueViewName/$file";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$additionalCSS = [
|
||||
...$additionalCSS,
|
||||
...$additionalCSS,
|
||||
'plugins/daterangepicker/daterangepicker.css',
|
||||
'plugins/vue/tt-components/css/tt-table.css',
|
||||
'plugins/vue/tt-components/css/tt-loader.css',
|
||||
'plugins/vue/tt-components/css/tt-position-manager.css',
|
||||
];
|
||||
|
||||
/**
|
||||
* Convert PascalCase to snake_case (e.g. PascalCase to pascal-case)
|
||||
* @param $str string PascalCase string
|
||||
* @param string $str PascalCase string
|
||||
* @return string snake-case string
|
||||
*/
|
||||
function pascalToSnakeCase(string $str): string {
|
||||
$snakeCase = preg_replace('/(?<!^)([A-Z])/', '-$1', $str);
|
||||
return strtolower($snakeCase);
|
||||
return strtolower(preg_replace('/(?<!^)([A-Z])/', '-$1', $str));
|
||||
}
|
||||
|
||||
$vueTagName = pascalToSnakeCase($vueViewName);
|
||||
|
||||
$vueHeaderPath = realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/vueHeader.php";
|
||||
if(!file_exists($vueHeaderPath)) {
|
||||
if (!file_exists($vueHeaderPath))
|
||||
$vueHeaderPath = realpath(dirname(__FILE__) . "/../../default") . "/vueHeader.php";
|
||||
}
|
||||
|
||||
include($vueHeaderPath); ?>
|
||||
|
||||
|
||||
<div id="app">
|
||||
<tt-page-title
|
||||
v-if="window['TT_CONFIG'] && window['TT_CONFIG']['PAGE_TITLE'] && window['TT_CONFIG']['PATH']"
|
||||
@@ -66,11 +49,11 @@ include($vueHeaderPath); ?>
|
||||
:path="window['TT_CONFIG']['PATH']">
|
||||
</tt-page-title>
|
||||
|
||||
|
||||
<<?php echo $vueTagName; ?>></<?php echo $vueTagName; ?>>
|
||||
<<?php echo $vueTagName; ?>>
|
||||
</<?php echo $vueTagName; ?>>
|
||||
</div>
|
||||
<script>
|
||||
const view = new Vue({el: '#app', data: {window: window}});
|
||||
</script>
|
||||
|
||||
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/footer.php"); ?>
|
||||
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/footer.php"); ?>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// Hide Articles
|
||||
|
||||
|
||||
|
||||
class WarehouseEShopController extends TTCrud {
|
||||
protected string $headerTitle = 'Energie Steiermark Shop';
|
||||
protected bool $createText = false;
|
||||
@@ -12,11 +11,13 @@ class WarehouseEShopController extends TTCrud {
|
||||
protected array $columns = [
|
||||
['key' => 'title', 'text' => 'Artikel', 'priority' => 11],
|
||||
['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'], 'priority' => 9],
|
||||
['key' => 'add', 'text' => 'Hinzufügen', 'table' => ['filter' => false,'sortable' => false, 'class' => 'width-120 text-center'], 'priority' => 5000]
|
||||
['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'], 'priority' => 9],
|
||||
['key' => 'add', 'text' => 'Hinzufügen', 'table' => ['filter' => false, 'sortable' => false, 'class' => 'width-120 text-center'], 'priority' => 5000]
|
||||
];
|
||||
|
||||
protected array $permissionCheck = ['WarehouseEShop'];
|
||||
|
||||
protected array $infoMessages = [
|
||||
'create' => 'Not possible',
|
||||
'update' => 'Not possible',
|
||||
@@ -24,10 +25,6 @@ class WarehouseEShopController extends TTCrud {
|
||||
'noChanges' => 'Keine Änderungen',
|
||||
];
|
||||
|
||||
public function permissionCheck(): bool {
|
||||
return $this->user->can(["WarehouseEShop"]);
|
||||
}
|
||||
|
||||
protected function prepareCrudConfig() {
|
||||
if (!$this->user->can('WarehouseAdmin')) {
|
||||
$this->columns[2]['table'] = false;
|
||||
@@ -62,5 +59,6 @@ class WarehouseEShopController extends TTCrud {
|
||||
"total_pages" => ceil($filteredAvailable / $perPage),
|
||||
"per_page" => $perPage,
|
||||
"filtered_available" => $filteredAvailable,
|
||||
"total_rows" => $totalRows]]); }
|
||||
"total_rows" => $totalRows]]);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ class WarehouseEShopOrderController extends TTCrud {
|
||||
['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']]
|
||||
];
|
||||
|
||||
protected array $permissionCheck = ['WarehouseEShop'];
|
||||
|
||||
protected array $additionalActions = [
|
||||
['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary'],
|
||||
['key' => 'showTrackingHistory', 'title' => 'Tracking Historie', 'class' => 'fas fa-truck text-primary'],
|
||||
@@ -38,10 +40,6 @@ class WarehouseEShopOrderController extends TTCrud {
|
||||
];
|
||||
//@formatter:on
|
||||
|
||||
public function permissionCheck(): bool {
|
||||
return $this->user->can(["WarehouseEShop"]);
|
||||
}
|
||||
|
||||
protected function customRowsHandler($rows): array {
|
||||
$statusToText = [
|
||||
'new' => 'Neu',
|
||||
|
||||
@@ -19,7 +19,7 @@ class WarehouseHistoryController {
|
||||
WarehouseHistoryModel::create(['table' => $mod,
|
||||
'row_id' => $postData['id'],
|
||||
'key' => $key,
|
||||
'old_value' => $currentData->$key,
|
||||
'old_value' => $currentData->$key ?? '',
|
||||
'new_value' => $value,
|
||||
'note' => '',
|
||||
'user_id' => $me->id,
|
||||
|
||||
@@ -7,23 +7,26 @@ class WarehouseOrderController extends TTCrud {
|
||||
|
||||
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']],
|
||||
['key' => 'orderNumber', 'text' => 'Bestellnummer', 'required' => true, 'modal' => false],
|
||||
['key' => 'delAddrCity', 'text' => 'Stadt', 'required' => true, 'modal' => false],
|
||||
['key' => 'delAddrEMail', 'text' => 'E-Mail', 'required' => true, 'modal' => false],
|
||||
['key' => 'delAddrLine', 'text' => 'Adresse', 'required' => true, 'modal' => false],
|
||||
['key' => 'delAddrName', 'text' => 'Name', 'required' => true, 'modal' => false],
|
||||
['key' => 'delAddrPLZ', 'text' => 'PLZ', 'required' => true, 'modal' => false],
|
||||
['key' => 'editor', 'text' => 'Bearbeiter', 'required' => true, 'modal' => false],
|
||||
['key' => 'note', 'text' => 'Notiz', 'required' => true, 'modal' => false],
|
||||
['key' => 'positions', 'text' => 'Positionen', 'required' => true, 'modal' => false],
|
||||
['key' => 'create', 'text' => 'Erstellt', 'required' => true, 'modal' => false],
|
||||
['key' => 'createBy', 'text' => 'Erstellt von', 'required' => true, 'modal' => ['type' => 'select']],
|
||||
['key' => 'actions',
|
||||
'text' => 'Aktionen',
|
||||
'required' => false,
|
||||
'modal' => false,
|
||||
'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']],
|
||||
];
|
||||
|
||||
protected array $permissionCheck = ['WarehouseAdmin'];
|
||||
|
||||
protected array $additionalActions = [['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary']];
|
||||
|
||||
protected array $infoMessages = ['create' => 'Bestellung wurde erfolgreich erstellt.',
|
||||
@@ -31,72 +34,36 @@ class WarehouseOrderController extends TTCrud {
|
||||
'delete' => 'Bestellung wurde gelöscht',
|
||||
'noChanges' => 'Keine Änderungen',];
|
||||
|
||||
public function permissionCheck(): bool {
|
||||
return $this->user->can(["WarehouseEShop"]);
|
||||
protected function beforeCreate(): bool {
|
||||
$currentCount = WarehouseOrderModel::count(['create' => ['from' => strtotime(date('Y-01-01'))]]);
|
||||
$this->postData['orderNumber'] = 'PO' . date('Y') . '-' . str_pad($currentCount + 1, 4, '0', STR_PAD_LEFT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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 getArticleDistributorDataAction() {
|
||||
$data = [];
|
||||
$article = $this->request->articleId;
|
||||
|
||||
}
|
||||
|
||||
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,
|
||||
]);
|
||||
if ($this->request->allDistributor === 'true') {
|
||||
foreach (WarehouseDistributorModel::getAll() as $distributor) {
|
||||
$data[] = [
|
||||
'id' => $distributor->id,
|
||||
'name' => $distributor->name,
|
||||
];
|
||||
}
|
||||
} elseif (!empty($article)) {
|
||||
foreach (WarehouseArticleDistributorModel::getAll(['articleId' => $this->request->articleId]) as $distributor) {
|
||||
$data[] = [
|
||||
'id' => $distributor->distributorId,
|
||||
'name' => WarehouseDistributorModel::get($distributor->distributorId)->name,
|
||||
'purchasePrice' => $distributor->purchasePrice,
|
||||
'externalArticleNumber' => $distributor->externalArticleNumber,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
self::returnJson($data);
|
||||
}
|
||||
|
||||
protected function beforeUpdate($postData): bool {
|
||||
|
||||
@@ -1,28 +1,36 @@
|
||||
<?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
|
||||
*
|
||||
* Represents a warehouse order with delivery details and related metadata.
|
||||
*
|
||||
* @property int $id Unique identifier for the warehouse order
|
||||
* @property string $orderNumber Unique order number
|
||||
* @property string $delAddrCity City of the delivery address
|
||||
* @property string $delAddrEMail Email associated with the delivery address
|
||||
* @property string $delAddrLine Line of the delivery address
|
||||
* @property string $delAddrName Name associated with the delivery address
|
||||
* @property string $delAddrPLZ Postal code of the delivery address
|
||||
* @property int $editor ID of the editor who last modified the order
|
||||
* @property string $note Additional notes for the order
|
||||
* @property string $positions Details about positions in the order
|
||||
* @property int $create Timestamp of the order creation
|
||||
* @property int $createBy ID of the user who created the order
|
||||
*/
|
||||
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 string $orderNumber;
|
||||
public string $delAddrCity;
|
||||
public string $delAddrEMail;
|
||||
public string $delAddrLine;
|
||||
public string $delAddrName;
|
||||
public string $delAddrPLZ;
|
||||
public int $editor;
|
||||
public string $note;
|
||||
public string $positions;
|
||||
public int $create;
|
||||
public int $createBy;
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @property mixed|null $name
|
||||
*/
|
||||
class WarehouseOrderItem extends mfBaseModel
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -41,7 +41,11 @@ class WarehouseOrderRequestController extends TTCrud {
|
||||
'table' => ['filter' => 'select'],
|
||||
'modal' => ['type' => 'select', 'items' => []]],
|
||||
['key' => 'warehouseLocation', 'text' => 'Lagerort', 'required' => false, 'type' => 'varchar'],
|
||||
['key' => 'canceled', 'text' => 'Storniert', 'required' => false, 'modal' => ['visible' => false, 'type' => 'select', 'items' => [['value' => 0, 'text' => 'Nein'], ['value' => 1, 'text' => 'Ja']]], 'table' => ['filter' => 'select']],
|
||||
['key' => 'canceled',
|
||||
'text' => 'Storniert',
|
||||
'required' => false,
|
||||
'modal' => ['visible' => false, 'type' => 'select', 'items' => [['value' => 0, 'text' => 'Nein'], ['value' => 1, 'text' => 'Ja']]],
|
||||
'table' => ['filter' => 'select']],
|
||||
['key' => 'note', 'text' => 'Notiz', 'required' => false, 'type' => 'textarea'],
|
||||
['key' => 'actions',
|
||||
'text' => 'Aktionen',
|
||||
@@ -50,6 +54,8 @@ class WarehouseOrderRequestController extends TTCrud {
|
||||
'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']],
|
||||
];
|
||||
|
||||
protected array $permissionCheck = ['WarehouseUser'];
|
||||
|
||||
protected array $additionalActions = [['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary']];
|
||||
|
||||
protected array $infoMessages = ['create' => 'Bestellwunsch wurde erstellt.',
|
||||
@@ -59,10 +65,6 @@ class WarehouseOrderRequestController extends TTCrud {
|
||||
|
||||
protected array $additionalJSVariables = ['BASE_URL' => '/WarehouseOrderRequest', 'WAREHOUSE_ADMIN' => true];
|
||||
|
||||
public function permissionCheck(): bool {
|
||||
return $this->user->can(["WarehouseUser"]);
|
||||
}
|
||||
|
||||
protected function prepareCrudConfig() {
|
||||
// Fill Users in createBy column
|
||||
$userArray = array_map(function ($user) {
|
||||
@@ -96,10 +98,10 @@ class WarehouseOrderRequestController extends TTCrud {
|
||||
}
|
||||
|
||||
protected function customAutoCompleteWare($value) {
|
||||
if (!is_numeric($value)) return [ 'id' => $value, 'title' => $value];
|
||||
if (!is_numeric($value)) return ['id' => $value, 'title' => $value];
|
||||
|
||||
$article = WarehouseArticleModel::get(intval($value));
|
||||
return [ 'id' => $article->id, 'title' => $article->title ];
|
||||
return ['id' => $article->id, 'title' => $article->title];
|
||||
}
|
||||
|
||||
protected function beforeUpdate($postData): bool {
|
||||
|
||||
57
db/migrations/20250131150000_warehouse_modify_10.php
Normal file
57
db/migrations/20250131150000_warehouse_modify_10.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class WarehouseModify10 extends AbstractMigration {
|
||||
public function up(): void {
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
// Drop the existing tables
|
||||
$this->table("WarehouseOrder")->drop()->save();
|
||||
$this->table("WarehouseOrderItem")->drop()->save();
|
||||
|
||||
// Create the new WarehouseOrder table
|
||||
$table = $this->table("WarehouseOrder", ["id" => false, "primary_key" => ["id"]]);
|
||||
$table->addColumn("id", "integer", ["identity" => true])
|
||||
->addColumn("orderNumber", "string", ["null" => false, "limit" => 255])
|
||||
->addColumn("delAddrCity", "string", ["null" => false, "limit" => 255])
|
||||
->addColumn("delAddrEMail", "string", ["null" => false, "limit" => 255])
|
||||
->addColumn("delAddrLine", "string", ["null" => false, "limit" => 255])
|
||||
->addColumn("delAddrName", "string", ["null" => false, "limit" => 255])
|
||||
->addColumn("delAddrPLZ", "string", ["null" => false, "limit" => 255])
|
||||
->addColumn("editor", "integer", ["null" => false])
|
||||
->addColumn("note", "text", ["null" => false])
|
||||
->addColumn("positions", "text", ["null" => false])
|
||||
->addColumn("create", "integer", ["null" => false])
|
||||
->addColumn("createBy", "integer", ["null" => false])
|
||||
->create();
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
$this->table("WarehouseOrder")->drop()->save();
|
||||
|
||||
$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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,8 @@ class TTCrud extends mfBaseController {
|
||||
$this->user = $me;
|
||||
$this->layout()->set('me', $me);
|
||||
|
||||
if (method_exists($this, 'permissionCheck')) {
|
||||
$allowed = $this->permissionCheck();
|
||||
if (!$allowed) {
|
||||
$this->redirect("Dashboard");
|
||||
}
|
||||
if (isset($this->permissionCheck) && !$me->can($this->permissionCheck)) {
|
||||
$this->redirect("Dashboard");
|
||||
} else if (!$me->is(["Admin"])) {
|
||||
$this->redirect("Dashboard");
|
||||
}
|
||||
@@ -45,42 +42,36 @@ class TTCrud extends mfBaseController {
|
||||
* @return array
|
||||
*/
|
||||
protected function getCheckArray(): array {
|
||||
$checkArray = [];
|
||||
|
||||
foreach ($this->columns as $column) {
|
||||
$checkArray[$column['key']] = ['required' => $column['required'] ?? false,
|
||||
'required_length' => $column['required_length'] ?? 0,
|
||||
'title' => $column['text'] ?? $column['key'],
|
||||
'regex' => $column['regex'] ?? false];
|
||||
}
|
||||
|
||||
return $checkArray;
|
||||
return array_map(fn($column) => [
|
||||
'required' => $column['required'] ?? false,
|
||||
'required_length' => $column['required_length'] ?? 0,
|
||||
'title' => $column['text'] ?? $column['key'],
|
||||
'regex' => $column['regex'] ?? false
|
||||
], $this->columns);
|
||||
}
|
||||
|
||||
|
||||
protected function indexAction() {
|
||||
$this->layout()->set('additionalJS', ['js/pages/WarehouseHistory/WarehouseHistoryModal.js']);
|
||||
$customJsFile = defined('BASEDIR') ? BASEDIR . "/public/js/pages/{$this->mod}/{$this->mod}.js" : null;
|
||||
$pageName = (defined('BASEDIR') && file_exists(BASEDIR . "/public/js/pages/{$this->mod}/{$this->mod}.js"))
|
||||
? $this->mod
|
||||
: "DefaultCrudView";
|
||||
|
||||
if ($customJsFile && file_exists($customJsFile)) {
|
||||
$pageName = $this->mod;
|
||||
} else {
|
||||
$pageName = "DefaultCrudView";
|
||||
}
|
||||
$JS_VARIABLES = [
|
||||
"CRUD_CONFIG" => $this->getCrudConfig(),
|
||||
"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"),
|
||||
"USER_ID" => $this->user->id
|
||||
];
|
||||
|
||||
$JS_VARIABLES = ["CRUD_CONFIG" => $this->getCrudConfig(),
|
||||
"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"),
|
||||
"USER_ID" => $this->user->id];
|
||||
|
||||
if ($this->additionalJSVariables && is_array($this->additionalJSVariables)) {
|
||||
$JS_VARIABLES = array_merge($JS_VARIABLES, $this->additionalJSVariables);
|
||||
}
|
||||
if (!empty($this->additionalJSVariables) && is_array($this->additionalJSVariables)) $JS_VARIABLES = array_merge($JS_VARIABLES, $this->additionalJSVariables);
|
||||
|
||||
Helper::renderVue($this, $pageName, $this->headerTitle, $JS_VARIABLES);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the configuration for the CRUD component for the Vue component.
|
||||
* @return array
|
||||
@@ -90,6 +81,13 @@ class TTCrud extends mfBaseController {
|
||||
$this->prepareCrudConfig();
|
||||
}
|
||||
|
||||
$column = array_search('createBy', array_column($this->columns, 'key'));
|
||||
if ($column !== false) {
|
||||
$this->columns[$column]['modal']['items'] = array_map(function ($user) {
|
||||
return ['value' => intval($user->id), 'text' => $user->name];
|
||||
}, UserModel::search(['employee' => true]));
|
||||
}
|
||||
|
||||
|
||||
$columns = array_map(function ($column) {
|
||||
if (isset($column['type']) && (!isset($column['modal']) || !isset($column['modal']['type']))) {
|
||||
@@ -186,7 +184,6 @@ 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;
|
||||
}
|
||||
@@ -194,12 +191,12 @@ class TTCrud extends mfBaseController {
|
||||
$this->postData['create'] = time();
|
||||
}
|
||||
|
||||
Helper::validateArray($this->postData, $this->checkArray);
|
||||
|
||||
if (method_exists($this, 'beforeCreate') && !$this->beforeCreate($this->postData)) {
|
||||
self::returnJson(['success' => false, 'message' => 'Ein Fehler ist aufgetreten.']);
|
||||
}
|
||||
|
||||
Helper::validateArray($this->postData, $this->checkArray);
|
||||
|
||||
$id = $this->model::create($this->postData);
|
||||
|
||||
if (method_exists($this, 'afterCreate')) {
|
||||
@@ -212,12 +209,12 @@ class TTCrud extends mfBaseController {
|
||||
}
|
||||
|
||||
protected function updateAction() {
|
||||
// if (property_exists($this->model, 'createBy') && !isset($this->postData['createBy'])) {
|
||||
// $this->postData['createBy'] = $this->user->id;
|
||||
// }
|
||||
// if (property_exists($this->model, 'create') && !isset($this->postData['create'])) {
|
||||
// $this->postData['create'] = time();
|
||||
// }
|
||||
if (property_exists($this->model, 'create') && isset($this->postData['create'])) {
|
||||
$this->postData['create'] = $this->model::get($this->postData['id'])->create;
|
||||
}
|
||||
if (property_exists($this->model, 'createBy') && isset($this->postData['createBy'])) {
|
||||
$this->postData['createBy'] = $this->model::get($this->postData['id'])->createBy;
|
||||
}
|
||||
|
||||
Helper::validateArray($this->postData, array_merge($this->checkArray, ['id' => ['required' => true]]));
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ $jsFiles = [
|
||||
"plugins/vue/tt-components/tt-number-range.js",
|
||||
"plugins/vue/tt-components/tt-checkbox.js",
|
||||
"plugins/vue/tt-components/tt-textarea.js",
|
||||
"plugins/vue/tt-components/tt-position-manager.js",
|
||||
];
|
||||
|
||||
|
||||
|
||||
27
public/js/pages/WarehouseOrder/WarehouseOrder.css
Normal file
27
public/js/pages/WarehouseOrder/WarehouseOrder.css
Normal file
@@ -0,0 +1,27 @@
|
||||
.warehouse-order-modal-positions-entry-container {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1fr 0.5fr 1fr 1fr 0.5fr;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
.warehouse-order-modal-positions-entry-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding-top: 13px;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.modal-lg, .modal-xl {
|
||||
/*max width either 90% or 1120px*/
|
||||
max-width: min(90vw, 1120px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.warehouse-order-modal-positions-entry-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr !important;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +1,169 @@
|
||||
// noinspection JSUnusedLocalSymbols
|
||||
Vue.component('warehouse-order-modal', {
|
||||
props: {
|
||||
id: {type: [String, Number], required: true},
|
||||
mode: {type: String, default: 'sign'}
|
||||
},
|
||||
template: `
|
||||
<tt-modal :show="true"
|
||||
@submit="submit"
|
||||
:delete="id !== 'create'"
|
||||
:title="id === 'create' ? 'Bestellung erstellen' : \`Bestellung #\${id} bearbeiten\`"
|
||||
@update:show="$emit('close')">
|
||||
<div style="width: 99%">
|
||||
<h4 class="text-center">Bestelldetails</h4>
|
||||
<tt-select label="Bearbeiter (XINON)"
|
||||
:options="window.TT_CONFIG.CRUD_CONFIG.columns.find(column => column.key === 'createBy')?.modal.items"
|
||||
sm
|
||||
row
|
||||
v-model="order.editor"/>
|
||||
|
||||
<hr>
|
||||
<h4 class="text-center">Positionen</h4>
|
||||
<tt-positions-manager
|
||||
ref="positionsManager"
|
||||
v-model="order.positions"
|
||||
:config="positionsConfig"
|
||||
@updateField-article="fetchDistributors"
|
||||
@updateField-distributorId="fetchDistributorData"
|
||||
/>
|
||||
|
||||
<hr>
|
||||
<h4 class="text-center">Lieferadresse</h4>
|
||||
<div style="display: grid; grid-gap: 10px; grid-template-columns: 2fr 2fr 1fr 1fr 2fr;">
|
||||
<tt-input label="Name" v-model="order.delAddrName" sm/>
|
||||
<tt-input label="Straße" v-model="order.delAddrLine" sm/>
|
||||
<tt-input label="PLZ" v-model="order.delAddrPLZ" sm/>
|
||||
<tt-input label="Ort" v-model="order.delAddrCity" sm/>
|
||||
<tt-input label="E-Mail" v-model="order.delAddrEMail" sm/>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<tt-textarea label="Notiz" v-model="order.note" sm row/>
|
||||
</div>
|
||||
</tt-modal>
|
||||
`,
|
||||
|
||||
data() {
|
||||
return {
|
||||
window: window,
|
||||
positionsConfig: {
|
||||
customOrdering: 'distributorId',
|
||||
fields: {
|
||||
article: {
|
||||
type: 'autocomplete',
|
||||
label: 'Artikel',
|
||||
apiUrl: '/WarehouseArticle/autoComplete',
|
||||
customFieldReference: 'WarehouseArticle',
|
||||
},
|
||||
distributorId: {type: 'select', label: 'Lieferant', options: [], customFieldReference: 'WarehouseDistributor'},
|
||||
distributorArticleNumber: {type: 'input', label: 'Lieferant Art-Nr.'},
|
||||
amount: {type: 'input', label: 'Menge', inputType: 'number'},
|
||||
buyPrice: {type: 'input', label: 'Einkaufspreis', inputType: 'number'},
|
||||
verwendung: {type: 'input', label: 'Verwendung'},
|
||||
},
|
||||
validateForm: (formData) => {
|
||||
const fields = [
|
||||
{key: 'amount', message: 'Bitte füllen Sie die Menge aus'},
|
||||
{key: 'distributorId', message: 'Bitte füllen Sie den Lieferanten aus'},
|
||||
{key: 'article', message: 'Bitte füllen Sie den Artikel aus'},
|
||||
{key: 'buyPrice', message: 'Bitte füllen Sie den Einkaufspreis aus'}
|
||||
];
|
||||
|
||||
for (const field of fields) {
|
||||
if (!formData[field.key]) {
|
||||
window.notify('error', field.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
order: {
|
||||
delAddrName: 'XINON GmbH',
|
||||
delAddrLine: 'Fladnitz im Raabtal 150',
|
||||
delAddrPLZ: '8322',
|
||||
delAddrCity: 'Studenzen',
|
||||
delAddrEMail: 'einkauf@xinon.at',
|
||||
note: '',
|
||||
editor: window.TT_CONFIG['USER_ID'],
|
||||
positions: [],
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
if (this.id !== 'create') {
|
||||
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getById`, {params: {id: this.id}});
|
||||
response.data.positions = JSON.parse(response.data.positions);
|
||||
this.order = response.data;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async submit() {
|
||||
if (this.order.positions.length === 0) return window.notify('error', 'Bitte fügen Sie mindestens eine Position hinzu.');
|
||||
|
||||
if (this.id === 'create') {
|
||||
const distributorIds = [...new Set(this.order.positions.map(position => position.distributorId))];
|
||||
|
||||
for (const distributorId of distributorIds) {
|
||||
const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/create`, {
|
||||
...this.order,
|
||||
distributorId,
|
||||
positions: this.order.positions.filter(position => position.distributorId === distributorId)
|
||||
}
|
||||
);
|
||||
if (response.data.success) window.notify('success', response.data.message ?? 'Bestellung erfolgreich erstellt');
|
||||
else window.notify('error',
|
||||
response.data.errors ? Object.values(response.data.errors).join('<br>') : response.data.message || 'Ein Fehler ist aufgetreten');
|
||||
}
|
||||
} else {
|
||||
const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/update`, this.order);
|
||||
if (response.data.success) window.notify('success', response.data.message ?? 'Bestellung erfolgreich aktualisiert');
|
||||
else window.notify('error',
|
||||
response.data.errors ? Object.values(response.data.errors).join('<br>') : response.data.message || 'Ein Fehler ist aufgetreten');
|
||||
}
|
||||
this.$emit('close');
|
||||
|
||||
},
|
||||
async fetchDistributors(article) {
|
||||
const url = `${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getArticleDistributorData`;
|
||||
const params = typeof article === 'string' ? {allDistributor: true} : {articleId: article};
|
||||
|
||||
const response = await axios.get(url, {params});
|
||||
this.positionsConfig.fields.distributorId.options = response.data.map(distributor => ({
|
||||
value: distributor.id,
|
||||
text: distributor.name,
|
||||
externalArticleNumber: distributor.externalArticleNumber || null,
|
||||
purchasePrice: distributor.purchasePrice || null,
|
||||
}));
|
||||
},
|
||||
async fetchDistributorData(distributorId) {
|
||||
if (distributorId && typeof this.$refs.positionsManager.formData.article === 'number') {
|
||||
const distributor = this.positionsConfig.fields.distributorId.options.find(distributor => parseInt(distributor.value) ===
|
||||
parseInt(distributorId));
|
||||
this.$refs.positionsManager.updateField('distributorArticleNumber', distributor.externalArticleNumber);
|
||||
this.$refs.positionsManager.updateField('buyPrice', distributor.purchasePrice);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Vue.component('warehouse-order', {
|
||||
//language=Vue
|
||||
template: `
|
||||
<tt-card>
|
||||
<tt-table-crud @openHistory="historyModal = true; historyModalId = $event.id"
|
||||
ref="table">
|
||||
|
||||
<template v-slot:create="{ row }">
|
||||
{{ window.moment(row.create * 1000).format('DD.MM.YYYY HH:mm:ss') }}
|
||||
</template>
|
||||
|
||||
<template v-slot:sum="{ row }">
|
||||
<div style="text-align: right">{{ row.sum.toFixed(2) }} €</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:expandedRow="{ row }">
|
||||
<div class="lazy-loading" :data-row-id="row.id">
|
||||
<tt-loader v-if="orderLazyLoad[row.id] === true"/>
|
||||
<div v-else>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item" v-for="item in orderLazyLoad[row.id]">
|
||||
{{ item.quantity }}x {{ item.articleName }} - {{ item.price.toFixed(2) }} €
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<warehouse-order-modal v-if="orderModalId" :id="orderModalId" @close="orderModalId = null;$refs.table.$refs.table.refreshTable()"/>
|
||||
<button @click="orderModalId = 'create'" class="btn btn-primary">Bestellung erstellen</button>
|
||||
<tt-table-crud emit-edit @edit="orderModalId = $event.id" ref="table">
|
||||
<template v-slot:expandedRow="{ row }"><span>Work in Progress</span></template>
|
||||
</tt-table-crud>
|
||||
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
|
||||
</tt-card>
|
||||
`, data() {
|
||||
return {
|
||||
window: window, historyModal: false, historyModalId: null, observer: null, orderLazyLoad: {},
|
||||
}
|
||||
}, mounted() {
|
||||
this.observer = new MutationObserver((mutations) => {
|
||||
const lazyLoadingElements = document.querySelectorAll('.lazy-loading');
|
||||
console.log(lazyLoadingElements);
|
||||
|
||||
// check row id and check if it is already defined in orderLazyLoad else alert('loading')
|
||||
// if it is defined do nothing
|
||||
|
||||
for (const element of lazyLoadingElements) {
|
||||
if (element.dataset.rowId in this.orderLazyLoad) {
|
||||
continue;
|
||||
}
|
||||
this.loadOrder(element.dataset.rowId);
|
||||
}
|
||||
|
||||
})
|
||||
this.observer.observe(document.querySelector('.tt-table-container'), {childList: true, subtree: true,});
|
||||
}, methods: {
|
||||
async loadOrder(rowId) {
|
||||
this.orderLazyLoad[rowId] = true;
|
||||
// use BASE_PATH . /WarehouseOrder/getOrderItems?id= + rowId
|
||||
const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getOrderItems?id=${rowId}`);
|
||||
console.log(response.data);
|
||||
this.orderLazyLoad[rowId] = response.data;
|
||||
|
||||
// force re-render of the table
|
||||
this.$refs.table.$forceUpdate();
|
||||
|
||||
window: window,
|
||||
orderModalId: null,
|
||||
|
||||
}
|
||||
}, beforeDestroy() {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
64
public/plugins/vue/tt-components/css/tt-position-manager.css
Normal file
64
public/plugins/vue/tt-components/css/tt-position-manager.css
Normal file
@@ -0,0 +1,64 @@
|
||||
.positions-manager {
|
||||
padding: 1rem;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.positions-manager .form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.positions-manager .form-container {
|
||||
display: flex;
|
||||
align-items: center; /* Vertically center */
|
||||
justify-content: flex-start;
|
||||
gap: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.positions-manager .form-container .button-wrapper {
|
||||
align-self: flex-end; /* Align button container at the bottom */
|
||||
}
|
||||
|
||||
.positions-manager .form-container [class*="tt-input"],
|
||||
.positions-manager .form-container [class*="tt-checkbox"] {
|
||||
flex: 1 1 200px; /* Flexible width with a minimum of 200px */
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.positions-manager .form-container .btn {
|
||||
align-self: flex-end; /* Align button at the bottom of the form area */
|
||||
}
|
||||
|
||||
.positions-manager table {
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.positions-manager table thead th {
|
||||
background-color: #f4f4f4;
|
||||
border-bottom: 2px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.positions-manager table th,
|
||||
.positions-manager table td {
|
||||
padding: 8px;
|
||||
vertical-align: middle; /* Vertically center text */
|
||||
}
|
||||
|
||||
.positions-manager table tbody tr:nth-child(even) {
|
||||
background-color: #f9f9f9; /* Alternate row color */
|
||||
}
|
||||
|
||||
.positions-manager table .btn {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.positions-manager .form-container .btn-sm,
|
||||
.positions-manager table .btn.btn-sm {
|
||||
padding: 0.3rem 0.6rem; /* Small button padding */
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ Vue.component('tt-autocomplete', {
|
||||
Einträge werden geladen...
|
||||
</li>
|
||||
|
||||
<template v-show="showSuggestions && displayingItems.length > 0 && isLoading !== true">
|
||||
<template v-show="showSuggestions && displayingItems.length && isLoading !== true">
|
||||
<li
|
||||
v-for="(item) in displayingItems.slice(0, 10)"
|
||||
:key="item.value"
|
||||
@@ -104,9 +104,15 @@ Vue.component('tt-autocomplete', {
|
||||
this.disableIDFetch = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue) {
|
||||
this.$emit('input', newValue);
|
||||
this.value = newValue;
|
||||
}
|
||||
if (oldValue && !newValue) {
|
||||
this.$emit('input', '');
|
||||
this.displayValue = '';
|
||||
}
|
||||
|
||||
|
||||
if (this.value && this.apiUrl) {
|
||||
@@ -120,14 +126,13 @@ Vue.component('tt-autocomplete', {
|
||||
const selectedItem = this.items.find(item => item.value === this.value);
|
||||
this.displayValue = selectedItem ? selectedItem.text : '';
|
||||
} else {
|
||||
this.$emit('input', '');
|
||||
if (this.returnText === false && !(typeof this.value === 'undefined' || this.value === '')) this.$emit('input', '');
|
||||
this.displayValue = this.displayValue.replace(this.oldDisplayValue, '');
|
||||
}
|
||||
},
|
||||
onInput(event) {
|
||||
this.displayValue = event.target.value;
|
||||
this.$emit('input', '');
|
||||
this.$emit('input', this.returnText ? this.displayValue : '');
|
||||
if (this.returnText) this.$emit('input', this.displayValue);
|
||||
this.fetchSuggestions();
|
||||
}, onFocus() {
|
||||
this.showSuggestions = true;
|
||||
@@ -136,10 +141,8 @@ Vue.component('tt-autocomplete', {
|
||||
this.showSuggestions = false;
|
||||
}, 200);
|
||||
}, fetchSuggestions() {
|
||||
if (this.displayValue.length < 3) {
|
||||
this.$set(this, 'displayingItems', []);
|
||||
return this.displayingItems = [];
|
||||
}
|
||||
this.$set(this, 'displayingItems', []);
|
||||
if (this.displayValue.length < 3) return;
|
||||
|
||||
if (!this.apiUrl) {
|
||||
|
||||
|
||||
163
public/plugins/vue/tt-components/tt-position-manager.js
Normal file
163
public/plugins/vue/tt-components/tt-position-manager.js
Normal file
@@ -0,0 +1,163 @@
|
||||
Vue.component('tt-positions-manager', {
|
||||
props: {
|
||||
value: {type: Array, required: false},
|
||||
config: {type: Object, required: true},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
window: window,
|
||||
positions: this.value,
|
||||
formData: {},
|
||||
selectedIndex: null,
|
||||
resolvingFields: {},
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="positions-manager">
|
||||
<div class="form-container">
|
||||
<template v-for="(field, key) in config.fields">
|
||||
<slot :name="key" v-bind:field="field" v-bind:value="formData[key]">
|
||||
<tt-input
|
||||
v-if="field.type === 'input'"
|
||||
:label="field.label"
|
||||
v-model="formData[key]"
|
||||
@input="$emit('updateField-' + key, $event)"
|
||||
sm
|
||||
:type="field.inputType || 'text'"
|
||||
/>
|
||||
<tt-autocomplete
|
||||
v-else-if="field.type === 'autocomplete'"
|
||||
:label="field.label"
|
||||
v-model="formData[key]"
|
||||
@input="$emit('updateField-' + key, $event); window.console.log($event)"
|
||||
:api-url="window.TT_CONFIG['BASE_PATH'] + field.apiUrl"
|
||||
sm
|
||||
/>
|
||||
<tt-checkbox
|
||||
v-else-if="field.type === 'checkbox'"
|
||||
:label="field.label"
|
||||
@input="$emit('updateField-' + key, $event)"
|
||||
sm
|
||||
v-model="formData[key]"
|
||||
/>
|
||||
<tt-select
|
||||
v-else-if="field.type === 'select'"
|
||||
:label="field.label"
|
||||
@input="$emit('updateField-' + key, $event); window.console.log('updatefield-' + key, $event)"
|
||||
sm
|
||||
v-model="formData[key]"
|
||||
:options="field.options"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
<div class="button-wrapper">
|
||||
<tt-button @click="saveEntry" sm :additional-class="selectedIndex === null ? 'btn-primary' : 'btn-success'"
|
||||
:text="selectedIndex === null ? 'Hinzufügen' : 'Aktualisieren'"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="field in config.fields">{{ field.label }}</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(position, index) in positions" :key="index">
|
||||
<td v-for="(field, key) in config.fields">
|
||||
<template v-if="resolvingFields[index + key] === true">
|
||||
<div class="d-flex justify-content-center align-items-center">
|
||||
<div class="spinner-border spinner-border-sm text-primary" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<span v-else-if="resolvingFields[index + key]">{{ resolvingFields[index + key] }}</span>
|
||||
<span v-else>{{ formatFieldValue(position[key], field) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<button @click="editEntry(index)" class="btn btn-sm btn-primary">Editieren</button>
|
||||
<button @click="deleteEntry(index)" class="btn btn-sm btn-danger">Löschen</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`,
|
||||
methods: {
|
||||
updateField(key, value) {
|
||||
this.$set(this.formData, key, value);
|
||||
},
|
||||
async saveEntry() {
|
||||
if (this.config.validateForm && !await this.config.validateForm(this.formData)) return;
|
||||
|
||||
if (this.selectedIndex === null) this.positions.push(this.formData);
|
||||
else this.$set(this.positions, this.selectedIndex, this.formData);
|
||||
|
||||
if (this.config.customOrdering) {
|
||||
this.positions.sort((a, b) => a[this.config.customOrdering] - b[this.config.customOrdering]);
|
||||
}
|
||||
|
||||
this.$emit('input', this.positions);
|
||||
this.resetForm();
|
||||
},
|
||||
editEntry(index) {
|
||||
this.selectedIndex = index;
|
||||
this.formData = {...this.positions[index]};
|
||||
},
|
||||
deleteEntry(index) {
|
||||
this.positions.splice(index, 1);
|
||||
this.$emit('input', this.positions);
|
||||
},
|
||||
resetForm() {
|
||||
this.formData = {};
|
||||
this.selectedIndex = null;
|
||||
},
|
||||
formatFieldValue(value, field) {
|
||||
if (field.formatter) return field.formatter(value);
|
||||
return value;
|
||||
},
|
||||
async resolveFields() {
|
||||
for (let i = 0; i < this.positions.length; i++) {
|
||||
for (let key in this.config.fields) {
|
||||
if (this.config.fields[key].customFieldResolver) {
|
||||
this.$set(this.resolvingFields, i + key, true);
|
||||
const textValue = await this.config.fields[key].customFieldResolver(this.positions[i][key]);
|
||||
this.$set(this.resolvingFields, i + key, textValue);
|
||||
} else if (this.config.fields[key].customFieldReference) {
|
||||
this.$set(this.resolvingFields, i + key, true);
|
||||
if (this.config.fields[key].customFieldReference) {
|
||||
const entry = await axios.get(window.TT_CONFIG['BASE_PATH'] +
|
||||
'/' +
|
||||
this.config.fields[key].customFieldReference +
|
||||
'/getById?id=' +
|
||||
this.positions[i][key]);
|
||||
const textValue = entry.data.name ?? entry.data.title ?? entry.data.text ?? '[E] Key not found';
|
||||
console.log(textValue);
|
||||
this.$set(this.resolvingFields, i + key, textValue);
|
||||
} else this.$set(this.resolvingFields, i + key, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.config.customMethods) Object.assign(this, this.config.customMethods);
|
||||
},
|
||||
watch: {
|
||||
positions: {
|
||||
handler() {
|
||||
this.resolveFields().then();
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
value: {
|
||||
handler() {
|
||||
this.positions = this.value;
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user