diff --git a/services/nginx/app/classes/gatewayapi.php b/services/nginx/app/classes/gatewayapi.php index de09ea57..4f359e29 100644 --- a/services/nginx/app/classes/gatewayapi.php +++ b/services/nginx/app/classes/gatewayapi.php @@ -30,11 +30,14 @@ class gatewayapi implements gatewayapi_i /** * @inheritDoc */ - public function send(array $to, string $message): bool + public function send(array $to, string $message, bool $ignore_empty = false): bool { $this->requireEnabled(); // If there are no recipients, throw an exception if (empty($to)) { + if ($ignore_empty) { + return false; + } throw new Exception('No recipients provided'); } $data = [ diff --git a/services/nginx/app/objects/department_time_bookings_entries_o.php b/services/nginx/app/objects/department_time_bookings_entries_o.php new file mode 100644 index 00000000..ad99c04b --- /dev/null +++ b/services/nginx/app/objects/department_time_bookings_entries_o.php @@ -0,0 +1,293 @@ +setTable('department_time_bookings_entries'); + } + + /** + * Convert the object to an array + * @throws Exception If the object is not selected + * @throws Exception If the object is not found + */ + public function asArray(): array + { + self::requireSelected(); + return [ + 'id' => (int)$this->id, + 'department' => (int)$this->department->value(), + 'type' => (int)$this->type->value(), + 'start' => (string)$this->start->value(), + 'end' => (string)$this->end->value(), + 'note' => $this->note->value(), + 'reg' => $this->reg->value(), + 'phone' => (int)$this->phone->value(), + 'phone_country_code' => (int)$this->phone_country_code->value(), + ]; + } + + /** + * Add a new object to the database + * @param int $department_id The department id + * @param int $type_id The booking type id + * @param int $start The start timestamp of the start time + * @param int $end The end timestamp of the end time + * @param int $phone_country_code The phone country code + * @param int $phone The phone number + * @param null|string $note The note for the booking + * @param null|string $reg The registration number plate (optional) + * @return void + * @throws Exception If the object was not created successfully + */ + public function add( + int $department_id, + int $type_id, + string $start, + string $end, + int $phone_country_code, + int $phone, + ?string $note = null, + ?string $reg = null, + ): void + { + self::preChecks(...func_get_args()); + $tmp_id = self::add_object([ + 'department' => (int)$department_id, + 'type' => (int)$type_id, + 'start' => $start, + 'end' => $end, + 'note' => $note, + 'reg' => $reg, + 'phone' => $phone, + 'phone_country_code' => $phone_country_code, + ]); + $this->id = $tmp_id; + self::getObjectProperties(); + self::objectChanged(); + // Notify the parties involved + self::notifyBooking(true); + } + + /** + * Pre-checks for the add method + * @param int $department_id The department id + * @param int $type_id The booking type id + * @param string $start The start timestamp of the start time + * @param string $end The end timestamp of the end time + * @param int $phone_country_code The phone country code + * @param int $phone The phone number + * @param null|string $note The note for the booking + * @param null|string $reg The registration number plate (optional) + * @return void + * @throws Exception + */ + public static function preChecks( + int $department_id, + int $type_id, + string $start, + string $end, + int $phone_country_code, + int $phone, + ?string $note = null, + ?string $reg = null, + ): void + { + // Check if the type_id exists + $type = new department_time_bookings_types_o(); + $type->select($type_id); + $type->requireSelected(); + // Check if the type_id belongs to the department + if ((int)$type->department->value() !== (int)$department_id) { + throw new Exception('Type ID does not belong to the department'); + } + // Check if the time slot is during the opening hours + $opening_hours = new department_time_bookings_opening_hours_o(); + $opening_hours->selectByDepartment((int)$department_id); + if (!$opening_hours->isOpen($start, $end)) { + throw new Exception('The time slot is not during the opening hours.'); + } + // Make sure the start time is before the end time + if ($start >= $end) { + throw new Exception('The start time must be before the end time.'); + } + // Check if the time slot is already booked + $bookings = new department_time_bookings_entries_o(); + $bookings_overlapping = $bookings->getBookingsByDepartmentAndTime((int)$department_id, $start, $end); + if (count($bookings_overlapping) > 0) { + throw new Exception('The time slot is already booked'); + } + } + + private function getBookingsByDepartmentAndTime(int $department_id, string $start, string $end): array + { + global $db; + $query = "SELECT * FROM {$this->table} WHERE department = ? AND start < ? AND end > ?"; + $stmt = $db->prepare($query); + $stmt->bind_param('iss', $department_id, $end, $start); + $stmt->execute(); + $result = $stmt->get_result(); + $bookings = []; + while ($row = $result->fetch_assoc()) { + $bookings[] = $row; + } + $stmt->close(); + return $bookings; + } + + public function getObjectProperties(): void + { + $this->department = new object_property($this->table, $this->id, 'department', 'int', true); + $this->type = new object_property($this->table, $this->id, 'type', 'int', true); + $this->start = new object_property($this->table, $this->id, 'start', 'int', true); + $this->end = new object_property($this->table, $this->id, 'end', 'int', true); + $this->note = new object_property($this->table, $this->id, 'note', 'string', true); + $this->reg = new object_property($this->table, $this->id, 'reg', 'string', true); + $this->phone = new object_property($this->table, $this->id, 'phone', 'int', true); + $this->phone_country_code = new object_property($this->table, $this->id, 'phone_country_code', 'int', true); + $this->deleted_at = new object_property($this->table, $this->id, 'deleted_at', 'int', true); + } + + public function objectChanged(): void + { + //TODO: Add cache invalidation + } + + /** + * Notify the parties involved + * @param bool $is_new_booking True if the booking is new, false if it is an update + * @return void + * @throws Exception If the object is not selected + * @throws Exception If the department is not found + */ + public function notifyBooking(bool $is_new_booking): void + { + self::requireSelected(); + $department = new departments_o(); + $department->select((int)$this->department->value()); + $gateway = new gatewayapi(); + if ($gateway->isEnabled()) { + // Send the message to the department + $gateway->send( + [...$department->notificationSmsPhoneNumbers()], + ($is_new_booking + ? self::generateNewBookingMessage(true) + : self::generateUpdateBookingMessage(true)), + true, + ); + print_r($department->notificationSmsPhoneNumbers()); + // Get the phone numbers of the customer + $customer_phone_numbers = []; + $customer_phone_numbers[] = '' . $this->phone_country_code->value() . $this->phone->value(); + print_r($customer_phone_numbers); + // Send the message to the customer + $gateway->send( + [...$customer_phone_numbers], + ($is_new_booking + ? $this->generateNewBookingMessage(false) + : $this->generateUpdateBookingMessage(false)), + true, + ); + } + } + + /** + * Generate the message for a new booking + * @param bool $is_to_department True if the message is to the department, false if it is to the customer + * @return string The message + * @throws Exception If the object is not selected + * @throws Exception If the department is not found + */ + public function generateNewBookingMessage(bool $is_to_department = false): string + { + self::requireSelected(); + $department = new departments_o(); + $department->select((int)$this->department->value()); + $type = new department_time_bookings_types_o(); + $type->select((int)$this->type->value()); + + $message = ''; + if ($is_to_department) { + $message .= self::addNewline('Din afdeling har modtaget en ny booking'); + } else { + $message .= self::addNewline('Bookingbekræftelse'); + } + return $this->generateBookingMessageContent($department, $message, $type, $is_to_department); + } + + private function addNewline(string $message): string + { + return $message . "\n"; + } + + /** + * @param departments_o $department + * @param string $message + * @param department_time_bookings_types_o $type + * @param bool $is_to_department + * @return string + * @throws Exception + */ + public function generateBookingMessageContent(departments_o $department, string $message, department_time_bookings_types_o $type, bool $is_to_department): string + { + self::requireSelected(); + $message .= self::addNewline('Afdeling: ' . $department->name->value()); + $message .= self::addNewline('Tidspunkt: ' . (string)$this->start->value()); + $message .= self::addNewline('Type: ' . (string)$type->name->value()); + $registration_number = $this->reg->value(); + if (!empty($registration_number)) { + $message .= self::addNewline('Registreringsnummer: ' . $registration_number); + } + $message .= self::addNewline('Telefonnummer: +' . $this->phone_country_code->value() . $this->phone->value()); + if (!empty($this->note->value())) { + $message .= self::addNewline('Note: ' . $this->note->value()); + } + if (!$is_to_department) { + $message .= self::addNewline('For mere information, kontakt venligst afdelingen.'); + } + $message .= 'Denne besked er genereret automatisk og kan ikke besvares.'; + return $message; + } + + /** + * Generate the message for an updated booking + * @param bool $is_to_department True if the message is to the department, false if it is to the customer + * @return string The message + * @throws Exception If the object is not selected + * @throws Exception If the department is not found + */ + public function generateUpdateBookingMessage(bool $is_to_department = false): string + { + self::requireSelected(); + $department = new departments_o(); + $department->select((int)$this->department->value()); + $type = new department_time_bookings_types_o(); + $type->select((int)$this->type->value()); + $message = $is_to_department + ? self::addNewline('Din afdeling har modtaget en opdatering på en booking') + : self::addNewline('Du har modtaget en opdatering på en booking'); + return $this->generateBookingMessageContent($department, $message, $type, $is_to_department); + } +} \ No newline at end of file diff --git a/services/nginx/app/objects/department_time_bookings_opening_hours_o.php b/services/nginx/app/objects/department_time_bookings_opening_hours_o.php new file mode 100644 index 00000000..43eaeea3 --- /dev/null +++ b/services/nginx/app/objects/department_time_bookings_opening_hours_o.php @@ -0,0 +1,219 @@ +setTable('department_time_bookings_opening_hours'); + } + + /** + * Convert the object to an array + * @throws Exception If the object is not selected + * @throws Exception If the object is not foun + */ + public function asArray(): array + { + self::requireSelected(); + return [ + 'id' => (int)$this->id, + 'department_id' => (int)$this->department->value(), + 'monday_start' => $this->monday_start->value(), + 'monday_end' => $this->monday_end->value(), + 'tuesday_start' => $this->tuesday_start->value(), + 'tuesday_end' => $this->tuesday_end->value(), + 'wednesday_start' => $this->wednesday_start->value(), + 'wednesday_end' => $this->wednesday_end->value(), + 'thursday_start' => $this->thursday_start->value(), + 'thursday_end' => $this->thursday_end->value(), + 'friday_start' => $this->friday_start->value(), + 'friday_end' => $this->friday_end->value(), + 'saturday_start' => $this->saturday_start->value(), + 'saturday_end' => $this->saturday_end->value(), + 'sunday_start' => $this->sunday_start->value(), + 'sunday_end' => $this->sunday_end->value(), + ]; + } + + /** + * Select the object by department id + * @throws Exception + */ + public function selectByDepartment(int $department_id): void + { + // Check if the department has opening hours + $matches = self::getFieldsWhere([ + 'department' => $department_id + ], [ + 'id' + ]); + if (count($matches) > 0) { + $this->id = $matches[0]['id']; + self::getObjectProperties(); + } else { + // Create a new object if it doesn't exist + $this->add($department_id); + } + } + + public function getObjectProperties(): void + { + $this->department = new object_property($this->table, $this->id, 'department', 'int', true); + $this->monday_start = new object_property($this->table, $this->id, 'monday_start', 'string', true); + $this->monday_end = new object_property($this->table, $this->id, 'monday_end', 'string', true); + $this->tuesday_start = new object_property($this->table, $this->id, 'tuesday_start', 'string', true); + $this->tuesday_end = new object_property($this->table, $this->id, 'tuesday_end', 'string', true); + $this->wednesday_start = new object_property($this->table, $this->id, 'wednesday_start', 'string', true); + $this->wednesday_end = new object_property($this->table, $this->id, 'wednesday_end', 'string', true); + $this->thursday_start = new object_property($this->table, $this->id, 'thursday_start', 'string', true); + $this->thursday_end = new object_property($this->table, $this->id, 'thursday_end', 'string', true); + $this->friday_start = new object_property($this->table, $this->id, 'friday_start', 'string', true); + $this->friday_end = new object_property($this->table, $this->id, 'friday_end', 'string', true); + $this->saturday_start = new object_property($this->table, $this->id, 'saturday_start', 'string', true); + $this->saturday_end = new object_property($this->table, $this->id, 'saturday_end', 'string', true); + $this->sunday_start = new object_property($this->table, $this->id, 'sunday_start', 'string', true); + $this->sunday_end = new object_property($this->table, $this->id, 'sunday_end', 'string', true); + } + + /** + * Add a new object to the database + * @param int $department_id The department id + * @param string|null $monday_start The start time for Monday + * @param string|null $monday_end The end time for Monday + * @param string|null $tuesday_start The start time for Tuesday + * @param string|null $tuesday_end The end time for Tuesday + * @param string|null $wednesday_start The start time for Wednesday + * @param string|null $wednesday_end The end time for Wednesday + * @param string|null $thursday_start The start time for Thursday + * @param string|null $thursday_end The end time for Thursday + * @param string|null $friday_start The start time for Friday + * @param string|null $friday_end The end time for Friday + * @param string|null $saturday_start The start time for Saturday + * @param string|null $saturday_end The end time for Saturday + * @param string|null $sunday_start The start time for Sunday + * @param string|null $sunday_end The end time for Sunday + * @return void + * @throws Exception If the object was not created successfully + */ + public function add( + int $department_id, + null|string $monday_start = null, + null|string $monday_end = null, + null|string $tuesday_start = null, + null|string $tuesday_end = null, + null|string $wednesday_start = null, + null|string $wednesday_end = null, + null|string $thursday_start = null, + null|string $thursday_end = null, + null|string $friday_start = null, + null|string $friday_end = null, + null|string $saturday_start = null, + null|string $saturday_end = null, + null|string $sunday_start = null, + null|string $sunday_end = null + ): void + { + function ifEmptySetNull($value): string|null + { + return empty($value) ? null : $value; + } + + $tmp_id = self::add_object([ + 'department' => (int)$department_id, + ...(ifEmptySetNull($monday_start) ? ['monday_start' => $monday_start] : []), + ...(ifEmptySetNull($monday_end) ? ['monday_end' => $monday_end] : []), + ...(ifEmptySetNull($tuesday_start) ? ['tuesday_start' => $tuesday_start] : []), + ...(ifEmptySetNull($tuesday_end) ? ['tuesday_end' => $tuesday_end] : []), + ...(ifEmptySetNull($wednesday_start) ? ['wednesday_start' => $wednesday_start] : []), + ...(ifEmptySetNull($wednesday_end) ? ['wednesday_end' => $wednesday_end] : []), + ...(ifEmptySetNull($thursday_start) ? ['thursday_start' => $thursday_start] : []), + ...(ifEmptySetNull($thursday_end) ? ['thursday_end' => $thursday_end] : []), + ...(ifEmptySetNull($friday_start) ? ['friday_start' => $friday_start] : []), + ...(ifEmptySetNull($friday_end) ? ['friday_end' => $friday_end] : []), + ...(ifEmptySetNull($saturday_start) ? ['saturday_start' => $saturday_start] : []), + ...(ifEmptySetNull($saturday_end) ? ['saturday_end' => $saturday_end] : []), + ...(ifEmptySetNull($sunday_start) ? ['sunday_start' => $sunday_start] : []), + ...(ifEmptySetNull($sunday_end) ? ['sunday_end' => $sunday_end] : []) + ]); + $this->id = $tmp_id; + self::getObjectProperties(); + self::objectChanged(); + } + + public function objectChanged(): void + { + //TODO: Add cache invalidation + } + + /** + * Check if the department is open at the given time + * @param int $start The start timestamp of the booking + * @param int $end The end timestamp of the booking + * @return bool True if the department is open, false otherwise + * @throws Exception If the object is not selected + */ + public function isOpen(string $start, string $end): bool + { + self::requireSelected(); + $start = strtotime($start); + $end = strtotime($end); + $dayOfWeek = date('N', $start); // Get the day of the week (1 = Monday, 7 = Sunday) + // Parse the day of week to the name of the day + $dayOfWeek = self::getWeekdayName($dayOfWeek); + $startTime = date('H:i', $start); + $endTime = date('H:i', $end); + $openingStart = $this->{"{$dayOfWeek}_start"}->value(); + $openingEnd = $this->{"{$dayOfWeek}_end"}->value(); + if ($openingStart === null || $openingEnd === null) { + return false; // Closed on this day + } + $openingStartTime = date('H:i', strtotime($openingStart)); + $openingEndTime = date('H:i', strtotime($openingEnd)); + return ($startTime >= $openingStartTime && $endTime <= $openingEndTime); + } + + /** + * Get the name of the day of the week + * @param int $dayOfWeek The day of the week (1 = Monday, 7 = Sunday) + * @return string The name of the day + * @throws Exception + */ + private function getWeekdayName(int $dayOfWeek): string + { + return match ($dayOfWeek) { + 1 => 'monday', + 2 => 'tuesday', + 3 => 'wednesday', + 4 => 'thursday', + 5 => 'friday', + 6 => 'saturday', + 7 => 'sunday', + default => throw new Exception('Invalid day of the week'), + }; + } +} \ No newline at end of file diff --git a/services/nginx/app/objects/department_time_bookings_types_o.php b/services/nginx/app/objects/department_time_bookings_types_o.php new file mode 100644 index 00000000..35c49f00 --- /dev/null +++ b/services/nginx/app/objects/department_time_bookings_types_o.php @@ -0,0 +1,83 @@ +setTable('department_time_bookings_types'); + } + + /** + * Convert the object to an array + * @throws Exception If the object is not selected + * @throws Exception If the object is not found + */ + public function asArray(): array + { + self::requireSelected(); + return [ + 'id' => (int)$this->id, + 'department' => (int)$this->department->value(), + 'name' => $this->name->value(), + 'description' => $this->description->value(), + 'duration' => (int)$this->duration->value(), + ]; + } + + /** + * Add a new object to the database + * @param int $department_id The department id + * @param string $name The name of the booking type + * @param null|string $description The description of the booking type + * @param int $duration The duration of the booking type (in minutes) + * @return void + * @throws Exception If the object was not created successfully + */ + public function add( + int $department_id, + string $name, + ?string $description = null, + int $duration = 0 + ): void + { + + $tmp_id = self::add_object([ + 'department' => (int)$department_id, + 'name' => $name, + 'description' => $description, + 'duration' => (int)$duration, + ]); + $this->id = $tmp_id; + self::getObjectProperties(); + self::objectChanged(); + } + + public function getObjectProperties(): void + { + $this->department = new object_property($this->table, $this->id, 'department', 'int', true); + $this->name = new object_property($this->table, $this->id, 'name', 'string', true); + $this->description = new object_property($this->table, $this->id, 'description', 'string', true); + $this->duration = new object_property($this->table, $this->id, 'duration', 'int', true); + $this->deleted_at = new object_property($this->table, $this->id, 'deleted_at', 'int', true); + } + + public function objectChanged(): void + { + //TODO: Add cache invalidation + } +} \ No newline at end of file diff --git a/services/nginx/app/routes/departmentTimeBookingsRoute.php b/services/nginx/app/routes/departmentTimeBookingsRoute.php new file mode 100644 index 00000000..245bfa49 --- /dev/null +++ b/services/nginx/app/routes/departmentTimeBookingsRoute.php @@ -0,0 +1,352 @@ + List */ + $this->get('/department/timebookings/opening-hours', function () { + global $response; + self::requirePermission('department_timebookings_opening_hours_get'); + $user = (new authentication())->get_user(); + if ($user) { + $specific_department = null; + if (self::isParametersSet(['department'])) { + self::requireType((int)self::getParameter('department'), self::type_int()); + self::requireMinValue((int)self::getParameter('department'), 1); + self::requireSameLength(self::getParameter('department'), (int)self::getParameter('department')); + self::requireDepartmentAccess((int)self::getParameter('department')); + $specific_department = (int)self::getParameter('department'); + // Select the specific department + $department_time_bookings_opening_hours = new department_time_bookings_opening_hours_o(); + $department_time_bookings_opening_hours->selectByDepartment( + (int)self::getParameter('department') + ); + $response->success($department_time_bookings_opening_hours->asArray()); + } + $department_time_bookings_opening_hours = new department_time_bookings_opening_hours_o(); + $result = $department_time_bookings_opening_hours->listObjectsWithPaginationIfSet( + function ($tmp_object_array) { + return [ + ...$tmp_object_array, + 'id' => (int)$tmp_object_array['id'], + 'department' => (int)$tmp_object_array['department'], + ]; + }, + $department_time_bookings_opening_hours->forceRestrictFilters( + [ + // This makes sure that the user can only see orders from the departments they explicitly have access to + 'department' => ($specific_department ?? $user->getGroup()->getDepartments()), + ] + ) + ); + (new logs_o())->add('department_time_bookings_opening_hours', 'global', 0, $user->id, 'DEPARTMENT_TIME_BOOKINGS_OPENING_HOURS_GET', 'Get department time bookings opening hours'); + $response->success($result); + } else { + (new logs_o())->add('department_time_bookings_opening_hours', 'global', 0, 0, 'DEPARTMENT_TIME_BOOKINGS_OPENING_HOURS_GET', 'Get department time bookings opening hours failed'); + $response->error('Invalid session', 400); + } + }, + [ + 'department_timebookings_opening_hours_get' => 'List department time bookings opening hours, in the departments the user has access to', + ] + ); + + /** Department Time Bookings -> Update */ + $this->put('/department/timebookings/opening-hours', function () { + global $response; + self::requirePermission('department_timebookings_opening_hours_put'); + $user = (new authentication())->get_user(); + if ($user) { + self::requireParameters(['id']); + self::requireType((int)self::getParameter('id'), self::type_int()); + self::requireMinValue((int)self::getParameter('id'), 1); + self::requireSameLength(self::getParameter('id'), (int)self::getParameter('id')); + $department_time_bookings_opening_hours = new department_time_bookings_opening_hours_o(); + $department_time_bookings_opening_hours->select( + (int)self::getParameter('id') + ); + // Require the department access + self::requireDepartmentAccess((int)$department_time_bookings_opening_hours->department->value()); + function ifEmptySetNull($value): ?string + { + return $value === '' ? null : $value; + } + + // Update the department time bookings opening hours + $department_time_bookings_opening_hours->update([ + ...(self::isParametersSet(['monday_start']) ? ['monday_start' => ifEmptySetNull(self::getParameter('monday_start'))] : []), + ...(self::isParametersSet(['monday_end']) ? ['monday_end' => ifEmptySetNull(self::getParameter('monday_end'))] : []), + ...(self::isParametersSet(['tuesday_start']) ? ['tuesday_start' => ifEmptySetNull(self::getParameter('tuesday_start'))] : []), + ...(self::isParametersSet(['tuesday_end']) ? ['tuesday_end' => ifEmptySetNull(self::getParameter('tuesday_end'))] : []), + ...(self::isParametersSet(['wednesday_start']) ? ['wednesday_start' => ifEmptySetNull(self::getParameter('wednesday_start'))] : []), + ...(self::isParametersSet(['wednesday_end']) ? ['wednesday_end' => ifEmptySetNull(self::getParameter('wednesday_end'))] : []), + ...(self::isParametersSet(['thursday_start']) ? ['thursday_start' => ifEmptySetNull(self::getParameter('thursday_start'))] : []), + ...(self::isParametersSet(['thursday_end']) ? ['thursday_end' => ifEmptySetNull(self::getParameter('thursday_end'))] : []), + ...(self::isParametersSet(['friday_start']) ? ['friday_start' => ifEmptySetNull(self::getParameter('friday_start'))] : []), + ...(self::isParametersSet(['friday_end']) ? ['friday_end' => ifEmptySetNull(self::getParameter('friday_end'))] : []), + ...(self::isParametersSet(['saturday_start']) ? ['saturday_start' => ifEmptySetNull(self::getParameter('saturday_start'))] : []), + ...(self::isParametersSet(['saturday_end']) ? ['saturday_end' => ifEmptySetNull(self::getParameter('saturday_end'))] : []), + ...(self::isParametersSet(['sunday_start']) ? ['sunday_start' => ifEmptySetNull(self::getParameter('sunday_start'))] : []), + ...(self::isParametersSet(['sunday_end']) ? ['sunday_end' => ifEmptySetNull(self::getParameter('sunday_end'))] : []), + ]); + (new logs_o())->add('department_time_bookings_opening_hours', 'global', 0, $user->id, 'DEPARTMENT_TIME_BOOKINGS_OPENING_HOURS_PUT', 'Update department time bookings opening hours'); + $response->success($department_time_bookings_opening_hours->asArray()); + } else { + (new logs_o())->add('department_time_bookings_opening_hours', 'global', 0, 0, 'DEPARTMENT_TIME_BOOKINGS_OPENING_HOURS_PUT', 'Update department time bookings opening hours failed'); + $response->error('Invalid session', 400); + } + }, + [ + 'department_timebookings_opening_hours_put' => 'Update department time bookings opening hours', + ] + ); + + /** Department Time Bookings -> Types */ + $this->get('/department/timebookings/types', function () { + global $response; + self::requirePermission('department_timebookings_types_get'); + $user = (new authentication())->get_user(); + if ($user) { + $specific_department = null; + if (self::isParametersSet(['department'])) { + self::requireType((int)self::getParameter('department'), self::type_int()); + self::requireMinValue((int)self::getParameter('department'), 1); + self::requireSameLength(self::getParameter('department'), (int)self::getParameter('department')); + self::requireDepartmentAccess((int)self::getParameter('department')); + $specific_department = (int)self::getParameter('department'); + } + $department_time_bookings_types = new department_time_bookings_types_o(); + $result = $department_time_bookings_types->listObjectsWithPaginationIfSet( + function ($tmp_object_array) { + return [ + ...$tmp_object_array, + 'id' => (int)$tmp_object_array['id'], + 'department' => (int)$tmp_object_array['department'], + 'duration' => (int)$tmp_object_array['duration'], + ]; + }, + $department_time_bookings_types->forceRestrictFilters( + [ + // This makes sure that the user can only see orders from the departments they explicitly have access to + 'department' => ($specific_department ?? $user->getGroup()->getDepartments()), + ] + ) + ); + (new logs_o())->add('department_time_bookings_types', 'global', 0, $user->id, 'DEPARTMENT_TIME_BOOKINGS_TYPES_GET', 'Get department time bookings types'); + $response->success($result); + } else { + (new logs_o())->add('department_time_bookings_types', 'global', 0, 0, 'DEPARTMENT_TIME_BOOKINGS_TYPES_GET', 'Get department time bookings types failed'); + $response->error('Invalid session', 400); + } + }, + [ + 'department_timebookings_types_get' => 'List department time bookings types, in the departments the user has access to', + ] + ); + + /** Department Time Bookings -> Types -> Add */ + $this->post('/department/timebookings/types', function () { + global $response; + self::requirePermission('department_timebookings_types_post'); + $user = (new authentication())->get_user(); + if ($user) { + self::requireParameters(['department', 'name']); + self::requireType((int)self::getParameter('department'), self::type_int()); + self::requireMinValue((int)self::getParameter('department'), 1); + self::requireSameLength(self::getParameter('department'), (int)self::getParameter('department')); + self::requireDepartmentAccess((int)self::getParameter('department')); + // Add the department time bookings type + $department_time_bookings_types = new department_time_bookings_types_o(); + $department_time_bookings_types->add( + (int)self::getParameter('department'), + (string)self::getParameter('name'), + (string)(self::isParametersSet(['description']) ? self::getParameter('description') : null), + (int)(self::isParametersSet(['duration']) ? self::getParameter('duration') : 0) + ); + (new logs_o())->add('department_time_bookings_types', 'global', 0, $user->id, 'DEPARTMENT_TIME_BOOKINGS_TYPES_POST', 'Add department time bookings types'); + $response->success($department_time_bookings_types->asArray()); + } else { + (new logs_o())->add('department_time_bookings_types', 'global', 0, 0, 'DEPARTMENT_TIME_BOOKINGS_TYPES_POST', 'Add department time bookings types failed'); + $response->error('Invalid session', 400); + } + }, + [ + 'department_timebookings_types_post' => 'Add department time bookings types', + ] + ); + + /** Department Time Bookings -> Types -> Update */ + $this->put('/department/timebookings/types', function () { + global $response; + self::requirePermission('department_timebookings_types_put'); + $user = (new authentication())->get_user(); + if ($user) { + self::requireParameters(['id']); + self::requireType((int)self::getParameter('id'), self::type_int()); + self::requireMinValue((int)self::getParameter('id'), 1); + self::requireSameLength(self::getParameter('id'), (int)self::getParameter('id')); + $department_time_bookings_types = new department_time_bookings_types_o(); + $department_time_bookings_types->select( + (int)self::getParameter('id') + ); + // Require the department access + self::requireDepartmentAccess((int)$department_time_bookings_types->department->value()); + // Update the department time bookings type + $department_time_bookings_types->update([ + ...(self::isParametersSet(['name']) ? ['name' => (string)self::getParameter('name')] : []), + ...(self::isParametersSet(['description']) ? ['description' => (string)self::getParameter('description')] : []), + ...(self::isParametersSet(['duration']) ? ['duration' => (int)self::getParameter('duration')] : []), + ]); + (new logs_o())->add('department_time_bookings_types', 'global', 0, $user->id, 'DEPARTMENT_TIME_BOOKINGS_TYPES_PUT', 'Update department time bookings types'); + $response->success($department_time_bookings_types->asArray()); + } else { + (new logs_o())->add('department_time_bookings_types', 'global', 0, 0, 'DEPARTMENT_TIME_BOOKINGS_TYPES_PUT', 'Update department time bookings types failed'); + $response->error('Invalid session', 400); + } + }, + [ + 'department_timebookings_types_put' => 'Update department time bookings types', + ] + ); + + /** Department Time Bookings -> Types -> Delete */ + $this->delete('/department/timebookings/types', function () { + global $response; + self::requirePermission('department_timebookings_types_delete'); + $user = (new authentication())->get_user(); + if ($user) { + self::requireParameters(['id']); + self::requireType((int)self::getParameter('id'), self::type_int()); + self::requireMinValue((int)self::getParameter('id'), 1); + self::requireSameLength(self::getParameter('id'), (int)self::getParameter('id')); + $department_time_bookings_types = new department_time_bookings_types_o(); + $department_time_bookings_types->select( + (int)self::getParameter('id') + ); + // Require the department access + self::requireDepartmentAccess((int)$department_time_bookings_types->department->value()); + // Delete the department time bookings type + $department_time_bookings_types->delete(); + (new logs_o())->add('department_time_bookings_types', 'global', 0, $user->id, 'DEPARTMENT_TIME_BOOKINGS_TYPES_DELETE', 'Delete department time bookings types'); + $response->success(['success' => true]); + } else { + (new logs_o())->add('department_time_bookings_types', 'global', 0, 0, 'DEPARTMENT_TIME_BOOKINGS_TYPES_DELETE', 'Delete department time bookings types failed'); + $response->error('Invalid session', 400); + } + }, + [ + 'department_timebookings_types_delete' => 'Delete department time bookings types', + ] + ); + + /** Department Time Bookings -> Entries */ + $this->get('/department/timebookings/entries', function () { + global $response; + self::requirePermission('department_timebookings_entries_get'); + $user = (new authentication())->get_user(); + if ($user) { + $specific_department = null; + if (self::isParametersSet(['id'])) { + self::requireType((int)self::getParameter('id'), self::type_int()); + self::requireMinValue((int)self::getParameter('id'), 1); + self::requireSameLength(self::getParameter('id'), (int)self::getParameter('id')); + // Get the object + $department_time_bookings_entries = new department_time_bookings_entries_o(); + $department_time_bookings_entries->select( + (int)self::getParameter('id') + ); + // Require the department access + self::requireDepartmentAccess((int)$department_time_bookings_entries->department->value()); + $response->success($department_time_bookings_entries->asArray()); + } + $department_time_bookings_entries = new department_time_bookings_entries_o(); + $result = $department_time_bookings_entries->listObjectsWithPaginationIfSet( + function ($tmp_object_array) { + return [ + ...$tmp_object_array, + 'id' => (int)$tmp_object_array['id'], + 'department' => (int)$tmp_object_array['department'], + 'type' => (int)$tmp_object_array['type'], + 'start' => (string)$tmp_object_array['start'], + 'end' => (string)$tmp_object_array['end'], + 'note' => $tmp_object_array['note'], + 'reg' => $tmp_object_array['reg'], + 'phone' => (int)$tmp_object_array['phone'], + 'phone_country_code' => (int)$tmp_object_array['phone_country_code'], + ]; + }, + $department_time_bookings_entries->forceRestrictFilters( + [ + // This makes sure that the user can only see orders from the departments they explicitly have access to + 'department' => ($specific_department ?? $user->getGroup()->getDepartments()), + ] + ) + ); + (new logs_o())->add('department_time_bookings_entries', 'global', 0, $user->id, 'DEPARTMENT_TIME_BOOKINGS_ENTRIES_GET', 'Get department time bookings entries'); + $response->success($result); + } else { + (new logs_o())->add('department_time_bookings_entries', 'global', 0, 0, 'DEPARTMENT_TIME_BOOKINGS_ENTRIES_GET', 'Get department time bookings entries failed'); + $response->error('Invalid session', 400); + } + }, + [ + 'department_timebookings_entries_get' => 'List department time bookings entries, in the departments the user has access to', + ] + ); + + /** Department Time Bookings -> Entries -> Add */ + $this->post('/department/timebookings/entries', function () { + global $response; + self::requirePermission('department_timebookings_entries_post'); + $user = (new authentication())->get_user(); + if ($user) { + self::requireParameters(['department', 'type', 'start', 'end']); + self::requireType((int)self::getParameter('department'), self::type_int()); + self::requireMinValue((int)self::getParameter('department'), 1); + self::requireSameLength(self::getParameter('department'), (int)self::getParameter('department')); + self::requireDepartmentAccess((int)self::getParameter('department')); + + // Add the department time bookings entry + $department_time_bookings_entries = new department_time_bookings_entries_o(); + $department_time_bookings_entries->add( + (int)self::getParameter('department'), + (int)self::getParameter('type'), + (string)self::getParameter('start'), + (string)self::getParameter('end'), + (int)(self::isParametersSet(['phone_country_code']) ? self::getParameter('phone_country_code') : 0), + (int)(self::isParametersSet(['phone']) ? self::getParameter('phone') : 0), + (self::isParametersSet(['note']) ? (string)self::getParameter('note') : null), + (self::isParametersSet(['reg']) ? (string)self::getParameter('reg') : null) + ); + (new logs_o())->add('department_time_bookings_entries', 'global', 0, $user->id, 'DEPARTMENT_TIME_BOOKINGS_ENTRIES_POST', 'Add department time bookings entries'); + $response->success($department_time_bookings_entries->asArray()); + } else { + (new logs_o())->add('department_time_bookings_entries', 'global', 0, 0, 'DEPARTMENT_TIME_BOOKINGS_ENTRIES_POST', 'Add department time bookings entries failed'); + $response->error('Invalid session', 400); + } + }, + [ + 'department_timebookings_entries_post' => 'Add department time bookings entries', + ] + ); + } +} \ 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 b3a93748..2c5f987e 100644 --- a/services/nginx/app/traits/db_object_t.php +++ b/services/nginx/app/traits/db_object_t.php @@ -382,7 +382,7 @@ trait db_object_t } } elseif (preg_match('/^(.+?)-has_key/', $field, $matches)) { // If the filter is e.g. customer_number-has_key:invoiceOrdersIndividually - + // Get the field name and the key $fieldName = $matches[1]; // Set the key to the value, without the (optional) "!" prefix @@ -811,6 +811,12 @@ trait db_object_t global $db; $set = []; foreach ( $data as $key => $value ) { + // If the value is null, set it to null + if ($value === null || (is_string($value) && strtolower($value) === 'null')) { + $set[] = "$key = NULL"; + continue; + } + $value = $db->escape_string($value); $set[] = "$key = '$value'"; }