Merge branch 'master' into fronkdev

This commit is contained in:
Frank Schubert
2025-04-10 13:27:50 +02:00
74 changed files with 2760 additions and 1027 deletions
+14 -193
View File
@@ -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;
}
@@ -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]);
}
}
+55 -1
View File
@@ -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 = [];
+2 -2
View File
@@ -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);
}
}
}
+1 -1
View File
@@ -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;
}
+35 -2
View File
@@ -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;
}
}
+48 -2
View File
@@ -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');
}
}
}
+6
View File
@@ -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;
+23
View File
@@ -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;