'datetime', 'end' => 'datetime', 'load' => 'int', ]; /** * The attributes that should be cast to Enums. * * @var array */ protected $enumCasts = [ 'activity' => MacroScheduleActivity::class ]; /** * Indicates if the model should be timestamped. * * @var boolean */ public $timestamps = false; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'activity', 'load', 'comment', 'start', 'end', 'project_id' ]; /** * Perform any actions required after the model boots. * * @return void */ protected static function booted() { static::updating(function (self $activity) { if ( $activity->getRawOriginal('start') !== $activity->start->format('Y-m-d H:i:s') and $activity->start->clone()->setTime(0, 0)->lessThan(now()->setTime(0, 0)) ) { throw ValidationException::withMessages([ 'Il n\'est pas possible de modifier la date de début à une date passé' ]); } if ( $activity->getRawOriginal('end') !== $activity->end->format('Y-m-d H:i:s') and $activity->end->clone()->setTime(0, 0)->lessThan(now()->setTime(0, 0)) ) { throw ValidationException::withMessages([ 'Il n\'est pas possible de modifier la date de fin à une date passé' ]); } if (Carbon::parse($activity->getRawOriginal('start'))->lessThan(now()->setTime(0, 0))) { throw ValidationException::withMessages([ 'Il n\'est pas possible de modifier la date de début d\'une activité qui est déjà passée' ]); } if (Carbon::parse($activity->getRawOriginal('end'))->lessThan(now()->setTime(0, 0))) { throw ValidationException::withMessages([ 'Il n\'est pas possible de modifier la date de fin d\'une activité dejà terminée' ]); } }); } // * RELATIONSHIPS. /** * BelongsTo Project * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function project() { return $this->belongsTo(Project::class); } // * METHODS /** * Set the start date and specifiy wether it begins in * the morning or in the afternoon. * * @param Carbon $date * @param boolean $isMorning * @return void */ public function setStartDate(Carbon $date, bool $isMorning) { $date = $isMorning ? $date->setTime(0, 0) : $date->setTime(12, 00); $this->attributes['start'] = $date->format('Y-m-d H:i:s'); } /** * Set the end date and specifiy wether it begins in * the morning or in the afternoon. * * @param Carbon $date * @param boolean $isMorning * @return void */ public function setEndDate(Carbon $date, bool $isMorning) { $date = $isMorning ? $date->setTime(11, 59) : $date->setTime(23, 59); $this->attributes['end'] = $date->format('Y-m-d H:i:s'); } /** * Set the start date attribute. * * @param mixed $value * @return void * @throws \ErrorException This attribute cannot direct be assigned. * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass */ public function setStartDateAttribute($value) { throw new \ErrorException('The start date attribute should be assigned only using setDate method'); } /** * Set the end date attribute. * * @param mixed $value * @return void * @throws \ErrorException This attribute cannot direct be assigned. * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass */ public function setEndDateAttribute($value) { throw new \ErrorException('The end date attribute should be assigned only using setDate method'); } /** * Overlap activity using an other activity dates, * over the source activity si completely overlaped, * it will be removed * * @param self $activity * @return void * @throws \Throwable If updating (or removing) this activity failed. * @throws \ErrorException If no overlap action was done even the two activities overlaps. * @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber */ public function overlapUsingActivity(self $activity) { /** @var \Illuminate\Support\Carbon */ $selfStart = $this->start; /** @var \Illuminate\Support\Carbon */ $selfEnd = $this->end; /** @var \Illuminate\Support\Carbon */ $targetStart = $activity->start; /** @var \Illuminate\Support\Carbon */ $targetEnd = $activity->end; // ? Does target overlap self ? if (!$activity->doesPeriodCoverActivity($selfStart, $selfEnd)) { return; } // ? Does target completely overlap self ? if ($targetStart->lessThanOrEqualTo($selfStart) and $targetEnd->greaterThanOrEqualTo($selfEnd)) { $this->deleteOrFail(); return; } // ? Does target begins inside self ? if ($selfStart->toPeriod($selfEnd)->contains($targetStart)) { $newDate = $targetStart->clone()->subMinute(); if ($newDate->isWeekend()) { // * ignore weekend $newDate->previous(Carbon::FRIDAY); $newDate->addMinute(); } $this->setEndDate( $newDate, \boolval($newDate->hour < 12 and $newDate->hour >= 0) ); $this->saveOrFail(); return; } // ? Does target ends inside self ? if ($selfStart->toPeriod($selfEnd)->contains($targetEnd)) { $newDate = $targetEnd->clone()->addMinute(); if ($newDate->isWeekend()) { // * ignore weekend $newDate->next(Carbon::MONDAY); } $this->setStartDate( $newDate, \boolval($newDate->hour < 12 and $newDate->hour >= 0) ); $this->saveOrFail(); return; } throw new \ErrorException('No overlap action was done.'); } /** * Check a period covers this activity * * @param \Illuminate\Support\Carbon $start * @param \Illuminate\Support\Carbon $end * @return boolean */ public function doesPeriodCoverActivity(Carbon $start, Carbon $end): bool { if (!$this->start or !$this->end) { return false; } $overlap = $start->toPeriod($end)->overlaps($this->start, $this->end); return $overlap; } /** * Convert the model instance to an array. * * @return array */ public function toArray() { return array_merge(parent::toArray(), [ 'start' => $this->start->formatLocalized('%Y-%m-%dT%H:%M:%S'), 'end' => $this->end->formatLocalized('%Y-%m-%dT%H:%M:%S') ]); } }