Document WeatherAPI and department weather endpoints in OpenAPI
This commit is contained in:
+169
@@ -6470,6 +6470,99 @@ paths:
|
||||
'200':
|
||||
description: Exchange rates retrieved successfully
|
||||
|
||||
|
||||
/modules/weatherapi/current:
|
||||
get:
|
||||
tags:
|
||||
- Modules
|
||||
summary: Get current weather
|
||||
description: Get current weather data from WeatherAPI for a location query
|
||||
operationId: weatherApiCurrent
|
||||
parameters:
|
||||
- name: q
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Location query (e.g. city, postal code, or latitude,longitude)
|
||||
responses:
|
||||
'200':
|
||||
description: Current weather retrieved successfully
|
||||
|
||||
/modules/weatherapi/forecast:
|
||||
get:
|
||||
tags:
|
||||
- Modules
|
||||
summary: Get weather forecast
|
||||
description: Get forecast weather data from WeatherAPI
|
||||
operationId: weatherApiForecast
|
||||
parameters:
|
||||
- name: q
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Location query (e.g. city, postal code, or latitude,longitude)
|
||||
- name: days
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 14
|
||||
description: Number of forecast days
|
||||
responses:
|
||||
'200':
|
||||
description: Forecast weather retrieved successfully
|
||||
|
||||
/modules/weatherapi/search:
|
||||
get:
|
||||
tags:
|
||||
- Modules
|
||||
summary: Search weather locations
|
||||
description: Search location suggestions from WeatherAPI
|
||||
operationId: weatherApiSearch
|
||||
parameters:
|
||||
- name: q
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Search text
|
||||
responses:
|
||||
'200':
|
||||
description: Location search results retrieved successfully
|
||||
|
||||
/departments/weather:
|
||||
get:
|
||||
tags:
|
||||
- Departments
|
||||
summary: Get department weather timeline
|
||||
description: Returns hourly weather, washes, hours and productivity status for a department
|
||||
operationId: getDepartmentWeatherTimeline
|
||||
parameters:
|
||||
- name: id
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
description: Department ID
|
||||
responses:
|
||||
'200':
|
||||
description: Department weather timeline retrieved successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SuccessResponse'
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/DepartmentWeatherTimelineEntry'
|
||||
|
||||
/modules/entra/users:
|
||||
get:
|
||||
tags:
|
||||
@@ -6923,6 +7016,31 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ModuleConfigUpdateResponse'
|
||||
|
||||
|
||||
/weatherapi/config:
|
||||
get:
|
||||
tags: [Config]
|
||||
summary: Get WeatherAPI config
|
||||
operationId: getWeatherApiConfig
|
||||
responses:
|
||||
'200':
|
||||
description: WeatherAPI configuration retrieved successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/WeatherApiConfigListResponse'
|
||||
post:
|
||||
tags: [Config]
|
||||
summary: Update WeatherAPI config
|
||||
operationId: updateWeatherApiConfig
|
||||
responses:
|
||||
'200':
|
||||
description: WeatherAPI configuration updated successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ModuleConfigUpdateResponse'
|
||||
|
||||
/gatewayapi/config:
|
||||
get:
|
||||
tags: [Config]
|
||||
@@ -7898,6 +8016,19 @@ components:
|
||||
- type: string
|
||||
required: [module, variable, type, value]
|
||||
|
||||
|
||||
WeatherApiConfigEntry:
|
||||
type: object
|
||||
properties:
|
||||
module: { type: string, enum: [weatherapi] }
|
||||
variable: { type: string, enum: [enabled, secret_key] }
|
||||
type: { type: string, enum: [bool, string] }
|
||||
value:
|
||||
oneOf:
|
||||
- type: boolean
|
||||
- type: string
|
||||
required: [module, variable, type, value]
|
||||
|
||||
GatewayApiConfigEntry:
|
||||
type: object
|
||||
properties:
|
||||
@@ -8083,6 +8214,15 @@ components:
|
||||
data: { type: array, items: { $ref: '#/components/schemas/FxRatesApiConfigEntry' } }
|
||||
required: [data]
|
||||
|
||||
|
||||
WeatherApiConfigListResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/ModuleConfigEnvelopeBase'
|
||||
- type: object
|
||||
properties:
|
||||
data: { type: array, items: { $ref: '#/components/schemas/WeatherApiConfigEntry' } }
|
||||
required: [data]
|
||||
|
||||
GatewayApiConfigListResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/ModuleConfigEnvelopeBase'
|
||||
@@ -8163,6 +8303,35 @@ components:
|
||||
data: { type: array, items: { $ref: '#/components/schemas/SelfServeConfigEntry' } }
|
||||
required: [data]
|
||||
|
||||
|
||||
DepartmentWeatherStatus:
|
||||
type: string
|
||||
enum: [unknown, healthy, degraded, unhealthy]
|
||||
|
||||
DepartmentWeatherCondition:
|
||||
type: string
|
||||
enum: [clear, mostly_clear, partly_cloudy, mostly_cloudy, overcast, rain, showers, thunderstorm, snow, fog]
|
||||
|
||||
DepartmentWeatherTimelineEntry:
|
||||
type: object
|
||||
properties:
|
||||
time:
|
||||
type: string
|
||||
example: '01:00'
|
||||
weather:
|
||||
$ref: '#/components/schemas/DepartmentWeatherCondition'
|
||||
washes:
|
||||
type: integer
|
||||
minimum: 0
|
||||
example: 0
|
||||
hours:
|
||||
type: integer
|
||||
minimum: 0
|
||||
example: 10
|
||||
status:
|
||||
$ref: '#/components/schemas/DepartmentWeatherStatus'
|
||||
required: [time, weather, washes, hours, status]
|
||||
|
||||
ModuleConfigEntry:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace classes;
|
||||
|
||||
use Exception;
|
||||
use interfaces\weatherapi_i;
|
||||
use weatherapi\weatherapi_c;
|
||||
|
||||
require_once WD . '/modules/weatherapi/weatherapi_c.php';
|
||||
|
||||
class weatherapi implements weatherapi_i
|
||||
{
|
||||
public weatherapi_c $config;
|
||||
|
||||
private string $api_url = 'https://api.weatherapi.com/v1/';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = new weatherapi_c();
|
||||
}
|
||||
|
||||
public function requireModuleEnabled(): void
|
||||
{
|
||||
if (!$this->config->enabled->isTrue()) {
|
||||
throw new Exception('The weatherapi module is not enabled');
|
||||
}
|
||||
}
|
||||
|
||||
public function requireValidSecretKey(): void
|
||||
{
|
||||
if ($this->config->secret_key->getVariableValue() === null || $this->config->secret_key->getVariableValue() === '') {
|
||||
throw new Exception('Invalid secret key defined in the config (weatherapi_secret_key_c)');
|
||||
}
|
||||
}
|
||||
|
||||
public function sendRequest(string $endpoint, array $query = []): object
|
||||
{
|
||||
$this->requireModuleEnabled();
|
||||
$this->requireValidSecretKey();
|
||||
|
||||
$endpoint = ltrim($endpoint, '/');
|
||||
$query = array_merge(['key' => $this->config->secret_key->getVariableValue()], $query);
|
||||
$url = $this->api_url . $endpoint . '?' . http_build_query($query);
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_ENCODING => '',
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => 'GET',
|
||||
]);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
$status_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($curl);
|
||||
curl_close($curl);
|
||||
|
||||
if ($error) {
|
||||
throw new Exception('cURL request failed: ' . $error);
|
||||
}
|
||||
|
||||
if ($response === false || $response === '') {
|
||||
throw new Exception('Empty response from WeatherAPI');
|
||||
}
|
||||
|
||||
$decoded = json_decode($response);
|
||||
if (json_last_error() !== JSON_ERROR_NONE || !is_object($decoded) && !is_array($decoded)) {
|
||||
throw new Exception('Invalid response from WeatherAPI');
|
||||
}
|
||||
|
||||
if ($status_code >= 400) {
|
||||
$message = 'WeatherAPI request failed with HTTP ' . $status_code;
|
||||
if (is_object($decoded) && isset($decoded->error->message)) {
|
||||
$message .= ': ' . $decoded->error->message;
|
||||
}
|
||||
throw new Exception($message);
|
||||
}
|
||||
|
||||
return (object)$decoded;
|
||||
}
|
||||
|
||||
public function current(string $query, array $options = []): object
|
||||
{
|
||||
return $this->sendRequest('current.json', array_merge(['q' => $query], $options));
|
||||
}
|
||||
|
||||
public function forecast(string $query, int $days = 1, array $options = []): object
|
||||
{
|
||||
return $this->sendRequest('forecast.json', array_merge(['q' => $query, 'days' => $days], $options));
|
||||
}
|
||||
|
||||
public function history(string $query, string $date, array $options = []): object
|
||||
{
|
||||
return $this->sendRequest('history.json', array_merge(['q' => $query, 'dt' => $date], $options));
|
||||
}
|
||||
|
||||
public function astronomy(string $query, string $date, array $options = []): object
|
||||
{
|
||||
return $this->sendRequest('astronomy.json', array_merge(['q' => $query, 'dt' => $date], $options));
|
||||
}
|
||||
|
||||
public function timezone(string $query, array $options = []): object
|
||||
{
|
||||
return $this->sendRequest('timezone.json', array_merge(['q' => $query], $options));
|
||||
}
|
||||
|
||||
public function sports(string $query, array $options = []): object
|
||||
{
|
||||
return $this->sendRequest('sports.json', array_merge(['q' => $query], $options));
|
||||
}
|
||||
|
||||
public function search(string $query, array $options = []): object
|
||||
{
|
||||
return $this->sendRequest('search.json', array_merge(['q' => $query], $options));
|
||||
}
|
||||
|
||||
public function marine(string $query, int $days = 1, array $options = []): object
|
||||
{
|
||||
return $this->sendRequest('marine.json', array_merge(['q' => $query, 'days' => $days], $options));
|
||||
}
|
||||
|
||||
public function future(string $query, string $date, array $options = []): object
|
||||
{
|
||||
return $this->sendRequest('future.json', array_merge(['q' => $query, 'dt' => $date], $options));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace interfaces;
|
||||
|
||||
interface weatherapi_i
|
||||
{
|
||||
public function requireModuleEnabled(): void;
|
||||
|
||||
public function requireValidSecretKey(): void;
|
||||
|
||||
public function sendRequest(string $endpoint, array $query = []): object;
|
||||
|
||||
public function current(string $query, array $options = []): object;
|
||||
|
||||
public function forecast(string $query, int $days = 1, array $options = []): object;
|
||||
|
||||
public function history(string $query, string $date, array $options = []): object;
|
||||
|
||||
public function astronomy(string $query, string $date, array $options = []): object;
|
||||
|
||||
public function timezone(string $query, array $options = []): object;
|
||||
|
||||
public function sports(string $query, array $options = []): object;
|
||||
|
||||
public function search(string $query, array $options = []): object;
|
||||
|
||||
public function marine(string $query, int $days = 1, array $options = []): object;
|
||||
|
||||
public function future(string $query, string $date, array $options = []): object;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace weatherapi\config;
|
||||
|
||||
use Exception;
|
||||
use traits\module_config_variable;
|
||||
|
||||
class weatherapi_enabled_c
|
||||
{
|
||||
use module_config_variable;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
self::setupConfigVariable(
|
||||
'weatherapi',
|
||||
'enabled',
|
||||
'bool',
|
||||
true,
|
||||
null,
|
||||
'Whether the weatherapi module is enabled or not',
|
||||
'1',
|
||||
false,
|
||||
'false'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace weatherapi\config;
|
||||
|
||||
use Exception;
|
||||
use traits\module_config_variable;
|
||||
|
||||
class weatherapi_secret_key_c
|
||||
{
|
||||
use module_config_variable;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
self::setupConfigVariable(
|
||||
'weatherapi',
|
||||
'secret_key',
|
||||
'string',
|
||||
false,
|
||||
null,
|
||||
'The API token for weatherapi.com',
|
||||
'1',
|
||||
true,
|
||||
''
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace weatherapi;
|
||||
|
||||
require_once WD . '/modules/weatherapi/config/weatherapi_enabled_c.php';
|
||||
require_once WD . '/modules/weatherapi/config/weatherapi_secret_key_c.php';
|
||||
|
||||
use traits\module_config_t;
|
||||
use weatherapi\config\weatherapi_enabled_c;
|
||||
use weatherapi\config\weatherapi_secret_key_c;
|
||||
|
||||
class weatherapi_c
|
||||
{
|
||||
use module_config_t;
|
||||
|
||||
public weatherapi_enabled_c $enabled;
|
||||
public weatherapi_secret_key_c $secret_key;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setupConfig('weatherapi');
|
||||
$this->allowUpdate([
|
||||
weatherapi_enabled_c::class,
|
||||
weatherapi_secret_key_c::class,
|
||||
]);
|
||||
|
||||
$this->enabled = new weatherapi_enabled_c();
|
||||
$this->secret_key = new weatherapi_secret_key_c();
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ use classes\recaptcha;
|
||||
use classes\response;
|
||||
use classes\router;
|
||||
use classes\stripe;
|
||||
use classes\weatherapi;
|
||||
use objects\logs_o;
|
||||
use traits\route_t;
|
||||
|
||||
@@ -412,6 +413,45 @@ class moduleConfigRoute
|
||||
]
|
||||
);
|
||||
|
||||
/** WeatherAPI config > GET */
|
||||
$this->get('/weatherapi/config', function () {
|
||||
global $response;
|
||||
$this->requirePermission('weatherapi_config');
|
||||
$user = (new authentication())->get_user();
|
||||
if ($user) {
|
||||
(new logs_o())->add('weatherapi_config', 'global', 1, $user->id, 'WEATHERAPI_CONFIG', 'Successfully fetched weatherapi config');
|
||||
$response->success(
|
||||
(new weatherapi())->config->getConfigRequest()
|
||||
);
|
||||
} else {
|
||||
(new logs_o())->add('weatherapi_config', 'global', 1, 0, 'WEATHERAPI_CONFIG', 'No user found, or invalid session');
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
},
|
||||
[
|
||||
'weatherapi_config' => 'Get weatherapi config'
|
||||
]
|
||||
);
|
||||
/** WeatherAPI config > POST */
|
||||
$this->post('/weatherapi/config', function () {
|
||||
global $response;
|
||||
$this->requirePermission('weatherapi_config');
|
||||
$user = (new authentication())->get_user();
|
||||
if ($user) {
|
||||
(new logs_o())->add('weatherapi_config', 'global', 1, $user->id, 'WEATHERAPI_CONFIG', 'Successfully updated weatherapi config');
|
||||
$response->success(
|
||||
(new weatherapi())->config->postConfigRequest()
|
||||
);
|
||||
} else {
|
||||
(new logs_o())->add('weatherapi_config', 'global', 1, 0, 'WEATHERAPI_CONFIG', 'No user found, or invalid session');
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
},
|
||||
[
|
||||
'weatherapi_config' => 'Update weatherapi config'
|
||||
]
|
||||
);
|
||||
|
||||
/** XLVask config > GET */
|
||||
$this->get('/xlvask/config', function () {
|
||||
global $response;
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
namespace routes;
|
||||
|
||||
use classes\authentication;
|
||||
use classes\response;
|
||||
use classes\router;
|
||||
use classes\weatherapi;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use objects\department_time_bookings_opening_hours_o;
|
||||
use objects\departments_o;
|
||||
use objects\logs_o;
|
||||
use objects\orders_o;
|
||||
use traits\route_t;
|
||||
|
||||
class moduleWeatherAPIRoute
|
||||
{
|
||||
use route_t;
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
global /** @var response $response */
|
||||
/** @var router $router */
|
||||
$router, $response;
|
||||
|
||||
$this->get('/modules/weatherapi/current', function () {
|
||||
global $response;
|
||||
self::requirePermission('modules_weatherapi_current');
|
||||
$user = (new authentication())->get_user();
|
||||
if (!$user) {
|
||||
$response->error('Invalid session', 400);
|
||||
return;
|
||||
}
|
||||
|
||||
self::requireParameters(['q']);
|
||||
self::requireType('q', self::type_string());
|
||||
$result = (new weatherapi())->current(self::getParameter('q'));
|
||||
(new logs_o())->add('modules_weatherapi', 'global', 1, $user->id, 'MODULES_WEATHERAPI', 'Current weather request completed');
|
||||
$response->success($result, 200);
|
||||
}, [
|
||||
'modules_weatherapi_current' => 'Get current weather data from WeatherAPI',
|
||||
]);
|
||||
|
||||
$this->get('/modules/weatherapi/forecast', function () {
|
||||
global $response;
|
||||
self::requirePermission('modules_weatherapi_forecast');
|
||||
$user = (new authentication())->get_user();
|
||||
if (!$user) {
|
||||
$response->error('Invalid session', 400);
|
||||
return;
|
||||
}
|
||||
|
||||
self::requireParameters(['q']);
|
||||
self::requireType('q', self::type_string());
|
||||
$days = (int)(self::getParameter('days') ?? 1);
|
||||
$result = (new weatherapi())->forecast(self::getParameter('q'), $days);
|
||||
(new logs_o())->add('modules_weatherapi', 'global', 1, $user->id, 'MODULES_WEATHERAPI', 'Forecast weather request completed');
|
||||
$response->success($result, 200);
|
||||
}, [
|
||||
'modules_weatherapi_forecast' => 'Get weather forecast data from WeatherAPI',
|
||||
]);
|
||||
|
||||
$this->get('/modules/weatherapi/search', function () {
|
||||
global $response;
|
||||
self::requirePermission('modules_weatherapi_search');
|
||||
$user = (new authentication())->get_user();
|
||||
if (!$user) {
|
||||
$response->error('Invalid session', 400);
|
||||
return;
|
||||
}
|
||||
|
||||
self::requireParameters(['q']);
|
||||
self::requireType('q', self::type_string());
|
||||
$result = (new weatherapi())->search(self::getParameter('q'));
|
||||
(new logs_o())->add('modules_weatherapi', 'global', 1, $user->id, 'MODULES_WEATHERAPI', 'Weather location search completed');
|
||||
$response->success($result, 200);
|
||||
}, [
|
||||
'modules_weatherapi_search' => 'Search location data from WeatherAPI',
|
||||
]);
|
||||
|
||||
$this->get('/departments/weather', function () {
|
||||
global $response;
|
||||
self::requirePermission('departments_weather_get');
|
||||
$user = (new authentication())->get_user();
|
||||
if (!$user) {
|
||||
$response->error('Invalid session', 400);
|
||||
return;
|
||||
}
|
||||
|
||||
self::requireParameters(['id']);
|
||||
self::requireType((int)self::getParameter('id'), self::type_int());
|
||||
self::requireMinValue((int)self::getParameter('id'), 1);
|
||||
self::requireDepartmentAccess((int)self::getParameter('id'));
|
||||
|
||||
$department = (new departments_o())->select((int)self::getParameter('id'));
|
||||
$lat = (float)$department->latitude->value();
|
||||
$lon = (float)$department->longitude->value();
|
||||
if ($lat === 0.0 && $lon === 0.0) {
|
||||
$response->error('Department does not have GPS coordinates configured', 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$opening_hours = new department_time_bookings_opening_hours_o();
|
||||
$opening_hours->selectByDepartment((int)self::getParameter('id'));
|
||||
|
||||
$weather = (new weatherapi())->forecast($lat . ',' . $lon, 2);
|
||||
$timeline = self::buildDepartmentWeatherTimeline((int)self::getParameter('id'), $weather, $opening_hours);
|
||||
|
||||
(new logs_o())->add('modules_weatherapi', (int)self::getParameter('id'), 1, $user->id, 'DEPARTMENTS_WEATHER_GET', 'Department weather timeline fetched');
|
||||
$response->success($timeline, 200);
|
||||
}, [
|
||||
'departments_weather_get' => 'Get department weather timeline with washes and productivity status',
|
||||
'department_access_:id' => 'Access weather timeline for a specific department',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function buildDepartmentWeatherTimeline(int $department_id, object $forecast, department_time_bookings_opening_hours_o $opening_hours): array
|
||||
{
|
||||
$hourly_weather = [];
|
||||
foreach (($forecast->forecast->forecastday ?? []) as $day) {
|
||||
foreach (($day->hour ?? []) as $hour) {
|
||||
$key = (new DateTime((string)$hour->time))->format('Y-m-d H:00');
|
||||
$hourly_weather[$key] = self::mapWeatherCondition((int)($hour->condition->code ?? 1000), (string)($hour->condition->text ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
$entries = [];
|
||||
$slot = new DateTime(date('Y-m-d H:00:00'));
|
||||
$slot->add(new DateInterval('PT1H'));
|
||||
|
||||
for ($i = 0; $i < 24; $i++) {
|
||||
$slot_key = $slot->format('Y-m-d H:00');
|
||||
$weather = $hourly_weather[$slot_key] ?? 'mostly_clear';
|
||||
$is_open = self::isDepartmentOpenAtHour($opening_hours, $slot);
|
||||
$hours = $is_open ? self::getDailyOpenHours($opening_hours, $slot) : 0;
|
||||
$washes = self::countWashesForHour($department_id, $slot);
|
||||
$entries[] = [
|
||||
'time' => $slot->format('H:00'),
|
||||
'weather' => $weather,
|
||||
'washes' => $washes,
|
||||
'hours' => $hours,
|
||||
'status' => self::calculateStatus($washes, $hours),
|
||||
];
|
||||
$slot->add(new DateInterval('PT1H'));
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
private function calculateStatus(int $washes, int $hours): string
|
||||
{
|
||||
if ($hours <= 0) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
$ratio = $washes / $hours;
|
||||
if ($ratio >= 0.8) {
|
||||
return 'healthy';
|
||||
}
|
||||
if ($ratio >= 0.4) {
|
||||
return 'degraded';
|
||||
}
|
||||
return 'unhealthy';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function isDepartmentOpenAtHour(department_time_bookings_opening_hours_o $opening_hours, DateTime $date_time): bool
|
||||
{
|
||||
$weekday = strtolower($date_time->format('l'));
|
||||
$start = $opening_hours->{"{$weekday}_start"}->value();
|
||||
$end = $opening_hours->{"{$weekday}_end"}->value();
|
||||
if ($start === null || $end === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$current = $date_time->format('H:i');
|
||||
$start_hour = (new DateTime((string)$start))->format('H:i');
|
||||
$end_hour = (new DateTime((string)$end))->format('H:i');
|
||||
|
||||
return $current >= $start_hour && $current < $end_hour;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getDailyOpenHours(department_time_bookings_opening_hours_o $opening_hours, DateTime $date_time): int
|
||||
{
|
||||
$weekday = strtolower($date_time->format('l'));
|
||||
$start = $opening_hours->{"{$weekday}_start"}->value();
|
||||
$end = $opening_hours->{"{$weekday}_end"}->value();
|
||||
if ($start === null || $end === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$start_dt = new DateTime((string)$start);
|
||||
$end_dt = new DateTime((string)$end);
|
||||
$hours = ((int)$end_dt->format('U') - (int)$start_dt->format('U')) / 3600;
|
||||
|
||||
return max(0, (int)round($hours));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function countWashesForHour(int $department_id, DateTime $hour_start): int
|
||||
{
|
||||
$start = clone $hour_start;
|
||||
$end = clone $hour_start;
|
||||
$end->add(new DateInterval('PT59M59S'));
|
||||
|
||||
return (new orders_o())->countWashesInDateRange(
|
||||
$start->format('Y-m-d H:i:s'),
|
||||
$end->format('Y-m-d H:i:s'),
|
||||
$department_id
|
||||
);
|
||||
}
|
||||
|
||||
private function mapWeatherCondition(int $code, string $text): string
|
||||
{
|
||||
$rain = [1063, 1150, 1153, 1180, 1183, 1186, 1189, 1192, 1195, 1240, 1243, 1246];
|
||||
$showers = [1072, 1168, 1171, 1198, 1201, 1249, 1252];
|
||||
$snow = [1066, 1069, 1114, 1117, 1204, 1207, 1210, 1213, 1216, 1219, 1222, 1225, 1237, 1255, 1258, 1261, 1264];
|
||||
$thunder = [1087, 1273, 1276, 1279, 1282];
|
||||
|
||||
if ($code === 1000) {
|
||||
return 'clear';
|
||||
}
|
||||
if ($code === 1003) {
|
||||
return 'mostly_clear';
|
||||
}
|
||||
if ($code === 1006) {
|
||||
return 'partly_cloudy';
|
||||
}
|
||||
if ($code === 1009) {
|
||||
return 'mostly_cloudy';
|
||||
}
|
||||
if ($code === 1030 || str_contains(strtolower($text), 'overcast')) {
|
||||
return 'overcast';
|
||||
}
|
||||
if ($code === 1135 || $code === 1147) {
|
||||
return 'fog';
|
||||
}
|
||||
if (in_array($code, $thunder, true)) {
|
||||
return 'thunderstorm';
|
||||
}
|
||||
if (in_array($code, $snow, true)) {
|
||||
return 'snow';
|
||||
}
|
||||
if (in_array($code, $showers, true)) {
|
||||
return 'showers';
|
||||
}
|
||||
if (in_array($code, $rain, true)) {
|
||||
return 'rain';
|
||||
}
|
||||
|
||||
return 'mostly_clear';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user