Merge branch 'master' into fronkdev
This commit is contained in:
@@ -99,6 +99,18 @@ $pagination_entity_name = "Zustimmungserklärungen";
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-2">
|
||||
<label class="form-label" for="filter_electric_approval">Elektriker freigegeben</label>
|
||||
<select name="filter[electric_approval]" id="filter_electric_approval" class="form-control">
|
||||
<option value="">Alle</option>
|
||||
<option value="!NULL" <?=(array_key_exists("electric_approval", $filter) && $filter["electric_approval"] == "!NULL") ? "selected='selected'" : ""?>>Ja</option>
|
||||
<option value="NULL" <?=(array_key_exists("electric_approval", $filter) && $filter["electric_approval"] == "NULL") ? "selected='selected'" : ""?>>Nein</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
|
||||
<div class="col-2">
|
||||
<label class="form-label" for="filter_conduit_installed_ftu">Leerrohr bis HAK</label>
|
||||
<select name="filter[conduit_installed_ftu]" id="filter_conduit_installed_ftu" class="form-control">
|
||||
@@ -108,10 +120,7 @@ $pagination_entity_name = "Zustimmungserklärungen";
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
|
||||
<div class="col-2">
|
||||
<div class="col-1">
|
||||
<label class="form-label" for="filter_inhouse_cabling">Inhouse erledigt</label>
|
||||
<select name="filter[inhouse_cabling]" id="filter_inhouse_cabling" class="form-control">
|
||||
<option value="">Alle</option>
|
||||
@@ -331,6 +340,8 @@ $pagination_entity_name = "Zustimmungserklärungen";
|
||||
<th>KG</th>
|
||||
<th>GST-Nr.</th>
|
||||
<th>Einlagezahl</th>
|
||||
<th>Wohneinheiten</th>
|
||||
<th>Bestellungen</th>
|
||||
<th class="text-center">Anzahl Eigentümer</th>
|
||||
<th>Anfrageresultat</th>
|
||||
<th></th>
|
||||
@@ -354,6 +365,8 @@ $pagination_entity_name = "Zustimmungserklärungen";
|
||||
<td><?=$item->kg?></td>
|
||||
<td><?=$item->gst?></td>
|
||||
<td><?=$item->ez?></td>
|
||||
<td><?=$item->adb_hausnummer->unit_count ?? ''?></td>
|
||||
<td><?=$item->preorder_count?></td>
|
||||
<td class="text-center">
|
||||
|
||||
<?php
|
||||
|
||||
@@ -144,7 +144,7 @@ $pagination_entity_name = "Adressen";
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Elektriker</th>
|
||||
<th>Freigabe Elektriker</th>
|
||||
<td class="text-monospace"><a data-toggle="modal" data-target="#electricianDateModal" href="#"><i class="fas fa-fw fa-edit"></i></a> <?=($item->inspection_date_electrician) ? date("d.m.Y", $item->inspection_date_electrician) : ""?></td>
|
||||
<td><input type="checkbox" id="inspection_electrician" class="switchery" data-size="small" data-color="#25b343" data-toggle-param="inspection_electrician" <?=($item->inspection_electrician) ? "checked='checked'" : ""?> /></td>
|
||||
<td>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<?php if(array_key_exists("status_id", $filter)): ?>
|
||||
<?=($filter['status_id'] == $status->id) ? "selected='selected'" : ""?>
|
||||
<?php else: ?>
|
||||
<?=($status->id == 3) ? "selected='selected'" : ""?>
|
||||
<?=(!in_array($me->id, ["145","62","56"]) && $status->id == 3) ? "selected='selected'" : ""?>
|
||||
<?php endif; ?>
|
||||
>
|
||||
<?=$status->code?> - <?=__($status->name."-t")?></option>
|
||||
@@ -104,6 +104,15 @@
|
||||
<input type="text" class="form-control" name="filter[building_street]" id="filter_building_street" value="<?=$filter['building_street']?>" />
|
||||
</div>
|
||||
|
||||
<div class="col-2">
|
||||
<label class="form-label" for="filter_linework_enabled">Baufreigabe</label>
|
||||
<select name="filter[linework_enabled]" id="filter_linework_enabled" class="form-control">
|
||||
<option value="">Alle</option>
|
||||
<option value="1" <?=(array_key_exists("linework_enabled", $filter) && $filter["linework_enabled"] == "1") ? "selected='selected'" : ""?>>Ja</option>
|
||||
<option value="0" <?=(array_key_exists("linework_enabled", $filter) && $filter["linework_enabled"] == "0") ? "selected='selected'" : ""?>>Nein</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,198 +1,451 @@
|
||||
<?php
|
||||
$pagination_baseurl = $this->getUrl($Mod,"Index");
|
||||
$pagination_baseurl_params = ["filter" => $filter];
|
||||
$pagination_entity_name = "Patchungen";
|
||||
|
||||
if(!is_array($filter)) $filter = [];
|
||||
// --- Configuration ---
|
||||
$pagination_baseurl = $this->getUrl($Mod,"Index");
|
||||
$pagination_baseurl_params = ["filter" => $filter]; // Filters passed via GET
|
||||
$pagination_entity_name = "Patchungen";
|
||||
|
||||
// Ensure $filter is always an array
|
||||
if(!is_array($filter)) {
|
||||
$filter = [];
|
||||
}
|
||||
|
||||
// Helper function for safe output (assuming not already handled by framework/template engine)
|
||||
function e($string) {
|
||||
return htmlspecialchars($string ?? '', ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
$additionalHead = [
|
||||
'<link rel="manifest" href="/assets/pwa/patching-manifest.json">'
|
||||
];
|
||||
|
||||
?>
|
||||
<?php include(realpath(dirname(__FILE__)."/../../$mfLayoutPackage")."/header.php"); ?>
|
||||
|
||||
<!-- start page title -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="page-title-box">
|
||||
<div class="page-title-right">
|
||||
<ol class="breadcrumb m-0">
|
||||
<li class="breadcrumb-item"><a href="<?=self::getUrl("Dashboard")?>"><?=MFAPPNAME_SLUG?></a></li>
|
||||
<li class="breadcrumb-item active">Patchungen</li>
|
||||
</ol>
|
||||
</div>
|
||||
<h4 class="page-title">Patchungen</h4>
|
||||
<div class="col-12">
|
||||
<div class="page-title-box">
|
||||
<div class="page-title-right">
|
||||
<ol class="breadcrumb m-0">
|
||||
<li class="breadcrumb-item"><a href="<?=e(self::getUrl("Dashboard"))?>"><?=e(MFAPPNAME_SLUG)?></a></li>
|
||||
<li class="breadcrumb-item active">Patchungen</li>
|
||||
</ol>
|
||||
</div>
|
||||
<h4 class="page-title">Patchungen</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end page title -->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body mb-3">
|
||||
<h4 class="header-title mb-3">Filter</h4>
|
||||
|
||||
<form method="get" action="<?=self::getUrl("Patching")?>">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<label class="form-label" for="filter_network_id">Netzgebiet</label>
|
||||
<select name="filter[network_id]" id="filter_network_id" class="form-control">
|
||||
<option></option>
|
||||
<?php foreach($mynetworks as $fnet): ?>
|
||||
<option value="<?=$fnet->id?>" <?=($filter['network_id'] == $fnet->id) ? "selected='selected'" : ""?>><?=$fnet->name?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-2">
|
||||
<label class="form-label" for="filter_pop_id">POP</label>
|
||||
<select name="filter[pop_id]" id="filter_pop_id" class="form-control">
|
||||
<option></option>
|
||||
<?php foreach($mynetworks as $fnet): ?>
|
||||
<?php if(is_array($fnet->pops) && count($fnet->pops)): ?>
|
||||
<optgroup label="<?=$fnet->name?>">
|
||||
<?php foreach($fnet->pops as $pop): ?>
|
||||
<option value="<?=$pop->id?>" <?=($filter['pop_id'] == $pop->id) ? "selected='selected'" : ""?>><?=$pop->name?></option>
|
||||
<?php endforeach; ?>
|
||||
</optgroup>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-2">
|
||||
<label class="form-label" for="filter_patched">Patchstatus</label>
|
||||
<select name="filter[patched]" id="filter_patched" class="form-control">
|
||||
<option value="0" <?=($filter['patched'] < 1) ? "selected='selected'" : ""?>>Nicht gepatched</option>
|
||||
<option value="1" <?=($filter['patched'] == 1) ? "selected='selected'" : ""?>>Gepatched</option>
|
||||
<option value="2" <?=($filter['patched'] == 2) ? "selected='selected'" : ""?>>Vorpatchen</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-2">
|
||||
<label class="form-label" for="filter_hide_delayed_finish">Verzögerte Herstellung</label>
|
||||
<select name="filter[hide_delayed_finish]" id="filter_hide_delayed_finish" class="form-control">
|
||||
<option value="0" <?=(array_key_exists("hide_delayed_finish", $filter) &&$filter['hide_delayed_finish'] != 1) ? "selected='selected'" : ""?>>Anzeigen</option>
|
||||
<option value="1" <?=(!array_key_exists("hide_delayed_finish", $filter) || $filter['hide_delayed_finish'] == 1) ? "selected='selected'" : ""?>>Nicht anzeigen</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-1">
|
||||
<label class="form-label" for="filter_code">Objekt ID</label>
|
||||
<input type="text" class="form-control" name="filter[code]" id="filter_code" value="<?=$filter['code']?>" />
|
||||
</div>
|
||||
|
||||
<div class="col-2">
|
||||
<label class="form-label" for="filter_street">Straße</label>
|
||||
<input type="text" class="form-control" name="filter[street]" id="filter_street" value="<?=$filter['street']?>" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="col">
|
||||
<button type="submit" class="btn btn-primary">Filter anwenden</button>
|
||||
<a class="btn btn-secondary" href="<?=self::getUrl("Patching")?>">Filter zurücksetzen</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body mb-3">
|
||||
<h4 class="header-title">Patchungen</h4>
|
||||
<div class="col-lg-12">
|
||||
|
||||
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination.php"); ?>
|
||||
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination-summary.php"); ?>
|
||||
<div class="card">
|
||||
<div class="card-body mb-3">
|
||||
<h4 class="header-title mb-3">Filter</h4>
|
||||
|
||||
|
||||
<table class="table table-striped table-hover">
|
||||
<tr class="table-bordered text-center">
|
||||
<th colspan="4">Standort</th>
|
||||
<th colspan="2">ODF</th>
|
||||
<th colspan="5">Abschluss/Device</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr class="table-bordered">
|
||||
<th>Netzgebiet</th>
|
||||
<th>POP</th>
|
||||
<th>Kunde</th>
|
||||
<th>Standort</th>
|
||||
<th>Patchposition ODF</th>
|
||||
<th>ODF Port</th>
|
||||
<th>Typ</th>
|
||||
<th>Splitter / Gerät</th>
|
||||
<th>Port</th>
|
||||
<th>Gepatched</th>
|
||||
<th>Von</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<?php foreach($terminations as $term): ?>
|
||||
<tr>
|
||||
<td><?=$term->building->network->name?></td>
|
||||
<td>
|
||||
<?php if($term->getPop()): ?>
|
||||
<?=$term->getPop()->name?>
|
||||
<?php else: ?>
|
||||
<?=$term->building->pop->name?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?=($term->order->owner) ? $term->order->owner->customer_number : ""?><br /> <?=($term->order->owner) ? $term->order->owner->getCompanyOrName() : ""?></td>
|
||||
<td>
|
||||
<?=$term->building->street?><br />
|
||||
<?=$term->building->zip?> <?=$term->building->city?>
|
||||
</td>
|
||||
<td class="text-mono text-primary" title="Schrank: <?=($term->workflowitems["ist_schrank"]->value->id) ? $term->workflowitems["ist_schrank"]->value->value_string : $term->workflowitems["schrank"]->value->value_string?> / Einschub: <?=($term->workflowitems["ist_baugruppe"]->value->id) ? $term->workflowitems["ist_baugruppe"]->value->value_string : $term->workflowitems["baugruppe"]->value->value_string?> / Modul: <?=($term->workflowitems["ist_modul"]->value->id) ? $term->workflowitems["ist_modul"]->value->value_string : $term->workflowitems["modul"]->value->value_string?> / Port: <?=($term->workflowitems["ist_ports"]->value->id) ? $term->workflowitems["ist_ports"]->value->value_string : $term->workflowitems["ports"]->value->value_string?>">
|
||||
<?=($term->workflowitems["ist_schrank"]->value->id) ? $term->workflowitems["ist_schrank"]->value->value_string : $term->workflowitems["schrank"]->value->value_string?> /
|
||||
<?=($term->workflowitems["ist_baugruppe"]->value->id) ? $term->workflowitems["ist_baugruppe"]->value->value_string : $term->workflowitems["baugruppe"]->value->value_string?> /
|
||||
<?=($term->workflowitems["ist_modul"]->value->id) ? $term->workflowitems["ist_modul"]->value->value_string : $term->workflowitems["modul"]->value->value_string?> /
|
||||
<?=($term->workflowitems["ist_ports"]->value->id) ? $term->workflowitems["ist_ports"]->value->value_string : $term->workflowitems["ports"]->value->value_string?>
|
||||
</td>
|
||||
<td>
|
||||
<select name="linework_port" form="term-form-<?=$term->id?>" class="form-control">
|
||||
<?php foreach($term->getLineworkportPairs() as $ports): ?>
|
||||
<?php foreach($ports as $p): ?>
|
||||
<option value="<?=$p?>" <?=($term->patching->linework_ports == $p) ? "selected='selected'" : ""?>><?=$p?></option>
|
||||
<?php endforeach; ?>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select name="device_type" form="term-form-<?=$term->id?>" class="form-control">
|
||||
<option></option>
|
||||
<option value="splitter" <?=($term->patching->device_type == "splitter") ? "selected='selected'" : ""?>>Splitter</option>
|
||||
<option value="pon" <?=($term->patching->device_type == "pon") ? "selected='selected'" : ""?>>Shared PON-Port</option>
|
||||
<option value="switch" <?=($term->patching->device_type == "switch") ? "selected='selected'" : ""?>>Switch</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" class="form-control" form="term-form-<?=$term->id?>" name="device_name" value="<?=$term->patching->device_name?>" placeholder="Splitte / Gerät" /></td>
|
||||
<td><input type="text" class="form-control" form="term-form-<?=$term->id?>" name="device_port" value="<?=$term->patching->device_port?>" placeholder="Port" /></td>
|
||||
<td><input type="checkbox" class="form-control" form="term-form-<?=$term->id?>" name="patched" value="1" <?=($term->patching->patched == 1) ? "checked='checked'" : ""?> /></td>
|
||||
<td <?=($term->patching->patched == 1 && $term->patching->patched_by) ? "title='Gepatched: ".date("d.m.Y H:i",$term->patching->patched_date)." von ".$term->patching->patcher->name." (".$term->patching->patcher->address->getCompanyOrName(true).")'" : ""?>>
|
||||
|
||||
<?php if($term->patching->patched == 1 && $term->patching->patched_by): ?>
|
||||
<?=($term->patching->patcher) ? $term->patching->patcher->getAbbrName() : ""?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<form method="post" id="term-form-<?=$term->id?>" action="<?=self::getUrl("Patching","save", ["s" => $pagination['start'], "filter" => $filter])?>">
|
||||
<input type="hidden" name="termination_id" value="<?=$term->id?>" />
|
||||
<input type="submit" class="btn btn-primary" value="Speichern" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
|
||||
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination-summary.php"); ?>
|
||||
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination.php"); ?>
|
||||
</div>
|
||||
<form method="get" action="<?=e(self::getUrl("Patching"))?>">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12 col-md-6 col-lg-2 mb-3">
|
||||
<label class="form-label" for="filter_network_id">Netzgebiet</label>
|
||||
<select name="filter[network_id]" id="filter_network_id" class="form-control">
|
||||
<option value=""></option>
|
||||
<?php foreach($mynetworks as $fnet): ?>
|
||||
<option value="<?=e($fnet->id)?>" <?=($filter['network_id'] ?? null == $fnet->id) ? "selected='selected'" : ""?>><?=e($fnet->name)?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6 col-lg-2 mb-3">
|
||||
<label class="form-label" for="filter_pop_id">POP</label>
|
||||
<select name="filter[pop_id]" id="filter_pop_id" class="form-control">
|
||||
<option value=""></option>
|
||||
<?php foreach($mynetworks as $fnet): ?>
|
||||
<?php if(is_array($fnet->pops) && count($fnet->pops)): ?>
|
||||
<optgroup label="<?=e($fnet->name)?>">
|
||||
<?php foreach($fnet->pops as $pop): ?>
|
||||
<option value="<?=e($pop->id)?>" <?=($filter['pop_id'] ?? null == $pop->id) ? "selected='selected'" : ""?>><?=e($pop->name)?></option>
|
||||
<?php endforeach; ?>
|
||||
</optgroup>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6 col-lg-2 mb-3">
|
||||
<label class="form-label" for="filter_patched">Patchstatus</label>
|
||||
<select name="filter[patched]" id="filter_patched" class="form-control">
|
||||
<option value="0" <?=((int)($filter['patched'] ?? 0) < 1) ? "selected='selected'" : ""?>>Nicht gepatched</option>
|
||||
<option value="1" <?=((int)($filter['patched'] ?? 0) == 1) ? "selected='selected'" : ""?>>Gepatched</option>
|
||||
<option value="2" <?=((int)($filter['patched'] ?? 0) == 2) ? "selected='selected'" : ""?>>Vorpatchen</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6 col-lg-2 mb-3">
|
||||
<label class="form-label" for="filter_hide_delayed_finish">Verzögerte Herstellung</label>
|
||||
<select name="filter[hide_delayed_finish]" id="filter_hide_delayed_finish" class="form-control">
|
||||
<option value="0" <?=(isset($filter["hide_delayed_finish"]) && $filter['hide_delayed_finish'] != 1) ? "selected='selected'" : ""?>>Anzeigen</option>
|
||||
<option value="1" <?=(!isset($filter["hide_delayed_finish"]) || $filter['hide_delayed_finish'] == 1) ? "selected='selected'" : ""?>>Nicht anzeigen</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6 col-lg-1 mb-3">
|
||||
<label class="form-label" for="filter_code">Objekt ID</label>
|
||||
<input type="text" class="form-control" name="filter[code]" id="filter_code" value="<?=e($filter['code'] ?? '')?>" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6 col-lg-3 mb-3">
|
||||
<label class="form-label" for="filter_street">Straße</label>
|
||||
<input type="text" class="form-control" name="filter[street]" id="filter_street" value="<?=e($filter['street'] ?? '')?>" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary">Filter anwenden</button>
|
||||
<a class="btn btn-secondary" href="<?=e(self::getUrl("Patching"))?>">Filter zurücksetzen</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body mb-3">
|
||||
<h4 class="header-title">Patchungen</h4>
|
||||
|
||||
<?php // Include pagination controls - Top ?>
|
||||
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination.php"); ?>
|
||||
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination-summary.php"); ?>
|
||||
|
||||
<div class="table-responsive mt-3"> <?php // Added table-responsive wrapper and margin-top ?>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-bordered"> <?php // Added thead and border class ?>
|
||||
<tr>
|
||||
<th colspan="4" class="text-center">Standort</th>
|
||||
<th colspan="2" class="text-center">ODF</th>
|
||||
<th colspan="5" class="text-center">Abschluss/Device</th>
|
||||
<th>Aktion</th> <?php // Renamed last header ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Netzgebiet</th>
|
||||
<th class="text-nowrap">POP</th> <?php // Added text-nowrap for min-width effect ?>
|
||||
<th class="text-nowrap">Kunde</th> <?php // Added text-nowrap ?>
|
||||
<th>Standort</th> <?php // Allow wrapping ?>
|
||||
<th class="text-nowrap">Patchposition ODF</th> <?php // Added text-nowrap ?>
|
||||
<th style="min-width: 105px">ODF Port</th> <?php // Select handles resize ok ?>
|
||||
<th style="min-width: 195px">Typ</th> <?php // Select handles resize ok ?>
|
||||
<th style="min-width: 190px" class="text-nowrap">Splitter / Gerät</th> <?php // Added text-nowrap ?>
|
||||
<th style="min-width: 90px" class="text-nowrap">Port</th> <?php // Added text-nowrap ?>
|
||||
<th>Gepatched</th>
|
||||
<th class="text-nowrap">Von</th> <?php // Added text-nowrap ?>
|
||||
<th class="text-nowrap">Speichern</th> <?php // Added text-nowrap ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($terminations as $term): ?>
|
||||
<?php
|
||||
// --- Prepare complex data for cleaner output ---
|
||||
|
||||
// ODF Patch Position
|
||||
$wfItems = $term->workflowitems ?? [];
|
||||
$istSchrankValue = $wfItems["ist_schrank"]->value ?? null;
|
||||
$schrankValue = $wfItems["schrank"]->value ?? null;
|
||||
$istBaugruppeValue = $wfItems["ist_baugruppe"]->value ?? null;
|
||||
$baugruppeValue = $wfItems["baugruppe"]->value ?? null;
|
||||
$istModulValue = $wfItems["ist_modul"]->value ?? null;
|
||||
$modulValue = $wfItems["modul"]->value ?? null;
|
||||
$istPortsValue = $wfItems["ist_ports"]->value ?? null;
|
||||
$portsValue = $wfItems["ports"]->value ?? null;
|
||||
|
||||
$schrank = ($istSchrankValue && $istSchrankValue->id) ? $istSchrankValue->value_string : ($schrankValue ? $schrankValue->value_string : '');
|
||||
$baugruppe = ($istBaugruppeValue && $istBaugruppeValue->id) ? $istBaugruppeValue->value_string : ($baugruppeValue ? $baugruppeValue->value_string : '');
|
||||
$modul = ($istModulValue && $istModulValue->id) ? $istModulValue->value_string : ($modulValue ? $modulValue->value_string : '');
|
||||
$ports = ($istPortsValue && $istPortsValue->id) ? $istPortsValue->value_string : ($portsValue ? $portsValue->value_string : '');
|
||||
|
||||
$odfPositionTitle = "Schrank: " . e($schrank) . " / Einschub: " . e($baugruppe) . " / Modul: " . e($modul) . " / Port: " . e($ports);
|
||||
$odfPositionDisplay = e($schrank) . " / " . e($baugruppe) . " / " . e($modul) . " / " . e($ports);
|
||||
|
||||
// Patched By Info
|
||||
$patching = $term->patching ?? null;
|
||||
$patcher = $patching ? ($patching->patcher ?? null) : null;
|
||||
$patchedByTitle = '';
|
||||
$patchedByDisplay = '';
|
||||
if ($patching && $patching->patched == 1 && $patching->patched_by && $patcher) {
|
||||
$patcherName = $patcher->name ?? 'Unbekannt';
|
||||
$patcherCompany = $patcher->address ? $patcher->address->getCompanyOrName(true) : 'N/A';
|
||||
$patchedDate = date("d.m.Y H:i", $patching->patched_date);
|
||||
$patchedByTitle = "Gepatched: {$patchedDate} von " . e($patcherName) . " (" . e($patcherCompany) . ")";
|
||||
$patchedByDisplay = $patcher->getAbbrName() ?? '';
|
||||
}
|
||||
|
||||
// Customer Info
|
||||
$customer = $term->order->owner ?? null;
|
||||
$customerNumber = $customer ? $customer->customer_number : '';
|
||||
$customerName = $customer ? $customer->getCompanyOrName() : '';
|
||||
|
||||
// Location Info
|
||||
$building = $term->building ?? null;
|
||||
$networkName = $building ? ($building->network->name ?? '') : '';
|
||||
$popName = '';
|
||||
$termPop = $term->getPop(); // Call potentially expensive method once
|
||||
if ($termPop) {
|
||||
$popName = $termPop->name ?? '';
|
||||
} elseif ($building && $building->pop) {
|
||||
$popName = $building->pop->name ?? '';
|
||||
}
|
||||
$street = $building->street ?? '';
|
||||
$zip = $building->zip ?? '';
|
||||
$city = $building->city ?? '';
|
||||
|
||||
// Linework Ports
|
||||
$lineworkPorts = $term->getLineworkportPairs() ?? [];
|
||||
$currentLineworkPort = $patching ? $patching->linework_ports : null;
|
||||
|
||||
// Device Info
|
||||
$deviceType = $patching ? $patching->device_type : '';
|
||||
$deviceName = $patching ? $patching->device_name : '';
|
||||
$devicePort = $patching ? $patching->device_port : '';
|
||||
$isPatched = $patching ? ($patching->patched == 1) : false;
|
||||
?>
|
||||
<tr class="align-middle"> <?php // Added align-middle ?>
|
||||
<td><?=e($networkName)?></td>
|
||||
<td><?=e($popName)?></td>
|
||||
<td><?=e($customerNumber)?><br /><?=e($customerName)?></td>
|
||||
<td>
|
||||
<?=e($street)?><br />
|
||||
<?=e($zip)?> <?=e($city)?>
|
||||
</td>
|
||||
<td class="text-mono text-primary" title="<?=$odfPositionTitle?>">
|
||||
<?=$odfPositionDisplay?>
|
||||
</td>
|
||||
<td>
|
||||
<select name="linework_port" form="term-form-<?=e($term->id)?>" class="form-control"> <?php // Removed -sm ?>
|
||||
<?php foreach($lineworkPorts as $portGroup): ?>
|
||||
<?php foreach($portGroup as $p): ?>
|
||||
<option value="<?=e($p)?>" <?=($currentLineworkPort == $p) ? "selected='selected'" : ""?>><?=e($p)?></option>
|
||||
<?php endforeach; ?>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select name="device_type" form="term-form-<?=e($term->id)?>" class="form-control"> <?php // Removed -sm ?>
|
||||
<option value=""></option>
|
||||
<option value="splitter" <?=($deviceType == "splitter") ? "selected='selected'" : ""?>>Splitter</option>
|
||||
<option value="pon" <?=($deviceType == "pon") ? "selected='selected'" : ""?>>Shared PON-Port</option>
|
||||
<option value="switch" <?=($deviceType == "switch") ? "selected='selected'" : ""?>>Switch</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" class="form-control device-name-input" form="term-form-<?=e($term->id)?>" name="device_name" value="<?=e($deviceName)?>" placeholder="Splitter / Gerät" /></td> <?php // Removed -sm ?>
|
||||
<td><input type="text" class="form-control" form="term-form-<?=e($term->id)?>" name="device_port" value="<?=e($devicePort)?>" placeholder="Port" /></td> <?php // Removed -sm ?>
|
||||
<td class="text-center"> <?php // Added text-center ?>
|
||||
<input type="hidden" name="patched" value="0" form="term-form-<?=e($term->id)?>" /> <?php // Ensure 0 is sent if checkbox is unchecked ?>
|
||||
<input type="checkbox" class="form-check-input" style="position:unset !important;height: 2em; width: 2em;" form="term-form-<?=e($term->id)?>" name="patched" value="1" <?=($isPatched) ? "checked='checked'" : ""?> /> <?php // Adjusted checkbox style slightly ?>
|
||||
</td>
|
||||
<td <?=!empty($patchedByTitle) ? 'title="' . $patchedByTitle . '"' : ''?>>
|
||||
<?=e($patchedByDisplay)?>
|
||||
</td>
|
||||
<td>
|
||||
<form method="post" id="term-form-<?=e($term->id)?>" action="<?=e(self::getUrl("Patching","save", ["s" => $pagination['start'] ?? 0, "filter" => $filter]))?>">
|
||||
<input type="hidden" name="termination_id" value="<?=e($term->id)?>" />
|
||||
<button type="submit" class="btn btn-primary">Speichern</button> <?php // Changed value to button text, removed -sm ?>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div> <?php // End table-responsive ?>
|
||||
|
||||
<?php // Include pagination controls - Bottom ?>
|
||||
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination-summary.php"); ?>
|
||||
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination.php"); ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include(realpath(dirname(__FILE__)."/../../$mfLayoutPackage")."/footer.php"); ?>
|
||||
<style>
|
||||
@media (max-width: 768px) {
|
||||
.pagination {
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-selection__rendered {
|
||||
line-height: 38px !important;
|
||||
}
|
||||
.select2-container .select2-selection--single {
|
||||
height: 38px !important;
|
||||
}
|
||||
.select2-selection__arrow {
|
||||
height: 37px !important;
|
||||
}
|
||||
|
||||
@media all and (display-mode: standalone) {
|
||||
#topnav {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
window.DEVICES = <?= json_encode($devices) ?>;
|
||||
window.DEVICES = window.DEVICES.filter(device => {
|
||||
return device.name.toLowerCase().includes('olt') || device.name.toLowerCase().includes('pon')
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/Patching/sw', { scope: '/' })
|
||||
.then(registration => {
|
||||
console.log('Patching PWA Service Worker registered with scope:', registration.scope);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Patching PWA Service Worker registration failed:', error);
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof $().select2 === 'undefined') {
|
||||
console.error('Select2 library is not loaded.');
|
||||
// Optionally display an error to the user or fall back gracefully
|
||||
return; // Stop execution if Select2 is missing
|
||||
}
|
||||
|
||||
const ipNumRegex = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})[/-](\d+)$/;
|
||||
|
||||
// Helper: Find device object by IP
|
||||
function findDeviceByIp(ip) {
|
||||
if (!window.DEVICES || !Array.isArray(window.DEVICES)) {
|
||||
console.error('DEVICES array not found or invalid.');
|
||||
return null;
|
||||
}
|
||||
return window.DEVICES.find(device => device.ip === ip);
|
||||
}
|
||||
|
||||
// Helper: Update the original hidden input field's value
|
||||
function updateOriginalInput($originalInput, ip, number) {
|
||||
const cleanNumber = String(number).trim(); // Ensure number is a string and trimmed
|
||||
if (ip && cleanNumber) {
|
||||
$originalInput.val(ip + '/' + cleanNumber).trigger('change'); // Update and trigger change
|
||||
} else {
|
||||
$originalInput.val('').trigger('change'); // Clear if incomplete and trigger change
|
||||
}
|
||||
}
|
||||
|
||||
// --- Initialize for each device name input ---
|
||||
$('.device-name-input').each(function() {
|
||||
const $originalInput = $(this);
|
||||
const $td = $originalInput.closest('td'); // Get the parent cell for structure
|
||||
let initialValue = $originalInput.val().trim();
|
||||
|
||||
// Create wrapper for the Select2 component (Select + Number Input)
|
||||
// Hide it initially
|
||||
const $selectWrapper = $('<div class="device-select-wrapper" style="display: none; align-items: center; gap: 5px;"></div>');
|
||||
const $select = $('<select class="form-control device-select" style="flex-grow: 1; width: auto;"></select>'); // Let flexbox handle width
|
||||
const $numberInput = $('<input type="number" class="form-control device-number" placeholder="Nr." style="width: 70px; flex-grow: 0; flex-shrink: 0;">'); // Fixed width number input
|
||||
|
||||
// Populate Select options
|
||||
$select.append('<option value="">Gerät auswählen...</option>'); // Placeholder
|
||||
if (window.DEVICES && Array.isArray(window.DEVICES)) {
|
||||
window.DEVICES.forEach(device => {
|
||||
// Display name and IP for clarity in dropdown
|
||||
const displayText = `${device.name} (${device.ip})`;
|
||||
$select.append($('<option></option>').val(device.ip).text(displayText));
|
||||
});
|
||||
} else {
|
||||
console.warn("DEVICES array is missing or invalid for row:", $originalInput.attr('form'));
|
||||
// Optionally add a disabled option indicating an error
|
||||
$select.append('<option value="" disabled>Device list error</option>');
|
||||
}
|
||||
|
||||
|
||||
$selectWrapper.append($select).append($numberInput);
|
||||
$td.append($selectWrapper); // Add the hidden wrapper to the cell
|
||||
|
||||
// Function to show Select2 component and hide original input
|
||||
function showSelectComponent(ip, number) {
|
||||
$originalInput.hide();
|
||||
$select.val(ip || '').trigger('change.select2'); // Set value and update Select2 display
|
||||
$numberInput.val(number || '');
|
||||
$selectWrapper.css('display', 'flex'); // Use flex to show side-by-side
|
||||
}
|
||||
|
||||
// Function to show original input and hide Select2 component
|
||||
function showOriginalInput() {
|
||||
$selectWrapper.hide();
|
||||
$originalInput.show();
|
||||
}
|
||||
|
||||
// Initial state determination
|
||||
const match = initialValue.match(ipNumRegex);
|
||||
if (match) {
|
||||
const initialIp = match[1];
|
||||
const initialNumber = match[2];
|
||||
const foundDevice = findDeviceByIp(initialIp);
|
||||
if (foundDevice) {
|
||||
// Valid format and known device -> Show Select2 component
|
||||
showSelectComponent(initialIp, initialNumber);
|
||||
} else {
|
||||
// Valid format but unknown device -> Show original input
|
||||
showOriginalInput();
|
||||
}
|
||||
} else if (!initialValue) {
|
||||
// Empty -> Show Select2 component (empty)
|
||||
showSelectComponent(null, null);
|
||||
} else {
|
||||
// Invalid format -> Show original input
|
||||
showOriginalInput();
|
||||
}
|
||||
|
||||
// Initialize Select2 *after* adding options and inserting into DOM
|
||||
$select.select2({
|
||||
width: 'style', // Adjust width based on container
|
||||
// dropdownParent: $td // Optional: Attach dropdown to cell if needed for positioning
|
||||
});
|
||||
|
||||
// --- Event Listeners ---
|
||||
|
||||
// Update original input when Select2 or number changes
|
||||
$select.on('change', function() {
|
||||
const selectedIp = $(this).val();
|
||||
const currentNumber = $numberInput.val();
|
||||
updateOriginalInput($originalInput, selectedIp, currentNumber);
|
||||
});
|
||||
|
||||
$numberInput.on('input change', function() { // Use 'input change' for better responsiveness
|
||||
const currentIp = $select.val();
|
||||
const selectedNumber = $(this).val();
|
||||
updateOriginalInput($originalInput, currentIp, selectedNumber);
|
||||
});
|
||||
|
||||
// Check original input on change/blur to potentially switch view
|
||||
$originalInput.on('change blur', function() {
|
||||
const currentValue = $(this).val().trim();
|
||||
const currentMatch = currentValue.match(ipNumRegex);
|
||||
if (currentMatch) {
|
||||
const currentIp = currentMatch[1];
|
||||
const currentNumber = currentMatch[2];
|
||||
const device = findDeviceByIp(currentIp);
|
||||
if (device) {
|
||||
// Switched to valid/known format -> Show Select2
|
||||
showSelectComponent(currentIp, currentNumber);
|
||||
}
|
||||
// else: Keep showing original input (valid format, but unknown device)
|
||||
}
|
||||
// else: Keep showing original input (invalid format)
|
||||
});
|
||||
|
||||
}); // End .each loop
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<?php include(realpath(dirname(__FILE__)."/../../$mfLayoutPackage")."/footer.php"); ?>
|
||||
@@ -74,7 +74,7 @@
|
||||
<?php if(array_key_exists("status_id", $filter)): ?>
|
||||
<?=($filter['status_id'] == $status->id) ? "selected='selected'" : ""?>
|
||||
<?php else: ?>
|
||||
<?=($status->id == 3) ? "selected='selected'" : ""?>
|
||||
<?=(!in_array($me->id, ["145","62","56"]) && $status->id == 3) ? "selected='selected'" : ""?>
|
||||
<?php endif; ?>
|
||||
>
|
||||
<?=$status->code?> - <?=__($status->name."-b")?></option>
|
||||
@@ -91,6 +91,15 @@
|
||||
<label class="form-label" for="filter_street">Straße</label>
|
||||
<input type="text" class="form-control" name="filter[street]" id="filter_street" value="<?=$filter['street']?>" />
|
||||
</div>
|
||||
|
||||
<div class="col-2">
|
||||
<label class="form-label" for="filter_pipework_enabled">Baufreigabe</label>
|
||||
<select name="filter[pipework_enabled]" id="filter_pipework_enabled" class="form-control">
|
||||
<option value="">Alle</option>
|
||||
<option value="1" <?=(array_key_exists("pipework_enabled", $filter) && $filter["pipework_enabled"] == "1") ? "selected='selected'" : ""?>>Ja</option>
|
||||
<option value="0" <?=(array_key_exists("pipework_enabled", $filter) && $filter["pipework_enabled"] == "0") ? "selected='selected'" : ""?>>Nein</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -99,7 +108,10 @@
|
||||
<div class="row mt-2">
|
||||
<div>
|
||||
<button type="submit" class="btn btn-primary">Filter anwenden</button>
|
||||
<a class="btn btn-secondary" href="<?=self::getUrl("Pipework")?>">Filter zurücksetzen</a>
|
||||
<a class="btn btn-secondary" href="<?=self::getUrl("Pipework")?>">Filter zurücksetzen</a>
|
||||
<?php if ($me->is(["Admin"])) :?>
|
||||
<a class="btn btn-outline-secondary" href="<?=self::getUrl("Pipework", "History")?>">Zur Historie</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div style="width: 512px;">
|
||||
<div class="row">
|
||||
|
||||
@@ -322,8 +322,8 @@ $pagination_entity_name = "Vorbestellungen";
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-2">
|
||||
<label class="form-label" for="filter_rimo_workorder">Rimo Workorder Status</label>
|
||||
<div class="col-sm-12 col-md-1">
|
||||
<label class="form-label" for="filter_rimo_workorder">Workorder Status</label>
|
||||
<select name="filter[rimo_workorder_status][]" id="filter_rimo_workorder_status" multiple class="form-control">
|
||||
<option value="Clarify" <?=(isset($filter) && array_key_exists("rimo_workorder_status", $filter) && is_array($filter['rimo_workorder_status']) && in_array("Clarify", $filter['rimo_workorder_status'])) ? "selected='selected'" : ""?>>Clarify</option>
|
||||
<option value="Accepted" <?=(isset($filter) && array_key_exists("rimo_workorder_status", $filter) && is_array($filter['rimo_workorder_status'])&& in_array("Accepted", $filter['rimo_workorder_status'])) ? "selected='selected'" : ""?>>Accepted</option>
|
||||
@@ -335,6 +335,13 @@ $pagination_entity_name = "Vorbestellungen";
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-1">
|
||||
<label class="form-label" for="filter_fcp">FCP</label>
|
||||
<select name="filter[fcp][]" id="filter_fcp" multiple class="form-control">
|
||||
<option value="">Kein FCP gefunden</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-2">
|
||||
<label class="form-label" for="filter_rimo_workorder_team_id">Rimo Workorder Assigned Team</label>
|
||||
<select name="filter[rimo_workorder_team_id]" id="filter_rimo_workorder_team_id" class="form-control">
|
||||
@@ -848,6 +855,8 @@ $pagination_entity_name = "Vorbestellungen";
|
||||
|
||||
});
|
||||
|
||||
//fetch fcps and show on map
|
||||
getFCPs(preorderMap);
|
||||
|
||||
// calculate center position
|
||||
mapCenterPos = GetCenterFromDegrees(all_coords);
|
||||
@@ -856,6 +865,23 @@ $pagination_entity_name = "Vorbestellungen";
|
||||
return true;
|
||||
}
|
||||
|
||||
async function getFCPs(map) {
|
||||
var fcp = await $.get("<?=self::getUrl("Preorder", "Api")?>", {
|
||||
do: "getFCPsForCampaign",
|
||||
campaign_id: "<?=$campaign->id?>"
|
||||
});
|
||||
|
||||
if(fcp.status == "OK") {
|
||||
fcp.result.forEach((fcp) => {
|
||||
var icon = L.MakiMarkers.icon({icon: "viewpoint", color: "yellow", size: "m"});
|
||||
var marker = L.marker([fcp.lat, fcp.lng], {icon: icon}).addTo(map);
|
||||
var google_maps_link = "https://www.google.com/maps/search/?api=1&query=" + fcp.lat + "," + fcp.lng;
|
||||
var popup_content = "<a href='" + google_maps_link + "' target='_blank'>Google Maps</a><br />" + fcp.text;
|
||||
marker.bindPopup(popup_content);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function centerMap() {
|
||||
preorderMap.setView(mapCenterPos, 12);
|
||||
}
|
||||
@@ -1483,4 +1509,45 @@ $pagination_entity_name = "Vorbestellungen";
|
||||
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
const fcpSelect = $("#filter_fcp");
|
||||
const campaignSelect = $("#filter_preordercampaign_id");
|
||||
const apiUrl = "<?=self::getUrl("Preorder", "Api")?>";
|
||||
|
||||
fcpSelect.select2({ data: [], placeholder: "Bitte Kampagne auswählen", allowClear: true });
|
||||
|
||||
campaignSelect.on("change", function() {
|
||||
const campaign_id = $(this).val();
|
||||
if (!campaign_id) {
|
||||
fcpSelect.empty().select2({ data: [], placeholder: "Bitte Kampagne auswählen", allowClear: true });
|
||||
return;
|
||||
}
|
||||
$.get(apiUrl, { do: "getFCPsForCampaign", campaign_id: campaign_id }, (success) => {
|
||||
let fcpData = [];
|
||||
let opts = { data: [], placeholder: "Bitte Kampagne auswählen", allowClear: true };
|
||||
if (success?.status === "OK" && Array.isArray(success.result)) {
|
||||
fcpData = success.result;
|
||||
fcpData.unshift({ id: "", text: "" });
|
||||
fcpData.sort((a, b) => {
|
||||
const aN = a.text.replace(/\D/g, ""), bN = b.text.replace(/\D/g, "");
|
||||
return aN && bN ? parseInt(aN, 10) - parseInt(bN, 10) : a.text.localeCompare(b.text);
|
||||
});
|
||||
opts = { data: fcpData, placeholder: "", allowClear: true };
|
||||
fcpSelect.empty().select2(opts);
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const fcpValues = searchParams.getAll("filter[fcp][]");
|
||||
if (fcpValues && fcpValues.length > 0) {
|
||||
fcpSelect.val(fcpValues).trigger("change");
|
||||
}
|
||||
} else {
|
||||
fcpSelect.empty().select2(opts);
|
||||
}
|
||||
}, "json").fail(() => {
|
||||
fcpSelect.empty().select2({ data: [], placeholder: "Fehler", allowClear: true });
|
||||
});
|
||||
});
|
||||
campaignSelect.trigger("change");
|
||||
});
|
||||
</script>
|
||||
<?php include(realpath(dirname(__FILE__)."/../../$mfLayoutPackage")."/footer.php"); ?>
|
||||
@@ -12,6 +12,7 @@ if(!$no_filename) {
|
||||
$status_flags_header = [];
|
||||
foreach(PreorderStatusflagModel::getAll() as $sflag) {
|
||||
$status_flags_header[$sflag->code] = $sflag->code;
|
||||
$status_flags_header[$sflag->code . " Datum"] = $sflag->code . " Datum";
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -35,6 +36,16 @@ while($data = mysqli_fetch_object($res)):
|
||||
$statusflags = [];
|
||||
foreach($preorder->statusflags as $sflag) {
|
||||
$statusflags[$sflag->code] = $sflag->value->value ? 1 : 0;
|
||||
if($sflag->value->value) {
|
||||
$historyEntry = PreorderHistoryModel::getFirst([
|
||||
"preorder_id" => $preorder->id,
|
||||
"key" => "preorderstatusflag-".$sflag->id."-value",
|
||||
"new_value" => 1
|
||||
]);
|
||||
$statusflags[$sflag->code . " Datum"] = ($historyEntry) ? date("Y-m-d H:i:s", $historyEntry->create) : "";
|
||||
} else {
|
||||
$statusflags[$sflag->code . " Datum"] = "";
|
||||
}
|
||||
}
|
||||
|
||||
$discounts = [];
|
||||
|
||||
@@ -416,6 +416,33 @@
|
||||
<td class="text-monospace"><?=$preorder->adb_wohneinheit->ftu_data["id"]?>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>FCP</h3>
|
||||
<?php
|
||||
if($preorder->fcp): ?>
|
||||
<table class="table table-sm table-striped">
|
||||
<tr>
|
||||
<th>FCP Name:</th>
|
||||
<td class="text-monospace"><?=$preorder->fcp->name?>
|
||||
</tr><tr>
|
||||
<th>FCP External ID:</th>
|
||||
<td class="text-monospace"><?=$preorder->fcp->rimo_id?>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>FCP Execution State:</th>
|
||||
<td class="text-monospace"><?=$preorder->fcp->rimo_ex_state?>
|
||||
</tr><tr>
|
||||
<th>FCP Operational State:</th>
|
||||
<td class="text-monospace"><?=($preorder->fcp->rimo_op_state != "Undefined") ? $preorder->fcp->rimo_op_state : ""?>
|
||||
</tr><tr>
|
||||
<th>FCP Building Type:</th>
|
||||
<td class="text-monospace"><?=$preorder->fcp->building_type?>
|
||||
</tr>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<p>Kein FCP zugewiesen</p>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -73,6 +73,10 @@
|
||||
<?php else: ?>
|
||||
<a class="btn btn-secondary" href="<?=self::getUrl("Preorderlogistics")?>">Filter zurücksetzen</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<button id="printAllInView" type="button" class="btn btn-success">Alle in der Liste drucken</button>
|
||||
<button id="csvExportAddressesAndMarkAsSent" type="button" class="btn btn-success">CSV Export für Versand</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -106,7 +110,16 @@
|
||||
<th></th>
|
||||
</tr>
|
||||
<?php foreach($preorders as $preorder): ?>
|
||||
<tr class="preorder-list-tr" id="preorder-<?=$preorder->id?>">
|
||||
<tr class="preorder-list-tr" id="preorder-<?=$preorder->id?>"
|
||||
data-ucode="<?=$preorder->ucode?>"
|
||||
data-oaid="<?=$preorder->oaid?>"
|
||||
data-addr-name="<?=$preorder->company ? $preorder->company : $preorder->firstname." ".$preorder->lastname?>"
|
||||
data-addr-street="<?=$preorder->street?><?=($preorder->housenumber) ? " ".$preorder->housenumber : ""?>"
|
||||
data-addr-zip="<?=$preorder->zip?>"
|
||||
data-addr-city="<?=$preorder->city?>"
|
||||
data-phone="<?=$preorder->phone?>"
|
||||
data-email="<?=$preorder->email?>"
|
||||
>
|
||||
<td class="text-right align-middle"><button type="button" class="btn btn-sm btn-success font-weight-bold" onclick="printShippingSlip(<?=$preorder->id?>)"><i class="fas fa-fw fa-print"></i> DRUCKEN</button> </td>
|
||||
<td class="text-center align-middle">
|
||||
<label>Versandt <i class="fas fa-check text-success hidden" id="sent-label-<?=$preorder->id?>"></i>
|
||||
@@ -197,4 +210,108 @@
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function printAllInView() {
|
||||
console.log("printAllInView");
|
||||
$("#printAllInView").html('<i class="fas fa-spinner fa-spin"></i> Bitte warten...');
|
||||
$("#printAllInView").prop("disabled", true);
|
||||
|
||||
var preorderIds = [];
|
||||
$(".preorder-list-tr").each(function() {
|
||||
var id = $(this).attr("id").replace("preorder-", "");
|
||||
preorderIds.push(id);
|
||||
});
|
||||
|
||||
var printUrls = preorderIds.map(function(id) {
|
||||
return "<?=self::getUrl("Preorderlogistics", "print")?>?id=" + id;
|
||||
});
|
||||
|
||||
var printWindows = [];
|
||||
var loadedCount = 0;
|
||||
var combinedHtml = "";
|
||||
|
||||
function loadAndCombine(url, index) {
|
||||
$.get(url, function(html) {
|
||||
combinedHtml += html;
|
||||
loadedCount++;
|
||||
|
||||
if (loadedCount === printUrls.length) {
|
||||
var combinedWindow = window.open("", "_blank");
|
||||
combinedWindow.document.write(combinedHtml);
|
||||
combinedWindow.document.close();
|
||||
// sleep for 1 second to allow the window to load
|
||||
setTimeout(function() {
|
||||
combinedWindow.focus();
|
||||
combinedWindow.print();
|
||||
}, 1000);
|
||||
|
||||
$("#printAllInView").html('Alle in der Liste drucken');
|
||||
$("#printAllInView").prop("disabled", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
printUrls.forEach(loadAndCombine);
|
||||
}
|
||||
$("#printAllInView").on("click", printAllInView);
|
||||
|
||||
async function csvExportAddressesAndMarkAsSent(){
|
||||
//show confirmation dialog to the user browser api
|
||||
if(!confirm("Möchten Sie die Adressen als CSV exportieren und als versendet markieren?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("#csvExportAddressesAndMarkAsSent").html('<i class="fas fa-spinner fa-spin"></i> Bitte warten...');
|
||||
|
||||
// show a confirmation dialog to the user
|
||||
// use .preorder-list-tr and data-ucode, data-oaid, data-addr-name, data-addr-street, data-addr-zip, data-addr-city to export to csv
|
||||
// after exporting mark as sent for each preorder
|
||||
var preorderIds = [];
|
||||
$(".preorder-list-tr").each(function() {
|
||||
var id = $(this).attr("id").replace("preorder-", "");
|
||||
preorderIds.push(id);
|
||||
});
|
||||
|
||||
var csvData = "ucode,oaid,addr_name,addr_street,addr_zip,addr_city,phone,email\n";
|
||||
$(".preorder-list-tr").each(function() {
|
||||
var ucode = $(this).data("ucode");
|
||||
var oaid = $(this).data("oaid");
|
||||
var addr_name = $(this).data("addr-name");
|
||||
var addr_street = $(this).data("addr-street");
|
||||
var addr_zip = $(this).data("addr-zip");
|
||||
var addr_city = $(this).data("addr-city");
|
||||
var phone = $(this).data("phone");
|
||||
var email = $(this).data("email");
|
||||
|
||||
csvData += ucode + "," + oaid + "," + addr_name + "," + addr_street + "," + addr_zip + "," + addr_city + "," + phone + "," + email + "\n";
|
||||
});
|
||||
|
||||
// create a blob from the csv data
|
||||
var blob = new Blob([csvData], { type: "text/csv;charset=utf-8;" });
|
||||
var link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.setAttribute("download", "addresses.csv");
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
// mark all as sent
|
||||
|
||||
for (const preorderId of preorderIds) {
|
||||
await $.post("<?=self::getUrl("Preorderlogistics", "Api")?>", {
|
||||
do: "saveSent",
|
||||
id: preorderId,
|
||||
sent: 1
|
||||
});
|
||||
|
||||
$("#sent-label-" + preorderId).show();
|
||||
$("#sent-" + preorderId).prop("checked", true);
|
||||
}
|
||||
|
||||
window.notify('success', 'Adressen als CSV exportiert und als versendet markiert');
|
||||
$("#csvExportAddressesAndMarkAsSent").html('CSV Export für Versand');
|
||||
}
|
||||
$("#csvExportAddressesAndMarkAsSent").on("click", csvExportAddressesAndMarkAsSent);
|
||||
|
||||
</script>
|
||||
|
||||
<?php include(realpath(dirname(__FILE__)."/../../$mfLayoutPackage")."/footer.php"); ?>
|
||||
|
||||
@@ -17,17 +17,18 @@
|
||||
<style>
|
||||
body {
|
||||
margin-top: 0;
|
||||
padding-top: 20pt;
|
||||
font-family: "Calibri", "Open Sans", "Dejavu Sans", dejavusans, sans-serif;
|
||||
font-size: 12pt;
|
||||
font-size: 11pt;
|
||||
}
|
||||
.container {
|
||||
padding-top: 20pt;
|
||||
margin-left: 64pt;
|
||||
margin-right: 64pt;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<div class="container">
|
||||
<div style="text-align: right;">
|
||||
<img src="<?=self::getResourcePath()?>assets/images/rml-logo-header.png" style="height:80pt;" />
|
||||
@@ -42,8 +43,9 @@
|
||||
<?php endif; ?>
|
||||
<?php if($preorder->lastname): ?>
|
||||
<?=$preorder->firstname?> <?=$preorder->lastname?><br />
|
||||
|
||||
<?php endif; ?>
|
||||
<?=$preorder->street?><br />
|
||||
<?=$preorder->street?> <?=$preorder->housenumber?><br />
|
||||
<?=$preorder->zip?> <?=$preorder->city?>
|
||||
</p>
|
||||
<p style="text-align: right; padding-top: 4pt;">Liezen, <?=date("d.m.Y")?></p>
|
||||
@@ -98,11 +100,10 @@
|
||||
<div>
|
||||
<p style="font-size: 8pt">PS: Für die Region, bleibt in der Region, gehört der Region. Dieser Mehrwert zeichnet unsere regionale Glasfaser-Offensive in den 29 Gemeinden des Bezirks Liezen aus. Sie sind mit Ihrem Glasfaseranschluss jetzt ein Teil davon.</p>
|
||||
</div>
|
||||
<br><br>
|
||||
</div>
|
||||
|
||||
<img src="<?=self::getResourcePath()?>assets/images/rml-preorderlogistic-footer.png" style="width:100%;margin-top: 12pt;margin-left:4pt;margin-right:4pt" />
|
||||
</div>
|
||||
|
||||
<div style="position: absolute; bottom:0;">
|
||||
<img src="<?=self::getResourcePath()?>assets/images/rml-preorderlogistic-footer.png" style="width:100%;" />
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -21,6 +21,7 @@ $additionalCSS = [
|
||||
...$additionalCSS,
|
||||
'plugins/daterangepicker/daterangepicker.css',
|
||||
'plugins/vue/tt-components/css/tt-table.css',
|
||||
'plugins/vue/tt-components/css/tt-tooltip.css',
|
||||
'plugins/vue/tt-components/css/tt-loader.css',
|
||||
'plugins/vue/tt-components/css/tt-position-manager.css',
|
||||
];
|
||||
|
||||
@@ -75,6 +75,11 @@
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if(isset($additionalHead) && is_array($additionalHead) && count($additionalHead)):
|
||||
foreach($additionalHead as $head): ?>
|
||||
<?=$head?>
|
||||
<?php endforeach; endif;?>
|
||||
|
||||
<?php if(MFAPPNAME == "devthetool"): ?>
|
||||
<style type="text/css">
|
||||
body {
|
||||
|
||||
@@ -141,17 +141,16 @@
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if($me->is(["Admin"]) || $me->can(["Cpeprovisioning", "Cpeshipping"])): ?>
|
||||
<li class="has-submenu mobile-hide">
|
||||
<li class="has-submenu">
|
||||
<a href="#">
|
||||
<i class="fad fa-fw fa-running"></i>Netzbetrieb <div class="arrow-down"></div>
|
||||
</a>
|
||||
<ul class="submenu">
|
||||
<?php if($me->isAdmin() || $me->can("Cpeprovisioning")): ?><li><a href="<?=self::getUrl("Cpeprovisioning")?>"><i class="fad fa-fw fa-hdd text-info"></i> CPE Provisioning</a></li><?php endif; ?>
|
||||
<?php if($me->isAdmin() || $me->can("Cpeshipping")): ?><li><a href="<?=self::getUrl("Cpeshipping")?>"><i class="fad fa-fw fa-shipping-fast text-info"></i> CPE Versand</a></li><?php endif; ?>
|
||||
<?php if($me->isAdmin() || $me->can("Cpeprovisioning")): ?><li class="mobile-hide"><a href="<?=self::getUrl("Cpeprovisioning")?>"><i class="fad fa-fw fa-hdd text-info"></i> CPE Provisioning</a></li><?php endif; ?>
|
||||
<?php if($me->isAdmin() || $me->can("Cpeshipping")): ?><li class="mobile-hide"><a href="<?=self::getUrl("Cpeshipping")?>"><i class="fad fa-fw fa-shipping-fast text-info"></i> CPE Versand</a></li><?php endif; ?>
|
||||
<?php if($me->isAdmin()) : ?><li><a href="<?=self::getUrl("Domain")?>"><i class="fad fa-fw fa-globe text-info"></i> Domains</a></li><?php endif; ?>
|
||||
<?php if($me->isAdmin()) : ?><li><a href="<?=self::getUrl("IpNetwork")?>"><i class="fa-solid fa-network-wired text-info"></i> IPAM</a></li><?php endif; ?>
|
||||
<?php if($me->isAdmin()) : ?><li><a href="<?=self::getUrl("DeviceMonitoring/congestion")?>"><i class="fa-solid fa-block-brick-fire text-info"></i> Device Congestion</a></li><?php endif; ?>
|
||||
<?php if($me->isAdmin()) : ?><li><a href="<?=self::getUrl("MaintenanceNotification")?>"><i class="fa-solid fa-envelope-badge text-info"></i> Wartungsmeldungen</a></li><?php endif; ?>
|
||||
<?php if($me->isAdmin()) : ?><li class="mobile-hide"><a href="<?=self::getUrl("MaintenanceNotification")?>"><i class="fa-solid fa-envelope-badge text-info"></i> Wartungsmeldungen</a></li><?php endif; ?>
|
||||
<?php if($me->isAdmin()) : ?><li><a href="<?=self::getUrl("Radius")?>"><i class="fas fa-broadcast-tower text-info"></i> Radius</a></li><?php endif; ?>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -169,9 +168,10 @@
|
||||
<ul class="submenu">
|
||||
<?php if($me->can("WarehouseAdmin") || $me->can("WarehouseUser")): ?><li class="has-sub-submenu font-weight-bold"><a>XINON</a></li><?php endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin") || $me->can("WarehouseUser")): ?><li><a href="<?=self::getUrl("WarehouseArticle")?>"><i class="far fa-fw fa-box text-info"></i> Artikel</a></li><?php endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseItem")?>"><i class="far fa-fw fa-boxes text-info"></i> Lagerbestand (WIP)</a></li><?php endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseOrderRecommendation")?>"><i class="far fa-fw fa-box-full text-info"></i> Bestellvorschläge (WIP)</a></li><?php endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseOrder")?>"><i class="far fa-fw fa-shopping-bag text-info"></i> Bestellungen (WIP)</a></li><?php endif; ?>
|
||||
<!-- --><?php //if($me->can("WarehouseAdmin")): ?><!--<li><a href="--><?php //=self::getUrl("WarehouseItem")?><!--"><i class="far fa-fw fa-boxes text-info"></i> Lagerbestand (WIP)</a></li>--><?php //endif; ?>
|
||||
<!-- --><?php //if($me->can("WarehouseAdmin")): ?><!--<li><a href="--><?php //=self::getUrl("WarehouseOrderRecommendation")?><!--"><i class="far fa-fw fa-box-full text-info"></i> Bestellvorschläge (WIP)</a></li>--><?php //endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseOrder")?>"><i class="far fa-fw fa-shopping-bag text-info"></i> Bestellungen</a></li><?php endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseOffer")?>"><i class="far fa-fw fa-file-signature text-info"></i> Angebote</a></li><?php endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin") || $me->can("WarehouseUser")): ?><li><a href="<?=self::getUrl("WarehouseOrderRequest")?>"><i class="far fa-fw fa-shopping-cart text-info"></i> Bestellwünsche</a></li><?php endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin") || $me->can("WarehouseUser")): ?><li><a href="<?=self::getUrl("WarehouseShippingNote")?>"><i class="far fa-fw fa-shipping-fast text-info"></i> Lieferscheine</a></li><?php endif; ?>
|
||||
|
||||
|
||||
@@ -50,13 +50,21 @@
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if(MFAPPNAME == "devthetool"): ?>
|
||||
<style type="text/css">
|
||||
<?php if(isset($additionalHead) && is_array($additionalHead) && count($additionalHead)):
|
||||
foreach($additionalHead as $head): ?>
|
||||
<?=$head?>
|
||||
<?php endforeach; endif;?>
|
||||
|
||||
<style>
|
||||
<?php if(MFAPPNAME == "devthetool"): ?>
|
||||
body {
|
||||
border-left: 8px dashed #f672a7;
|
||||
}
|
||||
<?php endif; ?>
|
||||
body {
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
<?php endif; ?>
|
||||
|
||||
</head>
|
||||
|
||||
|
||||
@@ -1,195 +1,16 @@
|
||||
<?php
|
||||
|
||||
class ADBRimoFcp extends mfBaseModel {
|
||||
|
||||
protected function init() {
|
||||
$this->db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
|
||||
$this->table = "RimoFcp";
|
||||
}
|
||||
|
||||
public function getProperty($name) {
|
||||
if($this->$name == null) {
|
||||
|
||||
$classname = ucfirst($name);
|
||||
$idfield = $name."_id";
|
||||
$this->$name = new $classname($this->$idfield);
|
||||
|
||||
if($this->$name->id) {
|
||||
return $this->$name;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
/********************************
|
||||
* Begin static Model functions
|
||||
*/
|
||||
|
||||
public static function create(Array $data) {
|
||||
$model = new ADBRimoFcp();
|
||||
|
||||
$table_fields = [
|
||||
"netzgebiet_id", "name", "rimo_id", "label", "building_type", "rimo_ex_state", "rimo_op_state", "gps_lat", "gps_long",
|
||||
"create","edit"
|
||||
];
|
||||
|
||||
foreach($data as $field => $value) {
|
||||
if(in_array($field, $table_fields)) {
|
||||
$model->$field = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
public static function getFirst($filter) {
|
||||
$db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
|
||||
|
||||
$where = self::getSqlFilter($filter);
|
||||
$sql = "SELECT RimoFcp.* FROM RimoFcp
|
||||
WHERE $where
|
||||
ORDER BY name
|
||||
LIMIT 1";
|
||||
|
||||
mfLoghandler::singleton()->debug($sql);
|
||||
|
||||
$res = $db->query($sql);
|
||||
if($db->num_rows($res)) {
|
||||
$data = $db->fetch_object($res);
|
||||
$item = new ADBRimoFcp($data);
|
||||
if($item->id) {
|
||||
return $item;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getAll() {
|
||||
$items = [];
|
||||
|
||||
$db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
|
||||
|
||||
$res = $db->select("RimoFcp", "*", "1=1 ORDER BY name");
|
||||
if($db->num_rows($res)) {
|
||||
while($data = $db->fetch_object($res)) {
|
||||
$items[] = new ADBRimoFcp($data);
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static function count($filter) {
|
||||
$db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
|
||||
|
||||
$where = self::getSqlFilter($filter);
|
||||
$sql = "SELECT COUNT(*) as cnt FROM RimoFcp
|
||||
WHERE $where
|
||||
";
|
||||
|
||||
$res = $db->query($sql);
|
||||
if($db->num_rows($res)) {
|
||||
$data = $db->fetch_object($res);
|
||||
return $data->cnt;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static function search($filter, $limit = false) {
|
||||
$items = [];
|
||||
$db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
|
||||
|
||||
$where = self::getSqlFilter($filter);
|
||||
$sql = "SELECT RimoFcp.* FROM RimoFcp
|
||||
WHERE $where
|
||||
ORDER BY name";
|
||||
|
||||
mfLoghandler::singleton()->debug($sql);
|
||||
if(is_array($limit) && count($limit)) {
|
||||
if(is_numeric($limit['start']) && is_numeric($limit['count'])) {
|
||||
$sql .= " LIMIT ".$limit['start'].", ".$limit['count'];
|
||||
} elseif(is_numeric($limit['count'])) {
|
||||
$sql .= " LIMIT ".$limit['count'];
|
||||
}
|
||||
}
|
||||
|
||||
$res = $db->query($sql);
|
||||
if($db->num_rows($res)) {
|
||||
while($data = $db->fetch_object($res)) {
|
||||
$items[] = new ADBRimoFcp($data);
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
private static function getSqlFilter($filter) {
|
||||
$where = "1=1 ";
|
||||
|
||||
$db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
|
||||
|
||||
if(array_key_exists("netzgebiet_id", $filter)) {
|
||||
$netzgebiet_id = $filter['netzgebiet_id'];
|
||||
if(is_numeric($netzgebiet_id)) {
|
||||
$where .= " AND netzgebiet_id=$netzgebiet_id";
|
||||
} elseif(is_array($netzgebiet_id) && count($netzgebiet_id)) {
|
||||
$where .= " AND netzgebiet_id IN (". implode(",", $netzgebiet_id).")";
|
||||
} elseif($netzgebiet_id === null) {
|
||||
$where .= " AND netzgebiet_id IS NULL";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("name", $filter)) {
|
||||
$name = $db->escape($filter['name']);
|
||||
if($name) {
|
||||
$where .= " AND RimoFcp.name='$name'";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("label", $filter)) {
|
||||
$label = $db->escape($filter['label']);
|
||||
if($label) {
|
||||
$where .= " AND RimoFcp.label='$label'";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("building_type", $filter)) {
|
||||
$building_type = $db->escape($filter['building_type']);
|
||||
if($building_type) {
|
||||
$where .= " AND RimoFcp.building_type='$building_type'";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("rimo_ex_state", $filter)) {
|
||||
$rimo_ex_state = $db->escape($filter['rimo_ex_state']);
|
||||
if($rimo_ex_state) {
|
||||
$where .= " AND RimoFcp.rimo_ex_state='$rimo_ex_state'";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("rimo_op_state", $filter)) {
|
||||
$rimo_op_state = $db->escape($filter['rimo_op_state']);
|
||||
if($rimo_op_state) {
|
||||
$where .= " AND RimoFcp.rimo_op_state='$rimo_op_state'";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("rimo_id", $filter)) {
|
||||
$rimo_id = $db->escape($filter['rimo_id']);
|
||||
if($rimo_id) {
|
||||
$where .= " AND RimoFcp.rimo_id='$rimo_id'";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//var_dump($filter, $where);exit;
|
||||
return $where;
|
||||
}
|
||||
}
|
||||
class ADBRimoFcp extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public int $netzgebiet_id;
|
||||
public ?string $name;
|
||||
public string $rimo_id;
|
||||
public ?string $label;
|
||||
public ?string $building_type;
|
||||
public ?string $rimo_ex_state;
|
||||
public ?string $rimo_op_state;
|
||||
public ?float $gps_lat;
|
||||
public ?float $gps_long;
|
||||
public int $create;
|
||||
public int $edit;
|
||||
}
|
||||
128
application/ADBRimoFcp/ADBRimoFcpController.php
Normal file
128
application/ADBRimoFcp/ADBRimoFcpController.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
class ADBRimoFcpController extends TTCrud {
|
||||
protected string $headerTitle = 'Rimo FCPs';
|
||||
protected string $singleText = 'Rimo FCP';
|
||||
|
||||
// @formatter:off
|
||||
protected array $columns = [
|
||||
['key' => 'netzgebiet_id', 'text' => 'Netzgebiet', 'required' => true, 'modal' => ['type' => 'select', 'items' => []], 'table' => ['filter' => 'select']],
|
||||
['key' => 'name', 'text' => 'Name', 'required' => true],
|
||||
['key' => 'rimo_id', 'text' => 'Rimo ID', 'required' => true],
|
||||
['key' => 'label', 'text' => 'Label', 'required' => false],
|
||||
['key' => 'building_type', 'text' => 'Gebäudetyp', 'required' => false],
|
||||
['key' => 'rimo_ex_state', 'text' => 'Rimo Ex State', 'required' => false],
|
||||
['key' => 'rimo_op_state', 'text' => 'Rimo Op State', 'required' => false],
|
||||
['key' => 'gps_lat', 'text' => 'GPS Lat', 'required' => false],
|
||||
['key' => 'gps_long', 'text' => 'GPS Long', 'required' => false],
|
||||
['key' => 'create', 'text' => 'Erstellt', 'required' => true, 'modal' => false],
|
||||
['key' => 'edit', 'text' => 'Bearbeitet', 'required' => true, 'modal' => false, 'table' => false],
|
||||
['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center', 'priority' => 10]],
|
||||
];
|
||||
|
||||
public function prepareCrudConfig() {
|
||||
$netzgebiete = array_map(function ($netzgebiet) {
|
||||
return ['value' => $netzgebiet->id, 'text' => $netzgebiet->name];
|
||||
}, ADBNetzgebietModel::getAll());
|
||||
|
||||
$this->columns[0]['modal']['items'] = $netzgebiete;
|
||||
}
|
||||
|
||||
public function ImportFCPsAction() {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$fcpList = $input['data'] ?? [];
|
||||
$networkAreaId = $input['networkAreaId'];
|
||||
|
||||
$counts = ['new' => 0, 'upd' => 0];
|
||||
$now = date('U');
|
||||
|
||||
foreach ($fcpList as $fcpIn) {
|
||||
$rimoId = $fcpIn['ExternalID'] ?? null;
|
||||
if ($rimoId === null) $rimoId = $fcpIn['External ID'] ?? null;
|
||||
if ($rimoId === null) continue;
|
||||
|
||||
$data = [
|
||||
'netzgebiet_id' => $networkAreaId,
|
||||
'name' => $fcpIn['Name'] ?? null,
|
||||
'rimo_id' => $rimoId,
|
||||
'label' => $fcpIn['User label'] ?? null,
|
||||
'building_type' => $fcpIn['Building type'] ?? null,
|
||||
'rimo_ex_state' => $fcpIn['Execution state'] ?? null,
|
||||
'rimo_op_state' => $fcpIn['Operational state'] ?? null,
|
||||
'gps_lat' => floatval(str_replace(',', '.', $fcpIn['Latitude'] ?? '0')),
|
||||
'gps_long' => floatval(str_replace(',', '.', $fcpIn['Longitude'] ?? '0')),
|
||||
'edit' => $now
|
||||
];
|
||||
|
||||
$existing = ADBRimoFcp::getAll(['rimo_id' => $rimoId]);
|
||||
|
||||
if (count($existing) > 0 && $existing = $existing[0]) {
|
||||
$data['id'] = $existing->id;
|
||||
$data['create'] = $existing->create;
|
||||
ADBRimoFcp::update($data);
|
||||
$counts['upd']++;
|
||||
} else {
|
||||
$data['create'] = $now;
|
||||
ADBRimoFcp::create($data);
|
||||
$counts['new']++;
|
||||
}
|
||||
}
|
||||
|
||||
$msg = sprintf('%d new, %d updated FCPs.', $counts['new'], $counts['upd']);
|
||||
self::returnJson(['success' => true, 'message' => $msg]);
|
||||
}
|
||||
|
||||
public function ImportLocationsAction() {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$fcpsByName = array_column(ADBRimoFcp::getAll(['netzgebiet_id' => $input['networkAreaId']]), null, 'name');
|
||||
|
||||
$counts = ['upd' => 0, 'fcpNF' => 0, 'noFCP' => 0, 'noExtId' => 0];
|
||||
|
||||
foreach ($input['data'] as $loc) {
|
||||
$fcpName = trim($loc['FCP cluster name'] ?? '');
|
||||
$extId = $loc['ExternalID'] ?? null;
|
||||
if ($extId === null) $extId = $loc['External ID'] ?? null;
|
||||
|
||||
|
||||
if ($fcpName === '') { $counts['noFCP']++; continue; }
|
||||
if (!isset($fcpsByName[$fcpName])) { $counts['fcpNF']++; continue; }
|
||||
if ($extId === null) { $counts['noExtId']++; continue; }
|
||||
|
||||
$fcp = $fcpsByName[$fcpName];
|
||||
if ($hn = ADBHausnummerModel::getFirst(['rimo_id' => $extId])) {
|
||||
$hn->fcp_id = $fcp->id;
|
||||
$hn->rimo_fcp_name = $fcp->name;
|
||||
$hn->save();
|
||||
$counts['upd']++;
|
||||
}
|
||||
}
|
||||
|
||||
$msg = sprintf('Updated: %d, FCP not Found: %d, No FCP in the CSV: %d, No Rimo ID: %d',
|
||||
$counts['upd'], $counts['fcpNF'], $counts['noFCP'], $counts['noExtId']);
|
||||
self::returnJson(['success' => true, 'message' => $msg]);
|
||||
}
|
||||
|
||||
public function MapAction() {
|
||||
Helper::renderVue($this, "ADBRimoFcpMap", "ADBRimoFcpMap", [
|
||||
"MAPBOX_KEY" => TT_MAPBOX_TILE_API_TOKEN,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function getAllFCPsAction() {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$fcpList = ADBRimoFcp::getAll();
|
||||
$fcpData = array_map(function ($fcp) {
|
||||
return [
|
||||
'id' => $fcp->id,
|
||||
// 'rimo_ex_state' => $fcp->rimo_ex_state,
|
||||
// 'rimo_op_state' => $fcp->rimo_op_state,
|
||||
'gps_lat' => $fcp->gps_lat,
|
||||
'gps_long' => $fcp->gps_long
|
||||
];
|
||||
}, $fcpList);
|
||||
|
||||
self::returnJson(['success' => true, 'data' => $fcpData]);
|
||||
}
|
||||
}
|
||||
@@ -167,6 +167,13 @@ class BuildingModel {
|
||||
$where .= " AND Building.pipeworker_id=$pipeworker_id";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("pipework_enabled", $filter)) {
|
||||
$pipework_enabled = $filter['pipework_enabled'];
|
||||
if(!empty($pipework_enabled) || $pipework_enabled === "0") {
|
||||
$where .= " AND Building.pipework_enabled=$pipework_enabled";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("type", $filter) && is_array($filter['type']) && count($filter['type'])) {
|
||||
$ot = $filter['type'];
|
||||
@@ -267,5 +274,52 @@ class BuildingModel {
|
||||
//var_dump($filter, $where);exit;
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
||||
public static function getHistory($from, $to, $network_id, $street_filter): array {
|
||||
$sql = "SELECT b.id AS building_id,
|
||||
b.street AS building_street,
|
||||
b.zip AS building_zip,
|
||||
b.city AS building_city,
|
||||
wi.id AS item_id,
|
||||
wi.name AS item_name,
|
||||
wi.label AS item_label,
|
||||
wi.type AS item_type,
|
||||
wv.id AS value_id,
|
||||
wv.value_string,
|
||||
wv.value_int,
|
||||
wv.value_text,
|
||||
wv.changed AS last_edited_at,
|
||||
wv.changed_by AS last_edited_by_user_id
|
||||
FROM Workflowvalue wv
|
||||
JOIN Workflowitem wi ON wv.item_id = wi.id
|
||||
JOIN Building b ON wv.object_id = b.id";
|
||||
$where = ["wi.object_type = 'Building'", "wi.num < 150"];
|
||||
|
||||
if ($from !== null && $to !== null) {
|
||||
$where[] = "(wv.changed >= " . intval($from) . " AND wv.changed <= " . intval($to) . ")";
|
||||
}
|
||||
if ($network_id !== null) {
|
||||
$where[] = "b.network_id = " . intval($network_id);
|
||||
}
|
||||
if (!empty($street_filter)) {
|
||||
$escaped_street = addslashes($street_filter);
|
||||
$where[] = "b.street LIKE '%" . $escaped_street . "%'";
|
||||
}
|
||||
|
||||
$sql .= " WHERE " . implode(" AND ", $where);
|
||||
$sql .= " ORDER BY wv.changed DESC;";
|
||||
|
||||
$db = FronkDB::singleton();
|
||||
$res = $db->query($sql);
|
||||
|
||||
if ($db->num_rows($res)) {
|
||||
$items = [];
|
||||
while ($data = $db->fetch_object($res)) {
|
||||
$items[] = $data;
|
||||
}
|
||||
return $items;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,6 +607,15 @@ FROM ConstructionConsent
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("electric_approval", $filter)) {
|
||||
$inhouse_cabling = $filter["electric_approval"];
|
||||
if($inhouse_cabling == "!NULL") {
|
||||
$where .= " AND (inspection_electrician IS NOT NULL AND inspection_electrician != 0)";
|
||||
} elseif($inhouse_cabling == "NULL") {
|
||||
$where .= " AND (inspection_electrician IS NULL OR inspection_electrician = 0)";
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists("cwo", $filter) && !empty($filter['cwo'])) {
|
||||
$where .= "
|
||||
AND EXISTS (
|
||||
|
||||
@@ -507,9 +507,6 @@ class ConstructionConsentController extends mfBaseController {
|
||||
}
|
||||
|
||||
protected function apiAction() {
|
||||
if(!$this->me->is(["Admin","netowner"]) && !$this->me->can("Preorder")) {
|
||||
$this->redirect("Dashboard");
|
||||
}
|
||||
$do = $this->request->do;
|
||||
$data = [];
|
||||
|
||||
|
||||
@@ -393,7 +393,7 @@ class ContractController extends mfBaseController {
|
||||
$this->redirect("Dashboard");
|
||||
}
|
||||
|
||||
var_dump($_FILES);exit;
|
||||
//var_dump($_FILES);exit;
|
||||
|
||||
$r = $this->request;
|
||||
|
||||
@@ -1223,4 +1223,4 @@ class ContractController extends mfBaseController {
|
||||
}
|
||||
$this->returnJson($results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ class LineworkController extends mfBaseController {
|
||||
|
||||
|
||||
|
||||
if(!array_key_exists("status_id", $filter)) {
|
||||
if(!!in_array($this->me->id, ["145","62","56"]) && !array_key_exists("status_id", $filter)) {
|
||||
$termination_search["status_id"] = 3;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,15 @@ class PatchingController extends mfBaseController {
|
||||
|
||||
|
||||
$this->layout()->set("terminations", $terminations);
|
||||
|
||||
|
||||
$devices = DeviceModel::getAll();
|
||||
$this->layout()->set("devices", array_map(function($device) {
|
||||
return [
|
||||
"name" => $device->name,
|
||||
"ip" => $device->ip,
|
||||
];
|
||||
}, $devices));
|
||||
|
||||
}
|
||||
|
||||
private function getPreparedFilter($filter) {
|
||||
@@ -232,6 +240,31 @@ class PatchingController extends mfBaseController {
|
||||
$this->redirect("Patching","Index", $qs);
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected function swAction() {
|
||||
$javascript = "self.addEventListener('install', event => {
|
||||
console.log('Patching PWA Service Worker: Installing...');
|
||||
});
|
||||
|
||||
self.addEventListener('activate', event => {
|
||||
console.log('Patching PWA Service Worker: Activating...');
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
event.respondWith(fetch(event.request));
|
||||
});
|
||||
|
||||
console.log('Patching PWA Service Worker: Script loaded.');";
|
||||
|
||||
header("Content-Type: application/javascript");
|
||||
header("Service-Worker-Allowed: /");
|
||||
header("Cache-Control: no-cache");
|
||||
header("Pragma: no-cache");
|
||||
header("Expires: 0");
|
||||
|
||||
echo $javascript;
|
||||
exit;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -102,7 +102,7 @@ class PipeworkController extends mfBaseController {
|
||||
}
|
||||
|
||||
|
||||
if(!array_key_exists("status_id", $filter)) {
|
||||
if(!in_array($this->me->id, ["145","62","56"]) && !array_key_exists("status_id", $filter)) {
|
||||
$building_search["status_id"] = 3;
|
||||
}
|
||||
|
||||
@@ -461,5 +461,51 @@ class PipeworkController extends mfBaseController {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected function historyAction() {
|
||||
if (!$this->me->isAdmin()) {
|
||||
throw new Exception("Forbidden", 403);
|
||||
}
|
||||
|
||||
Helper::renderVue($this, "PipeworkHistory", "PipeworkHistory", [
|
||||
"IS_ADMIN" => $this->me->isAdmin(),
|
||||
"NETWORKS" => array_map(function ($network) {
|
||||
return [
|
||||
"value" => $network->id,
|
||||
"text" => $network->name,
|
||||
];
|
||||
}, NetworkModel::getAll()),
|
||||
"USERS" => array_map(function ($user) {
|
||||
return [
|
||||
"value" => $user->id,
|
||||
"text" => $user->name,
|
||||
];
|
||||
}, UserModel::search(['employee' => true])),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function historyAPIAction() {
|
||||
if (!$this->me->isAdmin()) self::sendError("Keine Berechtigung");
|
||||
|
||||
$from = $this->request->from;
|
||||
$to = $this->request->to;
|
||||
$network_id = $this->request->network_id;
|
||||
$street_filter = $this->request->street_filter;
|
||||
|
||||
// from and to is unix timestamp
|
||||
if ($from && $to) {
|
||||
$from = (int)$from;
|
||||
$to = (int)$to;
|
||||
if ($from > $to) self::sendError('Von kann nicht nach dem Bis-Datum liegen');
|
||||
$fourWeeksInSeconds = 2419200;
|
||||
// if (($to - $from) > $fourWeeksInSeconds) self::sendError('Der Zeitraum darf maximal 4 Wochen betragen');
|
||||
}
|
||||
|
||||
if ($from && $to && $network_id) {
|
||||
self::returnJson(["status" => "OK","data" => BuildingModel::getHistory($from,$to,$network_id,$street_filter)]);
|
||||
} else {
|
||||
self::sendError('Fehlerhafte Parameter');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ class Preorder extends mfBaseModel {
|
||||
private $building;
|
||||
private $adb_hausnummer;
|
||||
private $adb_wohneinheit;
|
||||
private $fcp;
|
||||
private $services;
|
||||
private $ordered_services;
|
||||
private $creator;
|
||||
@@ -1396,6 +1397,11 @@ class Preorder extends mfBaseModel {
|
||||
return $this->creator;
|
||||
}
|
||||
|
||||
if($name === 'fcp') {
|
||||
if(!$this->adb_hausnummer->fcp_id) return null;
|
||||
return ADBRimoFcp::get($this->adb_hausnummer->fcp_id);
|
||||
}
|
||||
|
||||
if($name == "editor") {
|
||||
$this->editor = new User($this->edit_by);
|
||||
return $this->editor;
|
||||
|
||||
@@ -1042,6 +1042,9 @@ class PreorderController extends mfBaseController {
|
||||
case "saveAttribute":
|
||||
$return = $this->saveAttributeApi();
|
||||
break;
|
||||
case "getFCPsForCampaign":
|
||||
$return = $this->getFCPsForCampaignApi();
|
||||
break;
|
||||
case "getFilteredPreorders":
|
||||
$return = $this->getFilteredPreordersApi();
|
||||
break;
|
||||
@@ -1085,6 +1088,17 @@ class PreorderController extends mfBaseController {
|
||||
$this->returnJson($data);
|
||||
}
|
||||
|
||||
protected function getFCPsForCampaignApi(): array {
|
||||
$campaign = new Preordercampaign($this->request->campaign_id);
|
||||
|
||||
if (!$campaign->id) return [];
|
||||
|
||||
return array_map(
|
||||
fn($fcp) => ["id" => $fcp->name ?? null, "text" => $fcp->name ?? null, 'lat' => $fcp->gps_lat ?? null, 'lng' => $fcp->gps_long ?? null],
|
||||
ADBRimoFcp::getAll(["netzgebiet_id" => intval($campaign->network->adb_netzgebiet_id)]) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
private function setBilledApi() {
|
||||
$preorder_id = $this->request->id;
|
||||
|
||||
|
||||
@@ -1005,6 +1005,29 @@ class PreorderModel
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($filter['fcp']) && array_key_exists("preordercampaign_id", $filter)) {
|
||||
$fcp = $filter['fcp'];
|
||||
$db = FronkDB::singleton();
|
||||
$campaign = new Preordercampaign($filter['preordercampaign_id']);
|
||||
if (is_array($fcp)) {
|
||||
$items = array_map(fn($i) => ADBRimoFcp::getAll([
|
||||
'netzgebiet_id' => intval($campaign->network->adb_netzgebiet_id),
|
||||
'name' => $i])[0], array_filter($fcp));
|
||||
|
||||
|
||||
|
||||
$items = array_map(fn($i) => $i->id, array_filter($items));
|
||||
if ($items) $where .= " AND adb_hausnummer.fcp_id IN (" . implode(',', $items) . ")";
|
||||
} else {
|
||||
$fcp = ADBRimoFcp::getAll([
|
||||
'netzgebiet_id' => intval($campaign->network->adb_netzgebiet_id),
|
||||
'name' => $fcp]);
|
||||
if ($fcp) $fcp = $fcp[0]->id;
|
||||
else $fcp = null;
|
||||
|
||||
$where .= " AND adb_hausnummer.rimo_fcp_name = '" . $db->escape($fcp) . "'";
|
||||
}
|
||||
}
|
||||
|
||||
// custom where clause
|
||||
if (array_key_exists("add-where", $filter)) {
|
||||
|
||||
@@ -146,6 +146,13 @@ class TerminationModel {
|
||||
$where .= " AND Termination.status_id = $status_id";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("linework_enabled", $filter)) {
|
||||
$linework_enabled = $filter['linework_enabled'];
|
||||
if(!empty($linework_enabled) || $linework_enabled === '0') {
|
||||
$where .= " AND Termination.linework_enabled=$linework_enabled";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("lineworker_id", $filter)) {
|
||||
$lineworker_id = $filter['lineworker_id'];
|
||||
|
||||
@@ -18,9 +18,9 @@ class WarehouseArticleController extends TTCrud {
|
||||
['key' => 'cheapestSellPrice', 'text' => 'Verkauf', 'modal' => false, 'table' => ['class' => 'text-center', 'suffix' => ' €']],
|
||||
['key' => 'warningAmount', 'text' => 'Warnmenge', 'required' => true,'modal' => ['type' => 'number'], 'table' => ['class' => 'text-center']], // Stock/inventory related
|
||||
['key' => 'criticalAmount', 'text' => 'Kritische Menge', 'required' => true,'modal' => ['type' => 'number'], 'table' => ['class' => 'text-center']], // Stock/inventory related
|
||||
['key' => 'isSerialDocumentation', 'text' => 'Seriennummern', 'required' => true,'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value
|
||||
['key' => 'isEShop', 'text' => 'Ist E-Shop', 'required' => true,'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value
|
||||
['key' => 'isEShopHide', 'text' => 'E-Shop Versteckt', 'required' => true,'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value
|
||||
['key' => 'isSerialDocumentation', 'text' => 'Seriennummern', 'required' => false,'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value
|
||||
['key' => 'isEShop', 'text' => 'Ist E-Shop', 'required' => false,'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value
|
||||
['key' => 'isEShopHide', 'text' => 'E-Shop Versteckt', 'required' => false,'modal' => ['type' => 'checkbox'], 'table' => false], // Boolean value
|
||||
['key' => 'actions', 'text' => 'Aktionen', 'required' => false, 'modal' => false, 'table' => ['filter' => false, 'sortable' => false, 'class' => 'text-center', 'priority' => 8]]
|
||||
];
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ class WarehouseArticleModel extends TTCrudBaseModel {
|
||||
public ?string $cheapestSellPrice;
|
||||
public int $warningAmount;
|
||||
public int $criticalAmount;
|
||||
public int $isEShop;
|
||||
public int $isEShopHide;
|
||||
public ?int $isEShop;
|
||||
public ?int $isEShopHide;
|
||||
public string $unit;
|
||||
public int $isSerialDocumentation;
|
||||
public ?int $isSerialDocumentation;
|
||||
public int $revenueAccount;
|
||||
|
||||
|
||||
|
||||
@@ -47,11 +47,13 @@ class WarehouseArticlePacketController extends TTCrud {
|
||||
|
||||
foreach ($subItems as $subItem) {
|
||||
$article = WarehouseArticleModel::get($subItem->id);
|
||||
$cheapestSellPrices = json_decode($article->cheapestSellPrice);
|
||||
$cheapestSellPrices = json_decode($article->cheapestSellPrice, true);
|
||||
// find in array cheapestSellPrices by title === 'Energie Steiermark' and get the price
|
||||
$articlePrice = array_values(array_filter($cheapestSellPrices, function ($cheapestSellPrice) {
|
||||
return $cheapestSellPrice->title === 'Energie Steiermark';
|
||||
}))[0]->price;
|
||||
return $cheapestSellPrice['title'] === 'Energie Steiermark';
|
||||
}));
|
||||
|
||||
$articlePrice = $articlePrice[0]['price'] ?? 0;
|
||||
|
||||
$calculatedSellPrice += $subItem->amount * $articlePrice;
|
||||
}
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
|
||||
class WarehouseOfferController extends TTCrud {
|
||||
protected string $headerTitle = 'Angebote';
|
||||
protected string $singleText = 'Angebot';
|
||||
protected bool $createText = false;
|
||||
|
||||
protected array $columns = [
|
||||
['key' => 'id', 'text' => 'ID', 'modal' => false],
|
||||
['key' => 'id', 'text' => 'ID', 'modal' => false, 'table' => 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' => 'editor', 'text' => 'Sachbearbeiter', 'required' => true, 'modal' => ['type' => 'select'], 'table' => ['filter' => 'select']],
|
||||
['key' => 'totalAmount', 'text' => 'Gesamtbetrag', 'required' => true, 'modal' => false],
|
||||
['key' => 'status', 'text' => 'Status', 'required' => true, 'modal' => ['type' => 'select']],
|
||||
['key' => 'status', 'text' => 'Status', 'required' => true],
|
||||
['key' => 'create', 'text' => 'Erstellt', 'required' => true, 'modal' => false],
|
||||
['key' => 'createBy', 'text' => 'Erstellt von', 'required' => true, 'modal' => ['type' => 'select']],
|
||||
['key' => 'createBy', 'text' => 'Erstellt von', 'required' => true, 'modal' => ['type' => 'select'], 'table' => ['filter' => 'select']],
|
||||
['key' => 'actions',
|
||||
'text' => 'Aktionen',
|
||||
'required' => false,
|
||||
@@ -24,27 +25,19 @@ class WarehouseOfferController extends TTCrud {
|
||||
];
|
||||
|
||||
protected array $permissionCheck = ['WarehouseAdmin'];
|
||||
protected array $additionalActions = [['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary']];
|
||||
|
||||
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 $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.',
|
||||
'delete' => 'Angebot wurde gelöscht',
|
||||
'noChanges' => 'Keine Änderungen',
|
||||
'sent' => 'Angebot wurde erfolgreich gesendet',
|
||||
];
|
||||
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(['employee' => true]));
|
||||
}
|
||||
|
||||
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);
|
||||
$this->postData['status'] = 'new';
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -57,4 +50,24 @@ class WarehouseOfferController extends TTCrud {
|
||||
protected function getHistoryAction() {
|
||||
self::returnJson((new WarehouseHistoryController)->getHistory($this->request->id, $this->mod, $this->columns));
|
||||
}
|
||||
|
||||
protected function createTemplateAction() {
|
||||
$_POST = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$templateId = WarehouseOfferTemplateModel::create([
|
||||
'templateName' => $_POST['name'],
|
||||
'positions' => $_POST['positions'],
|
||||
'totalDiscount' => $_POST['totalDiscount'],
|
||||
'paymentTerms' => $_POST['paymentTerms'],
|
||||
'deliveryTerms' => $_POST['deliveryTerms'],
|
||||
'closingText' => $_POST['closingText'],
|
||||
'notes' => $_POST['notes'],
|
||||
]);
|
||||
|
||||
self::returnJson(['success' => true, 'id' => $templateId]);
|
||||
}
|
||||
|
||||
protected function getTemplatesAction() {
|
||||
self::returnJson(WarehouseOfferTemplateModel::getAll());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*
|
||||
* @property int $id Unique identifier for the warehouse offer
|
||||
* @property string $offerNumber Unique offer number
|
||||
* @property string $reference Reference number for the offer
|
||||
* @property string $customerNumber Customer number
|
||||
* @property string $customerName Name of the customer
|
||||
* @property string $customerStreet Street address of the customer
|
||||
@@ -30,6 +31,7 @@
|
||||
class WarehouseOfferModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public string $offerNumber;
|
||||
public string $reference;
|
||||
public string $customerNumber;
|
||||
public string $customerName;
|
||||
public string $customerStreet;
|
||||
@@ -50,3 +52,31 @@ class WarehouseOfferModel extends TTCrudBaseModel {
|
||||
public int $create;
|
||||
public int $createBy;
|
||||
}
|
||||
|
||||
//SQL TO CREATE TABLE
|
||||
/*
|
||||
CREATE TABLE `warehouse_offer` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`offerNumber` varchar(255) NOT NULL,
|
||||
`customerNumber` varchar(255) NOT NULL,
|
||||
`customerName` varchar(255) NOT NULL,
|
||||
`customerStreet` varchar(255) NOT NULL,
|
||||
`customerCity` varchar(255) NOT NULL,
|
||||
`customerZip` varchar(255) NOT NULL,
|
||||
`customerVAT` varchar(255) NOT NULL,
|
||||
`editor` int(11) NOT NULL,
|
||||
`purpose` varchar(255) NOT NULL,
|
||||
`positions` text NOT NULL,
|
||||
`alternativePositions` text NOT NULL,
|
||||
`totalDiscount` float NOT NULL,
|
||||
`paymentTerms` varchar(255) NOT NULL,
|
||||
`deliveryTerms` varchar(255) NOT NULL,
|
||||
`closingText` varchar(255) NOT NULL,
|
||||
`notes` varchar(255) NOT NULL,
|
||||
`status` varchar(255) NOT NULL,
|
||||
`totalAmount` float NOT NULL,
|
||||
`create` int(11) NOT NULL,
|
||||
`createBy` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @property mixed|null $name
|
||||
*/
|
||||
class WarehouseOfferTemplate extends mfBaseModel
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WarehouseOfferTemplateModel
|
||||
*
|
||||
* Represents a warehouse offer template with key details.
|
||||
*
|
||||
* @property string $templateName Name of the template
|
||||
* @property string $positions Details about 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
|
||||
*/
|
||||
class WarehouseOfferTemplateModel extends TTCrudBaseModel
|
||||
{
|
||||
public string $templateName;
|
||||
public string $positions;
|
||||
public float $totalDiscount;
|
||||
public string $paymentTerms;
|
||||
public string $deliveryTerms;
|
||||
public string $closingText;
|
||||
public string $notes;
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
<?php /** @noinspection PhpUndefinedClassInspection */
|
||||
|
||||
/** @noinspection PhpUndefinedNamespaceInspection */
|
||||
|
||||
class WarehouseOrderController extends TTCrud {
|
||||
protected string $headerTitle = 'Lieferantenbestellungen';
|
||||
@@ -83,7 +85,7 @@ class WarehouseOrderController extends TTCrud {
|
||||
|
||||
foreach ($order['positions'] as &$position) {
|
||||
$position['distributorName'] = WarehouseDistributorModel::get($position['distributorId'])->name;
|
||||
$position['articleName'] = WarehouseArticleModel::get($position['article'])->title;
|
||||
$position['articleName'] = $position['article_text'] ?? WarehouseArticleModel::get($position['article'])->title;
|
||||
}
|
||||
|
||||
return $order;
|
||||
@@ -227,7 +229,7 @@ class WarehouseOrderController extends TTCrud {
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = "Neue Bestellung #$orderNumber";
|
||||
$mail->Body = "<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang='de'>
|
||||
<head>
|
||||
<title>XINON E-Mail Template</title>
|
||||
<meta charset='utf-8'/>
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @property mixed|null $name
|
||||
*/
|
||||
class WarehouseOrderRequest extends mfBaseModel
|
||||
{
|
||||
class WarehouseOrderRequest extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public ?int $addressId;
|
||||
public string $purpose;
|
||||
public string $positions;
|
||||
public ?string $note;
|
||||
public ?string $linkedOrderIds;
|
||||
public ?int $cancelled;
|
||||
public ?int $done;
|
||||
public int $create;
|
||||
public int $createBy;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
<?php /** @noinspection PhpVoidFunctionResultUsedInspection */
|
||||
<?php /** @noinspection PhpUndefinedClassInspection */
|
||||
/** @noinspection PhpUndefinedNamespaceInspection */
|
||||
|
||||
/** @noinspection PhpVoidFunctionResultUsedInspection */
|
||||
|
||||
class WarehouseOrderRequestController extends TTCrud {
|
||||
protected string $headerTitle = 'Bestellwünsche';
|
||||
@@ -10,6 +13,7 @@ class WarehouseOrderRequestController extends TTCrud {
|
||||
['key' => 'id', 'text' => 'Bestellnummer', 'table' => ['filter' => false], 'modal' => false],
|
||||
['key' => 'addressId', 'text' => 'Kunde', 'required' => false, 'type' => 'autocomplete', 'table' => ['class' => 'text-nowrap', 'filter' => 'autocomplete'], 'modal' => ['apiUrl' => 'Address/api?do=findAddress&fibu_primary_account=1', 'items' => '/Address/Api?do=findAddress&fibu_primary_account=1', 'type' => 'autocomplete']],
|
||||
['key' => 'purpose', 'text' => 'Verwendungszweck', 'required' => true],
|
||||
['key' => 'note', 'text' => 'Notiz', 'required' => false],
|
||||
['key' => 'positions', 'text' => 'Positionen', 'required' => true, 'modal' => ['type' => 'positions-manager', 'config' => [
|
||||
'header' => 'Positionen',
|
||||
'fields' => [
|
||||
@@ -69,10 +73,10 @@ class WarehouseOrderRequestController extends TTCrud {
|
||||
$cancel = filter_var($this->request->cancel, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0, 'max_range' => 1]]);
|
||||
|
||||
if (!$id || $cancel === false) self::returnJson(['error' => 'Ungültige Anfrage']);
|
||||
if (!(WarehouseOrderRequestModel::get($id))) self::returnJson(['error' => 'Bestellwunsch nicht gefunden']);
|
||||
if (!(WarehouseOrderRequest::get($id))) self::returnJson(['error' => 'Bestellwunsch nicht gefunden']);
|
||||
|
||||
$currentData = (array) WarehouseOrderRequestModel::get($id);
|
||||
WarehouseOrderRequestModel::update(array_merge($currentData, ['id' => $id, 'cancelled' => $cancel]));
|
||||
$currentData = (array) WarehouseOrderRequest::get($id);
|
||||
WarehouseOrderRequest::update(array_merge($currentData, ['id' => $id, 'cancelled' => $cancel]));
|
||||
self::returnJson(['success' => true]);
|
||||
}
|
||||
|
||||
@@ -86,13 +90,70 @@ class WarehouseOrderRequestController extends TTCrud {
|
||||
$done = filter_var($this->request->done, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0, 'max_range' => 1]]);
|
||||
|
||||
if (!$id || $done === false) self::returnJson(['error' => 'Ungültige Anfrage']);
|
||||
if (!(WarehouseOrderRequestModel::get($id))) self::returnJson(['error' => 'Bestellwunsch nicht gefunden']);
|
||||
if (!(WarehouseOrderRequest::get($id))) self::returnJson(['error' => 'Bestellwunsch nicht gefunden']);
|
||||
|
||||
$currentData = (array) WarehouseOrderRequestModel::get($id);
|
||||
WarehouseOrderRequestModel::update(array_merge($currentData, ['id' => $id, 'done' => $done]));
|
||||
$currentData = (array) WarehouseOrderRequest::get($id);
|
||||
WarehouseOrderRequest::update(array_merge($currentData, ['id' => $id, 'done' => $done]));
|
||||
self::returnJson(['success' => true]);
|
||||
}
|
||||
|
||||
private function getPHPMailer() {
|
||||
$mail = new PHPMailer\PHPMailer\PHPMailer(true);
|
||||
try {
|
||||
// Server settings
|
||||
$mail->isSMTP();
|
||||
$mail->Host = TT_WAREHOUSE_ORDER_SMTP_HOST;
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = TT_WAREHOUSE_ORDER_SMTP_USER;
|
||||
$mail->Password = TT_WAREHOUSE_ORDER_SMTP_PASS;
|
||||
$mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
|
||||
$mail->Port = 587;
|
||||
|
||||
return $mail;
|
||||
} catch (Exception $e) {
|
||||
self::returnJson(['error' => 'Mailer Error: ' . $mail->ErrorInfo]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
protected function afterCreate($orderRequest) {
|
||||
try {
|
||||
$mail = $this->getPHPMailer();
|
||||
|
||||
$mail->setFrom('einkauf@xinon.at', 'XINON Einkauf');
|
||||
$mail->addAddress('einkauf@xinon.at', 'XINON Einkauf');
|
||||
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = "Neuer Bestellwunsch #" . $orderRequest['id'] . " von " . $this->user->name . ' eingelangt';
|
||||
|
||||
// build html table and fetch articleId if set else use articleId_text if its a text article
|
||||
$html = '<table style="width: 100%; border-collapse: collapse;">';
|
||||
$html .= '<tr><th style="border: 1px solid #000; padding: 8px;">Artikel</th><th style="border: 1px solid #000; padding: 8px;">Menge</th><th style="border: 1px solid #000; padding: 8px;">Zweck</th></tr>';
|
||||
foreach ($orderRequest['positions'] as $position) {
|
||||
$articleId = isset($position['articleId']) ? WarehouseArticleModel::get($position['articleId'])->title : $position['articleId_text'];
|
||||
$html .= '<tr>';
|
||||
$html .= '<td style="border: 1px solid #000; padding: 8px;">' . htmlspecialchars($articleId) . '</td>';
|
||||
$html .= '<td style="border: 1px solid #000; padding: 8px;">' . htmlspecialchars($position['amount']) . '</td>';
|
||||
$html .= '<td style="border: 1px solid #000; padding: 8px;">' . htmlspecialchars($position['purpose']) . '</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
$html .= '</table>';
|
||||
|
||||
// Set the HTML content
|
||||
$mail->Body = "Neuer Bestellwunsch #" . $orderRequest['id'] . " von " . $this->user->name . ' eingelangt<br><br>' .
|
||||
'Notiz: ' . htmlspecialchars($orderRequest['note']) . '<br><br>' . $html;
|
||||
|
||||
// Send the email
|
||||
if (!$mail->send()) {
|
||||
self::returnJson(['error' => 'Message could not be sent. Mailer Error: ' . $mail->ErrorInfo]);
|
||||
exit;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
self::returnJson(['error' => 'Message could not be sent. Mailer Error: ' . $mail->ErrorInfo]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
protected function createNewLogAction() {
|
||||
$postData = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
class WarehouseOrderRequestModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public ?int $addressId;
|
||||
public string $purpose;
|
||||
public string $positions;
|
||||
public ?string $note;
|
||||
public ?string $linkedOrderIds;
|
||||
public ?int $cancelled;
|
||||
public ?int $done;
|
||||
public int $create;
|
||||
public int $createBy;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ class WarehouseShippingNoteController extends TTCrud {
|
||||
['value' => 'cancelled', 'text' => 'Storniert', 'icon' => 'fas fa-ban text-danger'],
|
||||
['value' => 'on_hold', 'text' => 'In Wartestellung', 'icon' => 'fas fa-pause text-warning'],
|
||||
]]],
|
||||
['key' => 'type', 'text' => 'Typ', 'required' => false],
|
||||
['key' => 'deliveryAddressName', 'text' => 'L.-Adr. Name', 'required' => true],
|
||||
['key' => 'deliveryAddressLine', 'text' => 'L.-Adr.', 'required' => true],
|
||||
['key' => 'deliveryAddressPLZ', 'text' => 'L.-Adr. PLZ', 'required' => true],
|
||||
@@ -29,6 +30,7 @@ class WarehouseShippingNoteController extends TTCrud {
|
||||
protected array $defaultOrder = ['key' => 'create', 'order' => 'DESC'];
|
||||
|
||||
protected array $additionalJSVariables = ['WAREHOUSE_ADMIN' => true];
|
||||
protected array $additionalHead = ['<link rel="manifest" href="/assets/pwa/shipping-note-manifest.json">'];
|
||||
|
||||
protected array $infoMessages = ['create' => 'Lieferschein wurde erstellt.',
|
||||
'update' => 'Lieferschein wurde aktualisiert',
|
||||
@@ -378,23 +380,13 @@ class WarehouseShippingNoteController extends TTCrud {
|
||||
}
|
||||
|
||||
protected function changeStatusAction() {
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
$json = json_decode(file_get_contents('php://input'), true);
|
||||
$id = $json['id'];
|
||||
$status = $json['status'];
|
||||
if (strlen($id) < 1) {
|
||||
http_response_code(500);
|
||||
self::returnJson(['success' => false, 'message' => 'Lieferschein wurde nicht gefunden']);
|
||||
}
|
||||
if (empty($json['id'])) self::sendError('Lieferschein wurde nicht gefunden');
|
||||
|
||||
$shippingNote = (array) WarehouseShippingNoteModel::get($id);
|
||||
if ($shippingNote['status'] === 'invoiced') {
|
||||
http_response_code(500);
|
||||
self::returnJson(['success' => false, 'message' => 'Status kann nicht geändert werden']);
|
||||
}
|
||||
$shippingNote = (array) WarehouseShippingNoteModel::get($json['id']);
|
||||
if ($shippingNote['status'] === 'invoiced') self::sendError('Status kann nicht geändert werden');
|
||||
|
||||
$shippingNote['status'] = $status;
|
||||
$shippingNote['status'] = $json['status'];
|
||||
WarehouseShippingNoteModel::update($shippingNote);
|
||||
$statusNiceText = [
|
||||
'new' => 'Neu',
|
||||
@@ -404,7 +396,7 @@ class WarehouseShippingNoteController extends TTCrud {
|
||||
'cancelled' => 'Storniert',
|
||||
'on_hold' => 'In Wartestellung',
|
||||
];
|
||||
self::returnJson(['success' => true, 'message' => 'Status wurde auf ' . $statusNiceText[$status] . ' geändert']);
|
||||
self::returnJson(['success' => true, 'message' => 'Status wurde auf ' . $statusNiceText[$json['status']] . ' geändert']);
|
||||
}
|
||||
|
||||
//TODO: either move this to TimerecordingCarController or make it better
|
||||
@@ -602,4 +594,31 @@ class WarehouseShippingNoteController extends TTCrud {
|
||||
$logs = WarehouseLogModel::getAll(['table' => 'WarehouseShippingNote','rowId' => $shippingNoteId], null, 0, ['order' => 'DESC', 'key' => 'create']);
|
||||
self::returnJson($logs);
|
||||
}
|
||||
|
||||
protected function swAction() {
|
||||
$javascript = "self.addEventListener('install', event => {
|
||||
console.log('Patching PWA Service Worker: Installing...');
|
||||
});
|
||||
|
||||
self.addEventListener('activate', event => {
|
||||
console.log('Patching PWA Service Worker: Activating...');
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
event.respondWith(fetch(event.request));
|
||||
});
|
||||
|
||||
console.log('Patching PWA Service Worker: Script loaded.');";
|
||||
|
||||
header("Content-Type: application/javascript");
|
||||
header("Service-Worker-Allowed: /");
|
||||
header("Cache-Control: no-cache");
|
||||
header("Pragma: no-cache");
|
||||
header("Expires: 0");
|
||||
|
||||
echo $javascript;
|
||||
exit;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
class WarehouseShippingNoteModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public ?int $billingAddressId;
|
||||
public ?string $type;
|
||||
public string $deliveryAddressName;
|
||||
public string $deliveryAddressLine;
|
||||
public string $deliveryAddressPLZ;
|
||||
|
||||
71
db/migrations/20250331090000_warehouse_modify_16.php
Normal file
71
db/migrations/20250331090000_warehouse_modify_16.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class WarehouseModify16 extends AbstractMigration {
|
||||
public function up(): void {
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
$WarehouseArticleTable = $this->table("WarehouseArticle");
|
||||
$WarehouseArticleTable
|
||||
->changeColumn("isEShop", "integer", ["default" => 0])
|
||||
->changeColumn("isEShopHide", "integer", ["default" => 0])
|
||||
->changeColumn("isSerialDocumentation", "integer", ["default" => 0])
|
||||
->save();
|
||||
|
||||
$WarehouseOfferTable = $this->table("WarehouseOffer", ["id" => false, "primary_key" => "id"]);
|
||||
$WarehouseOfferTable
|
||||
->addColumn("id", "integer", ["identity" => true])
|
||||
->addColumn("offerNumber", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("reference", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("customerNumber", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("customerName", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("customerStreet", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("customerCity", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("customerZip", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("customerVAT", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("editor", "integer", ["null" => false])
|
||||
->addColumn("purpose", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("positions", "text", ["null" => false])
|
||||
->addColumn("alternativePositions", "text", ["null" => false])
|
||||
->addColumn("totalDiscount", "float", ["null" => false])
|
||||
->addColumn("paymentTerms", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("deliveryTerms", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("closingText", "text", ["null" => false])
|
||||
->addColumn("notes", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("status", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("totalAmount", "float", ["null" => false])
|
||||
->addColumn("create", "integer", ["null" => false])
|
||||
->addColumn("createBy", "integer", ["null" => false])
|
||||
->save();
|
||||
|
||||
$WarehouseOfferTemplateTable = $this->table("WarehouseOfferTemplate", ["id" => false, "primary_key" => "id"]);
|
||||
$WarehouseOfferTemplateTable
|
||||
->addColumn("id", "integer", ["identity" => true])
|
||||
->addColumn("templateName", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("positions", "text", ["null" => false])
|
||||
->addColumn("totalDiscount", "float", ["null" => false])
|
||||
->addColumn("paymentTerms", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("deliveryTerms", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("closingText", "text", ["null" => false])
|
||||
->addColumn("notes", "text", ["null" => false])
|
||||
->save();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
// change table "WarehouseArticle" and set isEShop, isEShopHide, isSerialDocumentation to no default
|
||||
$WarehouseArticleTable = $this->table("WarehouseArticle");
|
||||
$WarehouseArticleTable
|
||||
->changeColumn("isEShop", "integer", ["default" => null])
|
||||
->changeColumn("isEShopHide", "integer", ["default" => null])
|
||||
->changeColumn("isSerialDocumentation", "integer", ["default" => null])
|
||||
->save();
|
||||
|
||||
$this->table("WarehouseOffer")->drop()->save();
|
||||
|
||||
$this->table("WarehouseOfferTemplate")->drop()->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
25
db/migrations/20250407160000_warehouse_modify_17.php
Normal file
25
db/migrations/20250407160000_warehouse_modify_17.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class WarehouseModify17 extends AbstractMigration {
|
||||
public function up(): void {
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
$WarehouseShippingNoteTable = $this->table("WarehouseShippingNote");
|
||||
$WarehouseShippingNoteTable
|
||||
->addColumn("type", "string", ["limit" => 255, "default" => ""])
|
||||
->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
$WarehouseShippingNoteTable = $this->table("WarehouseShippingNote");
|
||||
if ($WarehouseShippingNoteTable->hasColumn("type")) {
|
||||
$WarehouseShippingNoteTable
|
||||
->removeColumn("type")
|
||||
->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
db/migrations/20250408150000_warehouse_modify_18.php
Normal file
25
db/migrations/20250408150000_warehouse_modify_18.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class WarehouseModify18 extends AbstractMigration {
|
||||
public function up(): void {
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
$WarehouseShippingNoteTable = $this->table("WarehouseShippingNote");
|
||||
$WarehouseShippingNoteTable
|
||||
->changeColumn("type", "string", ["limit" => 255, "default" => null])
|
||||
->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
$WarehouseShippingNoteTable = $this->table("WarehouseShippingNote");
|
||||
if ($WarehouseShippingNoteTable->hasColumn("type")) {
|
||||
$WarehouseShippingNoteTable
|
||||
->changeColumn("type", "string", ["limit" => 255])
|
||||
->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,12 @@ FROM debian:bookworm
|
||||
# Install wkhtmltopdf
|
||||
RUN apt update
|
||||
RUN apt install wget libfontenc1 xfonts-75dpi xfonts-base xfonts-encodings xfonts-utils openssl build-essential libssl-dev libxrender-dev git-core libx11-dev libxext-dev libfontconfig1-dev libfreetype6-dev fontconfig -y
|
||||
# wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.stretch_amd64.deb
|
||||
# dpkg --force-all -i wkhtmltox_0.12.6-1.stretch_amd64.deb
|
||||
# wget https://www.mytaxexpress.com/download/libssl1.1_1.1.1f-1ubuntu2.17_amd64.deb
|
||||
# dpkg -i libssl1.1_1.1.1f-1ubuntu2.17_amd64.deb
|
||||
# wget https://archive.debian.org/debian/pool/main/libj/libjpeg8/libjpeg8_8b-1_amd64.deb
|
||||
# dpkg -i libjpeg8_8b-1_amd64.deb
|
||||
RUN wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.stretch_amd64.deb
|
||||
RUN dpkg --force-all -i wkhtmltox_0.12.6-1.stretch_amd64.deb
|
||||
RUN wget https://www.mytaxexpress.com/download/libssl1.1_1.1.1f-1ubuntu2.17_amd64.deb
|
||||
RUN dpkg -i libssl1.1_1.1.1f-1ubuntu2.17_amd64.deb
|
||||
RUN wget https://archive.debian.org/debian/pool/main/libj/libjpeg8/libjpeg8_8b-1_amd64.deb
|
||||
RUN dpkg -i libjpeg8_8b-1_amd64.deb
|
||||
|
||||
# Install apache2 and PHP and PHP modules
|
||||
RUN apt update && \
|
||||
|
||||
@@ -32,7 +32,7 @@ class Helper {
|
||||
$sql .= " AND `$columnName` = '" . $filterValue . "'";
|
||||
} else if (strpos($columnName, "|") !== false) {
|
||||
foreach (explode(" ", $filterValue) as $item)
|
||||
$sql .= " AND CONCAT(" . join(",", explode("|", $columnName)) . ") LIKE '%" . str_replace("%", "", $item) . "%'";
|
||||
$sql .= " AND CONCAT(" . join(",", explode("|", $columnName)) . ") LIKE '%" . $item . "%'";
|
||||
} else if ($filterValue[0] === "%") {
|
||||
$sql .= " AND `$columnName` LIKE '" . $filterValue . "'";
|
||||
} else if ($filterValue[strlen($filterValue) - 1] === "%") {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* @property array $columns
|
||||
* @property array $additionalActions
|
||||
* @property array $additionalJSVariables
|
||||
* @property array $additionalHead
|
||||
* @property array $infoMessages
|
||||
* @property bool $onlyView
|
||||
* @property array $defaultOrder
|
||||
@@ -33,8 +34,13 @@ class TTCrud extends mfBaseController {
|
||||
$this->redirect("Dashboard");
|
||||
}
|
||||
|
||||
$modelName = str_replace('Controller', 'Model', get_class($this));
|
||||
$this->model = new $modelName();
|
||||
$c = get_class($this);
|
||||
foreach ([str_replace('Controller', 'Model', $c), str_replace('Controller', '', $c)] as $m)
|
||||
if (class_exists($m)) {
|
||||
$this->model = new $m();
|
||||
break;
|
||||
}
|
||||
|
||||
$this->postData = json_decode(file_get_contents('php://input'), true);
|
||||
$this->checkArray = $this->getCheckArray();
|
||||
$this->infoMessages = $this->getInfoMessages();
|
||||
@@ -75,6 +81,7 @@ class TTCrud extends mfBaseController {
|
||||
];
|
||||
|
||||
if (!empty($this->additionalJSVariables) && is_array($this->additionalJSVariables)) $JS_VARIABLES = array_merge($JS_VARIABLES, $this->additionalJSVariables);
|
||||
if (!empty($this->additionalHead) && is_array($this->additionalHead)) $this->layout()->set('additionalHead', $this->additionalHead);
|
||||
|
||||
Helper::renderVue($this, $pageName, $this->headerTitle, $JS_VARIABLES);
|
||||
}
|
||||
@@ -273,8 +280,10 @@ class TTCrud extends mfBaseController {
|
||||
|
||||
$data = [];
|
||||
if (count($data) < 11) {
|
||||
$filter = [$filterKey => '%' . $this->request->q . '%'];
|
||||
$lazyData = $this->model::getAll($filter, 10);
|
||||
// $this->request->q replace ? with
|
||||
$data = $this->model::getAll([$textKey => $this->request->q . '%'], 10);
|
||||
$lazyData = $this->model::getAll([$filterKey => $this->request->q], 10);
|
||||
|
||||
$data = array_merge($data, $lazyData);
|
||||
$data = array_unique($data, SORT_REGULAR);
|
||||
$data = array_slice($data, 0, 10);
|
||||
|
||||
@@ -10,10 +10,24 @@ class TTCrudBaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
private static function getFullyQualifiedTable(): string {
|
||||
$table = str_replace('Model', '', get_called_class());
|
||||
$tableIncludesADBSuffix = strpos($table, 'ADB') !== false;
|
||||
$table = str_replace('ADB', '', $table);
|
||||
return "`" . ($tableIncludesADBSuffix ? ADDRESSDB_DBNAME : FRONKDB_DBNAME) . "`.`" . $table . "`";
|
||||
}
|
||||
|
||||
private static function getDB() {
|
||||
if (strpos(self::getFullyQualifiedTable(), ADDRESSDB_DBNAME) !== false)
|
||||
$FronkDB = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
|
||||
else $FronkDB = FronkDB::singleton();
|
||||
|
||||
return $FronkDB->link;
|
||||
}
|
||||
|
||||
public static function create($data) {
|
||||
$FronkDB = FronkDB::singleton();
|
||||
$db = $FronkDB->link;
|
||||
$table = self::getTable();
|
||||
$db = self::getDB();
|
||||
$table = self::getFullyQualifiedTable();
|
||||
self::checkAllFields($data, ['id']);
|
||||
|
||||
$sqlColumns = [];
|
||||
@@ -39,16 +53,12 @@ class TTCrudBaseModel {
|
||||
$sqlColumns[] = "`$field`";
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO `$table` (" . implode(", ", $sqlColumns) . ") VALUES (" . implode(", ", $sqlValues) . ")";
|
||||
$sql = "INSERT INTO $table (" . implode(", ", $sqlColumns) . ") VALUES (" . implode(", ", $sqlValues) . ")";
|
||||
$db->query($sql) or die($db->error);
|
||||
|
||||
return $db->insert_id;
|
||||
}
|
||||
|
||||
public static function getTable(): string {
|
||||
return str_replace('Model', '', get_called_class());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all required fields of the current class are present in a given data array.
|
||||
*
|
||||
@@ -87,11 +97,10 @@ class TTCrudBaseModel {
|
||||
|
||||
|
||||
public static function get($id): TTCrudBaseModel {
|
||||
$FronkDB = FronkDB::singleton();
|
||||
$db = $FronkDB->link;
|
||||
$db = self::getDB();
|
||||
$id = $db->real_escape_string($id);
|
||||
$table = self::getTable();
|
||||
$sql = "SELECT * FROM `$table` WHERE `id` = $id";
|
||||
$table = self::getFullyQualifiedTable();
|
||||
$sql = "SELECT * FROM $table WHERE `id` = $id";
|
||||
|
||||
$result = $db->query($sql);
|
||||
// as TTCRudBaseModel is abstract, we need to get the class name of the child class
|
||||
@@ -100,11 +109,10 @@ class TTCrudBaseModel {
|
||||
}
|
||||
|
||||
public static function count($filter = []): int {
|
||||
$FronkDB = FronkDB::singleton();
|
||||
$db = $FronkDB->link;
|
||||
$table = self::getTable();
|
||||
$db = self::getDB();
|
||||
$table = self::getFullyQualifiedTable();
|
||||
$filter = self::getSQLFilter($filter);
|
||||
$sql = "SELECT COUNT(*) as count FROM `$table` $filter";
|
||||
$sql = "SELECT COUNT(*) as count FROM $table $filter";
|
||||
$result = $db->query($sql);
|
||||
|
||||
return $result->fetch_assoc()['count'];
|
||||
@@ -128,11 +136,10 @@ class TTCrudBaseModel {
|
||||
}
|
||||
|
||||
public static function getAll($filter = [], $limit = null, $offset = 0, $order = ["key" => null]): array {
|
||||
$FronkDB = FronkDB::singleton();
|
||||
$db = $FronkDB->link;
|
||||
$table = self::getTable();
|
||||
$db = self::getDB();
|
||||
$table = self::getFullyQualifiedTable();
|
||||
$filter = self::getSQLFilter($filter);
|
||||
$sql = "SELECT * FROM `$table` $filter";
|
||||
$sql = "SELECT * FROM $table $filter";
|
||||
|
||||
$sql .= $order['key'] === null ? " ORDER BY `id` ASC" : " ORDER BY `" . $order['key'] . "` " . $order['order'];
|
||||
$sql .= $limit === null ? "" : " LIMIT " . $limit . " OFFSET " . $offset;
|
||||
@@ -153,9 +160,8 @@ class TTCrudBaseModel {
|
||||
}
|
||||
|
||||
public static function update($data) {
|
||||
$FronkDB = FronkDB::singleton();
|
||||
$db = $FronkDB->link;
|
||||
$table = self::getTable();
|
||||
$db = self::getDB();
|
||||
$table = self::getFullyQualifiedTable();
|
||||
|
||||
// Check if all fields are set
|
||||
self::checkAllFields($data);
|
||||
@@ -183,17 +189,16 @@ class TTCrudBaseModel {
|
||||
$values[] = $value === null ? "`$field` = NULL" : "`$field` = '" . $db->real_escape_string($value) . "'";
|
||||
}
|
||||
|
||||
$sql = "UPDATE `$table` SET " . implode(", ", $values) . " WHERE `id` = " . $db->real_escape_string($data['id']);
|
||||
$sql = "UPDATE $table SET " . implode(", ", $values) . " WHERE `id` = " . $db->real_escape_string($data['id']);
|
||||
$db->query($sql);
|
||||
return $db->affected_rows;
|
||||
}
|
||||
|
||||
public static function delete($id) {
|
||||
$FronkDB = FronkDB::singleton();
|
||||
$db = $FronkDB->link;
|
||||
$table = self::getTable();
|
||||
$db = self::getDB();
|
||||
$table = self::getFullyQualifiedTable();
|
||||
$id = $db->real_escape_string($id);
|
||||
$sql = "DELETE FROM `$table` WHERE `id` = $id";
|
||||
$sql = "DELETE FROM $table WHERE `id` = $id";
|
||||
$db->query($sql);
|
||||
return $db->affected_rows;
|
||||
}
|
||||
|
||||
@@ -371,7 +371,7 @@ class mfBaseController
|
||||
}
|
||||
|
||||
public static function sendError(string $message): void {
|
||||
http_response_code(422); // More appropriate status for validation errors
|
||||
http_response_code(500);
|
||||
self::returnJson(['success' => false, 'message' => $message]);
|
||||
exit;
|
||||
}
|
||||
|
||||
BIN
public/assets/images/pwa-thetool-192.png
Normal file
BIN
public/assets/images/pwa-thetool-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
public/assets/images/pwa-thetool-512.png
Normal file
BIN
public/assets/images/pwa-thetool-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
22
public/assets/pwa/patching-manifest.json
Normal file
22
public/assets/pwa/patching-manifest.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "thetool Patching",
|
||||
"short_name": "Patching",
|
||||
"description": "Patching tool for thetool.",
|
||||
"start_url": "/Patching/",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#000000",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/images/pwa-thetool-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/assets/images/pwa-thetool-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
22
public/assets/pwa/shipping-note-manifest.json
Normal file
22
public/assets/pwa/shipping-note-manifest.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "thetool Lieferscheine",
|
||||
"short_name": "Lieferscheine",
|
||||
"description": "Lieferscheine für thetool.",
|
||||
"start_url": "/WarehouseShippingNote/",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#000000",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/images/pwa-thetool-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/assets/images/pwa-thetool-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -48,6 +48,8 @@ $jsFiles = [
|
||||
"plugins/vue/tt-components/tt-checkbox.js",
|
||||
"plugins/vue/tt-components/tt-textarea.js",
|
||||
"plugins/vue/tt-components/tt-position-manager.js",
|
||||
"plugins/vue/tt-components/tt-tooltip.js",
|
||||
"plugins/vue/tt-components/tt-map.js",
|
||||
];
|
||||
|
||||
|
||||
|
||||
247
public/js/pages/ADBRimoFcp/ADBRimoFcp.js
Normal file
247
public/js/pages/ADBRimoFcp/ADBRimoFcp.js
Normal file
@@ -0,0 +1,247 @@
|
||||
Vue.component('csv-import-modal', {
|
||||
props: {
|
||||
show: { type: Boolean, required: true },
|
||||
title: { type: String, required: true },
|
||||
apiUrl: { type: String, required: true },
|
||||
showNetworkAreas: { type: Boolean, default: false },
|
||||
networkAreas: { type: Array, default: () => [] }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedFile: null,
|
||||
loading: false,
|
||||
errorMessage: null,
|
||||
fileInputKey: Date.now(),
|
||||
selectedNetworkArea: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleFileChange(event) {
|
||||
const file = event.target.files[0];
|
||||
this.resetInputState();
|
||||
if (!file) return;
|
||||
|
||||
const fileNameLower = file.name.toLowerCase();
|
||||
const allowedTypes = ['text/csv', 'application/vnd.ms-excel'];
|
||||
if (!fileNameLower.endsWith('.csv') && !allowedTypes.includes(file.type)) {
|
||||
this.errorMessage = 'Bitte wählen Sie eine gültige CSV-Datei aus (.csv).';
|
||||
this.resetFileInputVisuals();
|
||||
return;
|
||||
}
|
||||
this.selectedFile = file;
|
||||
},
|
||||
|
||||
readFileAsString() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.selectedFile) {
|
||||
return reject(new Error("Keine Datei ausgewählt."));
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => resolve(e.target.result);
|
||||
reader.onerror = () => reject(new Error("Fehler beim Lesen der Datei."));
|
||||
reader.readAsText(this.selectedFile, 'UTF-8');
|
||||
});
|
||||
},
|
||||
|
||||
parseCSV(csvText) {
|
||||
if (!csvText || typeof csvText !== 'string') return [];
|
||||
const lines = csvText.trim().split(/\r?\n/);
|
||||
if (lines.length < 2) return [];
|
||||
|
||||
const headers = lines[0].split(';').map(h => this.cleanValue(h));
|
||||
const data = [];
|
||||
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (!line) continue;
|
||||
|
||||
const values = line.split(';');
|
||||
if (values.length !== headers.length) continue;
|
||||
|
||||
const entry = {};
|
||||
headers.forEach((header, j) => {
|
||||
entry[header] = this.cleanValue(values[j]);
|
||||
});
|
||||
data.push(entry);
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
cleanValue(value) {
|
||||
if (typeof value !== 'string') return value;
|
||||
let cleaned = value.trim();
|
||||
if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
|
||||
cleaned = cleaned.slice(1, -1).replace(/""/g, '"');
|
||||
}
|
||||
return cleaned;
|
||||
},
|
||||
|
||||
async submit() {
|
||||
if (!this.selectedFile) {
|
||||
this.errorMessage = 'Bitte wählen Sie zuerst eine Datei aus.';
|
||||
return;
|
||||
}
|
||||
if (this.showNetworkAreas && !this.selectedNetworkArea) {
|
||||
this.errorMessage = 'Bitte wählen Sie einen Netzbereich aus.';
|
||||
return;
|
||||
}
|
||||
this.errorMessage = null;
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const csvString = await this.readFileAsString();
|
||||
const parsedData = this.parseCSV(csvString);
|
||||
|
||||
const payload = { data: parsedData };
|
||||
if (this.showNetworkAreas) {
|
||||
payload.networkAreaId = this.selectedNetworkArea;
|
||||
}
|
||||
|
||||
const response = await axios.post(this.apiUrl, payload);
|
||||
window.notify(response.data.success ? 'success' : 'warning', response.data.message || 'Import erfolgreich.');
|
||||
this.$emit('close', true);
|
||||
|
||||
} catch (error) {
|
||||
let backendMsg = error.response?.data?.message || '';
|
||||
let detailsMsg = '';
|
||||
if (error.response?.data?.errors) {
|
||||
detailsMsg = Object.values(error.response.data.errors).flat().join(', ');
|
||||
}
|
||||
this.errorMessage = `Importfehler: ${error.message || 'Unbekannter Fehler.'}${backendMsg ? ' Server: ' + backendMsg : ''}${detailsMsg ? ' Details: ' + detailsMsg : ''}`;
|
||||
window.notify('error', this.errorMessage);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
if (!this.loading) {
|
||||
this.$emit('close', false);
|
||||
}
|
||||
},
|
||||
|
||||
resetFileInputVisuals() {
|
||||
this.fileInputKey = Date.now();
|
||||
},
|
||||
|
||||
resetInputState() {
|
||||
this.selectedFile = null;
|
||||
this.errorMessage = null;
|
||||
},
|
||||
|
||||
resetAll() {
|
||||
this.resetInputState();
|
||||
this.resetFileInputVisuals();
|
||||
this.selectedNetworkArea = null;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(newVal) {
|
||||
if (!newVal) {
|
||||
this.resetAll();
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<tt-modal
|
||||
:show="show"
|
||||
@update:show="closeModal"
|
||||
:title="title"
|
||||
@submit="submit"
|
||||
:save-loading="loading"
|
||||
save-text="Importieren"
|
||||
:delete="false"
|
||||
>
|
||||
<tt-loader :absolute="false" v-if="loading"/>
|
||||
<template v-else>
|
||||
<div v-if="showNetworkAreas" class="form-group" style="margin: 10px 0">
|
||||
<tt-select
|
||||
label="Netzbereich auswählen"
|
||||
v-model="selectedNetworkArea"
|
||||
:options="networkAreas"
|
||||
:required="true"
|
||||
:disabled="loading || !networkAreas || networkAreas.length === 0"
|
||||
sm
|
||||
row
|
||||
/>
|
||||
<small v-if="!networkAreas || networkAreas.length === 0" class="form-text text-danger">
|
||||
Keine Netzbereiche verfügbar.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin: 10px 0">
|
||||
<label>CSV-Datei auswählen (Trennzeichen: Semikolon ';')</label>
|
||||
<input
|
||||
type="file"
|
||||
class="form-control"
|
||||
accept=".csv, text/csv, application/vnd.ms-excel"
|
||||
@change="handleFileChange"
|
||||
:key="fileInputKey"
|
||||
:disabled="loading"
|
||||
/>
|
||||
<small v-if="selectedFile" class="form-text text-muted">
|
||||
Ausgewählt: {{ selectedFile.name }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div v-if="errorMessage" class="alert alert-danger mt-2" role="alert">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</template>
|
||||
</tt-modal>
|
||||
`
|
||||
});
|
||||
|
||||
Vue.component('a-d-b-rimo-fcp', {
|
||||
template: `
|
||||
<tt-card>
|
||||
<tt-table-crud ref="table">
|
||||
<template #table-top-buttons>
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<tt-button icon="fas fa-upload" text="FCPs Importieren" additional-class="btn-outline-success" @click="showImportFCPModal = true" />
|
||||
<tt-button icon="fas fa-upload" text="Locations Importieren" additional-class="btn-outline-success" @click="showImportLocationsModal = true" />
|
||||
<tt-button text="Karte anzeigen" icon="fas fa-map" additional-class="btn-outline-primary" @click="window.location.href = window.TT_CONFIG['BASE_PATH'] + '/ADBRimoFcp/Map'" />
|
||||
</div>
|
||||
</template>
|
||||
</tt-table-crud>
|
||||
|
||||
<csv-import-modal
|
||||
v-if="showImportFCPModal"
|
||||
:show="showImportFCPModal"
|
||||
:show-network-areas="true"
|
||||
:network-areas="networkAreas"
|
||||
title="FCPs Importieren"
|
||||
:api-url="fcpApiUrl"
|
||||
@close="handleModalClose"
|
||||
/>
|
||||
|
||||
<csv-import-modal
|
||||
v-if="showImportLocationsModal"
|
||||
:show="showImportLocationsModal"
|
||||
:show-network-areas="true"
|
||||
:network-areas="networkAreas"
|
||||
title="Locations Importieren"
|
||||
:api-url="locationsApiUrl"
|
||||
@close="handleModalClose"
|
||||
/>
|
||||
</tt-card>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
showImportFCPModal: false,
|
||||
showImportLocationsModal: false,
|
||||
networkAreas: window.TT_CONFIG?.CRUD_CONFIG?.columns?.find(col => col.key === 'netzgebiet_id')?.modal?.items || [],
|
||||
fcpApiUrl: window.TT_CONFIG['BASE_PATH'] + '/ADBRimoFcp/ImportFCPs',
|
||||
locationsApiUrl: window.TT_CONFIG['BASE_PATH'] + '/ADBRimoFcp/ImportLocations',
|
||||
window
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleModalClose(importWasSuccessful) {
|
||||
this.showImportFCPModal = false;
|
||||
this.showImportLocationsModal = false;
|
||||
|
||||
if (importWasSuccessful) this.$refs.table.$refs.table.refreshTable();
|
||||
}
|
||||
}
|
||||
});
|
||||
93
public/js/pages/ADBRimoFcpMap/ADBRimoFcpMap.js
Normal file
93
public/js/pages/ADBRimoFcpMap/ADBRimoFcpMap.js
Normal file
@@ -0,0 +1,93 @@
|
||||
Vue.component('ADBRimoFcpMap', {
|
||||
data() {
|
||||
return {
|
||||
mapMarkers: [],
|
||||
isLoading: true,
|
||||
error: null,
|
||||
window,
|
||||
fetchUrl: window.TT_CONFIG.BASE_PATH + '/ADBRimoFcp/getAllFCPs',
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
await this.fetchAndPrepareData();
|
||||
},
|
||||
methods: {
|
||||
async fetchAndPrepareData() {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
try {
|
||||
const response = await axios.get(this.fetchUrl);
|
||||
if (response.data && response.data.success && Array.isArray(response.data.data)) {
|
||||
this.mapMarkers = response.data.data
|
||||
.filter(fcp => fcp.gps_lat != null && fcp.gps_long != null)
|
||||
.map(fcp => ({
|
||||
lat: fcp.gps_lat,
|
||||
lng: fcp.gps_long,
|
||||
options: {
|
||||
asyncPopupContent: async (markerData) => {
|
||||
const response = await axios.get(`${this.window.TT_CONFIG.BASE_PATH}/ADBRimoFcp/getById?id=${fcp.id}`);
|
||||
const fullFcpData = response.data;
|
||||
return `
|
||||
<div style="padding: 0px 5px 5px 5px; font-size: 0.85rem;">
|
||||
<h5 class="mb-3 mt-1">${fullFcpData.name}</h5>
|
||||
<table class="table table-sm table-borderless mb-0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font-weight-bold py-1">RIMO ID:</td>
|
||||
<td class="py-1" style="word-break: break-all;">${fullFcpData.rimo_id}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-weight-bold py-1">RIMO Ex State:</td>
|
||||
<td class="py-1">${fullFcpData.rimo_ex_state}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-weight-bold py-1">RIMO Op State:</td>
|
||||
<td class="py-1">${fullFcpData.rimo_op_state}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-weight-bold py-1">Building Type:</td>
|
||||
<td class="py-1">${fullFcpData.building_type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-weight-bold py-1">Coordinates:</td>
|
||||
<td class="py-1">
|
||||
<a href="https://maps.google.com/?q=${fullFcpData.gps_lat},${fullFcpData.gps_long}" target="_blank" class="text-primary">
|
||||
<i class="fas fa-map-marker-alt mr-1"></i>${fullFcpData.gps_lat},<br> ${fullFcpData.gps_long}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`; },
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
console.error("Invalid data format from API:", response.data);
|
||||
this.error = "Invalid data format received.";
|
||||
this.mapMarkers = [];
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error fetching FCP data:", err);
|
||||
this.error = "Failed to load FCP locations.";
|
||||
this.mapMarkers = [];
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<tt-card style="height: 80vh; position: relative;">
|
||||
<template #header>
|
||||
<h5>FCP Locations</h5>
|
||||
</template>
|
||||
<div v-if="!isLoading && error" class="alert alert-danger">
|
||||
{{ error }}
|
||||
</div>
|
||||
<tt-map :markers-data="mapMarkers" :loading="isLoading"></tt-map>
|
||||
<div v-if="!isLoading && !error && mapMarkers.length === 0" class="alert alert-info">
|
||||
No FCP locations found.
|
||||
</div>
|
||||
</tt-card>
|
||||
`
|
||||
});
|
||||
119
public/js/pages/PipeworkHistory/PipeworkHistory.js
Normal file
119
public/js/pages/PipeworkHistory/PipeworkHistory.js
Normal file
@@ -0,0 +1,119 @@
|
||||
Vue.component('pipework-history', {
|
||||
//language=Vue
|
||||
template: `<tt-card>
|
||||
|
||||
|
||||
<div class="filter-row">
|
||||
<tt-input label="Addresse" v-model="address" placeholder="Adresse" row sm/>
|
||||
<tt-autocomplete :items="window.TT_CONFIG.NETWORKS" v-model="selectedNetwork" label="Netzgebiet" row/>
|
||||
<tt-date-picker date-range v-model="dateRange" label="Datum" row sm/>
|
||||
|
||||
<tt-button :disabled="checkParameters !== true" @click="getPipeworkHistory" text="Anzeigen" sm icon="fa-solid fa-magnifying-glass" additional-class="btn-primary"/>
|
||||
<span v-if="checkParameters !== true" class="text-danger">{{ checkParameters }}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<tt-table v-if="pipeworkHistory.length > 0" :config="pipeworkHistoryTableConfig" :data="pipeworkHistory" :key="pipeworkHistoryTableIndex">
|
||||
<template #item_type="{ row }">
|
||||
<span v-if="row.value_int !== null">{{ row.value_int }}</span>
|
||||
<span v-else-if="row.value_string !== null">{{ row.value_string }}</span>
|
||||
<span v-else-if="row.value_text !== null">{{ row.value_text }}</span>
|
||||
<span v-else>Unbekannt</span>
|
||||
</template>
|
||||
|
||||
<template #last_edited_at="{ row }">
|
||||
<span v-if="row.last_edited_at">{{ window.moment.unix(row.last_edited_at).format('DD.MM.YYYY HH:mm') }}</span>
|
||||
<span v-else>Unbekannt</span>
|
||||
</template>
|
||||
|
||||
<template #last_edited_by_user_id="{ row }">
|
||||
<span v-if="row.last_edited_by_user_id">{{ window.TT_CONFIG.USERS.find(user => user.value === row.last_edited_by_user_id)?.text }}</span>
|
||||
<span v-else>Unbekannt</span>
|
||||
</template>
|
||||
|
||||
<template #item_id="{ row }">
|
||||
<tt-button @click="openPipework(row)" text="Zum Tiefbau" icon="fa-solid fa-pen-to-square" additional-class="btn-primary" sm/>
|
||||
</template>
|
||||
|
||||
</tt-table>
|
||||
</div>
|
||||
|
||||
|
||||
</tt-card>`,
|
||||
data() {
|
||||
return {
|
||||
window: window,
|
||||
address: '',
|
||||
dateRange: {
|
||||
from: window.moment().subtract(4, 'weeks').unix(),
|
||||
to: window.moment().unix()
|
||||
},
|
||||
selectedNetwork: null,
|
||||
pipeworkHistory: [],
|
||||
pipeworkHistoryLoading: true,
|
||||
pipeworkHistoryError: false,
|
||||
pipeworkHistoryTableIndex: 0,
|
||||
pipeworkHistoryTableConfig: {
|
||||
key: 'PipeworkHistoryTable',
|
||||
tableHeader: 'Tiefbau Historie',
|
||||
defaultPageSize: 50,
|
||||
headers: [
|
||||
{text: 'Straße', key: 'building_street'},
|
||||
{text: 'Ort', key: 'building_city'},
|
||||
{text: 'Feld', key: 'item_label'},
|
||||
{text: 'Wert', key: 'item_type'},
|
||||
{text: 'Editiert', key: 'last_edited_at'},
|
||||
{text: 'Von', key: 'last_edited_by_user_id'},
|
||||
{text: 'Actions', key: 'item_id'},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
checkParameters() {
|
||||
if (!this.selectedNetwork) {
|
||||
return 'Bitte Netzgebiet auswählen';
|
||||
} else if (this.dateRange.from && this.dateRange.to) {
|
||||
const from = window.moment.unix(this.dateRange.from);
|
||||
const to = window.moment.unix(this.dateRange.to);
|
||||
const diff = to.diff(from, 'days');
|
||||
if (diff > 28) {
|
||||
return 'Bitte Zeitraum von maximal 4 Wochen auswählen';
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getPipeworkHistory() {
|
||||
this.pipeworkHistoryLoading = true;
|
||||
this.pipeworkHistoryError = false;
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/Pipework/HistoryAPI`, {
|
||||
params: {
|
||||
network_id: this.selectedNetwork,
|
||||
street_filter: this.address,
|
||||
from: this.dateRange.from,
|
||||
to: this.dateRange.to
|
||||
}
|
||||
})
|
||||
this.pipeworkHistory = response.data.data;
|
||||
this.pipeworkHistoryLoading = false;
|
||||
this.pipeworkHistoryTableIndex++;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.pipeworkHistoryLoading = false;
|
||||
this.pipeworkHistoryError = true;
|
||||
this.window.notify('error', 'Fehler beim Abrufen der Daten');
|
||||
}
|
||||
},
|
||||
openPipework(row) {
|
||||
|
||||
const networkId = this.selectedNetwork;
|
||||
const street = row.building_street;
|
||||
|
||||
this.window.open(`${window.TT_CONFIG.BASE_PATH}/Pipework?filter[network_id]=${networkId}&filter[street]=${street}&filter[status_id]=`, '_blank');
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -295,7 +295,7 @@ Vue.component('warehouse-article', {
|
||||
<th>Preis</th>
|
||||
<th>Summe</th>
|
||||
</tr>
|
||||
<tr v-for="(item, index) in order.orders" :key="index">
|
||||
<tr v-for="(item, index) in order.orders" :key="index + item.id">
|
||||
<td>{{item.title}}</td>
|
||||
<td>{{item.amount}}</td>
|
||||
<td>{{item.purchasePrice}} €</td>
|
||||
@@ -363,5 +363,17 @@ Vue.component('warehouse-article', {
|
||||
window.location.href = `${window['TT_CONFIG']['BASE_PATH']}/WarehouseOrder`;
|
||||
}, 2000);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const table = this.$refs.table?.$refs?.table;
|
||||
if (!table) return;
|
||||
|
||||
const showId = new URLSearchParams(window.location.search).get('showId');
|
||||
const currentFilterId = table.filters?.id;
|
||||
|
||||
if ((showId && currentFilterId !== showId) || (!showId && currentFilterId)) {
|
||||
table.filters = showId ? { id: showId } : {};
|
||||
table.refreshTable();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
@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;
|
||||
max-width: min(90vw) !important;
|
||||
}
|
||||
}
|
||||
@@ -1,64 +1,73 @@
|
||||
Vue.component('warehouse-offer-modal', {
|
||||
props: {
|
||||
id: {type: [String, Number], required: true},
|
||||
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-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 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/>
|
||||
</div>
|
||||
<hr>
|
||||
<h4 class="text-center">Positionen</h4>
|
||||
<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-textarea label="Schlusstext" sm rows="11" row v-model="offer.closingText"/>
|
||||
<hr>
|
||||
<tt-textarea label="Notizen" v-model="offer.notes" sm row/>
|
||||
</div>
|
||||
</tt-modal>
|
||||
`,
|
||||
<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-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 1fr 2fr 1fr 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="USt-IdNr." v-model="offer.customerVAT" sm/>
|
||||
</div>
|
||||
<hr>
|
||||
<h4 class="text-center">Positionen</h4>
|
||||
<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-input label="Gesamtsumme" v-model="offerTotalPrice" sm row type="number" disabled/>
|
||||
<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-textarea label="Schlusstext" sm rows="11" row v-model="offer.closingText"/>
|
||||
<hr>
|
||||
<tt-textarea label="Notizen" v-model="offer.notes" sm row/>
|
||||
</div>
|
||||
|
||||
|
||||
<template v-slot:footer-prepend>
|
||||
<tt-input placeholder="Vorlagenname" no-form-group v-model="templateName"/>
|
||||
<tt-button text="Als Vorlage speichern" @click="saveTemplate" icon="fas fa-save" additional-class="btn-success"/>
|
||||
</template>
|
||||
</tt-modal>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
billAddrAutoCompleteUrl: window.TT_CONFIG['BASE_PATH'] + '/Address/Api?do=findAddress&fibu_primary_account=1',
|
||||
window: window,
|
||||
positionsConfig: {
|
||||
fields: {
|
||||
article: {
|
||||
type: 'autocomplete',
|
||||
label: 'Artikel',
|
||||
apiUrl: '/WarehouseArticle/autoComplete',
|
||||
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'},
|
||||
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'},
|
||||
unitPrice: {type: 'input', label: 'Einzelpreis', inputType: 'number'},
|
||||
discount: {type: 'input', label: 'Rabatt (%)', inputType: 'number'},
|
||||
},
|
||||
validateForm: (formData) => {
|
||||
const requiredFields = ['article', 'amount', 'unitPrice'];
|
||||
@@ -71,45 +80,46 @@ Vue.component('warehouse-offer-modal', {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
paymentTerms: [
|
||||
paymentTerms: [
|
||||
{value: 'net30', text: '30 Tage netto'},
|
||||
{value: 'net60', text: '60 Tage netto'},
|
||||
{value: 'immediate', text: 'Sofort fällig'},
|
||||
],
|
||||
deliveryTerms: [
|
||||
deliveryTerms: [
|
||||
{value: 'ex_works', text: 'Ab Werk'},
|
||||
{value: 'free_delivery', text: 'Frei Haus'},
|
||||
{value: 'fob', text: 'FOB'},
|
||||
],
|
||||
offer: {
|
||||
editor: window.TT_CONFIG['USER_ID'],
|
||||
customerNumber: '',
|
||||
reference: '',
|
||||
purpose: '',
|
||||
customerName: '',
|
||||
customerStreet: '',
|
||||
customerZip: '',
|
||||
customerCity: '',
|
||||
customerVAT: '',
|
||||
positions: [],
|
||||
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: '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: '',
|
||||
}
|
||||
totalDiscount: 0,
|
||||
paymentTerms: 'net30',
|
||||
deliveryTerms: 'ex_works',
|
||||
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: '',
|
||||
},
|
||||
templateName: '',
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@@ -122,6 +132,7 @@ Vue.component('warehouse-offer-modal', {
|
||||
},
|
||||
methods: {
|
||||
async submit() {
|
||||
this.offer.totalAmount = this.offerTotalPrice;
|
||||
if (this.offer.positions.length === 0) return window.notify('error', 'Bitte fügen Sie mindestens eine Position hinzu.');
|
||||
|
||||
const url = this.id === 'create'
|
||||
@@ -147,32 +158,107 @@ Vue.component('warehouse-offer-modal', {
|
||||
this.$refs.positionsManager.updateField('unit', response.data.unit);
|
||||
}
|
||||
},
|
||||
async saveTemplate() {
|
||||
if (!this.templateName) return window.notify('error', 'Bitte geben Sie einen Namen für die Vorlage ein.');
|
||||
if (this.offer.positions.length === 0) return window.notify('error', 'Bitte fügen Sie mindestens eine Position hinzu.');
|
||||
|
||||
const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOffer/createTemplate`, {
|
||||
name: this.templateName,
|
||||
positions: this.offer.positions,
|
||||
totalDiscount: this.offer.totalDiscount,
|
||||
paymentTerms: this.offer.paymentTerms,
|
||||
deliveryTerms: this.offer.deliveryTerms,
|
||||
closingText: this.offer.closingText,
|
||||
notes: this.offer.notes
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
window.notify('success', response.data.message ?? 'Vorlage erfolgreich gespeichert');
|
||||
} else {
|
||||
window.notify('error', response.data.message || 'Ein Fehler ist aufgetreten');
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'offer.customerNumber': async function () {
|
||||
if (!this.offer.customerNumber) return;
|
||||
|
||||
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/Address/api?do=getAddress&id=${this.offer.customerNumber}`);
|
||||
if (response.data.status !== 'OK' || !response.data.result.address) {
|
||||
this.window.notify('error', 'Kundenadresse konnte nicht gefunden werden');
|
||||
return;
|
||||
}
|
||||
|
||||
const address = response.data.result.address;
|
||||
this.offer.customerName = address.company || `${address.firstname} ${address.lastname}`;
|
||||
this.offer.customerStreet = address.street;
|
||||
this.offer.customerZip = address.zip;
|
||||
this.offer.customerCity = address.city;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
offerTotalPrice() {
|
||||
const totalPrice = this.offer.positions.reduce((total, position) => {
|
||||
if (!position.amount) return total;
|
||||
const discount = position.discount ? (position.unitPrice * position.amount) * position.discount / 100 : 0;
|
||||
return total + (position.unitPrice * position.amount) - discount;
|
||||
}, 0);
|
||||
|
||||
return totalPrice - (totalPrice * this.offer.totalDiscount / 100);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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>
|
||||
`,
|
||||
<tt-card>
|
||||
<warehouse-offer-modal v-if="offerModalId" :id="offerModalId" ref="modal"
|
||||
@close="offerModalId = null;$refs.table.$refs.table.refreshTable()"/>
|
||||
<div style="display: flex; gap: 8px">
|
||||
<button @click="offerModalId = 'create'" class="btn btn-primary">Angebot erstellen</button>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-primary dropdown-toggle" @click="offerTemplatesDropdown = !offerTemplatesDropdown">
|
||||
Angebot aus Vorlage erstellen <i class="fas fa-caret-down"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu" :class="{'show': offerTemplatesDropdown}">
|
||||
<li v-for="template in offerTemplates" @click="createOfferFromTemplate(template)">
|
||||
<a class="dropdown-item">{{ template.templateName }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<tt-table-crud emit-edit @edit="offerModalId = $event.id" ref="table">
|
||||
</tt-table-crud>
|
||||
</tt-card>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
window: window,
|
||||
window: window,
|
||||
offerModalId: null,
|
||||
offerTemplates: [],
|
||||
offerTemplatesDropdown: false,
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOffer/getTemplates`);
|
||||
this.offerTemplates = response.data;
|
||||
},
|
||||
methods: {
|
||||
async createOfferFromTemplate(template) {
|
||||
this.offerModalId = 'create';
|
||||
await this.$nextTick();
|
||||
|
||||
this.$refs.modal.offer.positions = JSON.parse(template.positions);
|
||||
this.$refs.modal.offer.totalDiscount = template.totalDiscount;
|
||||
this.$refs.modal.offer.paymentTerms = template.paymentTerms;
|
||||
this.$refs.modal.offer.deliveryTerms = template.deliveryTerms;
|
||||
this.$refs.modal.offer.closingText = template.closingText;
|
||||
this.$refs.modal.offer.notes = template.notes;
|
||||
|
||||
this.window.notify('success', 'Angebot aus Vorlage erstellt');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -278,8 +278,19 @@ Vue.component('warehouse-order-modal', {
|
||||
v-model="order.positions"
|
||||
:config="positionsConfig"
|
||||
@updateField-article="fetchDistributors"
|
||||
@updateField-article_text="fetchDistributors"
|
||||
@updateField-distributorId="fetchDistributorData"
|
||||
/>
|
||||
>
|
||||
<template #form-actions-append>
|
||||
<!-- v-if $refs.positionsManager.formData.article parse is int and not NaN we show a <tt-button> with a @click to BASE_PATH /WarehouseArticle?showId-->
|
||||
<tt-button
|
||||
v-if="!isNaN(parseInt($refs.positionsManager.formData.article))"
|
||||
text="Zum Artikel"
|
||||
sm
|
||||
additional-class="btn-outline-primary"
|
||||
@click="window.open(window.TT_CONFIG['BASE_PATH'] + '/WarehouseArticle?showId=' + $refs.positionsManager.formData.article)"/>
|
||||
</template>
|
||||
</tt-positions-manager>
|
||||
|
||||
<hr>
|
||||
<h4 class="text-center">Lieferadresse</h4>
|
||||
@@ -319,6 +330,7 @@ Vue.component('warehouse-order-modal', {
|
||||
return {
|
||||
window: window,
|
||||
showSendShippingNote: null,
|
||||
lastDistributorFetch: null,
|
||||
positionsConfig: {
|
||||
customOrdering: 'distributorId',
|
||||
fields: {
|
||||
@@ -327,6 +339,7 @@ Vue.component('warehouse-order-modal', {
|
||||
label: 'Artikel',
|
||||
apiUrl: '/WarehouseArticle/autoComplete',
|
||||
customFieldReference: 'WarehouseArticle',
|
||||
emitDisplayValue: true,
|
||||
},
|
||||
distributorId: {type: 'select', label: 'Lieferant', options: [], customFieldReference: 'WarehouseDistributor'},
|
||||
distributorArticleNumber: {type: 'input', label: 'Lieferant Art-Nr.'},
|
||||
@@ -334,23 +347,12 @@ Vue.component('warehouse-order-modal', {
|
||||
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;
|
||||
},
|
||||
validateFormOptions: [
|
||||
{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'}
|
||||
],
|
||||
},
|
||||
order: {
|
||||
extReference: '',
|
||||
@@ -373,23 +375,26 @@ Vue.component('warehouse-order-modal', {
|
||||
return;
|
||||
}
|
||||
|
||||
const orderRequest = JSON.parse(localStorage.getItem('WarehouseOrder_create'));
|
||||
if (!orderRequest) return;
|
||||
const orderRequests = JSON.parse(localStorage.getItem('WarehouseOrder_create'));
|
||||
if (!orderRequests) return;
|
||||
|
||||
const positions = JSON.parse(orderRequest.positions);
|
||||
this.order.positions = await Promise.all(positions.map(async p => {
|
||||
const distributor = (await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrder/getArticleDistributorData`,
|
||||
{params: {articleId: p.articleId}})).data[0];
|
||||
return {
|
||||
article: p.articleId,
|
||||
amount: p.amount,
|
||||
buyPrice: distributor.purchasePrice,
|
||||
distributorId: distributor.id,
|
||||
distributorArticleNumber: distributor.externalArticleNumber,
|
||||
verwendung: `${p.hasOwnProperty('purpose') ? p.purpose : ''} [Bestellwunsch: #${orderRequest.id}]`,
|
||||
linkedOrderRequestId: orderRequest.id
|
||||
};
|
||||
}));
|
||||
for (const orderRequest of orderRequests) {
|
||||
const positions = JSON.parse(orderRequest.positions);
|
||||
const parsedPositions = await Promise.all(positions.map(async p => {
|
||||
const distributor = (await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrder/getArticleDistributorData`,
|
||||
{params: {articleId: p.articleId}})).data[0];
|
||||
return {
|
||||
article: p.articleId,
|
||||
amount: p.amount,
|
||||
buyPrice: distributor.purchasePrice,
|
||||
distributorId: distributor.id,
|
||||
distributorArticleNumber: distributor.externalArticleNumber,
|
||||
verwendung: `${p.hasOwnProperty('purpose') ? p.purpose : ''} [Bestellwunsch: #${orderRequest.id}]`,
|
||||
linkedOrderRequestId: orderRequest.id
|
||||
};
|
||||
}));
|
||||
this.order.positions = [...this.order.positions, ...parsedPositions];
|
||||
}
|
||||
|
||||
localStorage.removeItem('WarehouseOrder_create');
|
||||
},
|
||||
@@ -433,7 +438,9 @@ Vue.component('warehouse-order-modal', {
|
||||
async fetchDistributors(article) {
|
||||
const url = `${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getArticleDistributorData`;
|
||||
const params = typeof article === 'string' ? {allDistributor: true} : {articleId: article};
|
||||
if (JSON.stringify(params) === JSON.stringify(this.lastDistributorFetch)) return;
|
||||
|
||||
this.lastDistributorFetch = params;
|
||||
const response = await axios.get(url, {params});
|
||||
this.positionsConfig.fields.distributorId.options = response.data.map(distributor => ({
|
||||
value: distributor.id,
|
||||
@@ -570,10 +577,8 @@ Vue.component('warehouse-order', {
|
||||
},
|
||||
methods: {
|
||||
async closeModal() {
|
||||
console.log("hi");
|
||||
this.orderModalId = null;
|
||||
this.changeStatusModalId = null;
|
||||
console.log("hi");
|
||||
await new Promise(resolve => setTimeout(resolve, 250));
|
||||
this.$refs.table.$refs.table.refreshTable();
|
||||
},
|
||||
|
||||
@@ -5,35 +5,35 @@ window['TT_CONFIG']['CRUD_CONFIG']['editCondition'] = (row) => {
|
||||
window.TT_CONFIG["CRUD_CONFIG"]["additionalActions"] = [
|
||||
...window.TT_CONFIG["CRUD_CONFIG"]["additionalActions"],
|
||||
{
|
||||
key: "cancelRequest",
|
||||
title: "Bestellwunsch stornieren",
|
||||
class: "fas fa-ban text-danger", // Instead of fa-times, use a ban icon
|
||||
key: "cancelRequest",
|
||||
title: "Bestellwunsch stornieren",
|
||||
class: "fas fa-ban text-danger", // Instead of fa-times, use a ban icon
|
||||
condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.cancelled === 0,
|
||||
},
|
||||
{
|
||||
key: "uncancelRequest",
|
||||
title: "Bestellwunsch wiederherstellen",
|
||||
class: "fas fa-undo text-warning", // Use an undo icon for restore, with a warning color
|
||||
key: "uncancelRequest",
|
||||
title: "Bestellwunsch wiederherstellen",
|
||||
class: "fas fa-undo text-warning", // Use an undo icon for restore, with a warning color
|
||||
condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.cancelled === 1,
|
||||
},
|
||||
{
|
||||
key: "createOrder",
|
||||
title: "Bestellung erstellen",
|
||||
class: "fas fa-shopping-cart text-primary", // Use shopping-cart to indicate order creation
|
||||
key: "addOrderToCart",
|
||||
title: "Bestellung in den Warenkorb",
|
||||
class: "fas fa-shopping-cart text-primary", // Use shopping-cart to indicate order creation
|
||||
condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1'
|
||||
&& row.cancelled === 0 && (!row.linkedOrderIds || row.linkedOrderIds.length === 0)
|
||||
&& JSON.parse(row.positions).filter(position => position.articleId_text).length === 0,
|
||||
},
|
||||
{
|
||||
key: "doneOrder",
|
||||
title: "Bestellwunsch erledigt",
|
||||
class: "fas fa-check-circle text-success", // Use check-circle for marking as done
|
||||
key: "doneOrder",
|
||||
title: "Bestellwunsch erledigt",
|
||||
class: "fas fa-check-circle text-success", // Use check-circle for marking as done
|
||||
condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.done === 0,
|
||||
},
|
||||
{
|
||||
key: "undoneOrder",
|
||||
title: "Bestellwunsch wieder offen",
|
||||
class: "fas fa-redo-alt text-info", // Use redo-alt to indicate reopening the order
|
||||
key: "undoneOrder",
|
||||
title: "Bestellwunsch wieder offen",
|
||||
class: "fas fa-redo-alt text-info", // Use redo-alt to indicate reopening the order
|
||||
condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.done === 1,
|
||||
},
|
||||
];
|
||||
@@ -41,12 +41,12 @@ window.TT_CONFIG["CRUD_CONFIG"]["additionalActions"] = [
|
||||
Vue.component('add-log-modal', {
|
||||
props: {
|
||||
orderRequestId: {type: Number, required: true},
|
||||
type: {type: String, default: 'accept'}
|
||||
type: {type: String, default: 'accept'}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
orderRequest: null,
|
||||
note: '',
|
||||
note: '',
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
@@ -58,11 +58,11 @@ Vue.component('add-log-modal', {
|
||||
window.notify('error', 'Bestellwunsch wurde storniert');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
methods: {
|
||||
async submit() {
|
||||
const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrderRequest/createNewLogAction`, {
|
||||
orderRequestId: this.orderRequestId,
|
||||
note: this.note,
|
||||
note: this.note,
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
@@ -75,85 +75,80 @@ Vue.component('add-log-modal', {
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<tt-modal :show="true" :delete="false" @submit="submit" @update:show="$emit('close')" title="Status ändern">
|
||||
<tt-loader :absolute="false" v-if="!orderRequest"/>
|
||||
<template v-else>
|
||||
<tt-textarea label="Bemerkung*" v-model="note" sm/>
|
||||
</template>
|
||||
</tt-modal>
|
||||
<tt-modal :show="true" :delete="false" @submit="submit" @update:show="$emit('close')" title="Status ändern">
|
||||
<tt-loader :absolute="false" v-if="!orderRequest"/>
|
||||
<template v-else>
|
||||
<tt-textarea label="Bemerkung*" v-model="note" sm/>
|
||||
</template>
|
||||
</tt-modal>
|
||||
|
||||
`
|
||||
`
|
||||
})
|
||||
|
||||
Vue.component('order-request-log', {
|
||||
props: {orderRequestId: {type: Number, required: true}},
|
||||
data: () => ({
|
||||
data: () => ({
|
||||
logs: []
|
||||
}),
|
||||
async mounted() {
|
||||
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrderRequest/getLogById`, {params: {orderRequestId: this.orderRequestId}});
|
||||
this.logs = response.data;
|
||||
const [{data: logs}, {data: order}] = await Promise.all([
|
||||
axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrderRequest/getLogById`, {params: {orderRequestId: this.orderRequestId}}),
|
||||
axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrderRequest/getById`, {params: {id: this.orderRequestId}})
|
||||
]);
|
||||
this.logs = logs;
|
||||
|
||||
const response2 = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrderRequest/getById`, {params: {id: this.orderRequestId}});
|
||||
// check if linkedOrderIds is set and if set length > 0 and if so, get the linked orders logs
|
||||
// and add them to the logs array and sort them by create date
|
||||
|
||||
// if response2.data.linkedOrderIds is a string try to parse it
|
||||
if (typeof response2.data.linkedOrderIds === 'string') {
|
||||
try {
|
||||
response2.data.linkedOrderIds = JSON.parse(response2.data.linkedOrderIds);
|
||||
} catch {
|
||||
}
|
||||
if (typeof order.linkedOrderIds === 'string') try {
|
||||
order.linkedOrderIds = JSON.parse(order.linkedOrderIds);
|
||||
} catch {
|
||||
order.linkedOrderIds = [];
|
||||
}
|
||||
|
||||
if (response2.data.linkedOrderIds && response2.data.linkedOrderIds.length > 0) {
|
||||
const linkedOrdersLogs = await Promise.all(
|
||||
response2.data.linkedOrderIds.map(async (id) => {
|
||||
const res1 = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getById`, {params: {id}});
|
||||
const res2 = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getLogById`, {params: {id}});
|
||||
if (!order.linkedOrderIds?.length) return;
|
||||
|
||||
return res2.data.map(log => {
|
||||
log.message = `${res1.data.orderNumber} - ${log.message}`;
|
||||
return log;
|
||||
})
|
||||
})
|
||||
);
|
||||
this.logs = this.logs.concat(...linkedOrdersLogs).sort((a, b) => b.create - a.create);
|
||||
}
|
||||
const linkedLogs = (await Promise.all(
|
||||
order.linkedOrderIds.map(async id => {
|
||||
const [{data: order}, {data: orderLogs}] = await Promise.all([
|
||||
axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getById`, {params: {id}}),
|
||||
axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder/getLogById`, {params: {id}})
|
||||
]);
|
||||
return orderLogs.map(log => ({...log, message: `${order.orderNumber} - ${log.message}`}));
|
||||
})
|
||||
)).flat();
|
||||
|
||||
|
||||
},
|
||||
this.logs = [...logs, ...linkedLogs].sort((a, b) => b.create - a.create);
|
||||
}
|
||||
,
|
||||
methods: {
|
||||
formatDate: date => window.moment(date * 1000).format('DD.MM.YYYY HH:mm'),
|
||||
formatDate: date => window.moment(date * 1000).format('DD.MM.YYYY HH:mm'),
|
||||
getUserName: id => window.TT_CONFIG.CRUD_CONFIG.columns.find(col => col.key === 'createBy')?.modal.items.find(u => u.value === id)?.text
|
||||
},
|
||||
//language=Vue
|
||||
template: `
|
||||
<div>
|
||||
<template v-if="logs.length > 0">
|
||||
<hr>
|
||||
<h3>Log</h3>
|
||||
<div v-for="log in logs" :key="log.id" class="alert alert-light">
|
||||
{{ formatDate(log.create) }} ({{ getUserName(log.createBy) }}) | {{ log.message }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
`
|
||||
<div>
|
||||
<template v-if="logs.length > 0">
|
||||
<hr>
|
||||
<h3>Log</h3>
|
||||
<div v-for="log in logs" :key="log.id" class="alert alert-light">
|
||||
{{ formatDate(log.create) }} ({{ getUserName(log.createBy) }}) | {{ log.message }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
|
||||
|
||||
Vue.component('linked-order-status', {
|
||||
props: ['linkedOrders'],
|
||||
data: () => ({
|
||||
orders: [],
|
||||
data: () => ({
|
||||
orders: [],
|
||||
statusTranslations: {
|
||||
new: 'Neu',
|
||||
accepted: 'Akzeptiert',
|
||||
ordered: 'Bestellt',
|
||||
sent: 'Versendet',
|
||||
new: 'Neu',
|
||||
accepted: 'Akzeptiert',
|
||||
ordered: 'Bestellt',
|
||||
sent: 'Versendet',
|
||||
partiallyDelivered: 'Teilweise geliefert',
|
||||
fullyDelivered: 'Geliefert',
|
||||
cancelled: 'Storniert',
|
||||
fullyDelivered: 'Geliefert',
|
||||
cancelled: 'Storniert',
|
||||
}
|
||||
}),
|
||||
async mounted() {
|
||||
@@ -163,85 +158,97 @@ Vue.component('linked-order-status', {
|
||||
},
|
||||
//language=Vue
|
||||
template: `
|
||||
<div>
|
||||
<div>
|
||||
<span v-for="(order, index) in orders" :key="order.id" :class="{ 'mt-1': index > 0 }"
|
||||
class="badge badge-pill badge-primary mr-1">{{ order.orderNumber }} - {{ statusTranslations[order.status] }}</span>
|
||||
</div>`
|
||||
</div>`
|
||||
});
|
||||
|
||||
Vue.component('warehouse-order-request-detail', {
|
||||
props: {
|
||||
positions: {
|
||||
type: Array,
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
//language=Vue
|
||||
template: `
|
||||
<div style="display: flex; justify-content: center; margin-bottom: 10px">
|
||||
<div class="WarehouseOrderRequestDetailTable">
|
||||
<div>ARTIKEL</div>
|
||||
<div>MENGE</div>
|
||||
<div>ZWECK</div>
|
||||
<template v-for="position in positions">
|
||||
<div>
|
||||
<tt-resolver v-if="position.articleId" reference="WarehouseArticle" :value="position.articleId"/>
|
||||
<span v-else>{{ position.articleId_text }}</span>
|
||||
</div>
|
||||
<div>{{ position.amount }}</div>
|
||||
<div>{{ position.purpose }}</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
<div style="display: flex; justify-content: center; margin-bottom: 10px">
|
||||
<div class="WarehouseOrderRequestDetailTable">
|
||||
<div>ARTIKEL</div>
|
||||
<div>MENGE</div>
|
||||
<div>ZWECK</div>
|
||||
<template v-for="position in positions">
|
||||
<div>
|
||||
<tt-resolver v-if="position.articleId" reference="WarehouseArticle" :value="position.articleId"/>
|
||||
<span v-else>{{ position.articleId_text }}</span>
|
||||
</div>
|
||||
<div>{{ position.amount }}</div>
|
||||
<div>{{ position.purpose }}</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
||||
|
||||
Vue.component('warehouse-order-request', {
|
||||
//language=Vue
|
||||
template: `
|
||||
<tt-card>
|
||||
<tt-table-crud @openHistory="openHistory"
|
||||
@cancelRequest="cancelRequest"
|
||||
@uncancelRequest="uncancelRequest"
|
||||
@doneOrder="doneOrder"
|
||||
@undoneOrder="undoneOrder"
|
||||
@createLog="createLog"
|
||||
@createOrder="createOrder"
|
||||
ref="crud">
|
||||
<template #linkedorderids="{row}">
|
||||
<linked-order-status :linkedOrders="row.linkedOrderIds" v-if="row.linkedOrderIds"/>
|
||||
</template>
|
||||
<template #expandedRow="{row}">
|
||||
<warehouse-order-request-detail :positions="JSON.parse(row['positions'])"/>
|
||||
<order-request-log :orderRequestId="row.id"/>
|
||||
<hr>
|
||||
<h4>Notiz</h4>
|
||||
<span>{{ row.note }}</span>
|
||||
</template>
|
||||
</tt-table-crud>
|
||||
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
|
||||
<add-log-modal v-if="addLogModalId"
|
||||
:orderRequestId="addLogModalId"
|
||||
@close="addLogModal = false; addLogModalId = null; $refs.crud.$refs.table.refreshTable()"/>
|
||||
</tt-card>
|
||||
`,
|
||||
data: () => ({
|
||||
<tt-card>
|
||||
<tt-table-crud @openHistory="openHistory"
|
||||
@cancelRequest="cancelRequest"
|
||||
@uncancelRequest="uncancelRequest"
|
||||
@doneOrder="doneOrder"
|
||||
@undoneOrder="undoneOrder"
|
||||
@createLog="createLog"
|
||||
@createOrder="createOrder"
|
||||
@addOrderToCart="addOrderToCart"
|
||||
ref="crud">
|
||||
<template #table-top-buttons>
|
||||
<tt-tooltip :text="createOrdersButtonToolTipText">
|
||||
<tt-button
|
||||
:disabled="orderShoppingCart.length === 0"
|
||||
@click="createOrder"
|
||||
additional-class="btn-outline-success text-center"
|
||||
:text="createOrdersButtonText" icon="fas fa-shopping-cart"/>
|
||||
</tt-tooltip>
|
||||
</template>
|
||||
|
||||
<template #linkedorderids="{row}">
|
||||
<linked-order-status :linkedOrders="row.linkedOrderIds" v-if="row.linkedOrderIds"/>
|
||||
</template>
|
||||
<template #expandedRow="{row}">
|
||||
<warehouse-order-request-detail :positions="JSON.parse(row['positions'])"/>
|
||||
<order-request-log :orderRequestId="row.id"/>
|
||||
<hr>
|
||||
<h4>Notiz</h4>
|
||||
<span>{{ row.note }}</span>
|
||||
</template>
|
||||
</tt-table-crud>
|
||||
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
|
||||
<add-log-modal v-if="addLogModalId"
|
||||
:orderRequestId="addLogModalId"
|
||||
@close="addLogModal = false; addLogModalId = null; $refs.crud.$refs.table.refreshTable()"/>
|
||||
</tt-card>
|
||||
`,
|
||||
data: () => ({
|
||||
window,
|
||||
historyModal: false,
|
||||
historyModalId: null,
|
||||
addLogModal: false,
|
||||
addLogModalId: null,
|
||||
showHiddenRequests: false,
|
||||
historyModal: false,
|
||||
historyModalId: null,
|
||||
addLogModal: false,
|
||||
addLogModalId: null,
|
||||
showHiddenRequests: false,
|
||||
showCanceledRequests: false,
|
||||
orderRequestModalId: null
|
||||
orderRequestModalId: null,
|
||||
orderShoppingCart: [],
|
||||
}),
|
||||
methods: {
|
||||
methods: {
|
||||
openHistory(e) {
|
||||
this.historyModal = true;
|
||||
this.historyModalId = e.id;
|
||||
},
|
||||
async cancelRequest(row, cancel) {
|
||||
async cancelRequest(row, cancel = '1') {
|
||||
if (!confirm('Bestellwunsch wirklich stornieren?')) return;
|
||||
const res = await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrderRequest/cancel?id=${row.id}&cancel=${cancel}`);
|
||||
window.notify(res.data.success ? 'success' : 'error',
|
||||
@@ -268,12 +275,26 @@ Vue.component('warehouse-order-request', {
|
||||
uncancelRequest(row) {
|
||||
this.cancelRequest(row, '0');
|
||||
},
|
||||
async createOrder(row) {
|
||||
const res = await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrderRequest/getById?id=${row.id}`);
|
||||
async createOrder() {
|
||||
if (this.orderShoppingCart.length > 0) {
|
||||
localStorage.setItem('WarehouseOrder_create', JSON.stringify(this.orderShoppingCart));
|
||||
window.location.href = `${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder`;
|
||||
} else window.notify('warning', 'Warenkorb ist leer');
|
||||
},
|
||||
async addOrderToCart(row) {
|
||||
const res = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrderRequest/getById?id=${row.id}`);
|
||||
if (res.data?.positions && typeof res.data.positions === 'string') {
|
||||
localStorage.setItem('WarehouseOrder_create', JSON.stringify(res.data));
|
||||
window.location.href = `${window.TT_CONFIG.BASE_PATH}/WarehouseOrder`;
|
||||
} else window.notify('error', res.data.message || 'Fehler beim erstellen der Bestellung');
|
||||
this.orderShoppingCart.push(res.data);
|
||||
window.notify('success', 'Bestellung in den Warenkorb gelegt');
|
||||
} else window.notify('error', res.data.message || 'Fehler beim hinzufügen der Bestellung');
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
createOrdersButtonText: function () {
|
||||
return this.orderShoppingCart.length > 0 ? `${this.orderShoppingCart.length} Bestellung(en) erstellen` : 'Bestellung(en) erstellen';
|
||||
},
|
||||
createOrdersButtonToolTipText: function () {
|
||||
return this.orderShoppingCart.length > 0 ? `Erstellt ${this.orderShoppingCart.length} Bestellung(en)` : 'Keine Bestellung im Warenkorb';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -188,4 +188,18 @@ input:disabled + .ios-switch-slider {
|
||||
.see-through-test-modal .modal {
|
||||
position: unset !important;
|
||||
background: unset !important;
|
||||
}
|
||||
|
||||
.see-through-test-modal .btn-secondary {
|
||||
display:none;
|
||||
}
|
||||
|
||||
@media all and (display-mode: standalone) {
|
||||
#topnav {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,12 @@ window.TT_CONFIG["CRUD_CONFIG"]["additionalActions"] = [
|
||||
"class": "fas fa-ban text-danger",
|
||||
"condition": (row) => (window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && ['new', 'in_progress', 'accepted'].includes(row.status)) || (row.status === 'new' && row.signature === null),
|
||||
},
|
||||
{
|
||||
"key": "status_to_new",
|
||||
"title": "Lieferschein wiedereröffnen",
|
||||
"class": "fas fa-redo-alt text-success",
|
||||
"condition": (row) => (window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.status === 'cancelled'),
|
||||
},
|
||||
{
|
||||
"key": "add_log",
|
||||
"title": "Log Eintrag hinzufügen",
|
||||
@@ -64,7 +70,7 @@ Vue.component('warehouse-shipping-note-positions', {
|
||||
<ul v-if="!loading">
|
||||
<li v-for="position in positions">
|
||||
<span>{{ position.amount }}x
|
||||
{{ position.article ? articleData[position.article]?.text : position.articlePacket ? articlePacketData[position.articlePacket]?.text : position.articleText }}</span>
|
||||
{{ position.article ? articleData[position.article]?.text : position.articlePacket ? articlePacketData[position.articlePacket]?.text : position.articleText ? position.articleText : position.article_text }}</span>
|
||||
</li>
|
||||
<template v-for="entry in hoursEntries">
|
||||
<li><span>{{ entry.hourCount }}h Arbeitszeit</span></li>
|
||||
@@ -294,8 +300,8 @@ Vue.component('warehouse-shipping-note-see-through', {
|
||||
<warehouse-shipping-note-modal v-if="currentRow" :id="currentRow.id" @close="fetchData" ref="modal"/>
|
||||
</div>
|
||||
<div v-if="activeTab === 'Logs'" style="flex: 1; overflow: auto;" class="see-through-test-modal">
|
||||
<warehouse-shipping-note-logs :shipping-note-id="currentRow.id"/>
|
||||
<add-log-modal-sn v-if="currentRow" :shipping-note-id="currentRow.id" @close="fetchData"/>
|
||||
<warehouse-shipping-note-logs :shipping-note-id="currentRow.id" :key="'logs' + logModalKey"/>
|
||||
<add-log-modal-sn v-if="currentRow" :shipping-note-id="currentRow.id" @close="logModalKey++" :key="'modal'+logModalKey"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -305,6 +311,7 @@ Vue.component('warehouse-shipping-note-see-through', {
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
logModalKey: 0,
|
||||
currentPage: 1,
|
||||
perPage: 1,
|
||||
rows: [],
|
||||
@@ -355,6 +362,10 @@ Vue.component('warehouse-shipping-note-see-through', {
|
||||
})
|
||||
|
||||
this.rows = response.data.rows;
|
||||
if (this.rows.length === 0) {
|
||||
this.$emit('close');
|
||||
this.window.notify('info', 'Keine Lieferscheine mit diesem Status gefunden');
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
@@ -408,6 +419,7 @@ Vue.component('warehouse-shipping-note', {
|
||||
@status_to_invoiced="changeStatus($event.id, 'invoiced')"
|
||||
@status_to_on_hold="changeStatus($event.id, 'on_hold')"
|
||||
@status_to_cancelled="changeStatus($event.id, 'cancelled')"
|
||||
@status_to_new="changeStatus($event.id, 'new')"
|
||||
@add_log="addLogModalId = $event.id"
|
||||
@edit="shippingNoteModalId = $event.id"
|
||||
ref="table">
|
||||
@@ -448,3 +460,15 @@ Vue.component('warehouse-shipping-note', {
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/WarehouseShippingNote/sw', { scope: '/' })
|
||||
.then(registration => {
|
||||
console.log('Patching PWA Service Worker registered with scope:', registration.scope);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Patching PWA Service Worker registration failed:', error);
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -21,6 +21,13 @@ Vue.component('warehouse-shipping-note-modal', {
|
||||
textElements: [],
|
||||
hoursEntries: [],
|
||||
},
|
||||
availableTypes: [
|
||||
{text: 'Xinon Intern (XI)', value: 'XI'},
|
||||
{text: 'Xinon Hersteller (XH)', value: 'XH'},
|
||||
{text: 'Energie Steiermark (ESTMK)', value: 'ESTMK'},
|
||||
{text: 'Steirische Breitband- und Digitalinfrastrukturgesellschaft (SBIDI)', value: 'SBIDI'},
|
||||
{text: 'Verrechnen (V)', value: 'V'},
|
||||
],
|
||||
hoursLoading: false,
|
||||
geoAddr: '',
|
||||
selectedBillingAddress: '',
|
||||
@@ -108,6 +115,7 @@ Vue.component('warehouse-shipping-note-modal', {
|
||||
<template v-if="window.TT_CONFIG['WAREHOUSE_ADMIN'] == true">
|
||||
<hr>
|
||||
<tt-autocomplete v-model="shippingNote.billingAddressId" :api-url="billAddrAutoCompleteUrl" label="Rechnungsadresse" sm row/>
|
||||
<tt-autocomplete v-model="shippingNote.type" :items="availableTypes" label="Typ" sm row return-text/>
|
||||
</template>
|
||||
|
||||
<tt-textarea v-model="shippingNote.note" label="Art der Arbeit" sm row/>
|
||||
@@ -171,8 +179,14 @@ Vue.component('warehouse-shipping-note-modal', {
|
||||
methods: {
|
||||
async submit() {
|
||||
this.loading = true;
|
||||
if (!this.shippingNote.positions.length && !this.shippingNote.hoursEntries.length)
|
||||
if (!this.shippingNote.positions.length && !this.shippingNote.hoursEntries.length) {
|
||||
this.loading = false;
|
||||
return window.notify('error', 'Mindestens eine Position oder eine Stundenbuchung sind erforderlich');
|
||||
}
|
||||
|
||||
if (this.availableTypes.find(t => t.text === this.shippingNote.type)) {
|
||||
this.shippingNote.type = this.availableTypes.find(t => t.name === this.shippingNote.type).value;
|
||||
}
|
||||
|
||||
const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseShippingNote/${this.id === 'create' ? 'create' : 'update'}`, this.shippingNote);
|
||||
|
||||
@@ -253,4 +267,4 @@ Vue.component('warehouse-shipping-note-signature-pad', {
|
||||
this.shippingNote = response.data;
|
||||
this.signaturePad = new SignaturePad(document.getElementById('signature-pad'));
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
96
public/plugins/vue/tt-components/css/tt-tooltip.css
Normal file
96
public/plugins/vue/tt-components/css/tt-tooltip.css
Normal file
@@ -0,0 +1,96 @@
|
||||
.tt-tooltip-wrapper {
|
||||
position: relative;
|
||||
display: inline-block; /* Or 'block' depending on your layout needs */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tt-tooltip-box {
|
||||
position: absolute;
|
||||
background-color: #333; /* Dark background */
|
||||
color: #fff; /* White text */
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
z-index: 10; /* Ensure it's above other elements */
|
||||
opacity: 0; /* Hidden by default, fade in */
|
||||
transition: opacity 0.3s;
|
||||
pointer-events: none; /* Prevent tooltip from interfering with mouse events */
|
||||
text-align: center; /* Center text */
|
||||
}
|
||||
|
||||
/* Make tooltip visible when showTooltip is true */
|
||||
.tt-tooltip-wrapper:hover .tt-tooltip-box {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
/* Positioning */
|
||||
.tt-tooltip-top {
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin-bottom: 5px; /* Space between element and tooltip */
|
||||
}
|
||||
|
||||
.tt-tooltip-bottom {
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.tt-tooltip-left {
|
||||
right: 100%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.tt-tooltip-right {
|
||||
left: 100%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* Optional: Add arrows */
|
||||
.tt-tooltip-box::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.tt-tooltip-top::after {
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
border-color: #333 transparent transparent transparent;
|
||||
}
|
||||
|
||||
.tt-tooltip-bottom::after {
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
border-color: transparent transparent #333 transparent;
|
||||
}
|
||||
|
||||
.tt-tooltip-left::after {
|
||||
top: 50%;
|
||||
left: 100%;
|
||||
margin-top: -5px;
|
||||
border-color: transparent transparent transparent #333;
|
||||
}
|
||||
|
||||
.tt-tooltip-right::after {
|
||||
top: 50%;
|
||||
right: 100%;
|
||||
margin-top: -5px;
|
||||
border-color: transparent #333 transparent transparent;
|
||||
}
|
||||
|
||||
.tt-tooltip-wrapper > * {
|
||||
display: inline-block; /* Ensure the tooltip wrapper behaves correctly */
|
||||
width: 100% !important;
|
||||
}
|
||||
@@ -126,7 +126,7 @@ Vue.component('tt-autocomplete', {
|
||||
this.displayValue = response.data[0].text;
|
||||
} else if (this.value) {
|
||||
const selectedItem = this.items.find(item => item.value === this.value);
|
||||
this.displayValue = selectedItem ? selectedItem.text : '';
|
||||
this.displayValue = selectedItem ? selectedItem.text : this.displayValue;
|
||||
} else {
|
||||
if (this.returnText === false && !(typeof this.value === 'undefined' || this.value === '')) this.$emit('input', '');
|
||||
this.displayValue = this.displayValue.replace(this.oldDisplayValue, '');
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
Vue.component('tt-button', {
|
||||
//language=Vue
|
||||
template: `
|
||||
<div>
|
||||
<template v-if="href">
|
||||
<a :href="href" class="btn" :class="buttonClasses" @click="handleClick">
|
||||
<a v-if="href" :href="href" class="btn" :class="buttonClasses" @click="handleClick">
|
||||
<template v-if="loading">
|
||||
<span class="spinner"></span>
|
||||
</template>
|
||||
@@ -13,9 +11,7 @@ Vue.component('tt-button', {
|
||||
{{text}}
|
||||
</template>
|
||||
</a>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button @click="handleClick" class="btn" :class="buttonClasses" :disabled="loading">
|
||||
<button v-else @click="handleClick" class="btn" :class="buttonClasses" :disabled="loading">
|
||||
<template v-if="loading">
|
||||
<span class="spinner"></span>
|
||||
</template>
|
||||
@@ -24,8 +20,6 @@ Vue.component('tt-button', {
|
||||
{{text}}
|
||||
</template>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
`,
|
||||
props: {
|
||||
sm: {type: Boolean, default: false},
|
||||
|
||||
@@ -10,6 +10,7 @@ Vue.component('tt-input', {
|
||||
hint: String,
|
||||
additionalProps: Object,
|
||||
sm: {type: Boolean, default: false},
|
||||
noFormGroup: {type: Boolean, default: false},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -22,7 +23,7 @@ Vue.component('tt-input', {
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="form-group" :class="{'row': row}">
|
||||
<div :class="{'row': row, 'form-group' : !noFormGroup}">
|
||||
<slot name="prepend"></slot>
|
||||
<label
|
||||
:class="{'col-form-label': row, 'col-sm-4': row, 'col-form-label-sm': sm && row}"
|
||||
|
||||
185
public/plugins/vue/tt-components/tt-map.js
Normal file
185
public/plugins/vue/tt-components/tt-map.js
Normal file
@@ -0,0 +1,185 @@
|
||||
Vue.component('tt-map', {
|
||||
props: {
|
||||
markersData: {
|
||||
type: Array,
|
||||
default: () => [] // Expecting [{ lat: Number, lng: Number, options: { maki?: Object, popup?: String, asyncPopupContent?: Function } }, ...]
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({}) // User overrides for defaults
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
map: null,
|
||||
markerLayer: null,
|
||||
tileLayers: { streets: null, satellite: null },
|
||||
mapType: localStorage.getItem('tt-map-type') || 'streets', // Default to 'streets' or stored preference
|
||||
internalLoading: true,
|
||||
scriptsLoaded: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isLoading() {
|
||||
return this.internalLoading || this.loading;
|
||||
},
|
||||
mapConfig() {
|
||||
const defaults = {
|
||||
center: [46.9, 15.4995],
|
||||
zoom: 11,
|
||||
mapboxKey: window.TT_CONFIG?.MAPBOX_KEY,
|
||||
streetsTileUrl: 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}',
|
||||
streetsTileId: 'mapbox/streets-v11',
|
||||
satelliteTileUrl: 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}',
|
||||
satelliteTileId: 'mapbox/satellite-streets-v12', // Or 'mapbox/satellite-v9'
|
||||
tileAttribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
clusterOptions: {},
|
||||
makiMarkerOptions: { icon: "marker", color: "#3b82f6", size: "m" }
|
||||
};
|
||||
return { ...defaults, ...this.config }; // Merge user config over defaults
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
try {
|
||||
await this.loadScripts();
|
||||
this.scriptsLoaded = true;
|
||||
this.initializeMap();
|
||||
this.updateMarkers();
|
||||
this.internalLoading = false;
|
||||
} catch (error) {
|
||||
console.error("Map Initialization Error:", error);
|
||||
this.internalLoading = false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadScripts() {
|
||||
const scripts = [
|
||||
{ type: 'link', url: 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' },
|
||||
{ type: 'script', url: 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js' },
|
||||
{ type: 'script', url: 'https://unpkg.com/leaflet-makimarkers@3.1.0/Leaflet.MakiMarkers.js' },
|
||||
{ type: 'link', url: 'https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css' },
|
||||
{ type: 'link', url: 'https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css' },
|
||||
{ type: 'script', url: 'https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js' }
|
||||
];
|
||||
|
||||
const promises = scripts.map(s => new Promise((resolve, reject) => {
|
||||
let el;
|
||||
if (s.type === 'script') {
|
||||
el = document.createElement('script');
|
||||
el.src = s.url; el.async = false; el.onload = resolve; el.onerror = reject;
|
||||
} else {
|
||||
el = document.createElement('link');
|
||||
el.rel = 'stylesheet'; el.href = s.url; resolve();
|
||||
}
|
||||
if (el) document.head.appendChild(el); else reject();
|
||||
}));
|
||||
return Promise.all(promises);
|
||||
},
|
||||
initializeMap() {
|
||||
if (!this.scriptsLoaded || !L || !L.MarkerClusterGroup || !L.MakiMarkers || !this.mapConfig.mapboxKey) return;
|
||||
|
||||
this.map = L.map(this.$refs.mapContainer, { preferCanvas: true }).setView(this.mapConfig.center, this.mapConfig.zoom);
|
||||
L.MakiMarkers.accessToken = this.mapConfig.mapboxKey;
|
||||
|
||||
this.tileLayers.streets = L.tileLayer(this.mapConfig.streetsTileUrl, {
|
||||
attribution: this.mapConfig.tileAttribution, maxZoom: 18, id: this.mapConfig.streetsTileId,
|
||||
tileSize: 512, zoomOffset: -1, accessToken: this.mapConfig.mapboxKey
|
||||
});
|
||||
this.tileLayers.satellite = L.tileLayer(this.mapConfig.satelliteTileUrl, {
|
||||
attribution: this.mapConfig.tileAttribution, maxZoom: 18, id: this.mapConfig.satelliteTileId,
|
||||
tileSize: 512, zoomOffset: -1, accessToken: this.mapConfig.mapboxKey
|
||||
});
|
||||
|
||||
this.tileLayers[this.mapType].addTo(this.map); // Add initial layer based on preference
|
||||
|
||||
this.markerLayer = L.markerClusterGroup(this.mapConfig.clusterOptions);
|
||||
this.map.addLayer(this.markerLayer);
|
||||
|
||||
// Invalidate size after initial load if container might not have been ready
|
||||
this.$nextTick(() => {
|
||||
this.map.invalidateSize();
|
||||
});
|
||||
// Add resize listener
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
},
|
||||
updateMarkers() {
|
||||
if (!this.map || !this.markerLayer || !this.scriptsLoaded) return;
|
||||
this.markerLayer.clearLayers();
|
||||
const markersToAdd = [];
|
||||
this.markersData.forEach(data => {
|
||||
if (data.lat != null && data.lng != null) {
|
||||
const makiOptions = { ...this.mapConfig.makiMarkerOptions, ...(data.options?.maki || {}) };
|
||||
const icon = L.MakiMarkers.icon(makiOptions);
|
||||
const marker = L.marker([data.lat, data.lng], { icon: icon });
|
||||
|
||||
if (data.options?.popup) {
|
||||
marker.bindPopup(data.options.popup);
|
||||
} else if (data.options?.asyncPopupContent && typeof data.options.asyncPopupContent === 'function') {
|
||||
marker.bindPopup(() => '<div class="popup-loader">Loading...</div>'); // Initial content
|
||||
marker.on('popupopen', async (e) => {
|
||||
const popup = e.popup;
|
||||
try {
|
||||
const content = await data.options.asyncPopupContent(data); // Pass marker data to function
|
||||
popup.setContent(content);
|
||||
} catch (error) {
|
||||
console.error("Error loading popup content:", error);
|
||||
popup.setContent('<div class="text-danger">Failed to load content.</div>');
|
||||
}
|
||||
popup.update(); // Adjust size
|
||||
});
|
||||
}
|
||||
|
||||
if (data.options?.tooltip) marker.bindTooltip(data.options.tooltip);
|
||||
markersToAdd.push(marker);
|
||||
}
|
||||
});
|
||||
if (markersToAdd.length > 0) this.markerLayer.addLayers(markersToAdd);
|
||||
},
|
||||
toggleMapType() {
|
||||
this.map.removeLayer(this.tileLayers[this.mapType]);
|
||||
this.mapType = this.mapType === 'streets' ? 'satellite' : 'streets';
|
||||
this.tileLayers[this.mapType].addTo(this.map);
|
||||
localStorage.setItem('tt-map-type', this.mapType);
|
||||
},
|
||||
handleResize() {
|
||||
if (this.map) {
|
||||
// Use debounce if resize events fire too rapidly
|
||||
this.map.invalidateSize();
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
markersData: { handler() { this.updateMarkers(); }, deep: true },
|
||||
loading(newVal) {
|
||||
// Optional: Invalidate map size when loading finishes, in case container size changed
|
||||
if (!newVal && this.map) {
|
||||
this.$nextTick(() => this.map.invalidateSize());
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
if (this.map) {
|
||||
this.map.remove();
|
||||
this.map = null;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div style="position: relative; width: 100%; height: 100%;">
|
||||
<div v-if="isLoading" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1001; background: rgba(255,255,255,0.7); display: flex; justify-content: center; align-items: center;">
|
||||
<tt-loader></tt-loader>
|
||||
</div>
|
||||
<div ref="mapContainer" style="width: 100%; height: 100%; z-index: 1;" :style="{ visibility: internalLoading ? 'hidden' : 'visible' }"></div>
|
||||
<button @click="toggleMapType"
|
||||
class="btn btn-light btn-sm"
|
||||
style="position: absolute; top: 10px; right: 10px; z-index: 1000;">
|
||||
<i :class="mapType === 'streets' ? 'fas fa-globe-americas' : 'fas fa-map'"></i>
|
||||
{{ mapType === 'streets' ? 'Satellite' : 'Map' }}
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
@@ -69,6 +69,7 @@ Vue.component('tt-positions-manager',
|
||||
:emit-display-value="field.emitDisplayValue || false"
|
||||
v-model="formData[key]"
|
||||
@input="$emit('updateField-' + key, $event)"
|
||||
@displayValue="$emit('updateField-' + key + '_text', $event)"
|
||||
:ref="'autocomplete-' + key"
|
||||
:api-url="window.TT_CONFIG['BASE_PATH'] + field.apiUrl"
|
||||
sm
|
||||
@@ -105,6 +106,8 @@ Vue.component('tt-positions-manager',
|
||||
:additional-class="selectedIndex === null ? 'btn-primary' : 'btn-success'"
|
||||
:text="selectedIndex === null ? 'Hinzufügen' : 'Aktualisieren'"/>
|
||||
</div>
|
||||
|
||||
<slot name="form-actions-append"></slot>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -170,9 +173,7 @@ Vue.component('tt-positions-manager',
|
||||
},
|
||||
checkEmitDisplayValueAutocomplete() {
|
||||
for (const [key, field] of Object.entries(this.config.fields)) {
|
||||
console.log("HI");
|
||||
if ((typeof field.showCondition === 'function' && field.showCondition(this.formData) === true || !field.showCondition) && field.type === 'autocomplete' && field.emitDisplayValue && (isNaN(this.formData[key]) || !this.formData[key]) && this.$refs['autocomplete-' + key][0]) {
|
||||
console.log("hi");
|
||||
this.$set(this.formData, key + '_text', this.$refs['autocomplete-' + key][0].displayValue);
|
||||
this.$delete(this.formData, key);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ Vue.component('tt-table-crud', {
|
||||
<tt-input v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'number'" v-model="crudModalData[column.key]" :label="column.text" type="number" sm row/>
|
||||
<tt-textarea v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'textarea'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
|
||||
<tt-select v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'select'" v-model="crudModalData[column.key]" :label="column.text" :options="column.items" sm row/>
|
||||
<tt-autocomplete v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'autocomplete'" v-model="crudModalData[column.key]" :label="column.text" :api-url="column.apiUrl" :items="column.items" sm row :return-text="column.returnText" />
|
||||
<tt-autocomplete v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'autocomplete'" v-model="crudModalData[column.key]" :label="column.text" :api-url="column.apiUrl" :items="typeof column.items === 'string' ? [] : column.items" sm row :return-text="column.returnText" />
|
||||
<tt-date-picker v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'datepicker'" v-model="crudModalData[column.key]" :label="column.text" sm row :date-range="false" :ref="column.key.toLowerCase() + '-modal-input'"/>
|
||||
<tt-icon-select v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'icon-select'" v-model="crudModalData[column.key]" :options="column.items" :label="column.text" sm row/>
|
||||
<tt-checkbox v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'checkbox'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
|
||||
|
||||
31
public/plugins/vue/tt-components/tt-tooltip.js
Normal file
31
public/plugins/vue/tt-components/tt-tooltip.js
Normal file
@@ -0,0 +1,31 @@
|
||||
Vue.component('tt-tooltip', {
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: 'Tooltip text'
|
||||
},
|
||||
position: {
|
||||
type: String,
|
||||
default: 'top', // Options: top, bottom, left, right
|
||||
validator: function (value) {
|
||||
// The value must match one of these strings
|
||||
return ['top', 'bottom', 'left', 'right'].indexOf(value) !== -1;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showTooltip: true
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div class="tt-tooltip-wrapper"
|
||||
@mouseenter="showTooltip = true"
|
||||
@mouseleave="showTooltip = false">
|
||||
<slot></slot> <div v-if="showTooltip" class="tt-tooltip-box" :class="['tt-tooltip-' + position]">
|
||||
{{ text }}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
|
||||
//require 'vendor/autoload.php';
|
||||
require("../../../config/config.php");
|
||||
|
||||
define('FRONKDB_SQLDEBUG',false);
|
||||
error_reporting(E_ALL & ~(E_NOTICE | E_STRICT | E_DEPRECATED));
|
||||
|
||||
require_once(LIBDIR."/mvcfronk/mfRouter/mfRouter.php");
|
||||
require_once(LIBDIR."/mvcfronk/mfBase/mfBaseModel.php");
|
||||
require_once(LIBDIR."/mvcfronk/mfBase/mfBaseController.php");
|
||||
|
||||
$me = new User(1);
|
||||
define("INTERNAL_USER_ID", $me->id);
|
||||
define("INTERNAL_USER_USERNAME", $me->username);
|
||||
define("MFBASE_BYPASS_LOGIN", true);
|
||||
|
||||
$filename = __DIR__."/import/SDIBuilding__Locations__FTTx___241204_PremNord.csv";
|
||||
|
||||
$db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
|
||||
$log = mfLoghandler::singleton();
|
||||
|
||||
$input = fopen($filename, "r");
|
||||
|
||||
$bom = "\xef\xbb\xbf";
|
||||
if(fgets($input, 4) !== $bom) {
|
||||
// BOM not found - rewind pointer to start of file.
|
||||
rewind($input);
|
||||
|
||||
}
|
||||
|
||||
$netzgebiet = new ADBNetzgebiet(2);
|
||||
$default_freigabe = json_encode(["interest", "provision", "order", "reorder"]);
|
||||
|
||||
$headers = [];
|
||||
|
||||
$u = 0;
|
||||
|
||||
$i = 0;
|
||||
while($csv = fgetcsv($input, 0, ";")) {
|
||||
$i++;
|
||||
|
||||
if($i == 1) {
|
||||
foreach($csv as $key => $name) {
|
||||
$headers[$name] = $key;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//var_dump($headers);exit;
|
||||
$fcp = false;
|
||||
|
||||
if(!trim($csv[1])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fcp_name = trim($csv[$headers["FCP cluster name"]]);
|
||||
$rimo_id = trim($csv[$headers["ExternalID"]]);
|
||||
|
||||
if(!$rimo_id) {
|
||||
echo "no rimo id\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!$fcp_name) {
|
||||
echo "no fcp name\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$fcp = ADBRimoFcp::getFirst(["netzgebiet_id" => $netzgebiet->id, "name" => $fcp_name]);
|
||||
if(!$fcp) {
|
||||
echo "FCP nicht gefunden in netzgebiet\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$building = ADBHausnummerModel::getFirst(["rimo_id" => $rimo_id]);
|
||||
if(!$building) {
|
||||
echo "Hausnummer nicht gefunden\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if($building->fcp_id != $fcp->id) {
|
||||
$building->fcp_id = $fcp->id;
|
||||
$building->save();
|
||||
}
|
||||
$u++;
|
||||
|
||||
|
||||
//echo implode(", ", $csv)."\n";
|
||||
//$gem_kz = trim($csv[61]);
|
||||
|
||||
}
|
||||
|
||||
echo "updated $u Hausnummern\n";
|
||||
@@ -1,102 +0,0 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
|
||||
//require 'vendor/autoload.php';
|
||||
require("../../../config/config.php");
|
||||
|
||||
define('FRONKDB_SQLDEBUG',false);
|
||||
error_reporting(E_ALL & ~(E_NOTICE | E_STRICT | E_DEPRECATED));
|
||||
|
||||
require_once(LIBDIR."/mvcfronk/mfRouter/mfRouter.php");
|
||||
require_once(LIBDIR."/mvcfronk/mfBase/mfBaseModel.php");
|
||||
require_once(LIBDIR."/mvcfronk/mfBase/mfBaseController.php");
|
||||
|
||||
$me = new User(1);
|
||||
|
||||
$filename = __DIR__."/import/SDIBuilding__FCPs__241204_PremNord.csv";
|
||||
|
||||
$db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
|
||||
$log = mfLoghandler::singleton();
|
||||
|
||||
$input = fopen($filename, "r");
|
||||
|
||||
$bom = "\xef\xbb\xbf";
|
||||
if(fgets($input, 4) !== $bom) {
|
||||
// BOM not found - rewind pointer to start of file.
|
||||
rewind($input);
|
||||
}
|
||||
|
||||
//$gemeinde_id = 1448;
|
||||
$netzgebiet = new ADBNetzgebiet(2);
|
||||
$default_freigabe = json_encode(["interest", "provision", "order", "reorder"]);
|
||||
|
||||
$headers = [];
|
||||
|
||||
$i = 0;
|
||||
while($csv = fgetcsv($input, 0, ";")) {
|
||||
$i++;
|
||||
|
||||
if($i == 1) {
|
||||
foreach($csv as $key => $name) {
|
||||
$headers[$name] = $key;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//var_dump($headers);exit;
|
||||
$fcp = false;
|
||||
|
||||
if(!trim($csv[1])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fcp_name = trim($csv[$headers["Name"]]);
|
||||
$rimo_id = trim($csv[$headers["ExternalID"]]);
|
||||
$label = trim($csv[$headers["User label"]]);
|
||||
$ex_state = trim($csv[$headers["Execution state"]]);
|
||||
$op_state = trim($csv[$headers["Operational state"]]);
|
||||
$gps_lat = trim($csv[$headers["Latitude"]]);
|
||||
$gps_long = trim($csv[$headers["Longitude"]]);
|
||||
$building_type = trim($csv[$headers["Building type"]]);
|
||||
|
||||
if(!$rimo_id) {
|
||||
echo "no rimo id\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!$fcp_name) {
|
||||
echo "no fcp name\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$data = [
|
||||
"netzgebiet_id" => $netzgebiet->id,
|
||||
"name" => $fcp_name,
|
||||
"rimo_id" => $rimo_id,
|
||||
"label" => $label,
|
||||
"building_type" => $building_type,
|
||||
"rimo_ex_state" => $ex_state,
|
||||
"rimo_op_state" => $op_state,
|
||||
"gps_lat" => str_replace(",",".",$gps_lat),
|
||||
"gps_long" => str_replace(",",".",$gps_long)
|
||||
];
|
||||
|
||||
$fcp = ADBRimoFcp::getFirst(["rimo_id" => $rimo_id]);
|
||||
if($fcp) {
|
||||
echo "update\n";
|
||||
$fcp->update($data);
|
||||
} else {
|
||||
echo "create\n";
|
||||
$fcp = ADBRimoFcp::create($data);
|
||||
}
|
||||
|
||||
if(!$fcp->save()) {
|
||||
die("Error saving FCP\n");
|
||||
}
|
||||
|
||||
//echo implode(", ", $csv)."\n";
|
||||
//$gem_kz = trim($csv[61]);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user