Merge branch 'master' into 'fronkdev'

# Conflicts:
#   Layout/default/ConstructionConsent/Form.php
This commit is contained in:
Frank Schubert
2025-02-14 14:46:24 +00:00
24 changed files with 487 additions and 171 deletions

View File

@@ -34,6 +34,8 @@ endforeach;
type="text/css"/>
<link href="<?= self::getResourcePath() ?>css/pages/Calendar/View.css?<?= $git_merge_ts ?>" rel="stylesheet"
type="text/css"/>
<link href="<?= self::getResourcePath() ?>plugins/summernote/summernote-bs4.min.css" rel="stylesheet">
<script type="text/javascript"
src="<?= self::getResourcePath() ?>assets/js/calendar/rrule/rrule.min.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript"
@@ -645,7 +647,10 @@ endforeach;
</div>
<div id="output"></div>
<script type="text/javascript" src="<?= self::getResourcePath() ?>plugins/select2/js/i18n/de.js"></script>
<script type="text/javascript" src="<?= self::getResourcePath() ?>plugins/select2/js/i18n/de.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript" src="<?=self::getResourcePath()?>plugins/summernote/summernote-bs4.min.js?<?= $git_merge_ts ?>"></script>
<script type="text/javascript" src="<?=self::getResourcePath()?>plugins/summernote/lang/summernote-de-DE.min.js?<?= $git_merge_ts ?>"></script>
<script>
let requestUrl = "<?= self::getUrl("Calendar", "api", ['do' => 'getCalendarEvents']) ?>";
let requestEventUrl = "<?= self::getUrl("Calendar", "api", ['do' => 'getCalendarEvent']) ?>";
@@ -669,7 +674,7 @@ endforeach;
var birthdays = [];
<?php
$counter = 10000;
$counter = 1000000;
foreach ($timerecordingholidays as $timerecordingholiday) :?>
holiDays.push({
id: <?= $counter ?>,

View File

@@ -55,9 +55,9 @@
</div>
<div class="form-group row">
<label class="col-lg-2 col-form-label" for="name">Name</label>
<label class="col-lg-2 col-form-label" for="name">Name*</label>
<div class="col-lg-10">
<input type="text" class="form-control" name="name" id="name" value="<?=(isset($item)) ? $item->name : ""?>" placeholder="z.B. Straße oder Adresse" />
<input type="text" class="form-control" name="name" id="name" value="<?=(isset($item)) ? $item->name : ""?>" placeholder="z.B. Straße oder Adresse" data-toggle="popover" data-trigger="focus" data-placement="left" data-content="Wird in der Zustimmungserklärung in der Objektanschrift verwendet." />
</div>
</div>
@@ -404,6 +404,22 @@
$("#consent_plan_image").change();
}
});
$(document).ready(function(){
$('[data-toggle="popover"]').popover();
});
</script>
<style>
.popover {
background-color: #ffff99;
}
.popover .arrow::after {
border-top-color: #ffff99;
}
</style>
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/footer.php"); ?>

View File

@@ -112,6 +112,7 @@ $pagination_entity_name = "Zustimmungserklärungen";
<th>Name</th>
<th>Objekttyp</th>
<th>Objektadresse</th>
<th>GST-Nr.</th>
<!-- <th title="Grundstücksnummer">GST-Nr.</th>-->
<!-- <th title="Grundbuch Einlagezahl">EZ</th>-->
<!-- <th>Besitzer</th>-->
@@ -137,7 +138,7 @@ $pagination_entity_name = "Zustimmungserklärungen";
<?=$item->adb_hausnummer->strasse->gemeinde->name?>
<?php endif; ?>
</td>
<!-- <td>--><?php //=$item->gstnr?><!--</td>-->
<td><?=$item->gst?></td>
<!-- <td>--><?php //=$item->ez?><!--</td>-->
<!-- <td>--><?php //=$item->owner_name?><!--</td>-->
<!-- <td>-->

View File

@@ -95,6 +95,10 @@ $mindate = date("Y-m-d", strtotime("+ 1 Month", $closedmonth));
.holiday-text {
color: #d70000;
}
.height-unset
{
height: unset;
}
</style>
<!-- start page title -->
@@ -314,16 +318,12 @@ $mindate = date("Y-m-d", strtotime("+ 1 Month", $closedmonth));
<div class="form-row">
<div class="col text-center">
<div class="input-group ">
<label class="col-form-label form-control"><span
class="text-bold">Sollzeit: </span><span id="must-time"
class="ml-1 text-normal"></span></label>
<label class="col-form-label form-control height-unset"><span class="text-bold">Sollzeit: </span><span id="must-time" class="ml-1 text-normal"></span></label>
</div>
</div>
<div class="col text-center">
<div class="input-group ">
<label class="col-form-label form-control"><span class="text-bold">Istzeit: </span><span
id="is-time"
class="ml-1 text-normal"></span></label>
<label class="col-form-label form-control height-unset"><span class="text-bold">Istzeit: </span><span id="is-time" class="ml-1 text-normal"></span></label>
</div>
</div>
</div>
@@ -332,19 +332,32 @@ $mindate = date("Y-m-d", strtotime("+ 1 Month", $closedmonth));
<div class="form-row">
<div class="col text-center">
<div class="input-group ">
<label class="col-form-label form-control fixed-state"><span
<label class="col-form-label form-control fixed-state height-unset"><span
class="text-bold">Urlaubstage: </span><span id="holidays"
class="ml-1 text-normal"></span></label>
</div>
</div>
<div class="col text-center">
<div class="input-group ">
<label id="plushours-label" class="col-form-label form-control fixed-state"><span
<label id="plushours-label" class="col-form-label form-control fixed-state height-unset"><span
class="text-bold">Gutzeit: </span><span
id="plushours"
class="ml-1 text-normal"></span></label>
</div>
</div>
</div>
</div>
<div class="col-lg-2" id="AllHours-div" style="display: none">
<div class="form-row">
<div class="col text-center">
<div class="input-group ">
<label class="col-form-label form-control height-unset"><span
class="text-bold">Gesamtzeit: </span><span id="AllHours"
class="ml-1 text-normal"></span></label>
</div>
</div>
</div>
</div>
</div>

