Skip to content

Commit

Permalink
clean(ZMS-3253): reduce complexity in AvailabilityUpdate and Availabi…
Browse files Browse the repository at this point in the history
…lityAdd
  • Loading branch information
Thomas Fink authored and Thomas Fink committed Feb 17, 2025
1 parent 6f98308 commit 8e1c425
Show file tree
Hide file tree
Showing 2 changed files with 457 additions and 140 deletions.
283 changes: 220 additions & 63 deletions zmsapi/src/Zmsapi/AvailabilityAdd.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,150 +24,307 @@
*/
class AvailabilityAdd extends BaseController
{
/**
* @SuppressWarnings(Param)
* @return ResponseInterface
*/
public function readResponse(
RequestInterface $request,
ResponseInterface $response,
array $args
): ResponseInterface {
(new Helper\User($request))->checkRights();
$resolveReferences = Validator::param('resolveReferences')->isNumber()->setDefault(2)->getValue();
$input = $this->validateAndGetInput();
$resolveReferences = $this->getResolveReferences();

DbConnection::getWriteConnection();

$result = $this->processAvailabilityAddition($input, $resolveReferences);

return $this->generateResponse($request, $response, $result);
}

private function validateAndGetInput(): array
{
$input = Validator::input()->isJson()->assertValid()->getValue();
if (!$input || count($input) === 0) {
throw new BadRequestException();
}

DbConnection::getWriteConnection();
$this->validateInputStructure($input);
return $input;
}

private function validateInputStructure(array $input): void
{
if (!isset($input['availabilityList']) || !is_array($input['availabilityList'])) {
throw new BadRequestException('Missing or invalid availabilityList.');
} elseif (empty($input['availabilityList']) || !isset($input['availabilityList'][0]['scope'])) {
}
if (empty($input['availabilityList']) || !isset($input['availabilityList'][0]['scope'])) {
throw new BadRequestException('Missing or invalid scope.');
} elseif (!isset($input['selectedDate'])) {
}
if (!isset($input['selectedDate'])) {
throw new BadRequestException("'selectedDate' is required.");
}
}

private function getResolveReferences(): int
{
return Validator::param('resolveReferences')->isNumber()->setDefault(2)->getValue();
}

private function processAvailabilityAddition(array $input, int $resolveReferences): Collection
{
$newCollection = $this->createNewCollection($input['availabilityList']);
$selectedDate = $this->createSelectedDateTime($input['selectedDate']);
$scope = new \BO\Zmsentities\Scope($input['availabilityList'][0]['scope']);

$this->validateAndCheckConflicts($newCollection, $scope, $selectedDate);

return $this->updateEntities($newCollection, $resolveReferences);
}

private function createNewCollection(array $availabilityList): Collection
{
$newCollection = new Collection();
foreach ($input['availabilityList'] as $item) {
foreach ($availabilityList as $item) {
$entity = new Entity($item);
$entity->testValid();
$newCollection->addEntity($entity);
}
return $newCollection;
}

private function createSelectedDateTime(string $selectedDate): \DateTimeImmutable
{
return \DateTimeImmutable::createFromFormat(
'Y-m-d H:i:s',
$selectedDate . ' 00:00:00'
);
}

private function validateAndCheckConflicts(
Collection $newCollection,
\BO\Zmsentities\Scope $scope,
\DateTimeImmutable $selectedDate
): void {
$conflicts = $this->getInitialConflicts($newCollection);
$mergedCollection = $this->getMergedCollection($newCollection, $scope);

$this->validateNewAvailabilities($newCollection, $mergedCollection, $selectedDate);

$filteredCollection = $this->getFilteredCollection($mergedCollection);
$this->checkExistingConflicts($conflicts, $filteredCollection, $selectedDate);

$scopeData = $input['availabilityList'][0]['scope'];
$scope = new \BO\Zmsentities\Scope($scopeData);
$selectedDate = \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $input['selectedDate'] . ' 00:00:00');
$weekday = (int)$selectedDate->format('N');
$filteredConflicts = $this->filterConflictsByWeekday(
$conflicts,
$filteredCollection,
$weekday
);

if ($filteredConflicts->count() > 0) {
throw new AvailabilityAddFailed();
}
}

private function getInitialConflicts(Collection $newCollection): \BO\Zmsentities\Collection\ProcessList
{
$conflicts = new \BO\Zmsentities\Collection\ProcessList();
$newVsNewConflicts = $newCollection->hasNewVsNewConflicts();
$conflicts->addList($newVsNewConflicts);
return $conflicts;
}

private function getMergedCollection(Collection $newCollection, \BO\Zmsentities\Scope $scope): Collection
{
$availabilityRepo = new AvailabilityRepository();
$existingCollection = $availabilityRepo->readAvailabilityListByScope($scope, 1);

$mergedCollection = new Collection();
foreach ($existingCollection as $existingAvailability) {
$mergedCollection->addEntity($existingAvailability);
}
return $mergedCollection;
}

private function validateNewAvailabilities(
Collection $newCollection,
Collection $mergedCollection,
\DateTimeImmutable $selectedDate
): void {
$validations = [];
foreach ($newCollection as $newAvailability) {
$startDate = (new \DateTimeImmutable())->setTimestamp($newAvailability->startDate);
$endDate = (new \DateTimeImmutable())->setTimestamp($newAvailability->endDate);
$startDateTime = new \DateTimeImmutable("{$startDate->format('Y-m-d')} {$newAvailability->startTime}");
$endDateTime = new \DateTimeImmutable("{$endDate->format('Y-m-d')} {$newAvailability->endTime}");

$currentValidation = $mergedCollection->validateInputs(
$startDateTime,
$endDateTime,
$selectedDate,
$newAvailability->kind ?? 'default',
$newAvailability->bookable['startInDays'],
$newAvailability->bookable['endInDays'],
$newAvailability->weekday
$validations = array_merge(
$validations,
$this->validateSingleAvailability($newAvailability, $mergedCollection, $selectedDate)
);
$validations = array_merge($validations, $currentValidation);

$mergedCollection->addEntity($newAvailability);
}

if (count($validations) > 0) {
throw new AvailabilityAddFailed();
}
}

$originId = null;
private function validateSingleAvailability(
Entity $availability,
Collection $mergedCollection,
\DateTimeImmutable $selectedDate
): array {
$startDate = (new \DateTimeImmutable())->setTimestamp($availability->startDate);
$endDate = (new \DateTimeImmutable())->setTimestamp($availability->endDate);
$startDateTime = new \DateTimeImmutable(
"{$startDate->format('Y-m-d')} {$availability->startTime}"
);
$endDateTime = new \DateTimeImmutable(
"{$endDate->format('Y-m-d')} {$availability->endTime}"
);

return $mergedCollection->validateInputs(
$startDateTime,
$endDateTime,
$selectedDate,
$availability->kind ?? 'default',
$availability->bookable['startInDays'],
$availability->bookable['endInDays'],
$availability->weekday
);
}

private function getFilteredCollection(Collection $mergedCollection): Collection
{
$originId = $this->findOriginId($mergedCollection);

$filtered = new Collection();
foreach ($mergedCollection as $availability) {
if (isset($availability->kind) && $availability->kind === 'origin' && isset($availability->id)) {
$originId = $availability->id;
break;
if ($this->shouldIncludeAvailability($availability, $originId)) {
$filtered->addEntity($availability);
}
}
return $filtered;
}

$mergedCollectionWithoutExclusions = new Collection();
foreach ($mergedCollection as $availability) {
private function findOriginId(Collection $collection): ?string
{
foreach ($collection as $availability) {
if (
(!isset($availability->kind) || $availability->kind !== 'exclusion') &&
(!isset($availability->id) || $availability->id !== $originId)
isset($availability->kind) &&
$availability->kind === 'origin' &&
isset($availability->id)
) {
$mergedCollectionWithoutExclusions->addEntity($availability);
return $availability->id;
}
}
return null;
}

private function shouldIncludeAvailability(Entity $availability, ?string $originId): bool
{
return (!isset($availability->kind) || $availability->kind !== 'exclusion') &&
(!isset($availability->id) || $availability->id !== $originId);
}

private function checkExistingConflicts(
\BO\Zmsentities\Collection\ProcessList $conflicts,
Collection $filteredCollection,
\DateTimeImmutable $selectedDate
): void {
[$earliestStartDateTime, $latestEndDateTime] = $filteredCollection
->getDateTimeRangeFromList($selectedDate);

[$earliestStartDateTime, $latestEndDateTime] = $mergedCollectionWithoutExclusions->getDateTimeRangeFromList(
\DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $input['selectedDate'] . ' 00:00:00')
$existingConflicts = $filteredCollection->checkAllVsExistingConflicts(
$earliestStartDateTime,
$latestEndDateTime
);
$existingConflicts = $mergedCollectionWithoutExclusions->checkAllVsExistingConflicts($earliestStartDateTime, $latestEndDateTime);
$conflicts->addList($existingConflicts);
}

// Filter conflicts by weekday
private function filterConflictsByWeekday(
\BO\Zmsentities\Collection\ProcessList $conflicts,
Collection $filteredCollection,
int $weekday
): \BO\Zmsentities\Collection\ProcessList {
$filteredConflicts = new \BO\Zmsentities\Collection\ProcessList();

foreach ($conflicts as $conflict) {
$availability1 = $conflict->getFirstAppointment()->getAvailability();
$availability2 = null;
foreach ($mergedCollectionWithoutExclusions as $avail) {
if (
$avail->id === $availability1->id ||
(isset($avail->tempId) && isset($availability1->tempId) && $avail->tempId === $availability1->tempId)
) {
$availability2 = $avail;
break;
}
}
$availability2 = $this->findMatchingAvailability(
$availability1,
$filteredCollection
);

// Check if either availability has the weekday bit set
$affectsSelectedDay = false;
if (isset($availability1->weekday[$weekday]) && (int)$availability1->weekday[$weekday] > 0) {
$affectsSelectedDay = true;
}
if ($availability2 && isset($availability2->weekday[$weekday]) && (int)$availability2->weekday[$weekday] > 0) {
$affectsSelectedDay = true;
if ($this->doesConflictAffectWeekday($availability1, $availability2, $weekday)) {
$filteredConflicts->addEntity($conflict);
}
}

// Only keep conflicts that affect the selected day
if ($affectsSelectedDay) {
$filteredConflicts->addEntity($conflict);
return $filteredConflicts;
}

private function findMatchingAvailability(
Entity $availability1,
Collection $collection
): ?Entity {
foreach ($collection as $avail) {
if (
$avail->id === $availability1->id ||
(isset($avail->tempId) &&
isset($availability1->tempId) &&
$avail->tempId === $availability1->tempId)
) {
return $avail;
}
}
return null;
}

if ($filteredConflicts->count() > 0) {
throw new AvailabilityAddFailed();
private function doesConflictAffectWeekday(
Entity $availability1,
?Entity $availability2,
int $weekday
): bool {
$weekdayKey = strtolower(date('l', strtotime("Sunday +{$weekday} days")));

if (
isset($availability1->weekday[$weekdayKey]) &&
(int)$availability1->weekday[$weekdayKey] > 0
) {
return true;
}

if (
$availability2 &&
isset($availability2->weekday[$weekdayKey]) &&
(int)$availability2->weekday[$weekdayKey] > 0
) {
return true;
}

return false;
}

private function updateEntities(Collection $newCollection, int $resolveReferences): Collection
{
$updatedCollection = new Collection();
foreach ($newCollection as $entity) {
$updatedEntity = $this->writeEntityUpdate($entity, $resolveReferences);
AvailabilitySlotsUpdate::writeCalculatedSlots($updatedEntity, true);
$updatedCollection->addEntity($updatedEntity);
}
return $updatedCollection;
}

private function generateResponse(
RequestInterface $request,
ResponseInterface $response,
Collection $updatedCollection
): ResponseInterface {
$message = Response\Message::create($request);
$message->data = $updatedCollection->getArrayCopy();

$response = Render::withLastModified($response, time(), '0');
return Render::withJson($response, $message->setUpdatedMetaData(), $message->getStatuscode());
return Render::withJson(
$response,
$message->setUpdatedMetaData(),
$message->getStatuscode()
);
}

protected function writeEntityUpdate($entity, $resolveReferences): Entity
Expand Down
Loading

0 comments on commit 8e1c425

Please sign in to comment.