Files
api/services/nginx/app/objects/departments_o.php
T

879 lines
35 KiB
PHP

<?php
namespace objects;
use classes\db;
use classes\departments_schema_bootstrap;
use classes\object_property;
use classes\slack;
use classes\stripe;
use Exception;
use traits\db_object_t;
class departments_o extends db
{
use db_object_t;
public object_property $name;
public object_property $description;
public object_property $economic_department_id; // The id of the department in the economic system (Can be null)
public object_property $slack_webhook; // The slack webhook for the department
public department_variables_o $variables; // The department variables object
public object_property $dimension; // The dimension of the department
public object_property $visible; // The visibility of the department
public object_property $archived; // Whether the department is archived
public object_property $custom_pricing_only; // Whether missing department prices must not fall back to defaults
public object_property $branding; // The branding of the department
public object_property $longitude; // The longitude of the department (Can be null)
public object_property $latitude; // The latitude of the department (Can be null)
public object_property $order_priority; // The order priority of the department (Lower number = higher priority)
public object_property $created_at;
public object_property $updated_at;
public function structure(): void
{
departments_schema_bootstrap::ensureTables();
$this->setTable('departments');
}
public function objectChanged(): void
{
// Clear the cache
redis->clear_departments();
}
/**
* Get the lanes associated with the department
* @retuns department_lanes_o[] The lanes associated with the department
* @throws Exception If the department is not selected
*/
public function getLanes(): array
{
self::requireSelected();
return (new department_lanes_o())->getDepartmentLanes($this->id);
}
/**
* Add a department
* @param string $name
* @param string $description
* @throws Exception If the object was not created successfully
*/
public function create(string $name, string $description): void
{
global $db;
// Avoid SQL injection
$name = $db->escape_string($name);
$description = $db->escape_string($description);
// Create a new record in the database
$sql = "INSERT INTO $this->table (name, description) VALUES ('$name', '$description')";
$db->query($sql);
// Get the id of the new record
$this->id = $db->insert_id();
// Set the values of the object properties
$this->getObjectProperties();
// Clear the cache
redis->clear_departments();
}
/**
* @throws Exception
* @return bool true if self-serve is enabled, false if it is disabled
*/
public function getSelfServeEnabled(): bool
{
self::requireSelected();
// Get the department variable
$department_variables = (new department_variables_o())->selectDepartment($this->id);
$enabled = $department_variables->getVariable('selfserve_enabled');
return $enabled === true;
}
/**
* Get the object properties of the department
* @throws Exception If the object is not selected
*/
public function getObjectProperties(): void
{
self::requireSelected();
$this->name = new object_property($this->table, $this->id, 'name', 'string', true);
$this->description = new object_property($this->table, $this->id, 'description', 'string', false);
$this->economic_department_id = new object_property($this->table, $this->id, 'economic_department_id', 'int', false);
$this->slack_webhook = new object_property($this->table, $this->id, 'slack_webhook', 'string', false);
$this->variables = (new department_variables_o())->selectDepartment($this->id);
$this->dimension = new object_property($this->table, $this->id, 'dimension', 'int', false);
$this->branding = new object_property($this->table, $this->id, 'branding', 'int', false);
$this->visible = new object_property($this->table, $this->id, 'visible', 'int', false);
$this->archived = new object_property($this->table, $this->id, 'archived', 'boolean', false);
$this->custom_pricing_only = new object_property($this->table, $this->id, 'custom_pricing_only', 'boolean', false);
$this->longitude = new object_property($this->table, $this->id, 'longitude', 'float', false);
$this->latitude = new object_property($this->table, $this->id, 'latitude', 'float', false);
$this->order_priority = new object_property($this->table, $this->id, 'order_priority', 'int', false);
$this->created_at = new object_property($this->table, $this->id, 'created_at', 'string', false);
$this->updated_at = new object_property($this->table, $this->id, 'updated_at', 'string', false);
}
public function edit(int $id, string $name, string $description, int $economic_department_id): void
{
global $db;
$this->id = $id;
// Avoid SQL injection
$name = $db->escape_string($name);
$description = $db->escape_string($description);
// If the economic_department_id is 0, set it to null
if ($economic_department_id === 0) {
$economic_department_id = null;
}
// Update the record in the database
$sql = "UPDATE $this->table SET name = '$name', description = '$description', economic_department_id = " . ($economic_department_id ?? 'NULL') . " WHERE id = $this->id";
$db->query($sql);
// Set the values of the object properties
$this->getObjectProperties();
// Clear the cache
redis->clear_department($id);
}
public function list($superUser = false): array
{
global $db;
// Check if the departments are cached
$departments = redis->get_departments();
// If the departments are not cached, get them from the database
if ($departments === null) {
$sql = "SELECT * FROM $this->table";
$result = $db->query($sql);
$departments = $db->fetch_all($result);
// Cache the departments (If it is not empty or null)
if (!empty($departments)) {
redis->cache_departments($departments);
}
}
// Only show the name, description and id if the user isn't a super user
if (!$superUser) {
// Map the departments to only show the name, description and id
$departments = array_map(function ($department) {
return [
'name' => $department['name'],
'description' => $department['description'],
'id' => $department['id'],
'visible' => $department['visible'],
'archived' => $department['archived'] ?? 0,
];
}, $departments);
}
return $departments;
}
public function getDepartmentById(int $id, bool $force_all = false): array
{
global $db;
// Check if the department is cached
$department = redis->get_department($id);
// If the department is not cached, get it from the database
if ($department === null || $force_all) {
$sql = "SELECT * FROM $this->table WHERE id = $id";
$result = $db->query($sql);
$department = $db->fetch_assoc($result);
// Cache the department (If it is not empty or null)
if (!empty($department)) {
redis->cache_department($id, $department);
}
}
return $department;
}
public function isCustomPricingOnly(int $department_id): bool
{
$department = $this->getDepartmentById($department_id);
return (bool)(int)($department['custom_pricing_only'] ?? 0);
}
/**
* Get the price of a product in a department
* @param int $department_id
* @param int $product_id
* @param int $price
* @return void
*/
public function setDepartmentProductPrice(int $department_id, int $product_id, int $price): void
{
global $db;
// Check if the record already exists
$this->removeDepartmentProductPriceIfExist($department_id, $product_id);
// If the price is 0, return
if ($price === 0) {
return;
}
// Create a new record in the database
$sql = "INSERT INTO product_department_prices (department_id, product_id, price) VALUES ($department_id, $product_id, $price)";
$db->query($sql);
}
/**
* Remove the price of a product in a department
* @param int $department_id
* @param int $product_id
* @return void
*/
private function removeDepartmentProductPriceIfExist(int $department_id, int $product_id): void
{
global $db;
// Get the price from the database
$sql = "SELECT * FROM product_department_prices WHERE department_id = $department_id AND product_id = $product_id";
$result = $db->query($sql);
if ($result->num_rows > 0) {
// Remove the record from the database
$sql = "DELETE FROM product_department_prices WHERE department_id = $department_id AND product_id = $product_id";
$db->query($sql);
}
}
public function getDepartmentProductPrices(int $department_id): array
{
global $db;
$sql = "SELECT * FROM product_department_prices WHERE department_id = $department_id";
$result = $db->query($sql);
return $db->fetch_all($result);
}
public function getDepartmentName(int $department): string
{
global $db;
// Check if the department name is cached
$name = redis->get_department_name($department);
// If the department name is not cached, get it from the database
if ($name === null) {
try {
$this->id = $department;
$this->getObjectProperties();
$name = $this->name->value();
// Cache the department name (If it is not empty or null)
if (!empty($name)) {
redis->cache_department_name($department, $name);
}
} catch (Exception $e) {
$name = 'Unable to get department name: ' . $department;
}
return $name;
}
return redis->get_department_name($department) ?? 'Unable to get department name: ' . $department;
}
/**
* Get the Stripe terminal location department variable
* @note This is a link to the department_variables_o::getVariable(stripe_terminal_location) method
* @return string The Stripe terminal location id
* @throws Exception If the department variable is not set
* @throws Exception If the department is not selected
*/
public function getStripeTerminalLocation(): string
{
self::requireSelected();
if (!$this->variables->isSet('stripe_terminal_location')) {
throw new Exception('The Stripe terminal location is not set for the department.');
}
return $this->variables->getVariable('stripe_terminal_location');
}
/**
* Check if the Stripe terminal location variable is set
* @note This is a link to the department_variables_o::isSet(stripe_terminal_location) method
* @return bool true If the Stripe terminal location is set
* @throws Exception If the department is not selected
*/
public function isStripeTerminalLocationSet(): bool
{
self::requireSelected();
return $this->variables->isSet('stripe_terminal_location');
}
/**
* Set the Stripe terminal location department variable
* @note This is a link to the department_variables_o::set(stripe_terminal_location) method
* @param string $location The Stripe terminal location id
* @throws Exception If the department is not selected
* @throws Exception If the department variable is not updated successfully
*/
public function setStripeTerminalLocation(string $location): void
{
self::requireSelected();
$this->variables->set('stripe_terminal_location', $location);
}
/**
* Get the Stripe terminal readers
* @note This is a link to the stripe_endpoint_readers::listLocation method
* @throws Exception If the department is not selected
* @throws Exception If the Stripe terminal location is not set for the department
*/
public function getStripeTerminalReaders(): \Stripe\Collection
{
self::requireSelected();
if (!$this->variables->isSet('stripe_terminal_location')) {
throw new Exception('The Stripe terminal location is not set for the department.');
}
return (new stripe())->readers->listLocation($this->variables->getVariable('stripe_terminal_location'));
}
/**
* Convert the object to an array
* @param array|null $options The options for the conversion (e.g. ['slack_webhook' => true])
* @return array The object as an array
* @throws Exception If the object is not selected
*/
public function asArray(array $options = null): array
{
self::requireSelected();
$tmp = [
'id' => (int)$this->id,
'name' => (string)$this->name->value(),
'description' => (string)$this->description->value(),
'economic_department_id' => (int)$this->economic_department_id->value(),
'dimension' => (int)$this->dimension->value(),
'branding' => (int)$this->branding->value(),
'longitude' => (float)$this->longitude->value(),
'latitude' => (float)$this->latitude->value(),
'order_priority' => (int)$this->order_priority->value(),
'created_at' => (string)$this->created_at->value(),
'updated_at' => (string)$this->updated_at->value(),
];
// Check if the webhook should be included
if ($options && $options['slack_webhook']) {
$tmp['slack_webhook'] = (string)$this->slack_webhook->value();
}
return $tmp;
}
/**
* Check if the Stripe is configured for the department
* @note This is a link to the department_variables_o::isSet(stripe_terminal_location) method
* @return bool true If the Stripe is configured
* @throws Exception If the department is not selected
*/
public function isStripeConfigured(): bool
{
self::requireSelected();
if ($this->variables->isSet('stripe_terminal_location')) {
return true;
}
return false;
}
/**
* @throws Exception If the department is not selected
* @throws Exception If the branding is not set
* @throws Exception If the branding is not found
*/
public function getBranding(): branding_o
{
self::requireSelected();
return (new branding_o())->select($this->branding->value());
}
/**
* Get the phone numbers for the SMS notification
* @note This is a link to the department_notification_sms_o::getDepartmentActivePhoneNumbers method
* @throws Exception If the department is not selected
*/
public function notificationSmsPhoneNumbers(): array
{
self::requireSelected();
return (new department_notification_sms_o())->getDepartmentActivePhoneNumbers($this->id);
}
/**
* @throws Exception
*/
public function isModuleTimeBookingsEnabled(): bool
{
self::requireSelected();
return $this->variables->getVariable('bookingsystem_time_based_enabled') === true;
}
/**
* Select a department by its name (case-sensitive, exact match)
* @throws Exception If the department selection fails or the department is not found
* @example "Hvidovre"
*/
public function selectByName(string $departmentName): self
{
$result = self::getFieldsWhere([
'name' => $departmentName,
], [
'id',
]);
if (empty($result)) {
throw new Exception('Department not found: ' . $departmentName);
}
$this->selectId($result[0]['id']);
return $this;
}
public function selectId(int $department_id): self
{
$this->id = $department_id;
$this->getObjectProperties();
return $this;
}
/**
* @return products_o[] An array of all products in the department's categories
* @throws Exception
*/
public function getAllProductInDepartmentCategories(): array
{
self::requireSelected();
// Get all the categories associated
$department_categories = (new department_categories_o())->getDepartmentCategoryObjects($this->id);
// Use array_reduce to combine both loops into a single operation
$products = array_reduce($department_categories, function ($products, $department_category) {
return array_merge($products, $department_category->getCategory()->getProducts());
}, []);
// Remove duplicate products
return array_unique($products, SORT_REGULAR);
}
/**
* If the department is excluded from invoicing
* @return bool
* @throws Exception If the department is not selected
*/
public function isExcludedFromInvoicing(): bool
{
self::requireSelected();
return $this->variables->getVariable('exclude_from_invoicing') === true;
}
/**
* Send department period statistics to Slack
* @param string $start_date (YYYY-MM-DD)
* @param string $end_date (YYYY-MM-DD)
* @param int[] $product_ids The product ids to include in the statistics (e.g. [25, [23, 24], 22, 27, 21, 26])
* @param bool $return_as_array Whether to return the results as an array or not
* @return string[]
* @throws Exception If the department is not selected
*/
public function sendPeriodStatisticsToSlack(string $start_date, string $end_date, array $product_ids = [
25,
[23, 24], // Used to merge two products into one percentage (Spot Free)
22,
27,
21,
26
], bool $return_as_array = false): array
{
self::requireSelected();
// Ensure only one message is sent a week using redis cache
$cacheKey = "department_{$this->id}_weekly_statistics_sent_v2";
// Get time until next monday at 00:00:00
$nextMonday = strtotime('next monday');
$cacheDuration = $nextMonday - time();
$lastSent = redis->get($cacheKey);
// If null or more than a week has passed since the last message, send a new message
$sendNow = match (true) {
$lastSent === null => true,
default => (time() - $lastSent) >= $cacheDuration
};
if (!$sendNow) {
return $return_as_array ? ["A weekly statistics message has already been sent for this department."] : ["A weekly statistics message has already been sent for this department."];
}
$this->cache($cacheKey, $cacheDuration);
$this->setCachedExpiration($cacheKey, $cacheDuration);
// Configuration
$department_id = $this->id;
$date_end = date('Y-m-d 23:59:59', strtotime($end_date)); // End date at 23:59:59
$date_start = date('Y-m-d 00:00:00', strtotime($start_date)); // Start date at 00:00:00
/**
* Weekly results for Roskilde
*
* Period: 29.01.2026 - 04.02.2026
* Washes: 67
* Fælg Flex: 55%
* Spot Free: 22%
* Special Sæbe: 11%
* 10 min ekstra: 15%
* Undervognsskyl: 44%
* Voks: 66%
*/
$max_addons = [];
$sold_addons = [];
$percentages = [];
$department = (new departments_o())->select((int)$this->id);
$weekNumber = (int)date('W', strtotime($date_end));
$tmp = "*Weekly results for {$department->name->value()} (Week {$weekNumber})*\n";
$tmp .= "Period: " . date('d.m.Y', strtotime($date_start)) . " - " . date('d.m.Y', strtotime($date_end)) . "\n";
// Washes
$wash_count = (new orders_o())->countWashesInDateRange($date_start, $date_end, $department_id);
$analytics = $this->analyzeAddonSalesData($product_ids, $date_start, $date_end, $department_id, $max_addons, $sold_addons, $percentages);
$tmp .= "Washes: $wash_count\n" . implode('', $analytics);
// If it should be returned as an array, return it as an array instead of sending it to Slack
if ($return_as_array) {
return explode("\n", trim($tmp));
}
// Check if there's a custom webhook for the department
$slack = new slack();
if (empty($this->slack_webhook->value())) {
// If no webhook is set for the department, use the default Slack notification
$slack->send_message($tmp);
} else {
// Set the custom webhook for the department
$slack->send_webhook_message($tmp, $this->slack_webhook->value());
}
return explode("\n", trim($tmp));
}
/**
* @param array $product_ids
* @param string $date_start
* @param string $date_end
* @param int|array $department_id
* @param array $max_addons
* @param array $sold_addons
* @param array $percentages
* @return array
* @throws Exception
*/
public function analyzeAddonSalesData(array $product_ids, string $date_start, string $date_end, int|array $department_id, array $max_addons, array $sold_addons, array $percentages): array
{
if (is_array($department_id)) {
// Combine percentages from multiple departments
$combined_percentages = [];
foreach ( $department_id as $dept_id ) {
$dept_percentages = $this->getThePercentageOfAddonsSoldOutOfMax($product_ids, $date_start, $date_end, $dept_id, $max_addons, $sold_addons, $percentages);
foreach ( $dept_percentages as $key => $value ) {
if (!isset($combined_percentages[$key])) {
$combined_percentages[$key] = 0;
}
$combined_percentages[$key] += $value;
}
}
$percentages = $combined_percentages;
} else {
// Single department
$percentages = $this->getThePercentageOfAddonsSoldOutOfMax($product_ids, $date_start, $date_end, $department_id, $max_addons, $sold_addons, $percentages);
}
$tmp = [];
foreach ( $product_ids as $product_id ) {
if (is_array($product_id)) {
// Merged product names
$product_names = [];
foreach ( $product_id as $pid ) {
$product_names[] = (new products_o())->select($pid)->name->value();
}
// Switch to joined names
$product_name = match (true) {
in_array(23, $product_id) && in_array(24, $product_id) => 'Spot Free',
default => implode(' + ', $product_names),
};
} else {
// Check single product name
$product_name = match ($product_id) {
25 => 'Fælg Flex',
22 => 'Special Sæbe',
27 => '10 min ekstra',
21 => 'Undervognsskyl',
26 => 'Voks',
default => (new products_o())->select($product_id)->name->value(),
};
}
$tmp[] = "{$product_name}: {$percentages[is_array($product_id) ? implode('_', $product_id) : $product_id]}%\n";
}
// Return the data
return $tmp;
}
/**
* @param array $product_ids
* @param string $date_start
* @param string $date_end
* @param int $department_id
* @param array $max_addons
* @param array $sold_addons
* @param array $percentages
* @return array
*/
public function getThePercentageOfAddonsSoldOutOfMax(array $product_ids, string $date_start, string $date_end, int $department_id, array &$max_addons, array &$sold_addons, array &$percentages): array
{
$product_options_o = new product_options_o();
// Get the percentage of addons sold out of max
foreach ( $product_ids as $product_id ) {
// Handle merged products
if (is_array($product_id)) {
$addon_sold_count = 0;
$addon_max_count = 0;
foreach ( $product_id as $pid ) {
$pid = (int)$pid;
$addon_sold_count += $product_options_o->countSoldAddonsInDateRange($pid, $date_start, $date_end, $department_id);
$addon_max_count += $product_options_o->getMaxAddonsInDateRange($pid, $date_start, $date_end, $department_id);
}
} else {
$pid = $product_id;
$addon_sold_count = $product_options_o->countSoldAddonsInDateRange($pid, $date_start, $date_end, $department_id);
$addon_max_count = $product_options_o->getMaxAddonsInDateRange($pid, $date_start, $date_end, $department_id);
}
// Prevent division by zero
if ($addon_max_count === 0) {
$addon_percentage_sold = 0;
} else {
$addon_percentage_sold = ($addon_sold_count / $addon_max_count) * 100;
}
$max_addons[is_array($product_id) ? implode('_', $product_id) : $product_id] = $addon_max_count;
$sold_addons[is_array($product_id) ? implode('_', $product_id) : $product_id] = $addon_sold_count;
$percentages[is_array($product_id) ? implode('_', $product_id) : $product_id] = number_format($addon_percentage_sold, 2);
}
return $percentages;
}
/**
* Get the total amount of addons sold in a department in a date range
* @param array $product_ids
* @param string $date_start
* @param string $date_end
* @param int $department_id
* @return float
*/
public function getTotalPercentageAddonsSoldInDepartment(array $product_ids, string $date_start, string $date_end, int $department_id): float
{
$max_addons = [];
$sold_addons = [];
$percentages = [];
$this->getThePercentageOfAddonsSoldOutOfMax($product_ids, $date_start, $date_end, $department_id, $max_addons, $sold_addons, $percentages);
$total_sold = array_sum($sold_addons);
$total_max = array_sum($max_addons);
if ($total_max === 0) {
return 0;
}
return ($total_sold / $total_max) * 100;
}
/**
* Get the total maximum addons available in a department in a date range
* @param array $product_ids
* @param string $date_start
* @param string $date_end
* @param int $department_id
* @return float
*/
public function getTotalPercentageMaxAddonsInDepartment(array $product_ids, string $date_start, string $date_end, int $department_id): float
{
return $this->getTotalPercentageAddonsSoldInDepartment($product_ids, $date_start, $date_end, $department_id);
}
/**
* Get the total amount of addons sold in a department in a date range
* @param array $product_ids
* @param string $date_start
* @param string $date_end
* @param int $department_id
* @return int
* @throws Exception
*/
public function getTotalAddonsSoldInDepartment(array $product_ids, string $date_start, string $date_end, int $department_id): int
{
$total_addons_sold = 0;
foreach ($product_ids as $product_id) {
$addon_sold_count = (new product_options_o())->countSoldAddonsInDateRange($product_id, $date_start, $date_end, $department_id);
$total_addons_sold += $addon_sold_count;
}
return $total_addons_sold;
}
/**
* Get the total maximum addons available in a department in a date range
* @param array $product_ids
* @param string $date_start
* @param string $date_end
* @param int $department_id
* @return int
* @throws Exception
*/
public function getTotalMaxAddonsInDepartment(array $product_ids, string $date_start, string $date_end, int $department_id): int
{
return self::methodCacheWithParameters(__METHOD__, func_get_args(), 120, function() use ($product_ids, $date_start, $date_end, $department_id) {
$total_max_addons = 0;
foreach ($product_ids as $product_id) {
$addon_max_count = (new product_options_o())->getMaxAddonsInDateRange($product_id, $date_start, $date_end, $department_id);
$total_max_addons += $addon_max_count;
}
return $total_max_addons;
});
}
/**
* Send a (truck wash internal) department statistic Slack notification
* @param string $date_start (YYYY-MM-DD)
* @param string $date_end (YYYY-MM-DD)
* @param int[] $department_ids
* @param int[] $product_ids
* @param bool $return_as_array Whether to return the results as an array or not
* @return null|array
* @throws Exception
*/
public function sendSlackInternalStatisticNotification(string $date_start, string $date_end, array $department_ids = [], array $product_ids = [
25,
[23, 24], // Used to merge two products into one percentage (Spot Free)
22,
27,
21,
26
], bool $return_as_array = false): null|array
{
$slack = new slack();
if (empty($department_ids)) {
$department_ids = $slack->get_internal_department_ids();
}
if (empty($department_ids)) {
throw new Exception('No department ids provided for the Slack internal statistic notification.');
}
if (empty($product_ids)) {
throw new Exception('No product ids provided for the Slack internal statistic notification.');
}
// Sort the departments by order_priority (lowest => first)
$departments_data = $this->getFieldsWhereIn(['id' => $department_ids], ['id', 'order_priority']);
usort($departments_data, function ($a, $b) {
return (int)$a['order_priority'] <=> (int)$b['order_priority'];
});
$department_ids = array_column($departments_data, 'id');
// Configuration
$date_end = date('Y-m-d 23:59:59', strtotime($date_end)); // End date at 23:59:59
$date_start = date('Y-m-d 00:00:00', strtotime($date_start)); // Start date at 00:00:00
/**
* Weekly results for Truck Wash
*
* Period: 29.01.2026 - 04.02.2026
* Washes:
*
* Taastrup: 300
* Hvidovre: 52
* Glostrup: 121
* Køge: 54
* Roskilde: 65
* Aarhus C: 32
* Taulov: 122
* - Total: 650
* Undervognsskyl
*
* Taastrup: 55%
* Hvidovre: 22%
* Glostrup: 22%
* Køge: 24%
* Roskilde: 54%
* Aarhus C: 66%
* Taulov: 55%
* - Total: 55%
* ...
*/
$weekNumber = (int)date('W', strtotime($date_end));
$tmp = "*Weekly results for Truck Wash (Week {$weekNumber})*\n";
$tmp .= "_Period: " . date('d.m.Y', strtotime($date_start)) . " - " . date('d.m.Y', strtotime($date_end)) . "_\n";
// Washes
$total_wash_count = 0;
$tmp .= "*Washes:*\n";
$orders_o = new orders_o();
foreach ( $department_ids as $department_id ) {
$wash_count = $orders_o->countWashesInDateRange($date_start, $date_end, $department_id);
$total_wash_count += $wash_count;
$department = (new departments_o())->select($department_id);
$tmp .= "> {$department->name->value()}: $wash_count\n";
}
$tmp .= "> - Total: {$total_wash_count}\n";
// Pre-instantiate products_o to reuse
$products_o = new products_o();
$max_addons = [];
$sold_addons = [];
$percentages = [];
// Loop through each product and get the percentages for each department
foreach ( $product_ids as $product_id ) {
// Reset totals for each product
$total_percentage = 0;
$total_count = 0;
$total_sold = 0;
// Header for the product
if (is_array($product_id)) {
$product_names = [];
foreach ( $product_id as $pid ) {
$products_o->select($pid);
$product_names[] = $products_o->name->value();
}
$product_name = match (true) {
in_array(23, $product_id) && in_array(24, $product_id) => 'Spot Free',
default => implode(' + ', $product_names),
};
} else {
$product_name = match ($product_id) {
25 => 'Fælg Flex',
22 => 'Special Sæbe',
27 => '10 min ekstra',
21 => 'Undervognsskyl',
26 => 'Voks',
default => $products_o->select($product_id)->name->value(),
};
}
$tmp .= "\n*{$product_name}:*\n";
$product_key = is_array($product_id) ? implode('_', $product_id) : $product_id;
// Loop through each department to get the percentage
foreach ( $department_ids as $department_id ) {
$department = (new departments_o())->select($department_id);
$this->getThePercentageOfAddonsSoldOutOfMax([$product_id], $date_start, $date_end, $department_id, $max_addons, $sold_addons, $percentages);
$percentage = $percentages[$product_key];
$total_count += $max_addons[$product_key];
$total_sold += $sold_addons[$product_key];
$tmp .= "> {$department->name->value()}: {$percentage}%\n";
}
// Get the total percentage for the product
if ($total_count !== 0) {
// Prevent division by zero
$total_percentage = ($total_sold / $total_count) * 100;
}
$tmp .= "> - Total: " . number_format($total_percentage, 2) . "%\n";
}
$array_of_results = [
"daily_management" => [],
"departments" => []
];
// Add $tmp to the daily management message
$array_of_results['daily_management'][] = $tmp;
// Send the message to the internal Slack webhook
if (!$return_as_array) {
$webhook = $slack->get_internal_department_goal_progress_webhook_url();
if ($webhook !== '') {
$slack->send_webhook_message($tmp, $webhook);
}
}
// Send a department-specific message for each internal department with the percentage of addons sold
foreach ( $department_ids as $department_id ) {
$department = (new departments_o())->select($department_id);
$tmp_dept = $department->sendPeriodStatisticsToSlack($date_start, $date_end, $product_ids, $return_as_array);
$array_of_results['departments'][$department_id] = $tmp_dept;
}
return $array_of_results;
}
}