View File

@@ -17,10 +17,8 @@
}
.customer-details {
vertical-align: bottom;
vertical-align: top;
font-size: 14px;
padding-left: 30pt;
width: 35%;
}
.invoice-details {
@@ -58,8 +56,8 @@
<table style="width: 100%; border-collapse: collapse;">
<tr>
<td class="customer-details" style="float: left">
<h3>Lieferant</h3>
<td class="customer-details">
<h3>{{ addressLine_header }}</h3>
<div>{{ addressLine_1 }}</div>
<div>{{ addressLine_2 }}</div>
<div>{{ addressLine_3 }}</div>
@@ -67,8 +65,15 @@
<div style="margin-bottom: 12pt"></div>
<div>{{ externalReference }}</div>
</td>
<td class="customer-details" align="top">
<h3>Rechnungsadresse</h3>
<td class="customer-details">
<h3>{{ shippingAddressLine_header }}</h3>
<div>{{ shippingAddressLine_1 }}</div>
<div>{{ shippingAddressLine_2 }}</div>
<div>{{ shippingAddressLine_3 }}</div>
<div>{{ shippingAddressLine_4 }}</div>
</td>
<td class="customer-details">
<h3>{{ billingAddressLine_header }}</h3>
<div>{{ billingAddressLine_1 }}</div>
<div>{{ billingAddressLine_2 }}</div>
<div>{{ billingAddressLine_3 }}</div>
@@ -76,12 +81,6 @@
<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>

View File

@@ -6,13 +6,51 @@
* @var Array $textElements
*/
$this->setReturnValue(['filename' => $order["id"] . ".pdf"]);
$texts = [
'EN' => [
'header' => 'XINON Supplier Order from ' . date("d.m.Y", $order["create"]),
'sum' => 'Sum',
'vat' => '20% VAT',
'total' => 'Total',
'taxFree' => 'Please provide tax-free delivery according to § 6a UStG (Austrian Sales tax law).<br>Our VAT ID number: ATU68711968. Delivery to Austria.',
'orderConfirmation' => 'Please send the order confirmation to office@xinon.at',
'table' => [
'pos' => 'POS',
'article' => 'Article',
'articleNumber' => 'Dist. art. nr.',
'amount' => 'Amount',
'unitPrice' => 'Unit price',
'totalPrice' => 'Total price'
]
],
'DE' => [
'header' => 'XINON Lieferantenbestellung vom ' . date("d.m.Y", $order["create"]),
'sum' => 'Summe',
'vat' => '20% MwSt',
'total' => 'Gesamt',
'taxFree' => 'Bitte um steuerfreie Lieferung gemäß § 6a UStG.<br> Unsere UID-Nr.: ATU68711968. Lieferung nach Österreich.',
'orderConfirmation' => 'Wir bitten um Zusendung der Auftragsbestätigung für die Bestellung an office@xinon.at.',
'table' => [
'pos' => 'POS',
'article' => 'Artikel',
'articleNumber' => 'Art.-Nr. Lieferant',
'amount' => 'Menge',
'unitPrice' => 'Einzelpreis',
'totalPrice' => 'Gesamtpreis'
]
]];
$text = $texts[in_array($distributorCountryText, ["Österreich", "Deutschland", "Schweiz"]) ? "DE" : "EN"];
?>
<!DOCTYPE html>
<html>
<head>
<title>Bestellung</title>
<meta charset="utf-8" />
<title><?= $text['header'] ?></title>
<meta charset="utf-8"/>
<style>
body {
margin-top: 0;
@@ -76,58 +114,78 @@ $this->setReturnValue(['filename' => $order["id"] . ".pdf"]);
</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>
<h2 style="text-align: center;color: #005384"><?= $text['header'] ?></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>
<th style="text-align: center;padding-right: 6pt"><?= $text['table']['pos'] ?></th>
<th style="text-align: center;padding-right: 6pt"><?= $text['table']['article'] ?></th>
<th style="text-align: center;padding-right: 6pt"><?= $text['table']['articleNumber'] ?></th>
<th style="text-align: right"><?= $text['table']['amount'] ?></th>
<th style="text-align: right"><?= $text['table']['unitPrice'] ?></th>
<th style="text-align: right;padding-right: 8pt"><?= $text['table']['totalPrice'] ?></th>
</tr>
<?php $i = 0; foreach($order['positions'] as $p):?>
<?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="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>
<tr class="<?= ($i % 2 == 0) ? "even" : "uneven" ?>">
<td></td>
<td style="text-align: left;max-width: 200px"><?= $p["articleDescription"] ?></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<?php $i++; endforeach; ?>
<tr class="uneven">
<?php
$sum = 0;
foreach($order['positions'] as $p){
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>
"><?= $text['sum'] ?>
</td>
<td style="text-align: right;border-top: 1px solid black;font-weight: bold
;border-bottom: 1px solid black;
"><?=number_format($sum, 2, ",", ".")?> €</td>
"><?= number_format($sum, 2, ",", ".") ?> €
</td>
</tr>
<?php if ($distributorCountryText === "Österreich"): ?>
<tr style="font-weight: bold; border-bottom: 1px solid black; background-color: #ebebeb;">
<td colspan="5" style="text-align: right;font-weight: bold;">20% MwSt</td>
<td style="text-align: right;font-weight: bold;"><?= number_format($sum * 0.2, 2, ",", ".") ?> €</td>
</tr>
<tr class="uneven" style="font-weight: bold; border-bottom: 3px double black; background-color: #ebebeb;">
<td colspan="5" style="text-align: right;font-weight: bold;"><?= $text['total'] ?></td>
<td style="text-align: right;font-weight: bold;"><?= number_format($sum * 1.2, 2, ",", ".") ?> €</td>
</tr>
<?php endif; ?>
</table>
<div>
<h3>Anmerkungen</h3>
<p>
<?=$order["note"]?>
</p>
<?php if ($distributorCountryText !== "Österreich"): ?>
<?= $text['taxFree'] ?>
<?php endif; ?>
<?= $text['orderConfirmation'] ?>
</div>
</body>
</html>

View File

@@ -11,6 +11,9 @@
</a>
<ul class="submenu">
<li><a href="<?=self::getUrl("Preorder")?>"><i class="far fa-fw fa-calendar-lines text-info"></i> Vorbestellung</a></li>
<?php if($me->is(["Admin","netowner","salespartner"]) && in_array($me->address_id, [1,209,5908,2187])): ?>
<li><a href="<?=self::getUrl("ConstructionConsentProject")?>"><i class="far fa-fw fa-clipboard-question text-info"></i> Zustimmungserklärungen</a></li>
<?php endif; ?>
</ul>
</li>
<?php else: ?>
@@ -198,7 +201,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,5908])): ?>
<?php if($me->is(["Admin","netowner","salespartner"]) && in_array($me->address_id, [1,209,5908,2187])): ?>
<li><a href="<?=self::getUrl("ConstructionConsentProject")?>"><i class="far fa-fw fa-clipboard-question text-info"></i> Zustimmungserklärungen</a></li>
<?php endif; ?>
</ul>

