Merge branch 'master' into fronkdev

This commit is contained in:
Frank Schubert
2025-02-11 14:32:51 +01:00
70 changed files with 3710 additions and 1165 deletions

View File

@@ -27,6 +27,7 @@
<a href="<?=self::getUrl("Address","edit", ['id' => $address->id, 's' => $s, 'f' => "view"])?>" class="btn btn-outline-success"><i class="fas fa-edit"></i> Adresse bearbeiten</a>
</div>
<div class="float-right">
<a href="<?=self::getUrl("Address","tickets", ["address_id" => $address->id])?>" class="btn btn-purple mr-1"><i class="far fa-user-headset"></i> Tickets</a>
<a href="<?=self::getUrl("Address","invoice", ["address_id" => $address->id])?>" class="btn btn-purple mr-1"><i class="far fa-file-invoice-dollar"></i> Rechungsübersicht</a>
</div>
</div>

View File

@@ -33,10 +33,10 @@ $pagination_entity_name = "Zustimmungserklärungen";
<form method="get" action="<?=self::getUrl("ConstructionConsent")?>">
<div class="row">
<div class="col-2">
<div class="col-3">
<label class="form-label" for="filter_project_id">Projekt</label>
<select name="filter[project_id]" id="filter_project_id" class="form-control">
<option></option>
<option value="">Alle</option>
<?php foreach(ConstructionConsentProject::getAll() as $p): ?>
<option value="<?=$p->id?>" <?=(is_array($filter) && array_key_exists("project_id", $filter) && $p->id == $filter["project_id"]) ? "selected='selected'" : ""?>><?=$p->name?></option>
<?php endforeach; ?>
@@ -46,13 +46,31 @@ $pagination_entity_name = "Zustimmungserklärungen";
<div class="col-2">
<label class="form-label" for="filter_object_type">Objektart</label>
<select name="filter[object_type]" id="filter_object_type" class="form-control">
<option></option>
<option value="">Alle</option>
<option value="building" <?=(array_key_exists("object_type", $filter) && $filter["object_type"] == "building") ? "selected='selected'" : ""?>>Gebäude</option>
<option value="street" <?=(array_key_exists("object_type", $filter) && $filter["object_type"] == "street") ? "selected='selected'" : ""?>>Straße</option>
</select>
</div>
<div class="col-1">
<div class="col-2">
<label class="form-label" for="filter_network">Netzgebiet</label>
<select name="filter[network]" id="filter_network" class="form-control">
<option value="">Alle</option>
<?php
$networks = array_key_exists("project_id", $filter) && $filter["project_id"] ? ConstructionConsentNetwork::search(["constructionconsentproject_id" => $filter["project_id"]]) : ConstructionConsentNetwork::getAll();
$networkOptions = [];
foreach($networks as $network):
if (array_key_exists($network->adb_netzgebiet_id, $networkOptions)) continue;
$networkOptions[$network->adb_netzgebiet_id] = $network;?>
<option value="<?=$network->adb_netzgebiet_id?>" <?=(is_array($filter) && array_key_exists("network", $filter) && $network->adb_netzgebiet_id == $filter["network"]) ? "selected='selected'" : ""?>><?=$network->adb_netzgebiet->name?></option>
<?php endforeach;
?>
</select>
</div>
<div class="col-2">
<label class="form-label" for="filter_address">Objektadresse</label>
<input type="text" class="form-control" name="filter[address]" id="filter_address" value="<?=(array_key_exists('address', $filter)) ? $filter['address'] : ""?>" />
</div>

View File

@@ -1,6 +1,3 @@
<?php
?>
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/header.php"); ?>
<link href="<?= self::getResourcePath() ?>assets/css/datatables-std.css" rel="stylesheet"
type="text/css"/>
@@ -154,11 +151,14 @@
</ol>
</div>
<h4 class="page-title">Device: <span class="font-weight-normal ml-1"><?= $devices->data->name ?></span>
<span class="ml-2">
<?php if ($me->is('Admin')) : ?>
<span class="ml-2">
<a href="<?= self::getUrl("Device", "edit", ["id" => $devices->id, 'returnto' => "device-detail"]) ?>">
<button class="btn btn-primary">Bearbeiten</button>
</a>
</span></h4>
</span>
<?php endif; ?>
</h4>
</div>
</div>
</div>
@@ -275,7 +275,7 @@ foreach ($devicesall as $deviceall) {
}
?>
<div class="col-12 col-lg-4 card-border">
<div class="col-12 <?= ($me->is('Admin')) ? 'col-lg-4' : 'col-lg-7' ?> card-border">
<div>
<h4><?= $StdHeader ?></h4>
</div>
@@ -289,7 +289,7 @@ foreach ($devicesall as $deviceall) {
<tr>
<th class="w-30">Pop Name</th>
<td>
<a href="<?= self::getUrl("Pop", "Detail", ["id" => $devices->pop->id]) ?>"><?= $devices->pop->name ?></a>
<?= ($me->is('Admin')) ? '<a href="' . self::getUrl("Pop", "Detail", ["id" => $devices->pop->id]) . '">' . $devices->pop->name . '</a>' : $devices->pop->name ?>
</td>
</tr>
<tr>
@@ -338,67 +338,75 @@ foreach ($devicesall as $deviceall) {
}
?>
</div>
<div class="col-12 col-lg-3 card-border">
<div class="overflow-auto">
<h4 class="float-left">Config Backups</h4>
<?php if ($devices->devicetype->devicemanufactor->config_backup > count()): ?>
<span><i title="Switch config" class="fa-light fa-rectangle-code code-ico"
data-toggle="modal" data-target="#configCode"></i></span>
<?php endif; ?>
<div class="float-right">
<a id="create-backup-href"
href="<?= self::getUrl("Device", "api", ['do' => 'createconfig', 'ip' => $devices->ip, 'id' => $devices->id]) ?>"
<button class="btn btn-primary "><span id="create-backup-button-text">Backup erstellen</span>
<span id="create-backup-load"></i></span></button>
</a></div>
</div>
<?php
if ($devicesconfig->success == "true" && $devicesconfig->data > count()) {
?>
<div>
<table class="table table-sm">
<thead>
<tr>
<th>Datum/Uhrzeit</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($devicesconfig->data as $config) :
$configfileCleartext = trim($config->config_cleartext);
$configfileCompressed = trim($config->config_compressed);
$configid = $config->id;
if ($configfileCleartext && $configfileCompressed) :
<?php if ($me->is('Admin')) : ?>
<div class="col-12 col-lg-3 card-border">
<div class="overflow-auto">
<h4 class="float-left">Config Backups</h4>
<?php if ($devices->devicetype->devicemanufactor->config_backup > count()): ?>
<span><i title="Switch config" class="fa-light fa-rectangle-code code-ico"
data-toggle="modal" data-target="#configCode"></i></span>
<?php endif;
if ($me->is('Admin')):
?>
<div class="float-right">
<a id="create-backup-href"
href="<?= self::getUrl("Device", "api", ['do' => 'createconfig', 'ip' => $devices->ip, 'id' => $devices->id]) ?>"
<button class="btn btn-primary "><span id="create-backup-button-text">Backup erstellen</span>
<span id="create-backup-load"></i></span></button>
</a></div>
<?php endif; ?>
</div>
<?php
if ($devicesconfig->success == "true" && $devicesconfig->data > count()) {
?>
<div>
<table class="table table-sm">
<thead>
<tr>
<th>Datum/Uhrzeit</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($devicesconfig->data as $config) :
$configfileCleartext = trim($config->config_cleartext);
$configfileCompressed = trim($config->config_compressed);
$configid = $config->id;
if ($configfileCleartext && $configfileCompressed) :
$configLinks = '<a href="' . self::getUrl("Device", "api", ['do' => 'getconfig', 'id' => $configid, 'format' => 'txt', 'filename' => $configfileCleartext]) . '">
$configLinks = '<a href="' . self::getUrl("Device", "api", ['do' => 'getconfig', 'id' => $configid, 'format' => 'txt', 'filename' => $configfileCleartext]) . '">
TXT</a> / <a href="' . self::getUrl("Device", "api", ['do' => 'getconfig', 'id' => $configid, 'format' => 'xml', 'filename' => $configfileCompressed]) . '">
XML</a>';
elseif ($configfileCleartext || $configfileCompressed) :
$configLinks = '<a href="' . self::getUrl("Device", "api", ['do' => 'getconfig', 'id' => $configid, 'format' => 'txt', 'filename' => $configfileCleartext . $configfileCompressed]) . '">
elseif ($configfileCleartext || $configfileCompressed) :
$configLinks = '<a href="' . self::getUrl("Device", "api", ['do' => 'getconfig', 'id' => $configid, 'format' => 'txt', 'filename' => $configfileCleartext . $configfileCompressed]) . '">
TXT</a>';
endif;
?>
<tr>
<td><?= date("d.m.Y/H:i", $config->config_timestamp); ?></td>
<td><?= $configLinks; ?></td>
endif;
?>
<tr>
<td><?= date("d.m.Y/H:i", $config->config_timestamp); ?></td>
<td><?= $configLinks; ?></td>
</tr>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php
} else {
</tbody>
</table>
</div>
<?php
} else {
?>
<div class="mt-1">
<h5 class="text-center">Keine Configs vorhanden</h5>
</div>
<?php
}
?>
<div class="mt-1">
<h5 class="text-center">Keine Configs vorhanden</h5>
</div>
<?php
}
?>
</div>
</div>
<?php
endif
?>
</div>
<?php
if ($devices->devicetype->olt && TT_MBI_API_ENABLE) :
@@ -647,6 +655,7 @@ foreach ($devicesall as $deviceall) {
$config = str_replace("&&YEAR&&", $year, $config);
$config = str_replace("&&MONTH&&", $month, $config);
$config = str_replace("&&DAY&&", $day, $config);
$config = str_replace("&&IP&&", $devices->ip, $config);
?>

View File

@@ -55,7 +55,7 @@ $pagination_entity_name = "Rechnungen";
<input type="text" class="form-control" name="filter[customer_number]" id="filter_customer_number" value="<?=(array_key_exists("customer_number", $filter)) ? $filter['customer_number'] : ""?>"/>
</div>
<div class="col-1">
<label class="form-label" for="filter_fibu_account_number">FIBU Konten</label>
<label class="form-label" for="filter_fibu_account_number">FIBU</label>
<input type="text" class="form-control" name="filter[fibu_account_number]" id="filter_fibu_account_number" value="<?=(array_key_exists("fibu_account_number", $filter)) ? $filter['fibu_account_number'] : ""?>"/>
</div>
<div class="col-1">
@@ -474,4 +474,4 @@ $pagination_entity_name = "Rechnungen";
</script>
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/footer.php"); ?>
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/footer.php"); ?>

View File

@@ -738,7 +738,9 @@ $pagination_entity_name = "Vorbestellungen";
var marker_popup_content = '<?php include(realpath(dirname(__FILE__))."/include/preorder_popup.php");?>';
// popup fields
const preorder_view_url = `<?=self::getUrl("Preorder")?>/Index?filter[ucode]=${preorder.ucode}#preorder=${preorder.id}`;
[
["PREORDER_URL", preorder_view_url],
["street", preorder.adb_strasse],
["hausnummer", preorder.adb_hausnummer],
["zip", preorder.adb_plz],

View File

@@ -37,6 +37,11 @@ ob_start();
<th class="font-weight-bold">Email:</th>
<td>{{EMAIL}}</td>
</tr>
</table>
<a href="{{PREORDER_URL}}" class="btn btn-primary text-white" target="_blank">
<i class="fas fa-eye"></i> Bestellung ansehen
</a>
<?=str_replace("\n"," ",ob_get_clean())?>

View File

@@ -4,6 +4,7 @@ foreach ($days as $key => $day) {
$daysSelect .= '<option value="' . $key . '">' . $day . '</option>';
}
$daysSelect .= "</select>";
?>
<script>
var daysselect = `<?= $daysSelect ?>`;
@@ -29,6 +30,15 @@ $daysSelect .= "</select>";
margin-left: 7px;
margin-top: 8px;
}
.remove-wh-history {
cursor: pointer;
color: #ff0606;
font-size: 17px;
float: right;
margin-left: 7px;
}
</style>
<!-- start page title -->
<div class="row">
@@ -214,6 +224,12 @@ $daysSelect .= "</select>";
<label class="col-form-label" for="workinghours"><span
id="workinghours-text">0 Stunden 0 Minuten</span></label>
</div>
<div class="col-lg-2 text-center">
<button type="button" id="change-workinghours" data-toggle="modal"
data-target="#closeWorkingHoursModal" class="btn btn-danger">
Arbeitszeit Abschluss
</button>
</div>
</div>
<?php
@@ -251,25 +267,113 @@ $daysSelect .= "</select>";
?>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-lg-2"></label>
<div class="col-lg-10">
<button type="submit" class="btn btn-primary">Speichern</button>
<a href="<?= self::getUrl("TimerecordingEmployee") ?>">
<button type="button" class="btn btn-secondary">Abbrechen</button>
</a>
<button type="submit" class="btn btn-primary">Speichern</button>
</div>
</div>
</form>
</div>
<?php if ($timerecordingworkinghourshistory) : ?>
<div class="card-body">
<div class="card no-shadow">
<div class="card-body">
<div class="row">
<div class="col-lg-6">
<h4 class="header-title mb-2">Arbeitszeiten Historie</h4>
<table id="datatable" class="table table-sm ">
<thead>
<tr>
<th class="text-center">Arbeitszeiten</th>
<th class="text-center">Wochenstunden</th>
<th class="text-center">Bis</th>
<th></th>
</tr>
</thead>
<tbody>
<?php
foreach ($timerecordingworkinghourshistory as $key => $timerecordingworkinghours):
if (!$timerecordingworkinghours['workinghours']) {
$historyworkingkours = '0 Stunden';
$historyworkingkoursText = "keine";
} else {
$historyworkingkoursText = $timerecordingworkinghours['workinghours'];
$hours = $timerecordingworkinghours['workingtime'] / 3600;
$historyworkingkours = $hours . ' Stunden';
}
?>
<tr>
<td class="text-center"><?= $historyworkingkoursText ?></td>
<td class="text-center"><?= $historyworkingkours ?></td>
<td class="text-center"><?= date('d.m.Y', $key) ?></td>
<td><i class="fa-regular fa-circle-minus remove-wh-history" data-id="<?= $timerecordingworkinghours['id'] ?>"></i>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="modal fade" id="closeWorkingHoursModal" tabindex="-1" role="dialog"
aria-labelledby="closeWorkingHoursModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle">Arbeitszeit abschließen</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group row justify-content-center ">
<label class="col-lg-5 col-form-label" for="user_id">Letzter Tag</label>
<div class="col-lg-5">
<input type="date" id="workingHoursCloseDate" class="form-control required">
</div>
</div>
<div class="form-group row justify-content-center ">
<div class="col-lg-12 row justify-content-center"><h4 class="p-0 m-1">Arbeitszeiten</h4></div>
<div class="col-lg-12 row justify-content-center"><h5 class="p-0 m-1"
id="workingHoursTime"></h5></div>
<div id="old-workingHours" class="col-lg-10">
<table class="table table-sm">
</table>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" id="save-close-workingHours">Abschließen</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Abbrechen</button>
</div>
</div>
</div>
</div>
<script type="text/javascript">
function checktime() {
var sum = 0;
@@ -320,7 +424,83 @@ $daysSelect .= "</select>";
})
$(document).ready(function () {
checktime();
$('#closeWorkingHoursModal').on('shown.bs.modal', function () {
$('#old-workingHours table').empty();
$('#workingHoursCloseDate').val('');
$('#workingHoursTime').text($('#workinghours-text').text());
if ($(".wtime").length > 0) {
$(".wtime").each(function (index) {
var day = $(this).find('select').val();
var start = $(this).find('.timestart').val();
var end = $(this).find('.timeend').val();
var dayname = $(this).find('select option:selected').text();
var html = '<tr><td>' + dayname + '</td><td>' + start + '</td><td>' + end + '</td></tr>';
$('#old-workingHours table').append(html);
});
} else {
$('#old-workingHours table').append('<tr><td class="text-center" colspan="3">Keine Arbeitszeiten</td></tr>');
}
$(".wtime").each(function (index) {
});
})
//old-workingHours
$('body').on('click', '#save-close-workingHours', function () {
let reqworkingHoursSaveUrl = '<?= self::getUrl("TimerecordingEmployee", "api", ['do' => 'saveWorkingHours']) ?>';
let workingHours = [];
$(".wtime").each(function (index) {
var day = $(this).find('select').val();
var start = $(this).find('.timestart').val();
var end = $(this).find('.timeend').val();
workingHours.push({day: day, start: start, end: end});
});
let required = true;
$('.required').each(function () {
if ($(this).val() == '') {
required = false;
$(this).addClass('is-invalid');
} else {
$(this).removeClass('is-invalid');
}
});
if (!required) {
return;
}
$.ajax({
url: reqworkingHoursSaveUrl,
type: 'POST',
data: {
workingHours: workingHours,
enddate: $('#workingHoursCloseDate').val(),
userid: <?= $timerecordinguser[0]->id ?>
},
success: function (data) {
$('#closeWorkingHoursModal').modal('hide');
location.reload();
}
});
});
$('body').on('click', '.remove-wh-history', function () {
let reqworkingHoursSaveUrl = '<?= self::getUrl("TimerecordingEmployee", "api", ['do' => 'deleteWorkingHours']) ?>';
let id = $(this).data('id');
if (!confirm('Arbeitszeitänderung löschen?')) {
return;
}
$.ajax({
url: reqworkingHoursSaveUrl,
type: 'POST',
data: {
id: id,
userid: <?= $timerecordinguser[0]->id ?>
},
success: function (data) {
location.reload();
}
});
});
});
</script>
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/footer.php"); ?>
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/footer.php"); ?>

View File

@@ -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"); ?>

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>Xinon Rechnung</title>
<meta charset="utf-8" />
</head>
<body style="border:0; margin: 0;font-family: sans-serif, Verdana;font-size: 11px;" onload="subst()">
<script>
function subst() {
var vars = {};
var query_strings_from_url = document.location.search.substring(1).split('&');
for (var query_string in query_strings_from_url) {
if (query_strings_from_url.hasOwnProperty(query_string)) {
var temp_var = query_strings_from_url[query_string].split('=', 2);
vars[temp_var[0]] = decodeURI(temp_var[1]);
}
}
var css_selector_classes = ['page', 'frompage', 'topage', 'webpage', 'section', 'subsection', 'date', 'isodate', 'time', 'title', 'doctitle', 'sitepage', 'sitepages'];
for (var css_class in css_selector_classes) {
if (css_selector_classes.hasOwnProperty(css_class)) {
var element = document.getElementsByClassName(css_selector_classes[css_class]);
for (var j = 0; j < element.length; ++j) {
element[j].textContent = vars[css_selector_classes[css_class]];
}
}
}
}
</script>
<div style="margin-bottom: 16px;height: 1px"></div>
<div style="color:grey;text-align: center;margin-bottom: 0">
<span>XINON GmbH | Fladnitz 150 | 8322 Studenzen</span><br>
<span>Tel.: +43 3115 40800 | E-Mail: office@xinon.at</span><br>
<span>UID: ATU68711968 | FN: 416556h | LG: Feldbach</span><br>
<span>IBAN: {{ bank_iban }} | BIC: {{ bank_bic }}</span><br>
</div>
<div style="text-align: right">Seite <span class="page"></span> von <span class="topage"></span></div>
<div style="margin-top: 16px;height: 1px"></div>
</body>
</html>

View File

@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html>
<head>
<title>XINON Shipping Note Header</title>
<meta charset="utf-8" />
<style>
body {
border: 0;
margin: 0;
font-family: sans-serif, Verdana;
font-size: 12px;
}
.info-table {
border-collapse: collapse;
width: 100%;
}
.customer-details {
vertical-align: bottom;
font-size: 14px;
padding-left: 30pt;
width: 35%;
}
.invoice-details {
border: 2px solid #e1e1e1;
padding: 6px;
}
.invoice-details td {
text-align: left;
}
.invoice-details td:first-child {
text-align: right;
}
.separator {
margin-top: 24px;
height: 1px;
}
#topSpacer {
margin-bottom: 32px;
height: 20px;
}
</style>
</head>
<body>
<div id="topSpacer"></div>
<div style="height: 50px; margin-bottom: 48px">
<img alt="Xinon Logo" src="{{ basedir }}/public/assets/images/xinon-full.png" style="text-align:left;height: 85px;">
</div>
<table style="width: 100%; border-collapse: collapse;">
<tr>
<td class="customer-details" style="float: left">
<h3>Lieferant</h3>
<div>{{ addressLine_1 }}</div>
<div>{{ addressLine_2 }}</div>
<div>{{ addressLine_3 }}</div>
<div>{{ addressLine_4 }}</div>
<div style="margin-bottom: 12pt"></div>
<div>{{ externalReference }}</div>
</td>
<td class="customer-details" align="top">
<h3>Rechnungsadresse</h3>
<div>{{ billingAddressLine_1 }}</div>
<div>{{ billingAddressLine_2 }}</div>
<div>{{ billingAddressLine_3 }}</div>
<div>{{ billingAddressLine_4 }}</div>
<div>{{ billingAddressLine_5 }}</div>
<div>{{ billingAddressLine_6 }}</div>
</td>
<td class="customer-details" style="float: right">
<div>{{ shippingAddressLine_1 }}</div>
<div>{{ shippingAddressLine_2 }}</div>
<div>{{ shippingAddressLine_3 }}</div>
<div>{{ shippingAddressLine_4 }}</div>
</td>
</tr>
</table>
<div class="separator"></div>
</body>
</html>

