Added FCPs to TheToolAdded WarehouseOffer and WarehouseOfferTemplate, also fixed menu for Lager Point

This commit is contained in:
Luca Haid
2025-04-01 15:19:18 +02:00
parent a802ff8e05
commit 97529ba95f
12 changed files with 494 additions and 410 deletions

View File

@@ -322,8 +322,8 @@ $pagination_entity_name = "Vorbestellungen";
</select> </select>
</div> </div>
<div class="col-sm-12 col-md-2"> <div class="col-sm-12 col-md-1">
<label class="form-label" for="filter_rimo_workorder">Rimo Workorder Status</label> <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"> <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="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> <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> </select>
</div> </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"> <div class="col-sm-12 col-md-2">
<label class="form-label" for="filter_rimo_workorder_team_id">Rimo Workorder Assigned Team</label> <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"> <select name="filter[rimo_workorder_team_id]" id="filter_rimo_workorder_team_id" class="form-control">
@@ -1482,4 +1489,45 @@ $pagination_entity_name = "Vorbestellungen";
} }
</script> </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"); ?> <?php include(realpath(dirname(__FILE__)."/../../$mfLayoutPackage")."/footer.php"); ?>

View File

@@ -416,6 +416,33 @@
<td class="text-monospace"><?=$preorder->adb_wohneinheit->ftu_data["id"]?> <td class="text-monospace"><?=$preorder->adb_wohneinheit->ftu_data["id"]?>
</tr> </tr>
</table> </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>
</div> </div>

View File

@@ -1,195 +1,16 @@
<?php <?php
class ADBRimoFcp extends mfBaseModel { class ADBRimoFcp extends TTCrudBaseModel {
public int $id;
protected function init() { public int $netzgebiet_id;
$this->db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME); public ?string $name;
$this->table = "RimoFcp"; public string $rimo_id;
} public ?string $label;
public ?string $building_type;
public function getProperty($name) { public ?string $rimo_ex_state;
if($this->$name == null) { public ?string $rimo_op_state;
public ?float $gps_lat;
$classname = ucfirst($name); public ?float $gps_long;
$idfield = $name."_id"; public int $create;
$this->$name = new $classname($this->$idfield); public int $edit;
}
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;
}
}

View File

@@ -0,0 +1,101 @@
<?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) 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 ($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]);
}
}

View File

