diff --git a/services/nginx/app/objects/departments_o.php b/services/nginx/app/objects/departments_o.php index b2b81ce6..ca2f2dee 100644 --- a/services/nginx/app/objects/departments_o.php +++ b/services/nginx/app/objects/departments_o.php @@ -446,22 +446,39 @@ class departments_o extends db * Send department period statistics to Slack * @param string $start_date (YYYY-MM-DD) * @param string $end_date (YYYY-MM-DD) - * @return void + * @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): void + 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"; + // 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; - $product_ids = [ - 25, - [23, 24], // Used to merge two products into one percentage (Spot Free) - 22, - 27, - 21, - 26 - ]; $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 /** @@ -487,6 +504,10 @@ class departments_o extends db $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(); @@ -497,6 +518,8 @@ class departments_o extends db // Set the custom webhook for the department $slack->send_webhook_message($tmp, $this->slack_webhook->value()); } + + return explode("\n", trim($tmp)); } /** @@ -686,7 +709,8 @@ class departments_o extends db * @param string $date_end (YYYY-MM-DD) * @param int[] $department_ids * @param int[] $product_ids - * @return void + * @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 = [ @@ -696,7 +720,7 @@ class departments_o extends db 27, 21, 26 - ]): void + ], bool $return_as_array = false): null|array { if (empty($department_ids)) { throw new Exception('No department ids provided for the Slack internal statistic notification.'); @@ -810,8 +834,25 @@ class departments_o extends db } $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 $slack = new slack(); - $slack->send_webhook_message($tmp, (new departments_o())->select(10)->slack_webhook->value()); + if (!$return_as_array) { + $slack->send_webhook_message($tmp, (new departments_o())->select(10)->slack_webhook->value()); + } + // 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; } } diff --git a/services/nginx/app/routes/InvoicingPeriodRoute.php b/services/nginx/app/routes/InvoicingPeriodRoute.php index 3707432b..ece1e295 100644 --- a/services/nginx/app/routes/InvoicingPeriodRoute.php +++ b/services/nginx/app/routes/InvoicingPeriodRoute.php @@ -62,7 +62,11 @@ class InvoicingPeriodRoute self::requireDateFormat($dateFrom, 'Y-m-d'); self::requireDateFormat($dateTo, 'Y-m-d'); // Add a day to the dateTo parameter to include the end date in the range - $dateTo = date('Y-m-d', strtotime($dateTo . ' +1 day')); + $dateFrom = date('Y-m-d 00:00:00', strtotime($dateFrom)); + $dateTo = date('Y-m-d 23:59:59', strtotime($dateTo)); + // Add date from and date to to the response meta + $response->add_meta('date_from', $dateFrom); + $response->add_meta('date_to', $dateTo); // Get the invoicing period for the user $response->success([...self::getInvoicingPeriod($dateFrom, $dateTo)]); } else { diff --git a/services/nginx/app/routes/departmentsRoute.php b/services/nginx/app/routes/departmentsRoute.php index 5330b75e..9d5a0c3c 100644 --- a/services/nginx/app/routes/departmentsRoute.php +++ b/services/nginx/app/routes/departmentsRoute.php @@ -429,5 +429,66 @@ class departmentsRoute 'department_access_:id' => 'Access the department' ] ); + + self::get('/departments/weekly-results', function () { + // Require the user to be logged in + global $response; + //self::requirePermission('view_department_weekly_results'); TODO: enable permission check once the feature is fully implemented and tested + // Get the user object + //$user = (new authentication())->get_user(); + $user = true; + // Check if the request was successful + if ($user) { + // Get the department results for the week + $departments_o = new departments_o(); + $weeks = 5; + $results = []; + for ($i = 0; $i < $weeks; $i++) { + // Get the week number + $week_number = date('W', strtotime("-$i week")); + // Skip the next week, only show show weeks where at least monday at 00:00:00 is in the past, to prevent showing incomplete data for the current week + // Get the weeks monday + $week_start = strtotime("-$i week Monday"); + if ($week_start < strtotime('now')) { + $week_monday = date('Y-m-d 00:00:00', $week_start); + // Get the weeks sunday from monday + $week_sunday = date('Y-m-d 23:59:59', strtotime("$week_monday +6 days")); + $results[$week_number] = [ + 'week_number' => $week_number, + 'week_monday' => $week_monday, + 'week_sunday' => $week_sunday, + 'results' => $departments_o->sendSlackInternalStatisticNotification( + $week_monday, + $week_sunday, + [1, 2, 3, 4, 5, 6, 7], + // Default value + [ + 25, + [23, 24], // Used to merge two products into one percentage (Spot Free) + 22, + 27, + 21, + 26 + ], + true // Return the results instead of sending the notification (This is used to show the results in the frontend, instead of sending them to slack. + ) + ]; + } + } + // Log the incident + //(new logs_o())->add('departments', 'global', 1, $user->id, 'VIEW_DEPARTMENT_WEEKLY_RESULTS', 'Successfully viewed department weekly results'); + // Return the results + $response->success($results); + } else { + // Log the incident + //(new logs_o())->add('departments', 'global', 1, 0, 'VIEW_DEPARTMENT_WEEKLY_RESULTS', 'No user found, or invalid session'); + // Return an error + $response->error('Invalid session', 400); + } + }, + [ + 'view_department_weekly_results' => 'View department weekly results' + ] + ); } } \ No newline at end of file diff --git a/services/nginx/app/traits/db_object_t.php b/services/nginx/app/traits/db_object_t.php index 33c0c435..bdd8616b 100644 --- a/services/nginx/app/traits/db_object_t.php +++ b/services/nginx/app/traits/db_object_t.php @@ -1000,11 +1000,13 @@ trait db_object_t * @param int $seconds The number of seconds to set the cached object expiration time * @param null $objectId * @return void + * @throws Exception If the object is not selected, and no object id is provided, it throws an exception */ public function setCachedExpiration(string $key, int $seconds, $objectId = null): void { // If the object id is not set, use the object id if (!$objectId) { + $this->requireSelected(); $objectId = $this->id; } // Set the expiration time for the cached data