View File

@@ -0,0 +1,133 @@
<?php
/**
* @var string $ressourcePathPrefix
* @var WarehouseOrderModel $order
* @var Array $positions
* @var Array $textElements
*/
$this->setReturnValue(['filename' => $order["id"] . ".pdf"]);
?>
<!DOCTYPE html>
<html>
<head>
<title>Bestellung</title>
<meta charset="utf-8" />
<style>
body {
margin-top: 0;
/*padding-top: 20pt;*/
font-family: "Open Sans", sans-serif, Verdana;
font-size: 12px;
}
tr {
page-break-inside: avoid;
}
.uneven {
background-color: #ebebeb;
}
table tr td:last-child {
text-align: right;
}
.additionalRow td:first-child {
text-align: left;
padding-left: 20pt;
}
th {
height: 28px;
}
#invoiceTable tr *:nth-child(5),
#invoiceTable tr *:nth-child(4),
#invoiceTable tr *:nth-child(3) {
text-align: right;
}
#invoiceTable tr *:not(:first-child) {
padding: 4px 0;
}
#invoiceTable tr td {
font-size: 11px;
}
tr.position td {
vertical-align: top;
}
tr.position td:first-child {
vertical-align: middle !important;
padding-left: 4pt;
}
#invoiceTable tr td:first-child {
max-width: 200pt;
}
</style>
</head>
<body>
<div>
<!--
TODO: enable option for showing prices
vertauschen
Die gelieferte Ware bleibt bis zur vollständigen Bezahlung in unserem Eigentum.
-->
<h2 style="text-align: center;color: #005384">XINON Lieferantenbestellung vom <?=date("d.m.Y", $order["create"])?></h2>
<table style="border-collapse: collapse; width: 100%;" id="invoiceTable">
<tr style="font-weight: bold; border-bottom: 1px solid black;" class="uneven">
<th style="text-align: center;padding-right: 6pt">Position</th>
<th style="text-align: center;padding-right: 6pt">Artikel</th>
<th style="text-align: center;padding-right: 6pt">Art.-Nr. Lieferant</th>
<th style="text-align: right">Menge</th>
<th style="text-align: right">Einzelpreis</th>
<th style="text-align: right;padding-right: 8pt">Gesamtpreis</th>
</tr>
<?php $i = 0; foreach($order['positions'] as $p):?>
<tr class="position <?=($i%2 == 0) ? "even" : "uneven" ?>">
<td style="text-align: center;"><?= $i + 1 ?></td>
<td style="text-align: left;padding-right: 8pt"><?=$p["articleName"]?></td>
<td style="text-align: center;padding-right: 8pt"><?=$p["distributorArticleNumber"]?></td>
<td style="text-align: right"><?=$p["amount"]?></td>
<td style="text-align: right"><?=number_format($p["buyPrice"], 2, ",", ".")?> €</td>
<td style="text-align: right;padding-right: 8pt"><?=number_format($p["amount"] * $p["buyPrice"], 2, ",", ".")?> €</td>
</tr>
<?php $i++; endforeach;?>
<!-- display a grey like header sum with top border to differentiate 2nd last td = Summe , last td is the calculated value both bold-->
<tr class="uneven">
<?php
$sum = 0;
foreach($order['positions'] as $p){
$sum += $p["amount"] * $p["buyPrice"];
}
?>
<td colspan="5" style="text-align: right;border-top: 1px solid black;font-weight: bold
;border-bottom: 1px solid black;
">Summe</td>
<td style="text-align: right;border-top: 1px solid black;font-weight: bold
;border-bottom: 1px solid black;
"><?=number_format($sum, 2, ",", ".")?> €</td>
</tr>
</table>
<div>
<h3>Anmerkungen</h3>
<p>
<?=$order["note"]?>
</p>
</div>
</body>
</html>

View File

@@ -49,7 +49,7 @@
</ul>
</li>
<?php endif; ?>
<?php if($me->is(["Admin"]) || ($me->is("netowner") && $me->hasGwrNetworks())): ?>
<?php if($me->is(["Admin"]) || ($me->is("netowner","lineplanner","pipeplanner","pipeworker","lineworker","salespartner"))): ?>
<li class="has-submenu">
<a href="#">
<i class="far fa-fw fa-database"></i>Stammdaten <div class="arrow-down"></div>
@@ -64,7 +64,11 @@
<li class="mobile-hide"><a href="<?=self::getUrl("Network")?>"><i class="fad fa-fw fa-network-wired text-info"></i> Netzgebiete</a></li>
<li class="" ><a href="<?=self::getUrl("Pop")?>"><i class="fad fa-fw fa-house text-info"></i> Pops</a></li>
<?php endif; ?>
<?php if($me->is(["Admin"]) || ($me->is("netowner","lineplanner","pipeplanner","pipeworker","lineworker","salespartner"))): ?>
<li class=""><a href="<?=self::getUrl("Device")?>"><i class="fad fa-fw fa-router text-info "></i> Devices</a></li>
<?php endif; ?>
<?php if($me->is(["Admin"])): ?>
<li class="has-sub-submenu"><a href="<?=self::getUrl("User")?>"><i class="fad fa-fw fa-users text-info"></i> Benutzer</a></li>
<li class="has-sub-submenu font-weight-bold mt-1 mobile-hide"><a>Grundstammdaten</a></li>
<?php endif; ?>
@@ -194,7 +198,7 @@
<?php if($me->is(["Admin","salespartner"]) && $me->can("Order")): ?>
<li><a href="<?=self::getUrl("Order")?>"><i class="far fa-fw fa-file-signature text-info"></i> Bestellungen</a></li>
<?php endif; ?>
<?php if($me->is(["Admin","netowner","salespartner"]) && in_array($me->address_id, [1,209])): ?>
<?php if($me->is(["Admin","netowner","salespartner"]) && in_array($me->address_id, [1,209,5908])): ?>
<li><a href="<?=self::getUrl("ConstructionConsentProject")?>"><i class="far fa-fw fa-clipboard-question text-info"></i> Zustimmungserklärungen</a></li>
<?php endif; ?>
</ul>

File diff suppressed because one or more lines are too long

View File