@@ -13,6 +13,7 @@ class Preorder extends mfBaseModel {
private $building; private $building;
private $adb_hausnummer; private $adb_hausnummer;
private $adb_wohneinheit; private $adb_wohneinheit;
private $fcp;
private $services; private $services;
private $ordered_services; private $ordered_services;
private $creator; private $creator;
@@ -1396,6 +1397,11 @@ class Preorder extends mfBaseModel {
return $this->creator; 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") { if($name == "editor") {
$this->editor = new User($this->edit_by); $this->editor = new User($this->edit_by);
return $this->editor; return $this->editor;

View File

@@ -1042,6 +1042,9 @@ class PreorderController extends mfBaseController {
case "saveAttribute": case "saveAttribute":
$return = $this->saveAttributeApi(); $return = $this->saveAttributeApi();
break; break;
case "getFCPsForCampaign":
$return = $this->getFCPsForCampaignApi();
break;
case "getFilteredPreorders": case "getFilteredPreorders":
$return = $this->getFilteredPreordersApi(); $return = $this->getFilteredPreordersApi();
break; break;
@@ -1085,6 +1088,17 @@ class PreorderController extends mfBaseController {
$this->returnJson($data); $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],
ADBRimoFcp::getAll(["netzgebiet_id" => $campaign->network->adb_netzgebiet_id]) ?? []
);
}
private function setBilledApi() { private function setBilledApi() {
$preorder_id = $this->request->id; $preorder_id = $this->request->id;

View File

@@ -1006,6 +1006,17 @@ class PreorderModel
} }
} }
if (!empty($filter['fcp'])) {
$fcp = $filter['fcp'];
$db = FronkDB::singleton();
if (is_array($fcp)) {
$items = array_map(fn($i) => "'" . $db->escape($i) . "'", array_filter($fcp));
if ($items) $where .= " AND adb_hausnummer.rimo_fcp_name IN (" . implode(',', $items) . ")";
} else {
$where .= " AND adb_hausnummer.rimo_fcp_name = '" . $db->escape($fcp) . "'";
}
}
// custom where clause // custom where clause
if (array_key_exists("add-where", $filter)) { if (array_key_exists("add-where", $filter)) {
$where .= " " . $filter['add-where']; $where .= " " . $filter['add-where'];

View File

@@ -33,8 +33,13 @@ class TTCrud extends mfBaseController {
$this->redirect("Dashboard"); $this->redirect("Dashboard");
} }
$modelName = str_replace('Controller', 'Model', get_class($this)); $c = get_class($this);
$this->model = new $modelName(); 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->postData = json_decode(file_get_contents('php://input'), true);
$this->checkArray = $this->getCheckArray(); $this->checkArray = $this->getCheckArray();
$this->infoMessages = $this->getInfoMessages(); $this->infoMessages = $this->getInfoMessages();

View File

@@ -10,10 +10,17 @@ class TTCrudBaseModel {
} }
} }
public 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 . "`";
}
public static function create($data) { public static function create($data) {
$FronkDB = FronkDB::singleton(); $FronkDB = FronkDB::singleton();
$db = $FronkDB->link; $db = $FronkDB->link;
$table = self::getTable(); $table = self::getFullyQualifiedTable();
self::checkAllFields($data, ['id']); self::checkAllFields($data, ['id']);
$sqlColumns = []; $sqlColumns = [];
@@ -39,16 +46,12 @@ class TTCrudBaseModel {
$sqlColumns[] = "`$field`"; $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); $db->query($sql) or die($db->error);
return $db->insert_id; 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. * Checks if all required fields of the current class are present in a given data array.
* *
@@ -90,8 +93,8 @@ class TTCrudBaseModel {
$FronkDB = FronkDB::singleton(); $FronkDB = FronkDB::singleton();
$db = $FronkDB->link; $db = $FronkDB->link;
$id = $db->real_escape_string($id); $id = $db->real_escape_string($id);
$table = self::getTable(); $table = self::getFullyQualifiedTable();
$sql = "SELECT * FROM `$table` WHERE `id` = $id"; $sql = "SELECT * FROM $table WHERE `id` = $id";
$result = $db->query($sql); $result = $db->query($sql);
// as TTCRudBaseModel is abstract, we need to get the class name of the child class // as TTCRudBaseModel is abstract, we need to get the class name of the child class
@@ -102,9 +105,9 @@ class TTCrudBaseModel {
public static function count($filter = []): int { public static function count($filter = []): int {
$FronkDB = FronkDB::singleton(); $FronkDB = FronkDB::singleton();
$db = $FronkDB->link; $db = $FronkDB->link;
$table = self::getTable(); $table = self::getFullyQualifiedTable();
$filter = self::getSQLFilter($filter); $filter = self::getSQLFilter($filter);
$sql = "SELECT COUNT(*) as count FROM `$table` $filter"; $sql = "SELECT COUNT(*) as count FROM $table $filter";
$result = $db->query($sql); $result = $db->query($sql);
return $result->fetch_assoc()['count']; return $result->fetch_assoc()['count'];
@@ -130,9 +133,9 @@ class TTCrudBaseModel {
public static function getAll($filter = [], $limit = null, $offset = 0, $order = ["key" => null]): array { public static function getAll($filter = [], $limit = null, $offset = 0, $order = ["key" => null]): array {
$FronkDB = FronkDB::singleton(); $FronkDB = FronkDB::singleton();
$db = $FronkDB->link; $db = $FronkDB->link;
$table = self::getTable(); $table = self::getFullyQualifiedTable();
$filter = self::getSQLFilter($filter); $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 .= $order['key'] === null ? " ORDER BY `id` ASC" : " ORDER BY `" . $order['key'] . "` " . $order['order'];
$sql .= $limit === null ? "" : " LIMIT " . $limit . " OFFSET " . $offset; $sql .= $limit === null ? "" : " LIMIT " . $limit . " OFFSET " . $offset;
@@ -155,7 +158,7 @@ class TTCrudBaseModel {
public static function update($data) { public static function update($data) {
$FronkDB = FronkDB::singleton(); $FronkDB = FronkDB::singleton();
$db = $FronkDB->link; $db = $FronkDB->link;
$table = self::getTable(); $table = self::getFullyQualifiedTable();
// Check if all fields are set // Check if all fields are set
self::checkAllFields($data); self::checkAllFields($data);
@@ -183,7 +186,7 @@ class TTCrudBaseModel {
$values[] = $value === null ? "`$field` = NULL" : "`$field` = '" . $db->real_escape_string($value) . "'"; $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); $db->query($sql);
return $db->affected_rows; return $db->affected_rows;
} }
@@ -191,9 +194,9 @@ class TTCrudBaseModel {
public static function delete($id) { public static function delete($id) {
$FronkDB = FronkDB::singleton(); $FronkDB = FronkDB::singleton();
$db = $FronkDB->link; $db = $FronkDB->link;
$table = self::getTable(); $table = self::getFullyQualifiedTable();
$id = $db->real_escape_string($id); $id = $db->real_escape_string($id);
$sql = "DELETE FROM `$table` WHERE `id` = $id"; $sql = "DELETE FROM $table WHERE `id` = $id";
$db->query($sql); $db->query($sql);
return $db->affected_rows; return $db->affected_rows;
} }

View File

@@ -0,0 +1,245 @@
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" />
</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'
}
},
methods: {
handleModalClose(importWasSuccessful) {
this.showImportFCPModal = false;
this.showImportLocationsModal = false;
if (importWasSuccessful) this.$refs.table.$refs.table.refreshTable();
}
}
});

View File

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

View File

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