Merge branch 'master' into fronkdev
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
?>
|
||||
|
||||
|
||||
@@ -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"); ?>
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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())?>
|
||||
@@ -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">×</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"); ?>
|
||||
|
||||
@@ -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"); ?>
|
||||
|
||||
43
Layout/default/WarehouseOrder/PDF_FOOTER.html
Normal file
43
Layout/default/WarehouseOrder/PDF_FOOTER.html
Normal 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>
|
||||
92
Layout/default/WarehouseOrder/PDF_HEADER.html
Normal file
92
Layout/default/WarehouseOrder/PDF_HEADER.html
Normal 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>
|
||||
133
Layout/default/WarehouseOrder/PDF_MAIN.php
Normal file
133
Layout/default/WarehouseOrder/PDF_MAIN.php
Normal 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>
|
||||
@@ -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
@@ -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'],
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ?? [];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// Hide Articles
|
||||
|
||||
|
||||
|
||||
class WarehouseEShopController extends TTCrud {
|
||||
protected string $headerTitle = 'Energie Steiermark Shop';
|
||||
protected bool $createText = false;
|
||||
@@ -12,11 +11,13 @@ class WarehouseEShopController extends TTCrud {
|
||||
protected array $columns = [
|
||||
['key' => 'title', 'text' => 'Artikel', 'priority' => 11],
|
||||
['key' => 'category', 'text' => 'Kategorie', 'table' => false],
|
||||
['key' => 'price', 'text' => 'Preis', 'table' => ['filter' => false,'sortable' => false,'class' => 'text-right']],
|
||||
['key' => 'amount', 'text' => 'Menge', 'table' => ['filter' => false,'sortable' => false,'class' => 'p-0 width-80'], 'priority' => 9],
|
||||
['key' => 'add', 'text' => 'Hinzufügen', 'table' => ['filter' => false,'sortable' => false, 'class' => 'width-120 text-center'], 'priority' => 5000]
|
||||
['key' => 'price', 'text' => 'Preis', 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-right']],
|
||||
['key' => 'amount', 'text' => 'Menge', 'table' => ['filter' => false, 'sortable' => false, 'class' => 'p-0 width-80'], 'priority' => 9],
|
||||
['key' => 'add', 'text' => 'Hinzufügen', 'table' => ['filter' => false, 'sortable' => false, 'class' => 'width-120 text-center'], 'priority' => 5000]
|
||||
];
|
||||
|
||||
protected array $permissionCheck = ['WarehouseEShop'];
|
||||
|
||||
protected array $infoMessages = [
|
||||
'create' => 'Not possible',
|
||||
'update' => 'Not possible',
|
||||
@@ -24,10 +25,6 @@ class WarehouseEShopController extends TTCrud {
|
||||
'noChanges' => 'Keine Änderungen',
|
||||
];
|
||||
|
||||
public function permissionCheck(): bool {
|
||||
return $this->user->can(["WarehouseEShop"]);
|
||||
}
|
||||
|
||||
protected function prepareCrudConfig() {
|
||||
if (!$this->user->can('WarehouseAdmin')) {
|
||||
$this->columns[2]['table'] = false;
|
||||
@@ -62,5 +59,6 @@ class WarehouseEShopController extends TTCrud {
|
||||
"total_pages" => ceil($filteredAvailable / $perPage),
|
||||
"per_page" => $perPage,
|
||||
"filtered_available" => $filteredAvailable,
|
||||
"total_rows" => $totalRows]]); }
|
||||
"total_rows" => $totalRows]]);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ class WarehouseEShopOrderController extends TTCrud {
|
||||
['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']]
|
||||
];
|
||||
|
||||
protected array $permissionCheck = ['WarehouseEShop'];
|
||||
|
||||
protected array $additionalActions = [
|
||||
['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary'],
|
||||
['key' => 'showTrackingHistory', 'title' => 'Tracking Historie', 'class' => 'fas fa-truck text-primary'],
|
||||
@@ -38,10 +40,6 @@ class WarehouseEShopOrderController extends TTCrud {
|
||||
];
|
||||
//@formatter:on
|
||||
|
||||
public function permissionCheck(): bool {
|
||||
return $this->user->can(["WarehouseEShop"]);
|
||||
}
|
||||
|
||||
protected function customRowsHandler($rows): array {
|
||||
$statusToText = [
|
||||
'new' => 'Neu',
|
||||
@@ -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'],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* @property mixed|null $name
|
||||
*/
|
||||
class WarehouseOrderItem extends mfBaseModel
|
||||
class WarehouseOffer extends mfBaseModel
|
||||
{
|
||||
|
||||
}
|
||||
56
application/WarehouseOffer/WarehouseOfferController.php
Normal file
56
application/WarehouseOffer/WarehouseOfferController.php
Normal 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));
|
||||
}
|
||||
}
|
||||
52
application/WarehouseOffer/WarehouseOfferModel.php
Normal file
52
application/WarehouseOffer/WarehouseOfferModel.php
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $orderId
|
||||
* @property int $articleId
|
||||
* @property int $quantity
|
||||
* @property int $price
|
||||
*/
|
||||
|
||||
class WarehouseOrderItemModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public int $orderId;
|
||||
public int $articleId;
|
||||
public int $quantity;
|
||||
public float $price;
|
||||
}
|
||||
@@ -41,7 +41,11 @@ class WarehouseOrderRequestController extends TTCrud {
|
||||
'table' => ['filter' => 'select'],
|
||||
'modal' => ['type' => 'select', 'items' => []]],
|
||||
['key' => 'warehouseLocation', 'text' => 'Lagerort', 'required' => false, 'type' => 'varchar'],
|
||||
['key' => 'canceled', 'text' => 'Storniert', 'required' => false, 'modal' => ['visible' => false, 'type' => 'select', 'items' => [['value' => 0, 'text' => 'Nein'], ['value' => 1, 'text' => 'Ja']]], 'table' => ['filter' => 'select']],
|
||||
['key' => 'canceled',
|
||||
'text' => 'Storniert',
|
||||
'required' => false,
|
||||
'modal' => ['visible' => false, 'type' => 'select', 'items' => [['value' => 0, 'text' => 'Nein'], ['value' => 1, 'text' => 'Ja']]],
|
||||
'table' => ['filter' => 'select']],
|
||||
['key' => 'note', 'text' => 'Notiz', 'required' => false, 'type' => 'textarea'],
|
||||
['key' => 'actions',
|
||||
'text' => 'Aktionen',
|
||||
@@ -50,6 +54,8 @@ class WarehouseOrderRequestController extends TTCrud {
|
||||
'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center']],
|
||||
];
|
||||
|
||||
protected array $permissionCheck = ['WarehouseUser'];
|
||||
|
||||
protected array $additionalActions = [['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary']];
|
||||
|
||||
protected array $infoMessages = ['create' => 'Bestellwunsch wurde erstellt.',
|
||||
@@ -59,10 +65,6 @@ class WarehouseOrderRequestController extends TTCrud {
|
||||
|
||||
protected array $additionalJSVariables = ['BASE_URL' => '/WarehouseOrderRequest', 'WAREHOUSE_ADMIN' => true];
|
||||
|
||||
public function permissionCheck(): bool {
|
||||
return $this->user->can(["WarehouseUser"]);
|
||||
}
|
||||
|
||||
protected function prepareCrudConfig() {
|
||||
// Fill Users in createBy column
|
||||
$userArray = array_map(function ($user) {
|
||||
@@ -96,10 +98,10 @@ class WarehouseOrderRequestController extends TTCrud {
|
||||
}
|
||||
|
||||
protected function customAutoCompleteWare($value) {
|
||||
if (!is_numeric($value)) return [ 'id' => $value, 'title' => $value];
|
||||
if (!is_numeric($value)) return ['id' => $value, 'title' => $value];
|
||||
|
||||
$article = WarehouseArticleModel::get(intval($value));
|
||||
return [ 'id' => $article->id, 'title' => $article->title ];
|
||||
return ['id' => $article->id, 'title' => $article->title];
|
||||
}
|
||||
|
||||
protected function beforeUpdate($postData): bool {
|
||||
|
||||
4
application/WarehouseProject/WarehouseProject.php
Normal file
4
application/WarehouseProject/WarehouseProject.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
class WarehouseProject extends mfBaseModel {
|
||||
}
|
||||
36
application/WarehouseProject/WarehouseProjectController.php
Normal file
36
application/WarehouseProject/WarehouseProjectController.php
Normal 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;
|
||||
}
|
||||
}
|
||||
15
application/WarehouseProject/WarehouseProjectModel.php
Normal file
15
application/WarehouseProject/WarehouseProjectModel.php
Normal 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;
|
||||
}
|
||||
57
db/migrations/20250131150000_warehouse_modify_10.php
Normal file
57
db/migrations/20250131150000_warehouse_modify_10.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class WarehouseModify10 extends AbstractMigration {
|
||||
public function up(): void {
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
// Drop the existing tables
|
||||
$this->table("WarehouseOrder")->drop()->save();
|
||||
$this->table("WarehouseOrderItem")->drop()->save();
|
||||
|
||||
// Create the new WarehouseOrder table
|
||||
$table = $this->table("WarehouseOrder", ["id" => false, "primary_key" => ["id"]]);
|
||||
$table->addColumn("id", "integer", ["identity" => true])
|
||||
->addColumn("orderNumber", "string", ["null" => false, "limit" => 255])
|
||||
->addColumn("delAddrCity", "string", ["null" => false, "limit" => 255])
|
||||
->addColumn("delAddrEMail", "string", ["null" => false, "limit" => 255])
|
||||
->addColumn("delAddrLine", "string", ["null" => false, "limit" => 255])
|
||||
->addColumn("delAddrName", "string", ["null" => false, "limit" => 255])
|
||||
->addColumn("delAddrPLZ", "string", ["null" => false, "limit" => 255])
|
||||
->addColumn("editor", "integer", ["null" => false])
|
||||
->addColumn("note", "text", ["null" => false])
|
||||
->addColumn("positions", "text", ["null" => false])
|
||||
->addColumn("create", "integer", ["null" => false])
|
||||
->addColumn("createBy", "integer", ["null" => false])
|
||||
->create();
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
$this->table("WarehouseOrder")->drop()->save();
|
||||
|
||||
$WarehouseOrder = $this->table("WarehouseOrder", ["signed" => true]);
|
||||
$WarehouseOrder
|
||||
->addColumn('distributorId', 'integer', ['null' => false])
|
||||
->addColumn('intRef', 'string', ['null' => true])
|
||||
->addColumn('extRef', 'string', ['null' => true])
|
||||
->addColumn('status', 'enum', ['values' => ['new', 'accepted', 'sent', 'done'], 'null' => false, 'default' => 'new'])
|
||||
->addColumn('sum', 'float', ['null' => true])
|
||||
->addColumn('trackingNumber', 'string', ['null' => true])
|
||||
->addColumn('create', 'integer', ['null' => false, 'default' => 1728541890])
|
||||
->addColumn('createBy', 'integer', ['null' => false, 'default' => 1])
|
||||
->create();
|
||||
|
||||
$WarehouseOrderItem = $this->table("WarehouseOrderItem", ["signed" => true]);
|
||||
$WarehouseOrderItem
|
||||
->addColumn('orderId', 'integer', ['null' => false])
|
||||
->addColumn('articleId', 'integer', ['null' => false])
|
||||
->addColumn('quantity', 'integer', ['null' => false])
|
||||
->addColumn('price', 'float', ['null' => false])
|
||||
->addForeignKey('orderId', 'WarehouseOrder', 'id')
|
||||
->addForeignKey('articleId', 'WarehouseArticle', 'id')
|
||||
->create();
|
||||
}
|
||||
}
|
||||
}
|
||||
21
db/migrations/20250131160000_warehouse_modify_11.php
Normal file
21
db/migrations/20250131160000_warehouse_modify_11.php
Normal 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();;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
db/migrations/20250204190000_warehouse_modify_12.php
Normal file
21
db/migrations/20250204190000_warehouse_modify_12.php
Normal 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();;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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") {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
36
lib/SNOPP/SNOPP.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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",
|
||||
];
|
||||
|
||||
|
||||
|
||||
55
public/js/pages/AddressTickets/AddressTickets.js
Normal file
55
public/js/pages/AddressTickets/AddressTickets.js
Normal 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');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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({
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
14
public/js/pages/WarehouseOffer/WarehouseOffer.css
Normal file
14
public/js/pages/WarehouseOffer/WarehouseOffer.css
Normal 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;
|
||||
}
|
||||
}
|
||||
178
public/js/pages/WarehouseOffer/WarehouseOffer.js
Normal file
178
public/js/pages/WarehouseOffer/WarehouseOffer.js
Normal 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,
|
||||
}
|
||||
},
|
||||
});
|
||||
52
public/js/pages/WarehouseOrder/WarehouseOrder.css
Normal file
52
public/js/pages/WarehouseOrder/WarehouseOrder.css
Normal 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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
14
public/js/pages/WarehouseProject/WarehouseProject.css
Normal file
14
public/js/pages/WarehouseProject/WarehouseProject.css
Normal 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;
|
||||
}
|
||||
}
|
||||
161
public/js/pages/WarehouseProject/WarehouseProject.js
Normal file
161
public/js/pages/WarehouseProject/WarehouseProject.js
Normal 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,
|
||||
}
|
||||
},
|
||||
});
|
||||
64
public/plugins/vue/tt-components/css/tt-position-manager.css
Normal file
64
public/plugins/vue/tt-components/css/tt-position-manager.css
Normal file
@@ -0,0 +1,64 @@
|
||||
.positions-manager {
|
||||
padding: 1rem;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.positions-manager .form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.positions-manager .form-container {
|
||||
display: flex;
|
||||
align-items: center; /* Vertically center */
|
||||
justify-content: flex-start;
|
||||
gap: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.positions-manager .form-container .button-wrapper {
|
||||
align-self: flex-end; /* Align button container at the bottom */
|
||||
}
|
||||
|
||||
.positions-manager .form-container [class*="tt-input"],
|
||||
.positions-manager .form-container [class*="tt-checkbox"] {
|
||||
flex: 1 1 200px; /* Flexible width with a minimum of 200px */
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.positions-manager .form-container .btn {
|
||||
align-self: flex-end; /* Align button at the bottom of the form area */
|
||||
}
|
||||
|
||||
.positions-manager table {
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.positions-manager table thead th {
|
||||
background-color: #f4f4f4;
|
||||
border-bottom: 2px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.positions-manager table th,
|
||||
.positions-manager table td {
|
||||
padding: 8px;
|
||||
vertical-align: middle; /* Vertically center text */
|
||||
}
|
||||
|
||||
.positions-manager table tbody tr:nth-child(even) {
|
||||
background-color: #f9f9f9; /* Alternate row color */
|
||||
}
|
||||
|
||||
.positions-manager table .btn {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.positions-manager .form-container .btn-sm,
|
||||
.positions-manager table .btn.btn-sm {
|
||||
padding: 0.3rem 0.6rem; /* Small button padding */
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ Vue.component('tt-autocomplete', {
|
||||
Einträge werden geladen...
|
||||
</li>
|
||||
|
||||
<template v-show="showSuggestions && displayingItems.length > 0 && isLoading !== true">
|
||||
<template v-show="showSuggestions && displayingItems.length && isLoading !== true">
|
||||
<li
|
||||
v-for="(item) in displayingItems.slice(0, 10)"
|
||||
:key="item.value"
|
||||
@@ -104,9 +104,15 @@ Vue.component('tt-autocomplete', {
|
||||
this.disableIDFetch = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue) {
|
||||
this.$emit('input', newValue);
|
||||
this.value = newValue;
|
||||
}
|
||||
if (oldValue && !newValue) {
|
||||
this.$emit('input', '');
|
||||
this.displayValue = '';
|
||||
}
|
||||
|
||||
|
||||
if (this.value && this.apiUrl) {
|
||||
@@ -120,14 +126,13 @@ Vue.component('tt-autocomplete', {
|
||||
const selectedItem = this.items.find(item => item.value === this.value);
|
||||
this.displayValue = selectedItem ? selectedItem.text : '';
|
||||
} else {
|
||||
this.$emit('input', '');
|
||||
if (this.returnText === false && !(typeof this.value === 'undefined' || this.value === '')) this.$emit('input', '');
|
||||
this.displayValue = this.displayValue.replace(this.oldDisplayValue, '');
|
||||
}
|
||||
},
|
||||
onInput(event) {
|
||||
this.displayValue = event.target.value;
|
||||
this.$emit('input', '');
|
||||
this.$emit('input', this.returnText ? this.displayValue : '');
|
||||
if (this.returnText) this.$emit('input', this.displayValue);
|
||||
this.fetchSuggestions();
|
||||
}, onFocus() {
|
||||
this.showSuggestions = true;
|
||||
@@ -136,10 +141,8 @@ Vue.component('tt-autocomplete', {
|
||||
this.showSuggestions = false;
|
||||
}, 200);
|
||||
}, fetchSuggestions() {
|
||||
if (this.displayValue.length < 3) {
|
||||
this.$set(this, 'displayingItems', []);
|
||||
return this.displayingItems = [];
|
||||
}
|
||||
this.$set(this, 'displayingItems', []);
|
||||
if (this.displayValue.length < 3) return;
|
||||
|
||||
if (!this.apiUrl) {
|
||||
|
||||
|
||||
170
public/plugins/vue/tt-components/tt-position-manager.js
Normal file
170
public/plugins/vue/tt-components/tt-position-manager.js
Normal 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
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user