Commit b8955048 authored by Jacob Priddy's avatar Jacob Priddy 👌

Finish up override code. Next add tests. Also update to PHP 8

parent 47f9024c
Pipeline #13193 failed with stages
in 1 minute and 32 seconds
......@@ -8,7 +8,7 @@
],
"license": "MIT",
"require": {
"php": "^7.4",
"php": "^8.0",
"devmarketer/easynav": "^1.0",
"fideloper/proxy": "^4.0",
"fruitcake/laravel-cors": "^1.0",
......
......@@ -6,7 +6,7 @@ namespace Source\Entities;
use Carbon\Carbon;
use InvalidArgumentException;
class Override
class Override extends SplittableDate
{
public const TYPE_OPEN = 0;
public const TYPE_LOCKED = 1;
......@@ -19,16 +19,6 @@ class Override
protected int $type;
/**
* @var \Carbon\Carbon
*/
protected Carbon $start;
/**
* @var \Carbon\Carbon
*/
protected Carbon $end;
/**
* @var \Carbon\Carbon|null
*/
......@@ -47,7 +37,7 @@ class Override
int $userId,
int $doorId,
int $type,
Carbon $start,
Carbon $begin,
Carbon $end,
?Carbon $createdAt = null,
?Carbon $updatedAt = null
......@@ -60,11 +50,10 @@ class Override
$this->userId = $userId;
$this->doorId = $doorId;
$this->type = $type;
$this->start = $start;
$this->end = $end;
$this->createdAt = $createdAt;
$this->updatedAt = $updatedAt;
$this->reason = $reason;
parent::__construct($begin, $end);
}
/**
......@@ -107,22 +96,6 @@ class Override
return $this->type;
}
/**
* @return \Carbon\Carbon
*/
public function getStart(): Carbon
{
return $this->start;
}
/**
* @return \Carbon\Carbon
*/
public function getEnd(): Carbon
{
return $this->end;
}
/**
* @return \Carbon\Carbon|null
*/
......@@ -168,10 +141,10 @@ class Override
}
if ($this->getEnd()) {
return $date->isBetween($this->getStart(), $this->getEnd());
return $date->isBetween($this->getBegin(), $this->getEnd());
}
return $date->greaterThanOrEqualTo($this->getStart());
return $date->greaterThanOrEqualTo($this->getBegin());
}
/**
......@@ -191,44 +164,4 @@ class Override
{
return $this->getId() === (int)$id;
}
/**
* Determines if the current override engulfs another
*
* @param \Source\Entities\Override $o
* @return bool
*/
public function engulfs(Override $o): bool
{
return $this->engulfsDates($o->getStart(), $o->getEnd());
}
/**
* @param \Carbon\Carbon $start
* @param \Carbon\Carbon $end
* @return bool
*/
public function engulfsDates(Carbon $start, Carbon $end): bool
{
return $this->getStart() < $start && $this->getEnd() > $end;
}
/**
* @param \Source\Entities\Override $o
* @return bool
*/
public function hasNoOverlapWith(Override $o): bool
{
return $this->hasNoOverlapWithDates($o->getStart(), $o->getEnd());
}
/**
* @param \Carbon\Carbon $start
* @param \Carbon\Carbon $end
* @return bool
*/
public function hasNoOverlapWithDates(Carbon $start, Carbon $end): bool
{
return $this->getStart() > $end || $this->getEnd() < $start;
}
}
......@@ -3,44 +3,8 @@
namespace Source\Entities;
use Carbon\Carbon;
class ScheduleEvent
class ScheduleEvent extends SplittableDate
{
/**
* @var \Carbon\Carbon
*/
protected Carbon $begin;
/**
* @var \Carbon\Carbon
*/
protected Carbon $end;
public function __construct(
Carbon $begin,
Carbon $end
) {
$this->begin = $begin;
$this->end = $end;
}
/**
* @return \Carbon\Carbon
*/
public function getBegin(): Carbon
{
return $this->begin;
}
/**
* @return \Carbon\Carbon
*/
public function getEnd(): Carbon
{
return $this->end;
}
/**
* @param \Source\Entities\ScheduleEvent $other
* @return int
......@@ -49,20 +13,4 @@ class ScheduleEvent
{
return $other->getBegin()->diffInRealSeconds($this->getBegin(), false);
}
/**
* @param \Carbon\Carbon $begin
*/
public function setBegin(Carbon $begin): void
{
$this->begin = $begin;
}
/**
* @param \Carbon\Carbon $end
*/
public function setEnd(Carbon $end): void
{
$this->end = $end;
}
}
<?php
namespace Source\Entities;
use Carbon\Carbon;
use JetBrains\PhpStorm\Pure;
class SplittableDate
{
/**
* @var \Carbon\Carbon
*/
protected Carbon $begin;
/**
* @var \Carbon\Carbon
*/
protected Carbon $end;
/**
* SplittableDate constructor.
*
* @param \Carbon\Carbon $begin
* @param \Carbon\Carbon $end
*/
public function __construct(Carbon $begin, Carbon $end)
{
$this->begin = $begin;
$this->end = $end;
}
/**
* @return \Carbon\Carbon
*/
public function getBegin(): Carbon
{
return $this->begin;
}
/**
* @return \Carbon\Carbon
*/
public function getEnd(): Carbon
{
return $this->end;
}
/**
* @param \Carbon\Carbon $begin
* @return \Source\Entities\SplittableDate
*/
public function setBegin(Carbon $begin): static
{
$this->begin = $begin;
return $this;
}
/**
* @param \Carbon\Carbon $end
* @return \Source\Entities\SplittableDate
*/
public function setEnd(Carbon $end): static
{
$this->end = $end;
return $this;
}
/**
* Determines if the current date set engulfs another
*
* @param \Source\Entities\SplittableDate $s
* @return bool
*/
#[Pure] public function engulfs(SplittableDate $s): bool
{
return $this->getBegin() <= $s->getBegin() && $this->getEnd() >= $s->getEnd();
}
/**
* @param \Source\Entities\SplittableDate $s
* @return bool
*/
#[Pure] public function hasNoOverlapWith(SplittableDate $s): bool
{
return $this->getBegin() >= $s->getEnd() || $this->getEnd() <= $s->getBegin();
}
/**
* Mangles the set passed in.
* Ensures there are no date sets.
* This method squashes any overlapping date sets into one continuous range.
*
* REQUIRES THAT THE SET IS ORDERED BY BEGIN DATE.
*
* @param static[] $set
* @return static[]
*/
public static function mergeSet(array $set): array
{
if (count($set) < 1) {
return [];
}
$merged = [];
$prev = array_shift($set);
$noMerge = true;
foreach ($set as $item) {
if ($item->getBegin() <= $prev->getEnd()) {
if ($prev->getEnd() < $item->getEnd()) {
$prev->setEnd($item->getEnd());
}
$noMerge = true;
} else {
$merged[] = $prev;
$noMerge = false;
$prev = $item;
}
}
if ($noMerge) {
$merged[] = $prev;
}
return $merged;
}
/**
* This method splits a date range into segments based upon a list of date ranges. It will split itself until
* there is no overlap between the passed in set of date ranges.
* THE SET PASSED IN MUST BE SORTED BY BEGIN
*
* Do not use the object after calling this method on it.
*
* @param static[] $sortedSet
* @param bool $merged Has the set been squashed to have no overlaps?
* @return static[]
*/
public function split(array $sortedSet, bool $merged = false): array
{
/*
* We will advance our begin through the ranges until we are done.
*/
$splits = [];
if (!$merged) {
$sortedSet = self::mergeSet($sortedSet);
}
foreach ($sortedSet as $item) {
if ($this->hasNoOverlapWith($item)) {
// If we have processed all in our range break so we can return the split.
if ($item->getBegin() >= $this->getEnd()) {
break;
}
} /*
* Closed engulfs an open
* |----this---|
* |-----item--------|
* In this case return as no more ranges are allowed.
*/
elseif ($item->engulfs($this)) {
// No need to process other items on this as we throw the rest out.
// Return the splits up to this point and call it a day.
return $splits;
} /*
* This engulfs an item
* |--------this---------|
* |---item---|
* In this case add the split item to the return set and advance our start.
*/
elseif ($this->engulfs($item)) {
/*
* |-this-a-|
* |--item--|
*/
$splits[] = (clone $this)->setEnd($item->getBegin());
/*
* Move our begin date along.
* |-this-|
* |--item--|
*/
$this->setBegin($item->getEnd());
} /*
* we are at the right side of an item
* |---this---|
* |-----item----|
* Advance our start and continue on.
*/
elseif ($this->getEnd() > $item->getEnd()) {
$this->setBegin($item->getEnd());
} /*
* Open is at the left side of a closed
* |---this---|
* |-----item----|
* In this case cut to the beginning of the item
*/
elseif ($this->getBegin() < $item->getBegin()) {
$splits[] = $this->setEnd($item->getBegin());
// We end here because since all items are supposed to be sorted by start date
// we have done all the processing we need to as nothing after this is going to have any overlap
// with us.
return $splits;
} else {
// Skip it. IDK what it would be. I think we cover all other cases???
continue;
}
}
// Add last split.
$splits[] = $this;
return $splits;
}
}
......@@ -49,7 +49,7 @@ class DatabaseOverridesRepository implements OverridesRepository
* @param $override
* @return \Source\Entities\Override
*/
protected function toOverrideFromRaw($override): Override
protected static function toOverrideFromRaw($override): Override
{
return new Override(
$override->id,
......@@ -111,7 +111,7 @@ QUERY;
':END' => $end,
]);
return array_map(static function (\App\Override $override) {
return array_map(static function ($override) {
return self::toOverrideFromRaw($override);
}, $overrides);
}
......@@ -126,7 +126,7 @@ QUERY;
$o->setAttribute('user_id', $override->getUserId());
$o->setAttribute('door_id', $override->getDoorId());
$o->setAttribute('type', $override->getType());
$o->setAttribute('start', $override->getStart());
$o->setAttribute('start', $override->getBegin());
$o->setAttribute('end', $override->getEnd());
$o->setAttribute('reason', $override->getReason());
......
......@@ -31,11 +31,11 @@ class InMemoryOverridesRepository implements OverridesRepository
if ($begin && $end) {
$include = $include &&
($begin->lessThanOrEqualTo($override->getEnd()) && $end->greaterThanOrEqualTo($override->getStart()));
($begin->lessThanOrEqualTo($override->getEnd()) && $end->greaterThanOrEqualTo($override->getBegin()));
} else if ($begin) {
$include = $include && $begin->lessThanOrEqualTo($override->getEnd());
} else if ($end) {
$include = $include && $end->greaterThanOrEqualTo($override->getStart());
$include = $include && $end->greaterThanOrEqualTo($override->getBegin());
}
return $include;
......@@ -49,11 +49,11 @@ class InMemoryOverridesRepository implements OverridesRepository
{
$overrides = collect(array_filter($this->overrides, static function (Override $override) use ($doorId, $begin, $end) {
return $override->hasDoorIdOf($doorId) &&
($begin->lessThanOrEqualTo($override->getEnd()) && $end->greaterThanOrEqualTo($override->getStart()));
($begin->lessThanOrEqualTo($override->getEnd()) && $end->greaterThanOrEqualTo($override->getBegin()));
}));
return $overrides
->sortBy(fn (Override $item) => $item->getStart())
->sortBy(fn (Override $item) => $item->getBegin())
->values()
->all();
}
......@@ -69,7 +69,7 @@ class InMemoryOverridesRepository implements OverridesRepository
$override->getUserId(),
$override->getDoorId(),
$override->getType(),
$override->getStart(),
$override->getBegin(),
$override->getEnd(),
$override->getCreatedAt() ?? Carbon::now(),
$override->getUpdatedAt() ?? Carbon::now()
......
......@@ -21,7 +21,8 @@ interface OverridesRepository
/**
* Gets active override during date (if any). Closed modes take precedence over open modes.
* They are sorted by their start date
* They are sorted by their start date. <-- MANY THINGS DEPEND ON THIS.
* SUCH AS IN SCHEDULE EVENT OVERRIDE MERGING. THINK CAREFULLY BEFORE CHANGING THAT
*
* @param string $doorId
* @param \Carbon\Carbon $begin
......
......@@ -206,7 +206,7 @@ abstract class BasePresenter
'user_id' => $override->getUserId(),
'door_id' => $override->getDoorId(),
'type' => $override->getType(),
'start' => self::formatDateTime($override->getStart()),
'start' => self::formatDateTime($override->getBegin()),
'end' => self::formatDateTime($override->getEnd()),
'created_at' => self::formatDateTime($override->getCreatedAt()),
'updated_at' => self::formatDateTime($override->getUpdatedAt()),
......
......@@ -105,7 +105,7 @@ class OverrideCommand extends Command
$override->getUserId(),
$override->getDoorId(),
$override->getType(),
$override->getStart(),
$override->getBegin(),
Carbon::now()->subSecond()
));
} catch (EntityNotFoundException $e) {
......
<?php
namespace Source\UseCases\Door\ScheduleEvents;
use Carbon\Carbon;
use Source\Entities\Override;
use Source\Entities\ScheduleEvent;
trait ScheduleEventOverrideMerge
{
/**
* @param \Source\Entities\Override[] $closed
* @param \Source\Entities\Override[] $open
* @return \Source\Entities\Override[]
*/
protected function splitOpenOverrides(array $closed, array $open): array
{
/*
* Preprocessing:
* Clip all overrides around closed mode overrides, then process all overrides. once we have resolved all
* overlap.
* Closed modes have precedence over open overrides.
*/
$nonOverlappingOverrides = [];
// TODO: Rework this there is bug when what happens if your open needs to be split by multiple closeds
// So you end up with a segmented thing where you will have a conclomeration of stuff, we need to merge
// with previous processed segments
foreach ($open as $oo) {
foreach ($closed as $co) {
// No action needed if no overlap
if ($co->hasNoOverlapWith($oo)) {
continue;
}
/*
* Closed engulfs an open
* |----open----|
* |-----closed--------|
* In this case ignore the open override.
*/
if ($co->engulfs($oo)) {
continue;
}
/*
* Open is at the left side of a closed
* |---open---|
* |-----closed----|
* In this case cut the open override.
*
* Node: This case and the other edge case take care of when an open engulfs a closed
*/
if ($oo->getStart() < $co->getStart()) {
$nonOverlappingOverrides[] = new Override(
$oo->getId(),
$oo->getReason(),
$oo->getUserId(),
$oo->getDoorId(),
$oo->getType(),
$oo->getStart(),
$co->getStart()
);
}
/*
* Open is at the right side of a closed
* |---open---|
* |-----closed----|
* In this case cut the open override.
*
* Node: This case and the other edge case take care of when an open engulfs a closed
*/
if ($oo->getEnd() > $co->getEnd()) {
$nonOverlappingOverrides[] = new Override(
$oo->getId(),
$oo->getReason(),
$oo->getUserId(),
$oo->getDoorId(),
$oo->getType(),
$co->getEnd(),
$oo->getEnd()
);