@@ -70,10 +70,27 @@ class CalendarController extends mfBaseController
$email->send();
} else if ($r->customer_info_type == 2) {
$sms = new SmsNotification();
$body = "Xinon Terminbestätigung:" . PHP_EOL . $r->customer_info_text;
$customerText = trim($r->customer_info_text);
$body = "Xinon Terminbestätigung:" . PHP_EOL . $customerText;
$sms->setBody($body);
$sms->setRecipient($r->customer_info_type_text);
$sms->send();
$customerNumber = trim($r->customer_info_type_text);
$customerNumber = str_replace(" ", "", $customerNumber);
$customerNumber = str_replace("(", "", $customerNumber);
$customerNumber = str_replace(")", "", $customerNumber);
$customerNumber = str_replace("-", "", $customerNumber);
$customerNumber = str_replace("/", "", $customerNumber);
$customerNumber = str_replace(".", "", $customerNumber);
$customerNumber = str_replace(",", "", $customerNumber);
$customerNumber = str_replace(";", "", $customerNumber);
$customerNumber = str_replace(":", "", $customerNumber);
if (preg_match('/^0/', $customerNumber)) {
$customerNumber = "+43" . substr($customerNumber, 1);
}
$sms->setRecipient($customerNumber);
if (!empty($customerNumber) && !empty($customerText)) {
$sms->send();
}
}
}
die();
@@ -104,10 +121,26 @@ class CalendarController extends mfBaseController
$email->send();
} else if ($r->customer_info_type == 2) {
$sms = new SmsNotification();
$body = "Xinon Terminbestätigung:" . PHP_EOL . $r->customer_info_text;
$customerText = trim($r->customer_info_text);
$body = "Xinon Terminbestätigung:" . PHP_EOL . $customerText;
$sms->setBody($body);
$sms->setRecipient($r->customer_info_type_text);
$sms->send();
$customerNumber = trim($r->customer_info_type_text);
$customerNumber = str_replace(" ", "", $customerNumber);
$customerNumber = str_replace("(", "", $customerNumber);
$customerNumber = str_replace(")", "", $customerNumber);
$customerNumber = str_replace("-", "", $customerNumber);
$customerNumber = str_replace("/", "", $customerNumber);
$customerNumber = str_replace(".", "", $customerNumber);
$customerNumber = str_replace(",", "", $customerNumber);
$customerNumber = str_replace(";", "", $customerNumber);
$customerNumber = str_replace(":", "", $customerNumber);
if (preg_match('/^0/', $customerNumber)) {
$customerNumber = "+43" . substr($customerNumber, 1);
}
$sms->setRecipient($customerNumber);
if (!empty($customerNumber) && !empty($customerText)) {
$sms->send();
}
}
}
die();
@@ -327,17 +360,18 @@ class CalendarController extends mfBaseController
echo trim($json);
die();
}
private function getTicket($r)
{
$project = new XinonProject();
$data=$project->searchSupportTickets($r->term);
$data = $project->searchSupportTickets($r->term);
foreach ($data as $key => $value) {
$rows[] = array(
'id' => $value['id'],
'text' => 'Ticket: '.$value['id']." ".$value['subject'],
'text' => 'Ticket: ' . $value['id'] . " " . $value['subject'],
'subject' => $value['subject'],
'mail' => $value['customField5'],
'mobilenumber' => $value['customField4'],

View File

@@ -47,6 +47,8 @@ class CalendarModel
3 => 'Grüne Kategorie', //ESTMK IBN
4 => 'Lila Kategorie', //Snopp IBN
5 => 'Rote Kategorie', //Störung
98 => 'Gru00fcne Kategorie', //ESTMK IBN
99 => 'Gr\u00fcne Kategorie', //ESTMK IBN
);
public static $specialCalendarColors = array(997 => '#bd0000', 998 => '#8000A3', 999 => '#08769b');
@@ -694,7 +696,7 @@ WHERE `TimerecordingCategory`.`hourday`!='1' AND `TimerecordingCategory`.`hourda
if (($r->customer_info_type_text)) {
$customer_info_type_text = $r->customer_info_type_text;
}
if ($r->customer_info_reminder_check) {
if ($r->customer_info_reminder_check || $r->customer_info_reminder_check == 0) {
$updateArray['customer_info_reminder'] = $r->customer_info_reminder_check;
}
$customerJson = array('customer_info_type' => $customer_info_type, 'customer_info_text' => $customer_info_text, 'customer_info_type_text' => $customer_info_type_text);

View File

@@ -353,6 +353,7 @@ class ConstructionConsent extends mfBaseModel {
$where = self::getSqlFilter($filter);
$sql = "SELECT COUNT(*) as cnt FROM ConstructionConsent
LEFT JOIN ".ADDRESSDB_DBNAME.".view_hausnummer ON (ConstructionConsent.adb_hausnummer_id = view_hausnummer.hausnummer_id)
WHERE $where";
//mfLoghandler::singleton()->debug($sql);
@@ -377,6 +378,7 @@ class ConstructionConsent extends mfBaseModel {
$where = self::getSqlFilter($filter);
$sql = "SELECT * FROM ConstructionConsent
LEFT JOIN ".ADDRESSDB_DBNAME.".view_hausnummer ON (ConstructionConsent.adb_hausnummer_id = view_hausnummer.hausnummer_id)
WHERE $where
ORDER BY $order";
@@ -494,6 +496,13 @@ class ConstructionConsent extends mfBaseModel {
}
}
if(array_key_exists("network", $filter)) {
$network = FronkDB::singleton()->escape($filter["network"]);
if($network) {
$where .= " AND view_hausnummer.netzgebiet_id=$network";
}
}
if(array_key_exists("add-where", $filter)) {

View File

@@ -10,9 +10,7 @@ class ConstructionConsentController extends mfBaseController {
$this->me = $me;
$this->layout()->set("me", $me);
if (!$me->is(["Admin"])) {
$this->redirect("Dashboard");
}
if (!($me->is(["Admin","netowner","salespartner"]) && in_array($me->address_id, [1,209,5908]))) $this->redirect("Dashboard");
}
protected function indexAction() : void {

View File

@@ -11,9 +11,7 @@ class ConstructionConsentContactController extends mfBaseController
$this->me = $me;
$this->layout()->set("me", $me);
if (!$me->is(["Admin"])) {
$this->redirect("Dashboard");
}
if (!($me->is(["Admin","netowner","salespartner"]) && in_array($me->address_id, [1,209,5908]))) $this->redirect("Dashboard");
}
protected function saveAction()

View File

@@ -9,9 +9,7 @@ class ConstructionConsentJournalController extends mfBaseController {
$this->me = $me;
$this->layout()->set("me",$me);
if(!$me->is(["Admin"])) {
$this->redirect("Dashboard");
}
if (!($me->is(["Admin","netowner","salespartner"]) && in_array($me->address_id, [1,209,5908]))) $this->redirect("Dashboard");
}
protected function saveAction() {

View File

@@ -11,9 +11,7 @@ class ConstructionConsentOwnerController extends mfBaseController
$this->me = $me;
$this->layout()->set("me", $me);
if (!$me->is(["Admin"])) {
$this->redirect("Dashboard");
}
if (!($me->is(["Admin","netowner","salespartner"]) && in_array($me->address_id, [1,209,5908]))) $this->redirect("Dashboard");
}
protected function uploadDocumentAction() {

View File

@@ -10,9 +10,7 @@ class ConstructionConsentProjectController extends mfBaseController {
$this->me = $me;
$this->layout()->set("me", $me);
if (!$me->is(["Admin"])) {
$this->redirect("Dashboard");
}
if (!($me->is(["Admin","netowner","salespartner"]) && in_array($me->address_id, [1,209,5908]))) $this->redirect("Dashboard");
}
protected function indexAction() : void {

View File

@@ -10,6 +10,7 @@ class DashboardNewController extends mfBaseController {
$me->loadMe();
$this->layout()->set("me", $me);
$this->me = $me;
if ($this->me->address_id === '5908') $this->me->address_id = '209';
}
protected function indexAction() {
@@ -73,7 +74,6 @@ class DashboardNewController extends mfBaseController {
$netowner_ids = isset($post['netOwners']) ? [$post['netOwners']] : [];
$campaign_ids = isset($post['campaigns']) ? [$post['campaigns']] : [];
$campaigns = [];
$all_campaigns = $this->me->is("Admin") ? PreordercampaignModel::getAll() : PreordercampaignModel::search(["owner_id" => $this->me->address_id]);
if (!empty($netowner_ids)) {
@@ -140,10 +140,16 @@ class DashboardNewController extends mfBaseController {
if (!empty($netowner_ids)) {
$all_campaigns = $this->me->is("Admin") ? PreordercampaignModel::getAll() : PreordercampaignModel::search(["owner_id" => $this->me->address_id]);
if ($all_campaigns[0] === NULL) {
http_response_code(500);
self::returnJson(["status" => 500, "message" => "Keine Kampagnen gefunden"]);
}
$campaign_ids = empty($campaign_ids) ?
array_map(fn($campaign) => $campaign->id, $all_campaigns) :
$campaign_ids;
$campaign_ids = array_filter($campaign_ids, function ($campaign_id) use ($netowner_ids) {
$campaign = new Preordercampaign($campaign_id);
return in_array($campaign->network->owner_id, $netowner_ids);
@@ -152,6 +158,12 @@ class DashboardNewController extends mfBaseController {
if (empty($campaign_ids) && !$this->me->is("Admin")) {
$owner_campaigns = PreordercampaignModel::search(["owner_id" => $this->me->address_id]);
if (empty($owner_campaigns)) {
http_response_code(500);
self::returnJson(["status" => 500, "message" => "Keine Kampagnen gefunden"]);
}
$campaign_ids = array_map(fn($campaign) => $campaign->id, $owner_campaigns);
}

View File

@@ -10,9 +10,20 @@ class DeviceController extends mfBaseController
$this->me = $me;
$this->layout()->set("me", $me);
if (!$me->is(["Admin"])) {
if (!$me->is(["Admin", "netowner", "lineplanner", "pipeplanner", "pipeworker", "lineworker"])) {
$this->redirect("Dashboard");
}
if ($this->me->is(["Admin"])) {
$this->allowedPops = null;
} else {
$networkIds = array_column($this->me->getProperty('my_networks'), 'id');
$pops=PopNetworkModel::search(['Networks' => $networkIds]);
foreach ($pops as $pop) {
$popIds[] = $pop->pop_id;
}
$this->allowedPops = $popIds;
}
}
protected function indexAction()
@@ -75,6 +86,13 @@ class DeviceController extends mfBaseController
$this->redirect("Device");
}
if (!$this->me->is(["Admin"])) {
if (!in_array($device->pop_id, $this->allowedPops)) {
$this->layout()->setFlash("Gerät nicht gefunden", "error");
$this->redirect("Device");
}
}
$this->layout()->setTemplate("Device/Detail");
$devicesconfig = DeviceModel::getconifg($id);
$devices = DeviceModel::getOne($id);
@@ -99,7 +117,6 @@ class DeviceController extends mfBaseController
$this->layout()->set("devicetypes", DevicetypeModel::getAll());
$this->layout()->set("pops", PopModel::getAll());
$this->layout()->set("devices", DeviceModel::getAll());
}
protected function editAction()
@@ -123,6 +140,11 @@ class DeviceController extends mfBaseController
protected function saveAction()
{
if (!$this->me->is(["Admin"])) {
$this->layout()->setFlash("Keine Berechtigung", "error");
$this->redirect("Device");
}
$r = $this->request;
$id = $r->id;
//var_dump($r->get());exit;
@@ -395,7 +417,8 @@ class DeviceController extends mfBaseController
private function getDevices()
{
$devices = DeviceModel::getAll();
if ($this->allowedPops === null) return [];
$devices = DeviceModel::search(['popIds' => $this->allowedPops]);
foreach ($devices as $device) {
$locationText = "";
@@ -415,7 +438,7 @@ class DeviceController extends mfBaseController
if ($device->last_config_backup) {
if (time() - $device->last_config_backup <= 172800) {
$backup = 'ok';
if ($device->autobackup==1) {
if ($device->autobackup == 1) {
$backup = 'auto';
}
} else {
@@ -443,5 +466,4 @@ class DeviceController extends mfBaseController
return $data ?? [];
}
}
}

View File

@@ -136,6 +136,13 @@ class DeviceModel
}
}
if (array_key_exists("popIds", $filter)) {
$popids = $filter['popIds'];
if (is_array($popids)) {
$where .= " AND pop_id IN (" . implode(",", $popids) . ")";
}
}
//var_dump($filter, $where);exit;
return $where;
}

View File

@@ -146,6 +146,11 @@ class PopNetworkModel
$where .= " AND network_id=$networkid";
}
}
if (array_key_exists("Networks", $filter)) {
$Networks = $filter['Networks'];
$where .= " AND network_id IN (" . implode(",", $Networks) . ")";
}
//var_dump($filter, $where);exit;
return $where;

View File

@@ -528,8 +528,36 @@ class TimerecordingController extends mfBaseController
$workingHours[$workinghour->day] = $workingHours[$workinghour->day] + $whend - $whstart;
}
}
$workinghourshistory = TimerecordingEmployeeWorkingHourHistoryModel::search(['user_id' => $userid]);
if ($workinghourshistory) {
$workingHoursHistory[2147483500]=$workingHours;
foreach ($workinghourshistory as $workinghourhistory) {
$whenddate = $workinghourhistory->enddate;
$workinghourhistoryhours = json_decode($workinghourhistory->workinghours, true);
foreach ($workinghourhistoryhours as $workinghourhistoryhour) {
$whstart = strtotime(date('Y-m-d', time()) . " " . $workinghourhistoryhour['start'] . ":00");
$whend = strtotime(date('Y-m-d', time()) . " " . $workinghourhistoryhour['end'] . ":00");
if (!$workingHoursHistory[$whenddate][$workinghourhistoryhour['day']]) {
$workingHoursHistory[$whenddate][$workinghourhistoryhour['day']] = $whend - $whstart;
} else {
$workingHoursHistory[$whenddate][$workinghourhistoryhour['day']] = $workingHoursHistory[$whenddate][$workinghourhistoryhour['day']] + $whend - $whstart;
}
}
}
}
//check if holiday is already in the list
foreach ($holidayDays as $key => $holidayDay) {
if ($workingHoursHistory)
{
foreach ($workingHoursHistory as $whkey => $whdata) {
$whtimestamp = strtotime(date('Y-m-d 23:59:59', $whkey));
$timestamp = strtotime($key);
if ($whtimestamp >= $timestamp)
{
$workingHours = $whdata;
}
}
}
if (($realholiDay[$key])) {
} else if ($workingHours[date('w', strtotime($key))]) {
@@ -605,6 +633,7 @@ class TimerecordingController extends mfBaseController
if ($plushours_timestamp > 0) {
$return = $this->getTimerecordingsApi(5, null, null, null, $plushours_timestamp, $endtime, $userid);
// echo $return['is'] ." ". $return['must'].PHP_EOL;
$diffTime = $return['is'] - $return['must'];
$plushours_now = $plushours_now + $diffTime;
} else {
@@ -681,6 +710,25 @@ class TimerecordingController extends mfBaseController
$workingHours[$workinghour->day] = $workingHours[$workinghour->day] + $whend - $whstart;
}
}
$workinghourshistory = TimerecordingEmployeeWorkingHourHistoryModel::search(['user_id' => $userid]);
if ($workinghourshistory) {
$workingHoursHistory[2147483500]=$workingHours;
foreach ($workinghourshistory as $workinghourhistory) {
$whenddate = $workinghourhistory->enddate;
$workinghourhistoryhours = json_decode($workinghourhistory->workinghours, true);
foreach ($workinghourhistoryhours as $workinghourhistoryhour) {
$whstart = strtotime(date('Y-m-d', time()) . " " . $workinghourhistoryhour['start'] . ":00");
$whend = strtotime(date('Y-m-d', time()) . " " . $workinghourhistoryhour['end'] . ":00");
if (!$workingHoursHistory[$whenddate][$workinghourhistoryhour['day']]) {
$workingHoursHistory[$whenddate][$workinghourhistoryhour['day']] = $whend - $whstart;
} else {
$workingHoursHistory[$whenddate][$workinghourhistoryhour['day']] = $workingHoursHistory[$whenddate][$workinghourhistoryhour['day']] + $whend - $whstart;
}
}
}
}
foreach ($holidays as $holiday) {
$holiDay[date('Y-m-d', $holiday->timestamp)] = $holiday->description;
}
@@ -704,6 +752,18 @@ class TimerecordingController extends mfBaseController
}
$dDate = date('Y-m-d', $timestamp + $WintertimeCompensation);
$dDay = date('w', $timestamp + $WintertimeCompensation);
if ($workingHoursHistory)
{
foreach ($workingHoursHistory as $whkey => $whdata) {
$whtimestamp = strtotime(date('Y-m-d 23:59:59', $whkey));
if ($whtimestamp >= $timestamp)
{
unset($workingHours);
$workingHours = $whdata;
}
}
}
if (!$holiDay[$dDate] && $dDate >= date('Y-m-d', $startdate) && $dDate <= date('Y-m-d', $enddate)) {
$mustSeconds = $mustSeconds + $workingHours[$dDay];
} elseif ($holiDay[$dDate]) {
@@ -727,6 +787,18 @@ class TimerecordingController extends mfBaseController
}
$dDate = date('Y-m-d', $timestamp + $WintertimeCompensation);
$dDay = date('w', $timestamp + $WintertimeCompensation);
if ($workingHoursHistory)
{
foreach ($workingHoursHistory as $whkey => $whdata) {
$whtimestamp = strtotime(date('Y-m-d 23:59:59', $whkey));
if ($whtimestamp >= $timestamp)
{
unset($workingHours);
$workingHours = $whdata;
}
}
}
if (!$holiDay[$dDate] && $dDate >= date('Y-m-d', $startdate) && $dDate <= date('Y-m-d', $enddate)) {
$mustSeconds = $mustSeconds + $workingHours[$dDay];
} elseif ($holiDay[$dDate]) {
@@ -749,6 +821,18 @@ class TimerecordingController extends mfBaseController
}
$dDate = date('Y-m-d', $timestamp + $WintertimeCompensation);
$dDay = date('w', $timestamp + $WintertimeCompensation);
if ($workingHoursHistory)
{
foreach ($workingHoursHistory as $whkey => $whdata) {
$whtimestamp = strtotime(date('Y-m-d 23:59:59', $whkey));
if ($whtimestamp >= $timestamp)
{
unset($workingHours);
$workingHours = $whdata;
}
}
}
if (!$holiDay[$dDate] && $dDate >= date('Y-m-d', $startdate) && $dDate <= date('Y-m-d', $enddate)) {
$mustSeconds = $mustSeconds + $workingHours[$dDay];
} elseif ($holiDay[$dDate]) {
@@ -770,9 +854,23 @@ class TimerecordingController extends mfBaseController
if (date('I', $timestamp) == 0) {
$WintertimeCompensation = 3600;
}
if ($workingHoursHistory)
{
foreach ($workingHoursHistory as $whkey => $whdata) {
$whtimestamp = strtotime(date('Y-m-d 23:59:59', $whkey));
if ($whtimestamp >= $timestamp)
{
unset($workingHours);
$workingHours = $whdata;
// echo date('Y-m-d 23:59:59', $whkey).PHP_EOL;
}
}
}
$dDate = date('Y-m-d', $timestamp + $WintertimeCompensation);
$dDay = date('w', $timestamp + $WintertimeCompensation);
if (!$holiDay[$dDate]) {
$mustSeconds = $mustSeconds + $workingHours[$dDay];
}
@@ -828,6 +926,17 @@ class TimerecordingController extends mfBaseController
$savecounter = 0;
$sumdays = 0;
for ($i = $starttimecalc; $i <= $endtimecalc; $i = $i + 86400) {
if ($workingHoursHistory)
{
foreach ($workingHoursHistory as $whkey => $whdata) {
$whtimestamp = strtotime(date('Y-m-d 23:59:59', $whkey));
if ($whtimestamp >= $i)
{
unset($workingHours);
$workingHours = $whdata;
}
}
}
$holidaycounter = $workingHours[date("w", $i)];
$daycheck = date("Y-m-d", $i);
if (!$holiDay[$daycheck]) {
@@ -877,6 +986,17 @@ class TimerecordingController extends mfBaseController
$savecounter = 0;
// echo $starttimecalc."<br>";
for ($i = $starttimecalc; $i <= $endtimecalc; $i = $i + 86400) {
if ($workingHoursHistory)
{
foreach ($workingHoursHistory as $whkey => $whdata) {
$whtimestamp = strtotime(date('Y-m-d 23:59:59', $whkey));
if ($whtimestamp >= $i)
{
unset($workingHours);
$workingHours = $whdata;
}
}
}
$holidaycounter = $workingHours[$timerecording->user_id][date("w", $i)];
$daycheck = date("Y-m-d", $i);
if (!$holiDay[$daycheck]) {

View File

@@ -16,6 +16,47 @@ class TimerecordingEmployeeController extends mfBaseController
}
}
protected function apiAction()
{
$do = $this->request->do;
$r = $this->request;
$data = [];
switch ($do) {
case "saveWorkingHours":
$userid = $r->userid;
$enddate = $r->enddate;
$workinghours = $r->workingHours;
$data = [];
$data['user_id'] = $userid;
$data['enddate'] = strtotime($enddate) + 10800;
$data['workinghours'] = json_encode($workinghours);
$timerecordingemployeesworkinghoursHistory = TimerecordingEmployeeWorkingHourHistoryModel::create($data);
$timerecordingemployeesworkinghoursHistory->save();
break;
case "deleteWorkingHours":
$id = $r->id;
$timerecordingemployeesworkinghoursHistory = new TimerecordingEmployeeWorkingHourHistory($id);
if (!$timerecordingemployeesworkinghoursHistory->id || $timerecordingemployeesworkinghoursHistory->id != $id) {
$data = ["status" => "error"];
$this->returnJson($data);
}
$timerecordingemployeesworkinghoursHistory->delete();
break;
default:
$data = ["status" => "error"];
$this->returnJson($data);
}
if (!is_array($return) || !count($return)) {
$data = ["status" => "error"];
$this->returnJson($data);
}
$data['status'] = "OK";
$data['result'] = $return;
$this->returnJson($data);
}
protected function indexAction()
{
@@ -56,6 +97,8 @@ class TimerecordingEmployeeController extends mfBaseController
$this->layout()->set("timerecordingemployees", $timerecordingemployees);
}
$timerecordingworkinghourshistory = $this->generateWorkingHoursHistory($userid);
$this->layout()->set("timerecordingworkinghourshistory", $timerecordingworkinghourshistory);
$timerecordinguser = UserModel::search(['worker_id' => $userid]);
$this->layout()->set("timerecordinguser", $timerecordinguser);
return $this->addAction();
@@ -202,6 +245,8 @@ class TimerecordingEmployeeController extends mfBaseController
} else if ($mode = "add") {
$this->layout()->setFlash("Personaladministration erfolgreich angelegt", "success");
}
$employee = new TimerecordingController();
$employee->updatePlushours($r->user_id);
$this->redirect("TimerecordingEmployee");
}
@@ -219,4 +264,28 @@ class TimerecordingEmployeeController extends mfBaseController
$this->redirect("TimerecordingEmployee");
}
protected function generateWorkingHoursHistory($userid)
{
$days_short = TimerecordingEmployeeWorkingHourModel::$days_short;
$TimerecordingEmployeeWorkingHourHistory = TimerecordingEmployeeWorkingHourHistoryModel::search(['user_id' => $userid]);
foreach ($TimerecordingEmployeeWorkingHourHistory as $key => $value) {
$workinghours = json_decode($value->workinghours, true);
$datetimetext = "";
$secondcounter = "";
foreach ($workinghours as $key2 => $data) {
$secondcounter = $secondcounter + strtotime(date("Y-m-d " . $data['end'] . ":00")) - strtotime(date("Y-m-d " . $data['start'] . ":00"));
$datetimetext .= $days_short[$data['day']] . " " . $data['start'] . " - " . $data['end'] . "<br>";
$datetimetext = TimerecordingEmployeeWorkingHourModel::cleardays($datetimetext);
}
$result[$value->enddate]['workinghours'] = $datetimetext;
$result[$value->enddate]['workingtime'] = $secondcounter;
$result[$value->enddate]['id'] = $value->id;
}
$employee = new TimerecordingController();
$employee->updatePlushours($userid);
return $result;
}
}

View File

@@ -0,0 +1,60 @@
<?php
class TimerecordingEmployeeWorkingHourHistory extends mfBaseModel
{
private $editor;
private $creator;
private $user;
public function getProperty($name)
{
if ($this->$name == null) {
if (!$this->id) {
return null;
}
if ($name == "creator") {
$this->creator = mfValuecache::singleton()->get("Worker-id-" . $this->create_by);
if ($this->creator === null) {
$this->creator = new User($this->create_by);
if ($this->creator->id) {
mfValuecache::singleton()->set("Worker-id-" . $this->create_by, $this->creator);
}
}
return $this->creator;
}
if ($name == "editor") {
$this->editor = mfValuecache::singleton()->get("Worker-id-" . $this->edit_by);
if ($this->editor === null) {
$this->editor = new User($this->edit_by);
if ($this->editor->id) {
mfValuecache::singleton()->set("Worker-id-" . $this->edit_by, $this->editor);
}
}
return $this->editor;
}
$classname = ucfirst($name);
$idfield = $name . "_id";
$this->$name = mfValuecache::singleton()->get("mfObjectmodel-$name-" . $this->$idfield);
if (!$this->$name) {
$this->$name = new $classname($this->$idfield);
}
if ($this->$name->id) {
mfValuecache::singleton()->set("mfObjectmodel-$name-" . $this->$name->id, $this->$name);
return $this->$name;
} else {
return null;
}
}
return $this->$name;
}
}

View File

@@ -0,0 +1,131 @@
<?php
class TimerecordingEmployeeWorkingHourHistoryController extends mfBaseController
{
protected function init()
{
$this->needlogin = true;
$me = new User();
$me->loadMe();
$this->me = $me;
$this->layout()->set("me", $me);
if (!$me->is(["Admin"])) {
$this->redirect("Dashboard");
}
}
protected function indexAction()
{
$this->layout()->setTemplate("TimerecordingEmployeeWorkingHourHistory/Index");
$timerecordingemployeeworkinghourhistorys = TimerecordingEmployeeWorkingHourHistoryModel::getAll();
$this->layout()->set("timerecordingemployeeworkinghourhistorys", $timerecordingemployeeworkinghourhistorys);
}
protected function addAction()
{
$users=UserModel::getAll();
$this->layout()->set("users", $users);
$this->layout()->setTemplate("TimerecordingEmployeeWorkingHourHistory/Form");
}
protected function editAction()
{
$id = $this->request->id;
if (!is_numeric($id) || !$id) {
$this->layout()->setFlash("tt History nicht gefunden", "error");
$this->redirect("TimerecordingEmployeeWorkingHourHistory");
}
$timerecordingemployeeworkinghourhistorys = new TimerecordingEmployeeWorkingHourHistory($id);
if ($timerecordingemployeeworkinghourhistorys->id != $id) {
$this->layout()->setFlash("tt History nicht gefunden", "error");
$this->redirect("TimerecordingEmployeeWorkingHourHistory");
}
$this->layout()->set("timerecordingemployeeworkinghourhistorys", $timerecordingemployeeworkinghourhistorys);
return $this->addAction();
}
protected function saveAction()
{
$r = $this->request;
$id = $r->id;
//var_dump($r->get());exit;
if (is_numeric($id) && $id > 0) {
$mode = "edit";
$timerecordingemployeeworkinghourhistorys = new TimerecordingEmployeeWorkingHourHistory($id);
if (!$timerecordingemployeeworkinghourhistorys->id) {
$this->layout()->setFlash("tt History nicht gefunden", "error");
$this->redirect("TimerecordingEmployeeWorkingHourHistory");
}
} else {
$mode = "add";
}
$data = [];
$data['user_id'] = trim($r->user_id);
$data['end'] = trim($r->end);
$data['times'] = trim($r->times);
if (!$data['user_id']) {
$data['user_id']=NULL;
}
if (!$data['end']) {
$data['end']=NULL;
}
if (!$data['times']) {
$data['times']=NULL;
}
// var_dump($_FILES);
// var_dump($upload);
// exit;
if ($mode == "edit") {
$timerecordingemployeeworkinghourhistorys->update($data);
} else {
$timerecordingemployeeworkinghourhistorys = TimerecordingEmployeeWorkingHourHistoryModel::create($data);
}
// var_dump($filestore);
// exit;
$id = $timerecordingemployeeworkinghourhistorys->save();
if (!$id) {
$this->layout()->setFlash("tt History konnte nicht angelegt werden", "error");
$this->redirect("TimerecordingEmployeeWorkingHourHistory");
}
if ($mode == "edit") {
$this->layout()->setFlash("tt History erfolgreich geändert", "success");
} else if ($mode = "add") {
$this->layout()->setFlash("tt History erfolgreich angelegt", "success");
}
$this->redirect("TimerecordingEmployeeWorkingHourHistory");
}
protected function deleteAction()
{
$id = $this->request->id;
$timerecordingemployeeworkinghourhistorys = new TimerecordingEmployeeWorkingHourHistory($id);
if (!$timerecordingemployeeworkinghourhistorys->id || $timerecordingemployeeworkinghourhistorys->id != $id) {
$this->layout()->setFlash("tt History nicht gefunden.", "error");
$this->redirect("TimerecordingEmployeeWorkingHourHistory");
}
$timerecordingemployeeworkinghourhistorys->delete();
$this->redirect("TimerecordingEmployeeWorkingHourHistory");
}
}

View File

@@ -0,0 +1,128 @@
<?php
class TimerecordingEmployeeWorkingHourHistoryModel
{
private $user_id;
private $enddate;
private $workinghours;
public static function find($data)
{
}
public static function create(array $data)
{
$model = new TimerecordingEmployeeWorkingHourHistory();
foreach ($data as $field => $value) {
if (property_exists(get_called_class(), $field)) {
if (substr($field, 0, 5) == "vlan_" && !$value) {
$model->$field = null;
continue;
}
$model->$field = $value;
}
}
$me = mfValuecache::singleton()->get("me");
if (!$me) {
$me = new User();
$me->loadMe();
mfValuecache::singleton()->set("me", $me);
}
if ($model->create_by === null) {
$model->create_by = $me->id;
}
if ($model->edit_by === null) {
$model->edit_by = $me->id;
}
return $model;
}
public static function getOne($id)
{
if (!is_numeric($id) || !$id) {
throw new Exception("Invalid number", 400);
}
$item = [];
$db = FronkDB::singleton();
$res = $db->select("TimerecordingEmployeeWorkingHourHistory", "*", "id=$id LIMIT 1");
if ($db->num_rows($res)) {
$data = $db->fetch_object($res);
$item = new TimerecordingEmployeeWorkingHourHistory($data);
}
return $item;
}
public static function getAll()
{
$items = [];
$db = FronkDB::singleton();
$res = $db->select("TimerecordingEmployeeWorkingHourHistory", "*", "1=1");
if ($db->num_rows($res)) {
while ($data = $db->fetch_object($res)) {
$items[] = new TimerecordingEmployeeWorkingHourHistory($data);
}
}
return $items;
}
public static function getFirst()
{
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$res = $db->select("TimerecordingEmployeeWorkingHourHistory", "*", "$where ");
if ($db->num_rows($res)) {
$data = $db->fetch_object($res);
$item = new TimerecordingEmployeeWorkingHourHistory($data);
if ($item->id) {
return $item;
} else {
return null;
}
}
return null;
}
public static function search($filter)
{
$items = [];
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$res = $db->select("TimerecordingEmployeeWorkingHourHistory", "*", "$where ORDER by enddate DESC");
if ($db->num_rows($res)) {
while ($data = $db->fetch_object($res)) {
$items[] = new TimerecordingEmployeeWorkingHourHistory($data);
}
}
return $items;
}
private static function getSqlFilter($filter)
{
$where = "1=1 ";
//var_dump($filter);exit;
if (array_key_exists("user_id", $filter)) {
$userid = $filter['user_id'];
if (is_numeric($userid)) {
$where .= " AND user_id=$userid";
}
}
//var_dump($filter, $where);exit;
return $where;
}
}

View File

@@ -95,6 +95,8 @@ class TimerecordingReportController extends mfBaseController
$workingHours[$workinghour->user_id][$workinghour->day] = $workingHours[$workinghour->user_id][$workinghour->day] + $whend - $whstart;
}
}
foreach ($this->holidays as $holiday) {
$holiDay[date('Y-m-d', $holiday->timestamp)] = $holiday->timestamp;
}
@@ -556,13 +558,32 @@ class TimerecordingReportController extends mfBaseController
$workingHours[$workinghour->day] = $workingHours[$workinghour->day] + $whend - $whstart;
}
}
$workinghourshistory = TimerecordingEmployeeWorkingHourHistoryModel::search(['user_id' => $user_id]);
if ($workinghourshistory) {
$workingHoursHistory[9732489200]=$workingHours;
foreach ($workinghourshistory as $workinghourhistory) {
$whenddate = $workinghourhistory->enddate;
$workinghourhistoryhours = json_decode($workinghourhistory->workinghours, true);
foreach ($workinghourhistoryhours as $workinghourhistoryhour) {
$whstart = strtotime(date('Y-m-d', time()) . " " . $workinghourhistoryhour['start'] . ":00");
$whend = strtotime(date('Y-m-d', time()) . " " . $workinghourhistoryhour['end'] . ":00");
if (!$workingHoursHistory[$whenddate][$workinghourhistoryhour['day']]) {
$workingHoursHistory[$whenddate][$workinghourhistoryhour['day']] = $whend - $whstart;
} else {
$workingHoursHistory[$whenddate][$workinghourhistoryhour['day']] = $workingHoursHistory[$whenddate][$workinghourhistoryhour['day']] + $whend - $whstart;
}
}
}
}
foreach ($this->holidays as $holiday) {
$holiDay[date('Y-m-d', $holiday->timestamp)] = $holiday->timestamp;
}
if ($datatype == 1) {
$kw = date('W', $dataweek);
$year = date(o, $dataweek);
$year = date('o', $dataweek);
$timestamp_montag = strtotime("{$year}-W{$kw}");
$timestamp_sonntag = strtotime("{$year}-W{$kw}-7");
$firstdate = strtotime(date("Y-m-d", $timestamp_montag) . " 00:00:00");
@@ -579,6 +600,16 @@ class TimerecordingReportController extends mfBaseController
}
$dDate = date('Y-m-d', $timestamp + $WintertimeCompensation);
$dDay = date('w', $timestamp + $WintertimeCompensation);
if ($workingHoursHistory)
{
foreach ($workingHoursHistory as $whkey => $whdata) {
$whtimestamp = strtotime(date('Y-m-d 23:59:59', $whkey));
if ($whtimestamp >= $timestamp)
{
$workingHours = $whdata;
}
}
}
if (!$holiDay[$dDate] && $dDate >= date('Y-m-d', $startdate) && $dDate <= date('Y-m-d', $enddate)) {
$mustSeconds = $mustSeconds + $workingHours[$dDay];
@@ -605,6 +636,17 @@ class TimerecordingReportController extends mfBaseController
}
$dDate = date('Y-m-d', $timestamp + $WintertimeCompensation);
$dDay = date('w', $timestamp + $WintertimeCompensation);
if ($workingHoursHistory)
{
foreach ($workingHoursHistory as $whkey => $whdata) {
$whtimestamp = strtotime(date('Y-m-d 23:59:59', $whkey));
if ($whtimestamp >= $timestamp)
{
$workingHours = $whdata;
}
}
}
if (!$holiDay[$dDate] && $dDate >= date('Y-m-d', $startdate) && $dDate <= date('Y-m-d', $enddate)) {
$mustSeconds = $mustSeconds + $workingHours[$dDay];
}
@@ -628,6 +670,16 @@ class TimerecordingReportController extends mfBaseController
}
$dDate = date('Y-m-d', $timestamp + $WintertimeCompensation);
$dDay = date('w', $timestamp + $WintertimeCompensation);
if ($workingHoursHistory)
{
foreach ($workingHoursHistory as $whkey => $whdata) {
$whtimestamp = strtotime(date('Y-m-d 23:59:59', $whkey));
if ($whtimestamp >= $timestamp)
{
$workingHours = $whdata;
}
}
}
if (!$holiDay[$dDate]) {
$mustSeconds = $mustSeconds + $workingHours[$dDay];
}
@@ -756,6 +808,16 @@ class TimerecordingReportController extends mfBaseController
$sumdays = 0;
for ($i = $starttimecalc; $i <= $endtimecalc; $i = $i + 86400) {
if ($workingHoursHistory)
{
foreach ($workingHoursHistory as $whkey => $whdata) {
$whtimestamp = strtotime(date('Y-m-d 23:59:59', $whkey));
if ($whtimestamp >= $i)
{
$workingHours = $whdata;
}
}
}
$holidaycounter = $workingHours[date("w", $i)];
$daycheck = date("Y-m-d", $i);
if (!$holiDay[$daycheck]) {
@@ -812,6 +874,16 @@ class TimerecordingReportController extends mfBaseController
$summcounter = 0;
$savecounter = 0;
for ($i = $starttimecalc; $i <= $endtimecalc; $i = $i + 86400) {
if ($workingHoursHistory)
{
foreach ($workingHoursHistory as $whkey => $whdata) {
$whtimestamp = strtotime(date('Y-m-d 23:59:59', $whkey));
if ($whtimestamp >= $i)
{
$workingHours = $whdata;
}
}
}
$holidaycounter = $workingHours[date("w", $i)];
$isSeconds = $isSeconds + $holidaycounter;
$summcounter = $summcounter + $holidaycounter;

View File

@@ -24,6 +24,8 @@ class WarehouseArticleController extends TTCrud {
['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center', 'priority' => 8]]
];
protected array $autocompleteColumns = ['articleNumber', 'title', 'description', 'category'];
protected array $additionalActions = [
['key' => 'openHistory','title' => 'Historie','class' => 'fas fa-history text-secondary'],
['key' => 'editDistributorEntries','title' => 'Lieferanten','class' => 'fas fa-truck text-cyan'],

View File

@@ -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]]);
}
}

View File

@@ -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',
@@ -338,6 +336,7 @@ class WarehouseEShopOrderController extends TTCrud {
$id = WarehouseEShopOrderModel::create(['status' => 'new',
'extRef' => $json['extRef'],
'deliveryMode' => $json['deliveryMode'],
'deliveryAddressAdditional' => $json['deliveryAddressAdditional'],
'deliveryAddressName' => $json['deliveryAddressName'],
'deliveryAddressLine' => $json['deliveryAddressLine'],
'deliveryAddressPLZ' => $json['deliveryAddressPLZ'],

View File

@@ -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,

View File

@@ -3,7 +3,7 @@
/**
* @property mixed|null $name
*/
class WarehouseOrderItem extends mfBaseModel
class WarehouseOffer extends mfBaseModel
{
}

View File

@@ -0,0 +1,56 @@
<?php
class WarehouseOfferController extends TTCrud {
protected string $headerTitle = 'Angebote';
protected bool $createText = false;
protected array $columns = [
['key' => 'id', 'text' => 'ID', 'modal' => false],
['key' => 'offerNumber', 'text' => 'Angebotsnummer', 'required' => true, 'modal' => false],
['key' => 'customerNumber', 'text' => 'Kundennummer', 'required' => true, 'modal' => false],
['key' => 'customerName', 'text' => 'Kundenname', 'required' => true, 'modal' => false],
['key' => 'customerCity', 'text' => 'Stadt', 'required' => true, 'modal' => false],
['key' => 'customerVAT', 'text' => 'UID', 'required' => true, 'modal' => false],
['key' => 'editor', 'text' => 'Sachbearbeiter', 'required' => true, 'modal' => false],
['key' => 'totalAmount', 'text' => 'Gesamtbetrag', 'required' => true, 'modal' => false],
['key' => 'status', 'text' => 'Status', 'required' => true, 'modal' => ['type' => 'select']],
['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'],
['key' => 'sendOffer', 'title' => 'Angebot senden', 'class' => 'fas fa-paper-plane text-success']
];
protected array $infoMessages = [
'create' => 'Angebot wurde erfolgreich erstellt.',
'update' => 'Angebot wurde aktualisiert.',
'delete' => 'Angebot wurde gelöscht',
'noChanges' => 'Keine Änderungen',
'sent' => 'Angebot wurde erfolgreich gesendet',
];
protected function beforeCreate(): bool {
$currentCount = WarehouseOfferModel::count(['create' => ['from' => strtotime(date('Y-01-01'))]]);
$this->postData['offerNumber'] = 'AN' . date('Y') . '-' . str_pad($currentCount + 1, 4, '0', STR_PAD_LEFT);
return true;
}
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));
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Class WarehouseOfferModel
*
* Represents a warehouse offer with customer details and related metadata.
*
* @property int $id Unique identifier for the warehouse offer
* @property string $offerNumber Unique offer number
* @property string $customerNumber Customer number
* @property string $customerName Name of the customer
* @property string $customerStreet Street address of the customer
* @property string $customerCity City of the customer
* @property string $customerZip Postal code of the customer
* @property string $customerVAT VAT number of the customer
* @property int $editor ID of the editor who last modified the offer
* @property string $purpose Purpose of the offer
* @property string $positions Details about positions in the offer
* @property string $alternativePositions Details about alternative positions in the offer
* @property float $totalDiscount Total discount applied to the offer
* @property string $paymentTerms Payment terms for the offer
* @property string $deliveryTerms Delivery terms for the offer
* @property string $closingText Closing text for the offer
* @property string $notes Additional notes for the offer
* @property string $status Current status of the offer
* @property float $totalAmount Total amount of the offer
* @property int $create Timestamp of the offer creation
* @property int $createBy ID of the user who created the offer
*/
class WarehouseOfferModel extends TTCrudBaseModel {
public int $id;
public string $offerNumber;
public string $customerNumber;
public string $customerName;
public string $customerStreet;
public string $customerCity;
public string $customerZip;
public string $customerVAT;
public int $editor;
public string $purpose;
public string $positions;
public string $alternativePositions;
public float $totalDiscount;
public string $paymentTerms;
public string $deliveryTerms;
public string $closingText;
public string $notes;
public string $status;
public float $totalAmount;
public int $create;
public int $createBy;
}

View File

@@ -1,110 +1,159 @@
<?php
//TODO: enable switching distributors in the order preview
class WarehouseOrderController extends TTCrud {
protected string $headerTitle = 'Lieferantenbestellungen';
protected bool $createText = false;
protected array $permissionCheck = ['WarehouseAdmin'];
//@formatter:off
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' => 'id', 'text' => 'ID', 'modal' => false, 'table' => false],
['key' => 'orderNumber', 'text' => 'Bestellnummer', 'required' => true, 'modal' => false],
['key' => 'distributor', 'text' => 'Lieferant', 'required' => false, 'modal' => false, 'table' => ['filter' => false]],
['key' => 'delAddrCity', 'text' => 'Stadt', 'required' => true, 'modal' => false, 'table' => false],
['key' => 'delAddrEMail', 'text' => 'E-Mail', 'required' => true, 'modal' => false, 'table' => false],
['key' => 'delAddrLine', 'text' => 'Adresse', 'required' => true, 'modal' => false, 'table' => false],
['key' => 'delAddrName', 'text' => 'Name', 'required' => true, 'modal' => false, 'table' => false],
['key' => 'delAddrPLZ', 'text' => 'PLZ', 'required' => true, 'modal' => false, 'table' => false],
['key' => 'editor', 'text' => 'Bearbeiter', 'required' => true, 'modal' => ['type' => 'select'], 'table' => ['filter' => 'select']],
['key' => 'note', 'text' => 'Notiz', 'required' => true, 'modal' => false, 'table' => false],
['key' => 'sum', 'text' => 'Summe', 'required' => false, 'modal' => false, 'table' => ['class' => 'text-right']],
['key' => 'status', 'text' => 'Status', 'required' => false, 'modal' => ['type' => 'select', 'items' => []], 'table' => ['filter' => 'select']],
['key' => 'positions', 'text' => 'Positionen', 'required' => true, 'modal' => false, 'table' => false],
['key' => 'extReference', 'text' => 'Externe Referenz', 'required' => true, 'modal' => false],
['key' => 'createBy', 'text' => 'Erstellt von', 'required' => true, 'modal' => ['type' => 'select'], 'table' => ['filter' => 'select']],
['key' => 'create', 'text' => 'Erstellt', 'required' => true, 'modal' => false],
['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']],
];
protected array $additionalActions = [['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary']];
//@formatter:on
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) {
protected function prepareCrudConfig(): void {
$editorColumnIndex = array_search('editor', array_column($this->columns, 'key'));
$this->columns[$editorColumnIndex]['modal']['items'] = array_map(function ($user) {
return ['value' => intval($user->id), 'text' => $user->name];
}, UserModel::search());
}, UserModel::search(['employee' => true]));
$statusIndex = array_search('status', array_column($this->columns, 'key'));
$this->columns[$statusIndex]['modal']['items'] = [
['value' => 'new', 'text' => 'Neu'],
['value' => 'accepted', 'text' => 'Akzeptiert'],
['value' => 'ordered', 'text' => 'Bestellt'],
['value' => 'sent', 'text' => 'Versendet'],
['value' => 'partiallyDelivered', 'text' => 'Teilweise geliefert'],
['value' => 'fullyDelivered', 'text' => 'Geliefert'],
['value' => 'cancelled', 'text' => 'Storniert'],
];
}
protected function createOrderAction() {
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
protected function beforeCreate(): bool {
$this->postData['orderNumber'] = 'PO' . date('Y') . '-' . str_pad(WarehouseOrderModel::count(['create' => ['from' => strtotime(date('Y-01-01'))]]) + 1, 4, '0', STR_PAD_LEFT);
$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));
protected function getArticleDistributorDataAction() {
$articleId = $this->request->articleId;
if ($this->request->allDistributor === 'true') self::returnJson(array_map(fn($d) => ['id' => $d->id,
'name' => $d->name], WarehouseDistributorModel::getAll()));
else if (!empty($articleId)) self::returnJson(array_map(fn($d) => ['id' => $d->distributorId,
'name' => WarehouseDistributorModel::get($d->distributorId)->name,
'purchasePrice' => $d->purchasePrice,
'externalArticleNumber' => $d->externalArticleNumber], WarehouseArticleDistributorModel::getAll(['articleId' => $articleId])));
else self::returnJson([]);
}
protected function getByIdParse(array $order): array {
$order['positions'] = json_decode($order['positions'], true);
foreach ($order['positions'] as &$position) {
$position['distributorName'] = WarehouseDistributorModel::get($position['distributorId'])->name;
$position['articleName'] = WarehouseArticleModel::get($position['article'])->title;
}
return $order;
}
protected function createPDFAction() {
$order = (array) WarehouseOrderModel::get($this->request->id);
$order['positions'] = json_decode($order['positions'], true);
// check if all positions have the same distributor
$distributorId = $order['positions'][0]['distributorId'];
foreach ($order['positions'] as $key => $position) {
if ($position['distributorId'] !== $distributorId) {
self::returnJson(['error' => 'Die Bestellung enthält Positionen von verschiedenen Lieferanten.']);
}
// we need to get the article name and distributor name for the pdf
$position['distributorName'] = WarehouseDistributorModel::get($position['distributorId'])->name;
$position['articleName'] = WarehouseArticleModel::get($position['article'])->title;
$order['positions'][$key] = $position;
}
$pdf_vars = ['order' => $order,
'distributor' => WarehouseDistributorModel::get($distributorId),
"bank_iban" => TT_INVOICE_BANK_IBAN,
"bank_bic" => TT_INVOICE_BANK_BIC,
"bank_bank" => TT_INVOICE_BANK_BANK,
"bank_owner" => TT_INVOICE_BANK_OWNER];
$countryText = CountryModel::search(['id' => WarehouseDistributorModel::get($distributorId)->countryId])[0]->name;
$headerHtml = file_get_contents(BASEDIR . "/Layout/default/WarehouseOrder/PDF_HEADER.html");
$headerHtml = str_replace("{{ basedir }}", BASEDIR, $headerHtml);
$headerHtml = str_replace("{{ externalReference }}","<strong>Ihre Referenz:</strong> ". $order['extReference'], $headerHtml);
$headerHtml = str_replace("{{ addressLine_1 }}", WarehouseDistributorModel::get($distributorId)->name, $headerHtml);
$headerHtml = str_replace("{{ addressLine_2 }}", WarehouseDistributorModel::get($distributorId)->address, $headerHtml);
$headerHtml = str_replace("{{ addressLine_3 }}", WarehouseDistributorModel::get($distributorId)->plz . " " . WarehouseDistributorModel::get($distributorId)->city, $headerHtml);
$headerHtml = str_replace("{{ addressLine_4 }}", $countryText, $headerHtml);
$headerHtml = str_replace("{{ billingAddressLine_1 }}", "Xinon GmbH", $headerHtml);
$headerHtml = str_replace("{{ billingAddressLine_2 }}", "Fladnitz im Raabtal 150", $headerHtml);
$headerHtml = str_replace("{{ billingAddressLine_3 }}", "8322 Studenzen", $headerHtml);
$headerHtml = str_replace("{{ billingAddressLine_4 }}", "Österreich", $headerHtml);
$headerHtml = str_replace("{{ billingAddressLine_5 }}", "einkauf@xinon.at", $headerHtml);
$headerHtml = str_replace("{{ billingAddressLine_6 }}", "<strong>Referenz: ". $order["orderNumber"] . "</strong>", $headerHtml);
// if order dellAddrLine is Fladnitz im Raabtal 150 we need to set all template strings to empty
$chk = $order['delAddrLine'] == "Fladnitz im Raabtal 150";
$headerHtml = str_replace("{{ shippingAddressLine_1 }}", $chk ? "" : $order['delAddrName'], $headerHtml);
$headerHtml = str_replace("{{ shippingAddressLine_2 }}", $chk ? "" : $order['delAddrLine'], $headerHtml);
$headerHtml = str_replace("{{ shippingAddressLine_3 }}", $chk ? "" : $order['delAddrPLZ'] . " " . $order['delAddrCity'], $headerHtml);
$headerHtml = str_replace("{{ shippingAddressLine_4 }}", $chk ? "" : $order['delAddrEMail'], $headerHtml);
$headerFile = BASEDIR . "/var/temp/order_header-" . date("U") . "-" . rand(1000, 9999) . ".html";
file_put_contents($headerFile, $headerHtml);
$footerHtml = file_get_contents(BASEDIR . "/Layout/default/WarehouseOrder/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/order_footer-" . date("U") . "-" . rand(1000, 9999) . ".html";
file_put_contents($footerFile, $footerHtml);
$pdf = new PdfForm("WarehouseOrder/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);
}
}

View File

@@ -1,28 +1,40 @@
<?php
//TODO: fix phpdoc
//TODO: migration for extReference
/**
* @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
* 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 $extReference External reference number
* @property int $distributorId ID of the distributor associated with the order
* @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
*/
// id, distributorId, intRef, extRef, status, trackingNumber, create, createBy
class WarehouseOrderModel extends TTCrudBaseModel {
public int $id;
public string $orderNumber;
public string $extReference;
public int $distributorId;
public ?string $intRef;
public ?string $extRef;
public float $sum;
public string $status;
public ?string $trackingNumber;
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;
}
?>

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -0,0 +1,4 @@
<?php
class WarehouseProject extends mfBaseModel {
}

View File

@@ -0,0 +1,36 @@
<?php
class WarehouseProjectController extends TTCrud {
protected string $headerTitle = 'Projekte';
protected string $createText = 'Neues Projekt erstellen';
//@formatter:off
protected array $columns = [
['key' => 'title', 'text' => 'Titel', 'required' => true, 'table' => ['class' => 'text-nowrap', 'priority' => 9]],
['key' => 'description', 'text' => 'Beschreibung', 'required' => true, 'table' => ['class' => 'text-nowrap']],
['key' => 'createBy', 'text' => 'Erstellt von', 'required' => true, 'type' => 'select', 'table' => ['class' => 'text-nowrap', 'filter' => 'select'], 'modal' => ['items' => [], 'type' => 'select']],
['key' => 'create', 'text' => 'Erstellt am', 'required' => true, 'table' => ['filter' => 'date', 'class' => 'text-center']],
['key' => 'address', 'text' => 'Adresse', 'required' => true, 'type' => 'autocomplete', 'table' => ['class' => 'text-nowrap', 'filter' => false], 'modal' => ['apiUrl' => '/Address/api?do=findAddress', 'items' => '/Address/api?do=findAddress', 'type' => 'autocomplete']],
['key' => 'status', 'text' => 'Status', 'required' => true, 'table' => ['filter' => 'select'], 'modal' => [ 'type' => 'select', 'items' => [ ['value' => 'erstellt', 'text' => 'Erstellt'], ['value' => 'in_bearbeitung', 'text' => 'In Bearbeitung'], ['value' => 'erledigt', 'text' => 'Erledigt'], ['value' => 'verrechnet', 'text' => 'Verrechnet']]]]
];
protected array $additionalActions = [
];
protected array $infoMessages = [
'create' => 'Projekt wurde erstellt',
'update' => 'Projekt wurde aktualisiert',
'delete' => 'Projekt wurde gelöscht',
'noChanges' => 'Keine Änderungen',
];
//@formatter:on
public function prepareCrudConfig() {
$users = array_map(function($user) {
return ['value' => $user->id, 'text' => $user->name];
}, UserModel::search(['employee' => true]));
$this->columns[1]['modal']['items'] = $users;
}
}

View File

@@ -0,0 +1,15 @@
<?php
class WarehouseProjectModel extends TTCrudBaseModel {
public int $id;
public string $title;
public string $description;
public string $startDate;
public string $endDate;
public string $status;
public string $priority;
public int $assignedTo;
public int $createBy;
public int $create;
}

View 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();
}
}
}

View File

@@ -0,0 +1,21 @@
<?php declare(strict_types = 1);
use Phinx\Migration\AbstractMigration;
final class WarehouseModify11 extends AbstractMigration {
public function up(): void {
if ($this->getEnvironment() == "thetool") {
$table = $this->table("WarehouseOrder");
$table->addColumn("distributorId", "integer", ["null" => false]);
$table->save();;
}
}
public function down(): void {
if ($this->getEnvironment() == "thetool") {
$table = $this->table("WarehouseOrder");
$table->removeColumn("distributorId");
$table->save();;
}
}
}

View File

@@ -0,0 +1,21 @@
<?php declare(strict_types = 1);
use Phinx\Migration\AbstractMigration;
final class WarehouseModify12 extends AbstractMigration {
public function up(): void {
if ($this->getEnvironment() == "thetool") {
$table = $this->table("WarehouseOrder");
$table->addColumn("extReference", "string", ["length" => 255, "null" => true]);
$table->save();;
}
}
public function down(): void {
if ($this->getEnvironment() == "thetool") {
$table = $this->table("WarehouseOrder");
$table->removeColumn("extReference");
$table->save();;
}
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class TimerecordingEmployeeWorkingHourHistory extends AbstractMigration
{
public function up(): void
{
if($this->getEnvironment() == "thetool") {
$table = $this->table("TimerecordingEmployeeWorkingHourHistory", ["signed" => true]);
$table->addColumn("user_id", "integer", ["null" => false]);
$table->addColumn("enddate", "integer", ["null" => false, "limit" => 64]);
$table->addColumn("workinghours", "string", ["null" => true]);
$table->addColumn("create_by", "integer", ["null" => false]);
$table->addColumn("edit_by", "integer", ["null" => false]);
$table->addColumn("create", "integer", ["null" => false]);
$table->addColumn("edit", "integer", ["null" => false]);
$table->save();
}
if($this->getEnvironment() == "addressdb") {
}
}
public function down(): void
{
if($this->getEnvironment() == "thetool") {
$this->table("TimerecordingEmployeeWorkingHourHistory")->drop()->save();
}
if($this->getEnvironment() == "addressdb") {
}
}
}

View File

@@ -8,7 +8,7 @@ class Helper {
* @param string $columnName The name of the column in the database table.
* @return string The SQL condition generated based on the filter value and column name.
*/
public static function generateFilterCondition($filterValue, string $columnName, bool $exactMatch = false): string {
public static function generateFilterCondition($filterValue, $columnName, bool $exactMatch = false): string {
$sql = "";
if (is_array($filterValue)) {
@@ -30,6 +30,9 @@ class Helper {
} else if (!empty($filterValue)) {
if ($exactMatch) {
$sql .= " AND `$columnName` = '" . $filterValue . "'";
} else if (strpos($columnName, "|") !== false) {
foreach (explode(" ", $filterValue) as $item)
$sql .= " AND CONCAT(" . join(",", explode("|", $columnName)) . ") LIKE '%" . str_replace("%", "", $item) . "%'";
} else if ($filterValue[0] === "%") {
$sql .= " AND `$columnName` LIKE '" . $filterValue . "'";
} else if ($filterValue[strlen($filterValue) - 1] === "%") {

36
lib/SNOPP/SNOPP.php Normal file
View File

@@ -0,0 +1,36 @@
<?php
/**
* Represents the SNOPP API.
*/
class SNOPP {
/**
* SNOPP constructor.
*/
public function __construct() {}
/**
* Search for support tickets using the SNOPP API.
* @return array - The search results.
*/
function getSNOPPTickets(): array {
$SNOPP_API_URL = SNOPP_API_URL;
$SNOPP_API_KEY = SNOPP_API_KEY;
$ctx_opts = [
'http' => [
'method' => 'GET',
'header' => "X-Api-Key: $SNOPP_API_KEY\r\n",
]
];
$snopp_output = file_get_contents("$SNOPP_API_URL/ticket/find?provider_id=all&status=open", false, stream_context_create($ctx_opts));
$ticket_obj = json_decode($snopp_output);
$tickets = $ticket_obj->result->tickets;
return $tickets;
}
}
?>

View File

@@ -10,6 +10,8 @@
* @property array $additionalJSVariables
* @property array $infoMessages
* @property bool $onlyView
* @property array $defaultOrder
* @property array $autocompleteColumns
*/
class TTCrud extends mfBaseController {
public User $user;
@@ -25,11 +27,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");
}
@@ -57,35 +56,43 @@ class TTCrud extends mfBaseController {
return $checkArray;
}
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
*/
protected function getCrudConfig(): array {
$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]));
}
if (method_exists($this, 'prepareCrudConfig')) {
$this->prepareCrudConfig();
}
@@ -186,7 +193,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 +200,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 +218,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]]));
@@ -249,18 +255,20 @@ class TTCrud extends mfBaseController {
}
protected function autocompleteAction() {
$searchedID = $this->request->searchedID;
$textKey = property_exists($this->model, 'name') ? 'name' : 'title';
if (strlen($searchedID) > 0) {
$filter = ['id' => $searchedID];
if (strlen($this->request->searchedID) > 0) {
$filter = ['id' => $this->request->searchedID];
$data = $this->model::getAll($filter, 10);
} else {
$filter = [$textKey => $this->request->q . '%'];
$data = $this->model::getAll($filter, 10);
if (isset($this->autocompleteColumns) && is_array($this->autocompleteColumns)) {
$filterKey = join('|', $this->autocompleteColumns);
} else {
$filterKey = $textKey;
}
$data = [];
if (count($data) < 11) {
$filter = [$textKey => $this->request->q];
$filter = [$filterKey => '%' . $this->request->q . '%'];
$lazyData = $this->model::getAll($filter, 10);
$data = array_merge($data, $lazyData);
$data = array_unique($data, SORT_REGULAR);
@@ -268,7 +276,6 @@ class TTCrud extends mfBaseController {
}
}
self::returnJson(array_map(function ($item) use ($textKey) {
return ['value' => $item->id, 'text' => $item->$textKey];
}, $data));
@@ -283,6 +290,7 @@ class TTCrud extends mfBaseController {
}
$data = (array) $this->model::get($id);
if (method_exists($this, 'getByIdParse') && !isset($_GET['disableParse'])) $data = $this->getByIdParse($data);
self::returnJson($data);
}
}

View File

@@ -81,17 +81,13 @@ class TTCrudBaseModel {
}
public static function get($id, $die= false): TTCrudBaseModel {
public static function get($id): 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();
@@ -109,16 +105,16 @@ class TTCrudBaseModel {
return $result->fetch_assoc()['count'];
}
public static function getSQLFilter($filter): string {
if (empty($filter)) {
return "";
}
public static function getSQLFilter(array $filter): string {
if (empty($filter)) return '';
$sql = 'WHERE 1=1';
$calledClass = get_called_class();
$sql = "WHERE 1=1";
foreach ($filter as $key => $value) {
if (!property_exists(get_called_class(), $key)) {
http_response_code(500);
throw new Exception("Field $key does not exist in " . get_called_class());
foreach (explode('|', $key) as $column) {
if (!property_exists($calledClass, $column)) {
throw new InvalidArgumentException("Field $column does not exist in $calledClass");
}
}
$sql .= Helper::generateFilterCondition($value, $key, gettype($value) === "integer");
}

View File

@@ -58,14 +58,13 @@ class XinonProject {
* @param int $pageSize - The number of results to return.
* @return array - The search results.
*/
public function searchSupportTickets(string $search, int $pageSize = 25): array {
public function searchSupportTickets(string $search, int $pageSize = 25, $overrideQueryParams = null): array {
$curl = curl_init();
$baseUrl = 'https://project.xinon.at/api/v3/projects/10/work_packages';
$queryParams = [
'pageSize' => 25,
'filters' => json_encode([['search' => ['operator' => '**', 'values' => [$search]]]])
];
$queryParams = ['pageSize' => $pageSize, 'filters' => json_encode([['search' => ['operator' => '**', 'values' => [$search]]]])];
if (!is_null($overrideQueryParams)) $queryParams = $overrideQueryParams;
curl_setopt_array($curl, array(
CURLOPT_URL => $baseUrl . '?' . http_build_query($queryParams),

View File

@@ -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",
];

View File

@@ -0,0 +1,55 @@
Vue.component('AddressTickets', {
template: `
<tt-card>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="thead-light">
<tr>
<th>Kundennummer</th>
<th>Erstellt am</th>
<th>Betreff</th>
<th>Letztes Update</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<tr v-for="ticket in parsedTickets" :key="ticket.id">
<td>{{ ticket.customField7 }}</td>
<td>{{ formatDate(ticket.createdAt) }}</td>
<td>{{ ticket.subject }}</td>
<td>{{ formatDate(ticket.updatedAt) }}</td>
<td>
<a :href="'https://project.xinon.at' + ticket.activitiesHref.replace('api/v3', 'projects/storungen-and-support').replace('activities', 'activity')" class="btn btn-primary btn-sm" target="_blank">
<i class="fas fa-external-link-alt mr-1"></i>
Ticket anzeigen
</a>
</td>
</tr>
</tbody>
</table>
</div>
</tt-card>
`,
data() {
return {
window: window,
}
},
computed: {
parsedTickets() {
return this.window.TT_CONFIG.TICKETS.map(ticket => ({
id: ticket.id,
customField7: ticket.customField7,
createdAt: ticket.createdAt,
subject: ticket.subject,
updatedAt: ticket.updatedAt,
activitiesHref: ticket._links.activities.href
}));
}
},
methods: {
formatDate(dateString) {
return this.window.moment(dateString).format('DD.MM.YYYY HH:mm');
}
}
});

View File

@@ -409,7 +409,11 @@ document.addEventListener('DOMContentLoaded', function () {
$('#customer').html('<option></option>');
}
if (data.data.customer_info_reminder.customer_info_reminder) {
$('#customer-info-reminder-check').prop('checked', true);
if (data.data.customer_info_reminder.customer_info_reminder == 1) {
$('#customer-info-reminder-check').prop('checked', true);
} else {
$('#customer-info-reminder-check').prop('checked', false);
}
}
$('#customer').select2({
placeholder: "Kunden Suche",
@@ -454,8 +458,12 @@ document.addEventListener('DOMContentLoaded', function () {
} else if (obj.customer_info_type == 2) {
typeText = 'SMS';
}
let customer_info_text = "";
if (obj.customer_info_text) {
customer_info_text = obj.customer_info_text.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1<br />$2');
}
let title = `<div class="text-center font-weight-500">Letzte gesendete Info (` + typeText + `):</div>
<div class="text-left">` + obj.customer_info_text.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1<br />$2') + `</div>
<div class="text-left">` + customer_info_text + `</div>
<div class="text-left"><span class="font-weight-500">gesendet an:</span> ` + obj.customer_info_type_text + `</div>
<div class="text-left"><span class="font-weight-500">gesendet von:</span> ` + obj.sendby + `</div>
<div class="text-left"><span class="font-weight-500">gesendet am:</span> ` + germanDateTime + `</div>
@@ -1706,8 +1714,7 @@ $(document).ready(function () {
}
}
});
}
else if ($(this).val() == "5") {
} else if ($(this).val() == "5") {
$('.customer-div').hide();
$('.ticket-div').show();
$('#ticket').select2({

View File

@@ -1,41 +1,54 @@
const deviceManufacturerFilterOptions = window?.TT_CONFIG?.DEVICE_MANUFACTURERS.map(manufacturer => ({
value: manufacturer.name,
text: manufacturer.name,
text: manufacturer.name,
}));
const deviceTypeFilterOptions = window?.TT_CONFIG?.DEVICE_TYPES.map(type => ({
value: type.name,
text: type.name,
text: type.name,
}));
Vue.component('device-view-switch', {
//language=Vue
template: `
<div class="device-view-switch" style="margin-bottom: 10px">
<div v-if="!isOverflowing" class="button-group" style="display:grid; grid-template-columns: repeat(3, 1fr); gap: 10px; justify-content: center; align-items: center; text-align: center; width: 100%;">
<button @click="$emit('input', 'DeviceTable')" :class="{ 'active': value === 'DeviceTable' }" class="btn btn-primary">Devices</button>
<button v-show="window.TT_CONFIG['IS_ADMIN'] === '1'" @click="$emit('input', 'DeviceManufacturer')" :class="{ 'active': value === 'DeviceManufacturer' }" class="btn btn-primary">Hersteller</button>
<button v-show="window.TT_CONFIG['IS_ADMIN'] === '1'" @click="$emit('input', 'DeviceType')" :class="{ 'active': value === 'DeviceType' }" class="btn btn-primary">Geräte Typen</button>
</div>
<div v-else>
<div class="dropdown">
<button @click="showDropdown = !showDropdown"
class="btn btn-primary dropdown-toggle">Ansicht</button>
<div v-show="showDropdown" class="dropdown-menu show">
<a href="#" @click="$emit('input', 'DeviceTable'); showDropdown = false" class="dropdown-item">Devices</a>
<a v-show="window.TT_CONFIG['IS_ADMIN'] === '1'" href="#" @click="$emit('input', 'DeviceManufacturer'); showDropdown = false" class="dropdown-item">Hersteller</a>
<a v-show="window.TT_CONFIG['IS_ADMIN'] === '1'" href="#" @click="$emit('input', 'DeviceType'); showDropdown = false" class="dropdown-item">Geräte Typen</a>
</div>
</div>
</div>
</div>
`,
props: ['value'],
<div class="device-view-switch" style="margin-bottom: 10px">
<div v-if="!isOverflowing"
class="button-group"
style="display:grid; grid-template-columns: repeat(3, 1fr); gap: 10px; justify-content: center; align-items: center; text-align: center; width: 100%;">
<button @click="$emit('input', 'DeviceTable')" :class="{ 'active': value === 'DeviceTable' }" class="btn btn-primary">Devices</button>
<button @click="$emit('input', 'DeviceManufacturer')"
:class="{ 'active': value === 'DeviceManufacturer' }"
class="btn btn-primary">Hersteller
</button>
<button @click="$emit('input', 'DeviceType')"
:class="{ 'active': value === 'DeviceType' }"
class="btn btn-primary">Geräte Typen
</button>
</div>
<div v-else>
<div class="dropdown">
<button @click="showDropdown = !showDropdown"
class="btn btn-primary dropdown-toggle">Ansicht
</button>
<div v-show="showDropdown" class="dropdown-menu show">
<a href="#" @click="$emit('input', 'DeviceTable'); showDropdown = false" class="dropdown-item">Devices</a>
<a href="#"
@click="$emit('input', 'DeviceManufacturer'); showDropdown = false"
class="dropdown-item">Hersteller</a>
<a href="#"
@click="$emit('input', 'DeviceType'); showDropdown = false"
class="dropdown-item">Geräte Typen</a>
</div>
</div>
</div>
</div>
`,
props: ['value'],
data() {
return {
window: window,
window: window,
isOverflowing: false,
showDropdown: false,
showDropdown: false,
};
},
mounted() {
@@ -53,115 +66,142 @@ Vue.component('device-view-switch', {
})
Vue.component('DeviceTable', {
//language=Vue
template: `
<tt-table :data="window['TT_CONFIG']['DEVICES']" :config="DeviceTableConfig" excel-export>
//language=Vue
template: `
<tt-table :data="window['TT_CONFIG']['DEVICES']" :config="DeviceTableConfig" excel-export>
<template v-slot:top-buttons>
<button type="button" class="btn btn-primary" @click="window.location = window['TT_CONFIG']['BASE_URL'] + '/Device/add'">
<i class="fas fa-plus"></i>
Device hinzufügen
</button>
</template>
<template v-slot:top-buttons v-if="window['TT_CONFIG']['IS_ADMIN'] === '1'">
<button type="button" class="btn btn-primary" @click="window.location = window['TT_CONFIG']['BASE_URL'] + '/Device/add'">
<i class="fas fa-plus"></i>
Device hinzufügen
</button>
</template>
<template v-slot:name="{ row }">
<a target="_blank" :href="window['TT_CONFIG']['BASE_URL'] +'/Device/Detail?id=' + row.id">{{row.name}}</a>
</template>
<template v-slot:name="{ row }">
<a target="_blank" :href="window['TT_CONFIG']['BASE_URL'] +'/Device/Detail?id=' + row.id">{{row.name}}</a>
</template>
<template v-slot:locationtext="{ row }">
<a target="_blank" :href="row['locationUrl']">{{row.locationText}}</a>
</template>
<template v-slot:locationtext="{ row }">
<template v-if="window['TT_CONFIG']['IS_ADMIN'] === '1'">
<a target="_blank" :href="row['locationUrl']">{{row.locationText}}</a>
</template>
<template v-else>
{{row.locationText}}
</template>
</template>
<template v-slot:actions="{ row }">
<a :href="window['TT_CONFIG']['BASE_URL'] +'/Device/edit/?id=' + row.id"><i class="far fa-edit" title="Bearbeiten"></i></a>
<a :href="window['TT_CONFIG']['BASE_URL'] +'/Device/delete/?id=' + row.id" onclick="if(!confirm('Device wirklich löschen?')) return false;" class="text-danger" title="Löschen"><i class="fas fa-trash "></i></a>
<template v-if="row.zabbix_host_id !== '0' && row.zabbix_host_id !== null">
<a :href="window['TT_CONFIG']['ZABBIX_URL'] + '/zabbix.php?action=latest.view&hostids%5B%5D=' + row.zabbix_host_id" target="_blank" class="text-info" title="Zabbix"><i class="fas fa-server"></i></a>
<a :href="window['TT_CONFIG']['BASE_URL'] + '/Graphing?id=' + row.zabbix_host_id + '&hostname=' + row.name" target="_blank" class="text-info" title="Graphen"><i class="fas fa-chart-line"></i></a>
</template>
</template>
<template v-slot:actions="{ row }">
<a :href="window['TT_CONFIG']['BASE_URL'] +'/Device/edit/?id=' + row.id"><i class="far fa-edit" title="Bearbeiten"></i></a>
<a :href="window['TT_CONFIG']['BASE_URL'] +'/Device/delete/?id=' + row.id"
onclick="if(!confirm('Device wirklich löschen?')) return false;"
class="text-danger"
title="Löschen"><i class="fas fa-trash "></i></a>
</tt-table>
`,
<template v-if="row.zabbix_host_id !== '0' && row.zabbix_host_id !== null">
<a :href="window['TT_CONFIG']['ZABBIX_URL'] + '/zabbix.php?action=latest.view&hostids%5B%5D=' + row.zabbix_host_id"
target="_blank"
class="text-info"
title="Zabbix"><i class="fas fa-server"></i></a>
<a :href="window['TT_CONFIG']['BASE_URL'] + '/Graphing?id=' + row.zabbix_host_id + '&hostname=' + row.name"
target="_blank"
class="text-info"
title="Graphen"><i class="fas fa-chart-line"></i></a>
</template>
</template>
</tt-table>
`,
data() {
return {
window: window,
DeviceTableConfig: {
key: 'DeviceTable',
tableHeader: 'Device-Liste',
defaultPageSize: 25,
headers: [
{ text: 'Device Name', key: 'name', sortable: true, class: 'text-nowrap', priority: 10 },
{ text: 'Hersteller', key: 'devicemanufactor', filter: 'select',class: 'text-nowrap text-center', filterOptions: deviceManufacturerFilterOptions },
{ text: 'Geräte Typ', key: 'devicetype', filter: 'select', class: 'text-nowrap text-center', filterOptions: deviceTypeFilterOptions , priority: 7},
{ text: 'Pop/Adresse', key: 'locationText', filter: 'search' , class: 'text-nowrap text-center'},
{ text: 'IP-Adresse', key: 'ip', filter: 'search',class: 'text-center', priority: 8},
{ text: 'Mac-Adresse', key: 'mac', filter: 'search',class: 'text-center' },
{ text: 'Seriennummer', key: 'serial', filter: 'search',class: 'text-center' },
{ text: 'Preis', key: 'price', filter: 'numberRange',class: 'text-center', suffix: ' €' },
{ text: 'max. Leistung', key: 'power', filter: 'numberRange',class: 'text-center', suffix: ' W' },
{
text: 'Backup',
key: 'backup',
filter: 'iconSelect',
filterOptions: [
{ value: 'ok', text: 'Configbackup aktuell', icon: 'fa-regular fa-circle-check text-success' },
{ value: 'auto', text: 'Configbackup aktuell', icon: 'fa-regular fa-circle-a text-success' },
{ value: 'aged', text: 'Letztes Configbackup älter als 48 Stunden', icon: 'fa-regular fa-circle-xmark' },
{ value: 'na', text: 'N/A', icon: 'fa-regular fa-ban text-warning' }
],
priority: 6,
sortable: false,
}, {
text: 'Status',
key: 'zabbix_online',
filter: 'iconSelect',
filterOptions: [
{ value: 0, text: 'Status unbekannt', icon: 'fa-regular fa-circle-xmark' },
{ value: 1, text: 'Offline', icon: 'fa-regular fa-circle-xmark text-danger' },
{ value: 2, text: 'Online', icon: 'fa-regular fa-circle-check text-success' },
],
priority: 5,
sortable: false,
},
{text: 'Aktionen', key: 'actions', class: 'text-center', sortable: false, filter: false, priority: 9},
],
},
}
return {
window: window,
DeviceTableConfig: {
key: 'DeviceTable',
tableHeader: 'Device-Liste',
defaultPageSize: 25,
headers: [],
},
}
},
mounted() {
this.DeviceTableConfig.headers = [
{text: 'Device Name', key: 'name', sortable: true, class: 'text-nowrap', priority: 10},
{text: 'Hersteller', key: 'devicemanufactor', filter: 'select', class: 'text-nowrap text-center', filterOptions: deviceManufacturerFilterOptions},
{text: 'Geräte Typ', key: 'devicetype', filter: 'select', class: 'text-nowrap text-center', filterOptions: deviceTypeFilterOptions, priority: 7},
{text: 'Pop/Adresse', key: 'locationText', filter: 'search', class: 'text-nowrap text-center'},
{text: 'IP-Adresse', key: 'ip', filter: 'search', class: 'text-center', priority: 8},
{text: 'Mac-Adresse', key: 'mac', filter: 'search', class: 'text-center'},
{text: 'Seriennummer', key: 'serial', filter: 'search', class: 'text-center'},
];
if (this.window.TT_CONFIG['IS_ADMIN'] === '1') {
this.DeviceTableConfig.headers = [
...this.DeviceTableConfig.headers,
{text: 'Preis', key: 'price', filter: 'numberRange', class: 'text-center', suffix: ' €'},
{text: 'max. Leistung', key: 'power', filter: 'numberRange', class: 'text-center', suffix: ' W'},
{
text: 'Backup',
key: 'backup',
filter: 'iconSelect',
filterOptions: [
{value: 'ok', text: 'Configbackup aktuell', icon: 'fa-regular fa-circle-check text-success'},
{value: 'auto', text: 'Configbackup aktuell', icon: 'fa-regular fa-circle-a text-success'},
{value: 'aged', text: 'Letztes Configbackup älter als 48 Stunden', icon: 'fa-regular fa-circle-xmark'},
{value: 'na', text: 'N/A', icon: 'fa-regular fa-ban text-warning'}
],
priority: 6,
sortable: false,
}, {
text: 'Status',
key: 'zabbix_online',
filter: 'iconSelect',
filterOptions: [
{value: 0, text: 'Status unbekannt', icon: 'fa-regular fa-circle-xmark'},
{value: 1, text: 'Offline', icon: 'fa-regular fa-circle-xmark text-danger'},
{value: 2, text: 'Online', icon: 'fa-regular fa-circle-check text-success'},
],
priority: 5,
sortable: false,
},
{text: 'Aktionen', key: 'actions', class: 'text-center', sortable: false, filter: false, priority: 9},
]
}
}
});
Vue.component('DeviceManufacturer', {
//language=Vue
template: `
<tt-table :data="window['TT_CONFIG']['DEVICE_MANUFACTURERS']" :config="DeviceManufacturerConfig" excel-export>
<tt-table :data="window['TT_CONFIG']['DEVICE_MANUFACTURERS']" :config="DeviceManufacturerConfig" excel-export>
<template v-slot:top-buttons>
<button type="button" class="btn btn-primary" @click="window.location = window['TT_CONFIG']['BASE_URL'] + '/Devicemanufactor/add'">
<i class="fas fa-plus"></i>Hersteller hinzufügen
</button>
</template>
<template v-slot:top-buttons>
<button type="button" class="btn btn-primary" @click="window.location = window['TT_CONFIG']['BASE_URL'] + '/Devicemanufactor/add'">
<i class="fas fa-plus"></i>Hersteller hinzufügen
</button>
</template>
<template v-slot:actions="{ row }">
<a :href="window['TT_CONFIG']['BASE_URL'] +'/Devicemanufactor/edit/?id=' + row.id"><i class="far fa-edit" title="Bearbeiten"></i></a>
<a :href="window['TT_CONFIG']['BASE_URL'] +'/Devicemanufactor/delete/?id=' + row.id" onclick="if(!confirm('Hersteller wirklich löschen?')) return false;" class="text-danger" title="Löschen"><i class="fas fa-trash "></i></a>
</template>
<template v-slot:actions="{ row }">
<a :href="window['TT_CONFIG']['BASE_URL'] +'/Devicemanufactor/edit/?id=' + row.id"><i class="far fa-edit" title="Bearbeiten"></i></a>
<a :href="window['TT_CONFIG']['BASE_URL'] +'/Devicemanufactor/delete/?id=' + row.id"
onclick="if(!confirm('Hersteller wirklich löschen?')) return false;"
class="text-danger"
title="Löschen"><i class="fas fa-trash "></i></a>
</template>
</tt-table>
`,
</tt-table>
`,
data() {
return {
window: window,
window: window,
DeviceManufacturerConfig: {
key: 'DeviceManufacturer',
tableHeader: 'Hersteller',
key: 'DeviceManufacturer',
tableHeader: 'Hersteller',
defaultPageSize: 25,
headers: [
{ text: 'Name', key: 'name', sortable: true, class: 'text-nowrap', priority: 10 },
{ text: 'Erstellungsdatum', key: 'created', filter: 'date', class: 'text-center'},
{ text: 'Erstellt von', key: 'creator', filter: 'search', class: 'text-center'},
{ text: 'Aktionen', key: 'actions', class: 'text-center', sortable: false, filter: false, priority: 9},
headers: [
{text: 'Name', key: 'name', sortable: true, class: 'text-nowrap', priority: 10},
{text: 'Erstellungsdatum', key: 'created', filter: 'date', class: 'text-center'},
{text: 'Erstellt von', key: 'creator', filter: 'search', class: 'text-center'},
{text: 'Aktionen', key: 'actions', class: 'text-center', sortable: false, filter: false, priority: 9},
],
},
}
@@ -171,36 +211,39 @@ Vue.component('DeviceManufacturer', {
Vue.component('DeviceType', {
//language=Vue
template: `
<tt-table :data="window['TT_CONFIG']['DEVICE_TYPES']" :config="DeviceTypeConfig" excel-export>
<template v-slot:top-buttons>
<button type="button" class="btn btn-primary" @click="window.location = window['TT_CONFIG']['BASE_URL'] + '/Devicetype/add'">
<i class="fas fa-plus"></i>Device Type hinzufügen
</button>
</template>
<template v-slot:actions="{ row }">
<a :href="window['TT_CONFIG']['BASE_URL'] +'/Devicetype/edit/?id=' + row.id"><i class="far fa-edit" title="Bearbeiten"></i></a>
<a :href="window['TT_CONFIG']['BASE_URL'] +'/Devicetype/delete/?id=' + row.id" onclick="if(!confirm('Gerätetyp wirklich löschen?')) return false;" class="text-danger" title="Löschen"><i class="fas fa-trash "></i></a>
</template>
</tt-table>
`,
<tt-table :data="window['TT_CONFIG']['DEVICE_TYPES']" :config="DeviceTypeConfig" excel-export>
<template v-slot:top-buttons>
<button type="button" class="btn btn-primary" @click="window.location = window['TT_CONFIG']['BASE_URL'] + '/Devicetype/add'">
<i class="fas fa-plus"></i>Device Type hinzufügen
</button>
</template>
<template v-slot:actions="{ row }">
<a :href="window['TT_CONFIG']['BASE_URL'] +'/Devicetype/edit/?id=' + row.id"><i class="far fa-edit" title="Bearbeiten"></i></a>
<a :href="window['TT_CONFIG']['BASE_URL'] +'/Devicetype/delete/?id=' + row.id"
onclick="if(!confirm('Gerätetyp wirklich löschen?')) return false;"
class="text-danger"
title="Löschen"><i class="fas fa-trash "></i></a>
</template>
</tt-table>
`,
data() {
return {
window: window,
return {
window: window,
DeviceTypeConfig: {
key: 'DeviceType',
tableHeader: 'Gerätetypen',
key: 'DeviceType',
tableHeader: 'Gerätetypen',
defaultPageSize: 25,
headers: [
{ text: 'Name', key: 'name', sortable: true, class: 'text-nowrap', priority: 10 },
{ text: 'Hersteller', key: 'manufacturer', filter: 'search', class: 'text-center'},
{ text: 'Preis', key: 'price', filter: 'numberRange', class: 'text-center', suffix: ' €' },
{ text: 'max. Leistung', key: 'power', filter: 'numberRange', class: 'text-center', suffix: ' W' },
{ text: 'Erstellungsdatum', key: 'created', filter: 'date', class: 'text-center'},
{ text: 'Erstellt von', key: 'creator', filter: 'search', class: 'text-center'},
{ text: 'Aktionen', key: 'actions', class: 'text-center', sortable: false, filter: false, priority: 9},
headers: [
{text: 'Name', key: 'name', sortable: true, class: 'text-nowrap', priority: 10},
{text: 'Hersteller', key: 'manufacturer', filter: 'search', class: 'text-center'},
{text: 'Preis', key: 'price', filter: 'numberRange', class: 'text-center', suffix: ' €'},
{text: 'max. Leistung', key: 'power', filter: 'numberRange', class: 'text-center', suffix: ' W'},
{text: 'Erstellungsdatum', key: 'created', filter: 'date', class: 'text-center'},
{text: 'Erstellt von', key: 'creator', filter: 'search', class: 'text-center'},
{text: 'Aktionen', key: 'actions', class: 'text-center', sortable: false, filter: false, priority: 9},
],
},
}
@@ -210,15 +253,15 @@ Vue.component('DeviceType', {
Vue.component('Device', {
//language=Vue
template: `
<tt-card>
<device-view-switch v-model="currentView"></device-view-switch>
<component :is="currentView"></component>
</tt-card>
`,
<tt-card>
<device-view-switch v-show="window['TT_CONFIG']['IS_ADMIN'] === '1'" v-model="currentView"></device-view-switch>
<component :is="currentView"></component>
</tt-card>
`,
data() {
return {
window: window,
window: window,
currentView: 'DeviceTable',
};
},

View File

@@ -19,7 +19,7 @@
.filters {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
grid-gap: 15px;
margin-bottom: 20px;
justify-content: center;

View File

@@ -273,6 +273,45 @@ Vue.component('radius-ont-parser', {
}
});
Vue.component('radius-online-state', {
props: ['username'],
template: `
<div>
<template v-if="data === null">
<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>
<template v-else>
<div style="border-radius: 10px;width: 110px;text-align: center;height:22px;background-color: rgba(161,253,96,0.7)"
v-if="data.online">
<span>{{ data.ip }}</span>
</div>
<div style="border-radius: 10px;width: 110px;text-align: center;height:22px;background-color: rgba(253,96,96,0.7)"
v-else>
<span>{{ data.ip }}</span>
</div>
</template>
</div>
`,
data() {
return {
data: null,
};
},
async created() {
await this.fetchOnlineState();
},
methods: {
async fetchOnlineState() {
const response = await fetch(window.TT_CONFIG['BASE_PATH'] + '/Radius/proxyUnsecureHTTPRequestToRadius?action2=fetchRadacct&username=' + this.username);
if (response.ok) this.data = await response.json();
}
}
})
Vue.component('radius', {
template: `
@@ -292,6 +331,7 @@ Vue.component('radius', {
<tt-autocomplete sm :api-url="billAddrAutoCompleteUrl" label="Rechnungsadresse" v-model="custnum" ref="billAddr"/>
<tt-input sm label="Username" name="username" v-model="username"/>
<tt-input sm label="Info" name="info" v-model="info"/>
<tt-checkbox v-model="checkOnlineState" label="Online-Status abfragen" sm/>
<tt-button sm icon="fas fa-search" text="Suchen" additional-class="btn-primary" style="justify-self:center" @click="loadRadiusUsers"/>
</div>
@@ -301,6 +341,7 @@ Vue.component('radius', {
<th>Kundennummer</th>
<th>Username</th>
<th>Info</th>
<th>Status</th>
<th>Aktionen</th>
</tr>
</thead>
@@ -317,6 +358,11 @@ Vue.component('radius', {
</a>
</td>
<td>{{ user.info }}</td>
<td>
<template v-if="checkOnlineState === 1">
<radius-online-state :username="user.username"/>
</template>
</td>
<td>
<tt-button sm icon="fas fa-sync" text="Details" @click="fetchRadacctData(user.username)" additional-class="btn-primary"/>
</td>
@@ -419,10 +465,12 @@ Vue.component('radius', {
custnum: '',
window: window,
showRadacctModal: false,
checkOnlineState: 0,
radacctData: null,
}
},
async mounted() {
console.log("hallo");
await this.loadFreeUsers();
},
methods: {
@@ -442,7 +490,11 @@ Vue.component('radius', {
});
const response = await fetch(`${window.TT_CONFIG['BASE_PATH']}/Radius/proxyUnsecureHTTPRequestToRadius?${params.toString()}`);
if (response.ok) {
this.radiusUsers = await response.json();
const users = await response.json()
if (users.length < 6) {
this.checkOnlineState = 1;
}
this.radiusUsers = users;
} else {
console.error('Failed to load radius users');
}

View File

@@ -0,0 +1,14 @@
@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;
}
}

View File

@@ -0,0 +1,178 @@
Vue.component('warehouse-offer-modal', {
props: {
id: {type: [String, Number], required: true},
mode: {type: String, default: 'edit'}
},
template: `
<tt-modal :show="true"
@submit="submit"
:delete="id !== 'create'"
:title="id === 'create' ? 'Angebot erstellen' : \`Angebot #\${id} bearbeiten\`"
@update:show="$emit('close')">
<div style="width: 99%"><h4 class="text-center">Angebotdetails</h4>
<tt-select label="Sachbearbeiter"
:options="window.TT_CONFIG.CRUD_CONFIG.columns.find(column => column.key === 'createBy')?.modal.items"
sm
row
v-model="offer.editor"/>
<tt-input label="Kundennummer" v-model="offer.customerNumber" sm row/>
<tt-input label="Kundenreferenz" v-model="offer.reference" sm row/>
<tt-textarea label="Angebotszweck" v-model="offer.purpose" sm row/>
<hr>
<h4 class="text-center">Kundenadresse</h4>
<div style="display: grid; grid-gap: 10px; grid-template-columns: 2fr 2fr 1fr 1fr 2fr;">
<tt-input label="Name" v-model="offer.customerName" sm/>
<tt-input label="Straße" v-model="offer.customerStreet" sm/>
<tt-input label="PLZ" v-model="offer.customerZip" sm/>
<tt-input label="Ort" v-model="offer.customerCity" sm/>
<tt-input label="UID" v-model="offer.customerVAT" sm/>
</div>
<hr>
<h4 class="text-center">Positionen</h4>
<tt-positions-manager ref="positionsManager" v-model="offer.positions" :config="positionsConfig" @updateField-article="fetchArticleData"/>
<hr>
<h4 class="text-center">Alternative Artikel</h4>
<tt-positions-manager ref="alternativePositionsManager" v-model="offer.alternativePositions" :config="alternativePositionsConfig"/>
<hr>
<tt-input label="Gesamtrabatt (%)" v-model="offer.totalDiscount" sm row type="number"/>
<tt-select label="Zahlungskonditionen" :options="paymentTerms" sm row v-model="offer.paymentTerms"/>
<tt-select label="Lieferkonditionen" :options="deliveryTerms" sm row v-model="offer.deliveryTerms"/>
<tt-select label="Schlusstext" :options="closingTexts" sm row v-model="offer.closingText"/>
<hr>
<tt-textarea label="Notizen" v-model="offer.notes" sm row/>
</div>
</tt-modal>
`,
data() {
return {
window: window,
positionsConfig: {
fields: {
article: {
type: 'autocomplete',
label: 'Artikel',
apiUrl: '/WarehouseArticle/autoComplete',
customFieldReference: 'WarehouseArticle',
},
amount: {type: 'input', label: 'Menge', inputType: 'number'},
unit: {type: 'input', label: 'Einheit'},
articleNumber: {type: 'input', label: 'Artikelnummer'},
unitPrice: {type: 'input', label: 'Einzelpreis', inputType: 'number'},
discount: {type: 'input', label: 'Rabatt (%)', inputType: 'number'},
},
validateForm: (formData) => {
const requiredFields = ['article', 'amount', 'unitPrice'];
for (const field of requiredFields) {
if (!formData[field]) {
window.notify('error', `Bitte füllen Sie ${this.positionsConfig.fields[field].label} aus`);
return false;
}
}
return true;
},
},
alternativePositionsConfig: {
fields: {
article: {type: 'input', label: 'Artikel'},
description: {type: 'textarea', label: 'Beschreibung'},
},
},
paymentTerms: [
{value: 'net30', text: '30 Tage netto'},
{value: 'net60', text: '60 Tage netto'},
{value: 'immediate', text: 'Sofort fällig'},
],
deliveryTerms: [
{value: 'ex_works', text: 'Ab Werk'},
{value: 'free_delivery', text: 'Frei Haus'},
{value: 'fob', text: 'FOB'},
],
closingTexts: [
{value: 'standard', text: 'Standardtext'},
{value: 'custom1', text: 'Angepasster Text 1'},
{value: 'custom2', text: 'Angepasster Text 2'},
],
offer: {
editor: window.TT_CONFIG['USER_ID'],
customerNumber: '',
reference: '',
purpose: '',
customerName: '',
customerStreet: '',
customerZip: '',
customerCity: '',
customerVAT: '',
positions: [],
alternativePositions: [],
totalDiscount: 0,
paymentTerms: 'net30',
deliveryTerms: 'ex_works',
closingText: 'standard',
notes: '',
}
}
},
async mounted() {
if (this.id !== 'create') {
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOffer/getById`, {params: {id: this.id}});
this.offer = response.data;
this.offer.positions = JSON.parse(this.offer.positions);
this.offer.alternativePositions = JSON.parse(this.offer.alternativePositions);
}
},
methods: {
async submit() {
if (this.offer.positions.length === 0) return window.notify('error', 'Bitte fügen Sie mindestens eine Position hinzu.');
const url = this.id === 'create'
? `${window.TT_CONFIG["BASE_PATH"]}/WarehouseOffer/create`
: `${window.TT_CONFIG["BASE_PATH"]}/WarehouseOffer/update`;
const response = await axios.post(url, this.offer);
if (response.data.success) {
window.notify('success', response.data.message ?? 'Angebot erfolgreich gespeichert');
this.$emit('close');
} else {
window.notify('error',
response.data.errors ? Object.values(response.data.errors).join('<br>') : response.data.message || 'Ein Fehler ist aufgetreten');
}
},
async fetchArticleData(article) {
if (typeof article === 'number') {
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseArticle/getById`, {params: {id: article}});
this.$refs.positionsManager.updateField('articleNumber', response.data.articleNumber);
this.$refs.positionsManager.updateField('unitPrice',
Object.values(JSON.parse(response.data.cheapestSellPrice)).find(price => price.title === 'Verkauf').price);
this.$refs.positionsManager.updateField('unit', response.data.unit);
}
},
},
});
Vue.component('warehouse-offer', {
template: `
<tt-card>
<warehouse-offer-modal v-if="offerModalId" :id="offerModalId" @close="offerModalId = null;$refs.table.$refs.table.refreshTable()"/>
<button @click="offerModalId = 'create'" class="btn btn-primary">Angebot erstellen</button>
<tt-table-crud emit-edit @edit="offerModalId = $event.id" ref="table">
<template v-slot:expandedRow="{ row }">
<div>
<h5>Notizen</h5>
<p>{{ row.notes }}</p>
<h5>Verlauf</h5>
<ul>
<li v-for="entry in row.journal">{{ entry.date }} - {{ entry.description }}</li>
</ul>
</div>
</template>
</tt-table-crud>
</tt-card>
`,
data() {
return {
window: window,
offerModalId: null,
}
},
});

View File

@@ -0,0 +1,52 @@
.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;
}
}
/* Expanded Row Styling */
.order-summary {
padding: 1rem;
}
.position-item {
margin-bottom: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
}
.position-header {
background-color: #f0f0f0;
padding: 0.5rem;
font-weight: bold;
}
.position-details {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
padding: 0.5rem;
}
.field-item {
margin-bottom: 0.5rem;
}

View File

@@ -1,70 +1,246 @@
// noinspection JSUnusedLocalSymbols
Vue.component('warehouse-order', {
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"/>
<tt-input label="Externe Referenz" v-model="order.extReference" sm row/>
<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: {
extReference: '',
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') return;
console.log(this.id);
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getById?disableParse`, {params: {id: this.id}});
response.data.positions = JSON.parse(response.data.positions);
this.order = response.data;
},
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) {
this.$emit('close');
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) {
this.$emit('close');
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');
}
},
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-detail', {
//language=Vue
template: `
<tt-card>
<tt-table-crud @openHistory="historyModal = true; historyModalId = $event.id"
ref="table">
<template v-slot:header><h4>Bestellungsdetails für #{{ loading ? 'Laden...' : order.orderNumber }}</h4></template>
<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>
<template v-if="loading">
<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>
<template v-else>
<h3>Lieferadresse</h3>
<div>{{order.delAddrName}}</div>
<div>{{order.delAddrEMail}}</div>
<div>{{order.delAddrLine}}</div>
<div>{{order.delAddrPLZ}} {{order.delAddrCity}}</div>
<div style="display: grid; grid-gap: 10px; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;margin-top: 24px">
<div><strong>Artikel</strong></div>
<div><strong>Menge</strong></div>
<div><strong>Preis</strong></div>
<div><strong>Lieferant</strong></div>
<div><strong>Verwendung</strong></div>
<div><strong>Summe</strong></div>
</div>
<div style="display: grid; grid-gap: 10px; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;" v-for="position in order.positions">
<div>{{ position.articleName }}</div>
<div>{{ position.amount }}</div>
<div>{{ position.buyPrice }}</div>
<div>{{ position.distributorName }}</div>
<div>{{ position.verwendung }}</div>
<div>{{ position.amount * position.buyPrice }}</div>
</div>
</template>
</tt-card>
`,
props: {
id: {type: [String, Number], required: true}
},
data() {
return {
window: window,
order: {},
loading: true
}
},
async mounted() {
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getById`, {params: {id: this.id}});
this.order = response.data;
this.loading = false;
},
});
Vue.component('warehouse-order', {
template: `
<tt-card>
<warehouse-order-modal v-if="orderModalId" :id="orderModalId" @close="closeOrderModal"/>
<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 }">
<warehouse-order-detail :id="row['id']"/>
</template>
<template v-slot:sum="{ row }">{{ calculateSum(JSON.parse(row["positions"])).toFixed(2)}} €</template>
<!-- TODO: think of a way here prob we add it to the database as field-->
<template v-slot:distributor="{ row }">{{ row.id % 2 == 0 ? 'Triotronik' : 'Discomp' }}</template>
</tt-table-crud>
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
</tt-card>
`, data() {
`,
data() {
return {
window: window, historyModal: false, historyModalId: null, observer: null, orderLazyLoad: {},
orderModalId: null,
}
}, 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();
},
methods: {
closeOrderModal() {
this.orderModalId = null;
this.$refs.table.$refs.table.refreshTable();
},
calculateSum(positions) {
return positions.reduce((sum, position) => sum + position.amount * position.buyPrice, 0);
}
}, beforeDestroy() {
this.observer.disconnect();
}
})
});

View File

@@ -0,0 +1,14 @@
@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;
}
}

View File

@@ -0,0 +1,161 @@
Vue.component('warehouse-project-modal', {
props: {
id: { type: [String, Number], required: true },
mode: { type: String, default: 'edit' }
},
template: `
<tt-modal :show="true"
@submit="submit"
:delete="id !== 'create'"
:title="id === 'create' ? 'Projekt erstellen' : \`Projekt #\${id} bearbeiten\`"
@update:show="$emit('close')">
<div style="width: 99%">
<h4 class="text-center">Projektübersicht</h4>
<tt-input label="Projektnummer" v-model="project.projectNumber" sm row disabled />
<tt-textarea label="Um was handelt es sich?" v-model="project.description" sm row/>
<hr>
<h4 class="text-center">Zeitraum</h4>
<div style="display: grid; grid-gap: 10px; grid-template-columns: 1fr 1fr;">
<tt-date-picker label="Startdatum" v-model="project.startDate" sm/>
<tt-date-picker label="Enddatum" v-model="project.endDate" sm/>
</div>
<hr>
<h4 class="text-center">Beteiligte Personen</h4>
<tt-select label="Personen (XINON MT)"
:options="participantsOptions"
v-model="project.participants"
sm row />
<tt-textarea label="Freitext für weitere Personen" v-model="project.additionalParticipants" sm row/>
<hr>
<h4 class="text-center">Projektübersicht</h4>
<tt-input label="Gesamtsumme des Projekts (€)" v-model.number="project.totalSum" sm row type="number"/>
<tt-positions-manager
ref="positionsManager"
v-model="project.positions"
:config="positionsConfig"
@updateField-article="fetchArticleData"
/>
<hr>
<h4 class="text-center">Lagerort</h4>
<tt-input label="Lagerort für dieses Projekt" v-model="project.storageLocation" sm row/>
<hr>
<tt-textarea label="Notizen" v-model="project.notes" sm row/>
</div>
</tt-modal>
`,
data() {
return {
window: window,
participantsOptions: [
{ value: 1, text: 'Person A' },
{ value: 2, text: 'Person B' },
{ value: 3, text: 'Person C' }
// Add more participants as needed
],
positionsConfig: {
fields: {
article: {
type: 'autocomplete',
label: 'Artikel',
apiUrl: '/WarehouseArticle/autoComplete',
customFieldReference: 'WarehouseArticle',
},
hoursRequired: { type: 'input', label: 'Benötigte Stunden', inputType: 'number' },
amountRequired: { type: 'input', label: 'Benötigte Menge', inputType: 'number' },
description: { type: 'textarea', label: 'Beschreibung' }
},
validateForm(formData) {
const requiredFields = ['article', 'hoursRequired', 'amountRequired'];
for (const field of requiredFields) {
if (!formData[field]) {
window.notify('error', `Bitte füllen Sie ${this.positionsConfig.fields[field].label} aus`);
return false;
}
}
return true;
}
},
project: {
projectNumber: '',
description: '',
startDate: null,
endDate: null,
participants: [],
additionalParticipants: '',
totalSum: 0,
positions: [],
storageLocation: '',
notes: ''
}
};
},
async mounted() {
if (this.id !== 'create') {
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseProject/getById`, { params: { id: this.id } });
this.project = response.data;
} else {
this.project.projectNumber = await this.generateProjectNumber();
}
},
methods: {
async submit() {
if (!this.project.description) return window.notify('error', 'Bitte geben Sie eine Beschreibung ein.');
const url = this.id === 'create'
? `${window.TT_CONFIG["BASE_PATH"]}/WarehouseProject/create`
: `${window.TT_CONFIG["BASE_PATH"]}/WarehouseProject/update`;
const response = await axios.post(url, this.project);
if (response.data.success) {
window.notify('success', response.data.message ?? 'Projekt erfolgreich gespeichert');
this.$emit('close');
} else {
window.notify('error', response.data.errors ? Object.values(response.data.errors).join('<br>') : response.data.message || 'Ein Fehler ist aufgetreten');
}
},
async fetchArticleData(article) {
if (typeof article === 'number') {
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseArticle/getById`, { params: { id: article } });
this.$refs.positionsManager.updateField('description', response.data.description);
}
},
async generateProjectNumber() {
const currentCount = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseProject/count`);
return `PRJ-${new Date().getFullYear()}-${String(currentCount.data + 1).padStart(4, '0')}`;
}
}
});
Vue.component('warehouse-project', {
template: `
<tt-card>
<warehouse-project-modal v-if="projectModalId" :id="projectModalId" @close="projectModalId = null;$refs.table.$refs.table.refreshTable()"/>
<button @click="projectModalId = 'create'" class="btn btn-primary">Angebot erstellen</button>
<tt-table-crud emit-edit @edit="projectModalId = $event.id" ref="table">
<template v-slot:expandedRow="{ row }">
<div>
<h5>Notizen</h5>
<p>{{ row.notes }}</p>
<h5>Verlauf</h5>
<ul>
<li v-for="entry in row.journal">{{ entry.date }} - {{ entry.description }}</li>
</ul>
</div>
</template>
</tt-table-crud>
</tt-card>
`,
data() {
return {
window: window,
projectModalId: null,
}
},
});

View 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;
}

View File

@@ -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) {

View File

@@ -0,0 +1,170 @@
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-textarea
v-else-if="field.type === 'textarea'"
:label="field.label"
v-model="formData[key]"
@input="$emit('updateField-' + key, $event)"
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
}
}
});

View File

@@ -229,6 +229,7 @@ Vue.component('tt-table', {
.isValid() ? moment.unix(row[key]).format('DD.MM.YYYY HH:mm') : moment(row[key])
.format('DD.MM.YYYY HH:mm')) : ''
}}</span>
<span v-else-if="key === 'create'">{{ window.moment(row[key] * 1000).format('DD.MM.YYYY HH:mm:ss') }}</span>
<i v-else-if="column.filter === 'iconSelect'"
:title="columns[key].filterOptions.find(option => option.value.toString() === row[key].toString())?.text"
:class="columns[key].filterOptions.find(option => option.value.toString() === row[key].toString())?.icon"></i>