View File

@@ -61,6 +61,8 @@
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.14.0/Sortable.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-draggable-next@2.1.0"></script>
<script type="text/javascript">
baseurl = '<?=self::getResourcePath()?>';
</script>

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ class DeviceController extends mfBaseController
$this->allowedPops = null;
} else {
$networkIds = array_column($this->me->getProperty('my_networks'), 'id');
$pops=PopNetworkModel::search(['Networks' => $networkIds]);
$pops = PopNetworkModel::search(['Networks' => $networkIds]);
foreach ($pops as $pop) {
$popIds[] = $pop->pop_id;
}
@@ -28,7 +28,7 @@ class DeviceController extends mfBaseController
protected function indexAction()
{
$deviceManufacturers = array_map(function($deviceManufacturer) {
$deviceManufacturers = array_map(function ($deviceManufacturer) {
return [
"id" => $deviceManufacturer->id,
"name" => $deviceManufacturer->name,
@@ -37,7 +37,7 @@ class DeviceController extends mfBaseController
];
}, DevicemanufactorModel::getAll());
$deviceTypes = array_map(function($deviceType) {
$deviceTypes = array_map(function ($deviceType) {
return [
"id" => $deviceType->id,
"name" => $deviceType->name,
@@ -96,7 +96,7 @@ class DeviceController extends mfBaseController
$this->layout()->setTemplate("Device/Detail");
$devicesconfig = DeviceModel::getconifg($id);
$devices = DeviceModel::getOne($id);
$devicesall= DeviceModel::getAll();
$devicesall = DeviceModel::getAll();
if ($devices->devicetype->olt == "1") {
$customer = DeviceModel::getOltCustomer($device->ip);
@@ -417,7 +417,9 @@ class DeviceController extends mfBaseController
private function getDevices()
{
if ($this->allowedPops === null) return [];
if (!$this->me->is(["Admin"])) {
if ($this->allowedPops === null) return [];
}
$devices = DeviceModel::search(['popIds' => $this->allowedPops]);
foreach ($devices as $device) {

View File

@@ -680,6 +680,7 @@ class TimerecordingController extends mfBaseController
$holiDays = 0;
$plusHours = 0;
$startdate = time();
$allhours=0;
if (!$userid) {
$userid = $this->me->id;
}
@@ -697,6 +698,10 @@ class TimerecordingController extends mfBaseController
}
$overtime = $employee[0]->overtime_now;
if ($employee[0]->only_admin) {
$getAllHours=TimerecordingModel::getAllHours($userid);
$getAllHours=$getAllHours[0]->gesamt_summe;
}
}
$workinghours = TimerecordingEmployeeWorkingHourModel::search(['user_id' => $userid]);
$holidays = TimerecordingHolidayModel::getAll();
@@ -1155,6 +1160,7 @@ class TimerecordingController extends mfBaseController
$json['time']['must'] = sprintf('%02dh:%02dm', floor($mustSeconds / 3600), floor($mustSeconds / 60 % 60));
$json['time']['holidays'] = $holiDays;
$json['time']['plushours'] = $plusHours;
$json['time']['AllHours'] = sprintf('%02dh:%02dm', floor($getAllHours / 3600), floor($getAllHours / 60 % 60));;
$json['data'] = $rows;
$json['recordsFiltered'] = $responsecount;
$json['recordsTotal'] = $responsecount;

View File

@@ -73,6 +73,21 @@ class TimerecordingModel
}
return $item;
}
public static function getAllHours($user_id)
{
$items = [];
$db = FronkDB::singleton();
$sql="SELECT SUM(`end` - `start`) AS gesamt_summe FROM Timerecording where user_id=$user_id AND `timerecordingCategory_id` =1";
$res = $db->query($sql);
if ($db->num_rows($res)) {
while ($data = $db->fetch_object($res)) {
$items[] = new Timerecording($data);
}
}
return $items;
}
public static function getAll()
{

View File

@@ -30,6 +30,10 @@ class WarehouseOfferController extends TTCrud {
['key' => 'sendOffer', 'title' => 'Angebot senden', 'class' => 'fas fa-paper-plane text-success']
];
protected array $additionalJS = ['
https://cdn.jsdelivr.net/npm/sortablejs@1.14.0/Sortable.min.js
https://cdn.jsdelivr.net/npm/vue-draggable-next@2.1.0'];
protected array $infoMessages = [
'create' => 'Angebot wurde erfolgreich erstellt.',
'update' => 'Angebot wurde aktualisiert.',

View File

@@ -9,18 +9,18 @@ class WarehouseOrderController extends TTCrud {
protected array $columns = [
['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' => 'distributorId', 'text' => 'Lieferant', 'required' => false, 'modal' => ['type' => 'select', 'items' => []], 'table' => ['filter' => 'select']],
['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' => 'note', 'text' => 'Notiz', 'required' => false, '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' => 'extReference', 'text' => 'Externe Referenz', 'required' => false, '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']],
@@ -32,6 +32,8 @@ class WarehouseOrderController extends TTCrud {
'delete' => 'Bestellung wurde gelöscht',
'noChanges' => 'Keine Änderungen',];
protected array $additionalActions = [['key' => 'openpdf', 'title' => 'PDF öffnen', 'class' => 'fas fa-file-pdf', 'color' => 'primary']];
protected function prepareCrudConfig(): void {
$editorColumnIndex = array_search('editor', array_column($this->columns, 'key'));
$this->columns[$editorColumnIndex]['modal']['items'] = array_map(function ($user) {
@@ -48,6 +50,11 @@ class WarehouseOrderController extends TTCrud {
['value' => 'fullyDelivered', 'text' => 'Geliefert'],
['value' => 'cancelled', 'text' => 'Storniert'],
];
$distributorIndex = array_search('distributorId', array_column($this->columns, 'key'));
$this->columns[$distributorIndex]['modal']['items'] = array_map(function ($distributor) {
return ['value' => intval($distributor->id), 'text' => $distributor->name];
}, WarehouseDistributorModel::getAll());
}
protected function beforeCreate(): bool {
@@ -91,40 +98,43 @@ class WarehouseOrderController extends TTCrud {
// 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;
$position['articleDescription'] = WarehouseArticleModel::get($position['article'])->description;
$order['positions'][$key] = $position;
}
$pdf_vars = ['order' => $order,
'distributor' => WarehouseDistributorModel::get($distributorId),
'distributorCountryText' => (new Country(WarehouseDistributorModel::get($distributorId)->countryId))->name,
"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;
$countryText = (new Country(WarehouseDistributorModel::get($distributorId)->countryId))->name;
$shouldGenerateEnglisch = !in_array($countryText, ['Österreich', 'Deutschland', 'Schweiz']);
$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("{{ externalReference }}", count($order['extReference']) > 0 ? "<strong>Ext. Ref.:</strong> ". $order['extReference'] : "", $headerHtml);
$headerHtml = str_replace("{{ addressLine_header }}", $shouldGenerateEnglisch ? "Supplier" : "Lieferant", $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_header }}", $shouldGenerateEnglisch ? "Billing Address" : "Rechnungsadresse", $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_3 }}", "A-8322 Studenzen", $headerHtml);
$headerHtml = str_replace("{{ billingAddressLine_4 }}", "UID: ATU68711968", $headerHtml);
$headerHtml = str_replace("{{ billingAddressLine_5 }}", "EORI-Nr.: ATEOS1000085074", $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_header }}", $chk ? "" : ($shouldGenerateEnglisch ? "Shipping Address" : "Lieferadresse"), $headerHtml);
$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);

View File

@@ -23,7 +23,7 @@
class WarehouseOrderModel extends TTCrudBaseModel {
public int $id;
public string $orderNumber;
public string $extReference;
public ?string $extReference;
public int $distributorId;
public string $delAddrCity;
public string $delAddrEMail;
@@ -31,7 +31,7 @@ class WarehouseOrderModel extends TTCrudBaseModel {
public string $delAddrName;
public string $delAddrPLZ;
public int $editor;
public string $note;
public ?string $note;
public string $positions;
public int $create;
public int $createBy;

View File

@@ -8,12 +8,17 @@ class WarehouseShippingNoteController extends TTCrud {
protected array $columns = [
['key' => 'id', 'text' => 'LS-Nr.', 'required' => false, 'modal' => false, 'table' => ['class' => 'text-nowrap']],
['key' => 'billingAddressId', 'text' => 'Rechnungsadresse', 'required' => true, 'type' => 'autocomplete', 'table' => ['class' => 'text-nowrap', 'filter' => 'autocomplete'], 'modal' => ['apiUrl' => 'Address/api?do=findAddress', 'items' => '/Address/Api?do=findAddress', 'type' => 'autocomplete']],
['key' => 'status', 'text' => 'Status', 'required' => true, 'table' => ['filter' => 'iconSelect'], 'modal' => ['type' => 'iconSelect', 'items' => [
['value' => 'new', 'text' => 'Neu', 'icon' => 'fas fa-star text-primary'],
['value' => 'in_progress', 'text' => 'In Bearbeitung', 'icon' => 'fas fa-cog text-warning'],
['value' => 'accepted', 'text' => 'Akzeptiert', 'icon' => 'fas fa-check text-success'],
['value' => 'invoiced', 'text' => 'In Rechnung gestellt', 'icon' => 'fas fa-file-invoice-dollar text-info'],
]]],
['key' => 'deliveryAddressName', 'text' => 'L.-Adr. Name', 'required' => true],
['key' => 'deliveryAddressLine', 'text' => 'L.-Adr.', 'required' => true],
['key' => 'deliveryAddressPLZ', 'text' => 'L.-Adr. PLZ', 'required' => true],
['key' => 'deliveryAddressEMail', 'text' => 'L.-Adr. EMail', 'required' => false, 'table' => false],
['key' => 'note', 'text' => 'Art der Arbeit', 'required' => true, 'table' => false],
['key' => 'status', 'text' => 'Status', 'required' => true, 'table' => ['filter' => 'select'], 'modal' => ['type' => 'select', 'items' => [['value' => 'new', 'text' => 'Neu'], ['value' => 'in_progress', 'text' => 'In Bearbeitung'], ['value' => 'accepted', 'text' => 'Akzeptiert'], ['value' => 'invoiced', 'text' => 'In Rechnung gestellt'],]]],
['key' => 'positions', 'text' => 'Positionen', 'required' => true, 'table' => false, 'modal' => false],
['key' => 'create', 'text' => 'Erstellt', 'required' => false, 'modal' => ['visible' => false], 'table' => ['filter' => 'date']],
['key' => 'createBy', 'text' => 'Erstellt von', 'required' => false, 'type' => 'autocomplete', 'table' => ['class' => 'text-nowrap', 'filter' => 'select'], 'modal' => ['items' => [], 'type' => 'select',]],

View File

@@ -38,6 +38,12 @@
margin-top: 20px;
}
.dashboard-buttons {
display: flex;
gap: 1rem;
margin-top: 20px;
}
@media (min-width: 768px) {
.dashboard-cards {

View File

@@ -190,6 +190,39 @@ Vue.component('tt-timeline-chart', {
Vue.component('dashboard-default', {
props: ['dashboardData'],
data() {
return {
selectedTimeframe: 'all',
window: window,
moment: moment
}
},
methods: {
exportTimeline() {
// we need all timelines with "ONT installiert", "Leerrohr", "Bestellungen"
const data = this.dashboardData.timeline[0].map((item, index) => ({
date: item.date,
bestellungen: item.value,
leerrohr: this.dashboardData.timeline_leerrohr[0][index].value,
ont_installiert: this.dashboardData.timeline_ont_installed[0][index].value
}));
// dont use any library for the csv
const csv = [
['Datum', 'Bestellungen', 'Leerrohr', 'ONT installiert'],
...data.map(item => [item.date, item.bestellungen, item.leerrohr, item.ont_installiert])
].map(row => row.join(';')).join('\n');
const blob = new Blob([csv], {type: 'text/csv'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'timeline.csv';
a.click();
URL.revokeObjectURL(url);
}
},
template: `
<div>
<h4 class="mt-4">Bestellungen</h4>
@@ -307,13 +340,25 @@ Vue.component('dashboard-default', {
<hr>
<h4 class="mt-4">Bestellverlauf</h4>
<!-- add 4 new buttons here tt-button "Letzten 4 Wochen, Letzte 3 Monate, Letzten 6 Monate-->
<div class="dashboard-buttons">
<tt-button text="CSV Download" additional-class="btn-success" @click="exportTimeline" icon="fas fa-download"/>
<tt-button text="Letzten 4 Wochen" additional-class="btn-primary" @click="selectedTimeframe = '4 weeks'" v-if="selectedTimeframe !== '4 weeks'"/>
<tt-button text="Letzte 3 Monate" additional-class="btn-primary" @click="selectedTimeframe = '3 months'" v-if="selectedTimeframe !== '3 months'"/>
<tt-button text="Letzte 6 Monate" additional-class="btn-primary" @click="selectedTimeframe = '6 months'" v-if="selectedTimeframe !== '6 months'"/>
<tt-button text="Alle" additional-class="btn-primary" @click="selectedTimeframe = 'all'" v-if="selectedTimeframe !== 'all'"/>
</div>
<div class="dashboard-chart">
<tt-timeline-chart
v-if="dashboardData.timeline.length > 0"
:datasets="[
{
label: 'Bestellungen',
data: dashboardData.timeline[0],
data: dashboardData.timeline[0].filter(item => {
return selectedTimeframe === 'all' || moment(item.date).isAfter(moment().subtract(selectedTimeframe.split(' ')[0], selectedTimeframe.split(' ')[1]))
}),
color: 'rgb(75, 192, 192)',
fill: true,
yAxisID: 'y',
@@ -321,7 +366,7 @@ Vue.component('dashboard-default', {
},
{
label: 'Leerrohr',
data: dashboardData.timeline_leerrohr[0],
data: dashboardData.timeline_leerrohr[0].filter(item => selectedTimeframe === 'all' || moment(item.date).isAfter(moment().subtract(selectedTimeframe.split(' ')[0], selectedTimeframe.split(' ')[1]))),
color: 'rgb(255, 99, 132)',
fill: false,
yAxisID: 'y',
@@ -329,7 +374,7 @@ Vue.component('dashboard-default', {
},
{
label: 'ONT installiert',
data: dashboardData.timeline_ont_installed[0],
data: dashboardData.timeline_ont_installed[0].filter(item => selectedTimeframe === 'all' || moment(item.date).isAfter(moment().subtract(selectedTimeframe.split(' ')[0], selectedTimeframe.split(' ')[1]))),
color: 'rgb(54, 162, 235)',
fill: false,
yAxisID: 'y',
@@ -347,6 +392,39 @@ Vue.component('dashboard-default', {
Vue.component('dashboard-rml', {
props: ['dashboardData'],
data() {
return {
selectedTimeframe: 'all',
window: window,
moment: moment
}
},
methods: {
exportTimeline() {
// we need all timelines with "ONT installiert", "Leerrohr", "Bestellungen"
const data = this.dashboardData.timeline[0].map((item, index) => ({
date: item.date,
bestellungen: item.value,
leerrohr: this.dashboardData.timeline_leerrohr[0][index].value,
ont_installiert: this.dashboardData.timeline_ont_installed[0][index].value
}));
// dont use any library for the csv
const csv = [
['Datum', 'Bestellungen', 'Leerrohr', 'ONT installiert'],
...data.map(item => [item.date, item.bestellungen, item.leerrohr, item.ont_installiert])
].map(row => row.join(';')).join('\n');
const blob = new Blob([csv], {type: 'text/csv'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'timeline.csv';
a.click();
URL.revokeObjectURL(url);
}
},
template: `
<div>
<h4 class="mt-4">Bestellungen</h4>
@@ -439,57 +517,67 @@ Vue.component('dashboard-rml', {
<hr>
<h4 class="mt-4">Bestellverlauf</h4>
<div class="dashboard-buttons">
<tt-button text="CSV Download" additional-class="btn-success" @click="exportTimeline" icon="fas fa-download"/>
<tt-button text="Letzten 4 Wochen" additional-class="btn-primary" @click="selectedTimeframe = '4 weeks'" v-if="selectedTimeframe !== '4 weeks'"/>
<tt-button text="Letzte 3 Monate" additional-class="btn-primary" @click="selectedTimeframe = '3 months'" v-if="selectedTimeframe !== '3 months'"/>
<tt-button text="Letzte 6 Monate" additional-class="btn-primary" @click="selectedTimeframe = '6 months'" v-if="selectedTimeframe !== '6 months'"/>
<tt-button text="Alle" additional-class="btn-primary" @click="selectedTimeframe = 'all'" v-if="selectedTimeframe !== 'all'"/>
</div>
<div class="dashboard-chart">
<tt-timeline-chart
v-if="dashboardData.timeline.length > 0"
:datasets="[
{
label: 'Bestellungen',
data: dashboardData.timeline[0],
color: 'rgb(23, 162, 184)',
fill: true,
yAxisID: 'y',
order: 5
},
{
label: 'Summe von 244+245',
data: dashboardData.timeline_inhouse_kabel_verlegt_efh[0],
color: 'rgb(0, 123, 255)',
fill: false,
yAxisID: 'y',
order: 4
},
{
label: 'Status 500 Energie Steiermark',
data: dashboardData.timeline_status_500_energie_steiermark[0],
color: 'rgb(40, 167, 69)',
fill: false,
yAxisID: 'y',
order: 3
},
{
label: 'Status 500 Magenta',
data: dashboardData.timeline_status_500_magenta[0],
color: 'rgb(226, 0, 116)',
fill: false,
yAxisID: 'y',
order: 2
},
{
label: 'Status 500 Salzburg AG',
data: dashboardData.timeline_status_500_salzburg_ag[0],
color: 'rgb(0, 102, 179)',
fill: false,
yAxisID: 'y',
order: 1
}
]"
{
label: 'Bestellungen',
data: dashboardData.timeline[0].filter(item => {
return selectedTimeframe === 'all' || moment(item.date).isAfter(moment().subtract(selectedTimeframe.split(' ')[0], selectedTimeframe.split(' ')[1]))
}),
color: 'rgb(75, 192, 192)',
fill: true,
yAxisID: 'y',
order: 3
},
{
label: 'Summe von 244+245',
data: dashboardData.timeline_inhouse_kabel_verlegt_efh[0].filter(item => selectedTimeframe === 'all' || moment(item.date).isAfter(moment().subtract(selectedTimeframe.split(' ')[0], selectedTimeframe.split(' ')[1]))),
color: 'rgb(0, 123, 255)',
fill: false,
yAxisID: 'y',
order: 2
},
{
label: 'Status 500 Energie Steiermark',
data: dashboardData.timeline_status_500_energie_steiermark[0].filter(item => selectedTimeframe === 'all' || moment(item.date).isAfter(moment().subtract(selectedTimeframe.split(' ')[0], selectedTimeframe.split(' ')[1]))),
color: 'rgb(40, 167, 69)',
fill: false,
yAxisID: 'y',
order: 1
},
{
label: 'Status 500 Magenta',
data: dashboardData.timeline_status_500_magenta[0].filter(item => selectedTimeframe === 'all' || moment(item.date).isAfter(moment().subtract(selectedTimeframe.split(' ')[0], selectedTimeframe.split(' ')[1]))),
color: 'rgb(226, 0, 116)',
fill: false,
yAxisID: 'y',
order: 1
},
{
label: 'Status 500 Salzburg AG',
data: dashboardData.timeline_status_500_salzburg_ag[0].filter(item => selectedTimeframe === 'all' || moment(item.date).isAfter(moment().subtract(selectedTimeframe.split(' ')[0], selectedTimeframe.split(' ')[1]))),
color: 'rgb(0, 102, 179)',
fill: false,
yAxisID: 'y',
order: 1
}
]"
label="KW"
:showGrid="false"
/>
</div>
</div>
`
</div> `
})

View File

@@ -15,29 +15,27 @@ Vue.component('warehouse-offer-modal', {
sm
row
v-model="offer.editor"/>
<tt-input label="Kundennummer" v-model="offer.customerNumber" sm row/>
<tt-autocomplete label="Kunde" v-model="offer.customerNumber" sm row :api-url="billAddrAutoCompleteUrl"/>
<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;">
<div style="display: grid; grid-gap: 10px; grid-template-columns: 2fr 1fr 2fr 1fr 1fr;">
<tt-input label="Name" v-model="offer.customerName" sm/>
<tt-input label="Kontakt" v-model="offer.contactPerson" 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"/>
<tt-positions-manager group-mode ref="positionsManager" v-model="offer.positions" :config="positionsConfig" @updateField-article="fetchArticleData"/>
<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"/>
<tt-textarea label="Schlusstext" sm rows="11" row v-model="offer.closingText"/>
<hr>
<tt-textarea label="Notizen" v-model="offer.notes" sm row/>
</div>
@@ -45,6 +43,7 @@ Vue.component('warehouse-offer-modal', {
`,
data() {
return {
billAddrAutoCompleteUrl: window.TT_CONFIG['BASE_PATH'] + '/Address/Api?do=findAddress&fibu_primary_account=1',
window: window,
positionsConfig: {
fields: {
@@ -57,6 +56,7 @@ Vue.component('warehouse-offer-modal', {
amount: {type: 'input', label: 'Menge', inputType: 'number'},
unit: {type: 'input', label: 'Einheit'},
articleNumber: {type: 'input', label: 'Artikelnummer'},
isAlternative: {type: 'checkbox', label: 'Alternativposition'},
unitPrice: {type: 'input', label: 'Einzelpreis', inputType: 'number'},
discount: {type: 'input', label: 'Rabatt (%)', inputType: 'number'},
},
@@ -71,12 +71,6 @@ Vue.component('warehouse-offer-modal', {
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'},
@@ -87,11 +81,6 @@ Vue.component('warehouse-offer-modal', {
{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: '',
@@ -107,7 +96,18 @@ Vue.component('warehouse-offer-modal', {
totalDiscount: 0,
paymentTerms: 'net30',
deliveryTerms: 'ex_works',
closingText: 'standard',
closingText: 'Sollten sich die Rohstoffpreise bzw. die Preise unserer Zulieferer um mehr als 10% innerhalb der Angebots bzw.\n' +
'\n' +
'Auftragsgültigkeit erhöhen (Stichtag Datum), sind wir gezwungen die Preise anzupassen.\n' +
'\n' +
'Diese Angebot hat eine Gültigkeit von 4 Wochen.\n' +
'\n' +
'Verrechnung erfolgt nach tatsächlichem Aufwand.\n' +
'\n' +
'Wir sind sicher, Ihnen ein konkurenzfähiges Angebot unterbreitet zu haben und sehen gern Ihrer Bestellung entgegen.\n' +
'\n' +
'Sollten Sie noch Fragen oder weitere Informationen benötigen stehen wir Ihnen jederzeit gern zu Verfügung.\n' +
' ',
notes: '',
}
}

View File

@@ -218,19 +218,20 @@ Vue.component('warehouse-order', {
<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">
<tt-table-crud emit-edit
@openpdf="window.open(window.TT_CONFIG['BASE_PATH'] + '/WarehouseOrder/createPDF?id=' + $event.id)"
@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>
</tt-card>
`,
data() {
return {
window: window,
orderModalId: null,
}
},

View File

@@ -87,6 +87,10 @@ table = $('#datatable').DataTable({
$('#must-time').text(json.time.must);
$('#holidays').text(json.time.holidays);
$('#plushours').text(json.time.plushours);
if (json.time.AllHours!="00h:00m") {
$('#AllHours-div').show();
$('#AllHours').text(json.time.AllHours);
}
if ($("#plushours").text().includes("-")) {
$('#plushours-label').css('background-color', '#fda7a7');
} else {

View File

@@ -55,7 +55,6 @@ Vue.component('tt-modal', {
tabindex="-1"
style="background: rgba(0, 0, 0, 0.5);"
ref="modal"
@mousedown="!isMobile ? $emit('update:show', false) : null"
@keydown.esc="$emit('update:show', false)"
v-if="show">
<div class="modal-dialog modal-lg modal-dialog-scrollable" role="document" @mousedown.stop>
@@ -81,4 +80,4 @@ Vue.component('tt-modal', {
</div>
</div>
`
})
})

View File

@@ -1,13 +1,42 @@
Vue.component('tt-resolver', {
props: {
value: {type: Number, required: true},
reference: {type: String, required: true},
},
data() {
return {
window: window,
loading: true,
text: '',
}
},
template: `
<div v-if="loading" 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>
<span v-else>{{ text }}</span>
`,
async created() {
const entry = await axios.get(window.TT_CONFIG['BASE_PATH'] + '/' + this.reference + '/getById?id=' + this.value);
this.text = entry.data.name ?? entry.data.title ?? entry.data.text ?? '[E] Key not found';
this.loading = false;
}
})
Vue.component('tt-positions-manager', {
props: {
value: {type: Array, required: false},
config: {type: Object, required: true},
value: {type: Array, required: false},
config: {type: Object, required: true},
groupMode: {type: Boolean, default: false},
},
data() {
return {
window: window,
positions: this.value,
formData: {},
groupName: '',
selectedIndex: null,
resolvingFields: {},
}
@@ -63,6 +92,12 @@ Vue.component('tt-positions-manager', {
</div>
</div>
<div class="form-container" v-if="groupMode">
<tt-input label="Gruppenname" v-model="groupName" sm/>
<tt-button @click="addGroup" sm text="Gruppe hinzufügen" additional-class="btn-primary"/>
</div>
<table class="table table-striped table-sm">
<thead>
<tr>
@@ -71,24 +106,50 @@ Vue.component('tt-positions-manager', {
</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>
<template v-if="groupMode">
<template v-for="(groupPositions, groupName) in groupedPositions">
<tr>
<td colspan="100%">
<h4 style="text-align: center;">{{ groupName }}</h4>
</td>
<tr v-for="(position, index) in groupPositions" :key="groupName + index">
<td v-for="(field, key) in config.fields">
<tt-resolver v-if="field.customFieldReference" :reference="field.customFieldReference" :value="position[key]"/>
<span v-else>{{ formatFieldValue(position[key], field) }}</span>
</td>
<td>
<select v-model="position._group" @change="$set(position, '_group', $event.target.value)">
<option v-for="group in allGroups" :value="group">{{ group }}</option>
</select>
<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>
</template>
</template>
<template v-else>
<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>
</div>
</template>
</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>
<span v-else-if="resolvingFields[index + key]">{{ resolvingFields[index + key] }}</span>
<span v-else>{{ formatFieldValue(position[key], field) }}</span>
</td>
<td>
<select v-model="position._group" @change="position._group = $event">
<option v-for="group in allGroups" :value="group">{{ group }}</option>
</select>
<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>
</template>
</tbody>
</table>
</div>
@@ -110,6 +171,10 @@ Vue.component('tt-positions-manager', {
this.$emit('input', this.positions);
this.resetForm();
},
addGroup() {
this.positions.push({_group: this.groupName});
this.groupName = '';
},
editEntry(index) {
this.selectedIndex = index;
this.formData = {...this.positions[index]};
@@ -133,7 +198,7 @@ Vue.component('tt-positions-manager', {
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) {
} else if (this.config.fields[key].customFieldReference && this.positions[i][key]) {
this.$set(this.resolvingFields, i + key, true);
if (this.config.fields[key].customFieldReference) {
const entry = await axios.get(window.TT_CONFIG['BASE_PATH'] +
@@ -153,7 +218,21 @@ Vue.component('tt-positions-manager', {
created() {
if (this.config.customMethods) Object.assign(this, this.config.customMethods);
},
watch: {
computed: {
groupedPositions() {
const groups = {};
for (const position of this.positions) {
const group = position._group ?? 'Keine Gruppe';
if (!groups[group]) groups[group] = [];
if (Object.keys(position).length !== 1) groups[group].push(position);
}
return groups;
},
allGroups() {
return Object.keys(this.groupedPositions);
}
},
watch: {
positions: {
handler() {
this.resolveFields().then();