From 3a8629d804a4109d0b7fcbfc2dbea228363927a1 Mon Sep 17 00:00:00 2001 From: Nick Korbel Date: Fri, 29 May 2020 09:38:10 -0400 Subject: [PATCH] Added ability to limit the total number of concurrent reservations for a schedule --- Domain/Access/ScheduleRepository.php | 862 +++++----- Pages/Admin/ManageSchedulesPage.php | 20 + Presenters/Admin/ManageSchedulesPresenter.php | 1418 +++++++++-------- Web/scripts/admin/schedule.js | 1374 ++++++++-------- build.xml | 2 +- lang/en_us.php | 4 + lib/Config/Configuration.php | 2 +- lib/Database/Commands/Commands.php | 5 +- lib/Database/Commands/ParameterNames.php | 1 + lib/Database/Commands/Queries.php | 3 +- lib/Server/FormKeys.php | 2 + readme.html | 5 + ...leTotalConcurrentReservationsRuleTests.php | 4 +- .../Schedule/ScheduleRepositoryTests.php | 4 +- tpl/Admin/Schedules/manage_schedules.tpl | 1253 ++++++++------- 15 files changed, 2575 insertions(+), 2384 deletions(-) diff --git a/Domain/Access/ScheduleRepository.php b/Domain/Access/ScheduleRepository.php index d0ba5705f..856ba69e4 100644 --- a/Domain/Access/ScheduleRepository.php +++ b/Domain/Access/ScheduleRepository.php @@ -25,446 +25,468 @@ interface IScheduleRepository { - /** - * @return array|Schedule[] - */ - public function GetAll(); - - /** - * @param int $scheduleId - * @return Schedule - */ - public function LoadById($scheduleId); - - /** - * @param string $publicId - * @return Schedule - */ - public function LoadByPublicId($publicId); - - /** - * @param Schedule $schedule - */ - public function Update(Schedule $schedule); - - /** - * @param Schedule $schedule - */ - public function Delete(Schedule $schedule); - - /** - * @param Schedule $schedule - * @param int $copyLayoutFromScheduleId - * @return int $insertedScheduleId - */ - public function Add(Schedule $schedule, $copyLayoutFromScheduleId); - - /** - * @param int $scheduleId - * @param ILayoutFactory $layoutFactory factory to use to create the schedule layout - * @return IScheduleLayout - */ - public function GetLayout($scheduleId, ILayoutFactory $layoutFactory); - - /** - * @param int $scheduleId - * @param ILayoutCreation $layout - */ - public function AddScheduleLayout($scheduleId, ILayoutCreation $layout); - - /** - * @param Date $periodDate - * @param int $scheduleId - * @return SchedulePeriod[] - */ - public function GetCustomLayoutPeriods(Date $periodDate, $scheduleId); - - /** - * @param Date $start - * @param Date $end - * @param int $scheduleId - * @return SchedulePeriod[] - */ - public function GetCustomLayoutPeriodsInRange(Date $start, Date $end, $scheduleId); - - /** - * @param int $pageNumber - * @param int $pageSize - * @param string|null $sortField - * @param string|null $sortDirection - * @param ISqlFilter $filter - * @return PageableData|Schedule[] - */ - public function GetList($pageNumber, $pageSize, $sortField = null, $sortDirection = null, $filter = null); - - /** - * @param int $scheduleId - * @param ScheduleLayout $layout - */ - public function UpdatePeakTimes($scheduleId, ScheduleLayout $layout); - - /** - * @param int $scheduleId - * @param Date $start - * @param Date $end - */ - public function AddCustomLayoutPeriod($scheduleId, Date $start, Date $end); - - /** - * @param int $scheduleId - * @param Date $start - */ - public function DeleteCustomLayoutPeriod($scheduleId, Date $start); - - /** - * @return array all public schedule ids in key value id=>publicid - */ - public function GetPublicScheduleIds(); + /** + * @return array|Schedule[] + */ + public function GetAll(); + + /** + * @param int $scheduleId + * @return Schedule + */ + public function LoadById($scheduleId); + + /** + * @param string $publicId + * @return Schedule + */ + public function LoadByPublicId($publicId); + + /** + * @param Schedule $schedule + */ + public function Update(Schedule $schedule); + + /** + * @param Schedule $schedule + */ + public function Delete(Schedule $schedule); + + /** + * @param Schedule $schedule + * @param int $copyLayoutFromScheduleId + * @return int $insertedScheduleId + */ + public function Add(Schedule $schedule, $copyLayoutFromScheduleId); + + /** + * @param int $scheduleId + * @param ILayoutFactory $layoutFactory factory to use to create the schedule layout + * @return IScheduleLayout + */ + public function GetLayout($scheduleId, ILayoutFactory $layoutFactory); + + /** + * @param int $scheduleId + * @param ILayoutCreation $layout + */ + public function AddScheduleLayout($scheduleId, ILayoutCreation $layout); + + /** + * @param Date $periodDate + * @param int $scheduleId + * @return SchedulePeriod[] + */ + public function GetCustomLayoutPeriods(Date $periodDate, $scheduleId); + + /** + * @param Date $start + * @param Date $end + * @param int $scheduleId + * @return SchedulePeriod[] + */ + public function GetCustomLayoutPeriodsInRange(Date $start, Date $end, $scheduleId); + + /** + * @param int $pageNumber + * @param int $pageSize + * @param string|null $sortField + * @param string|null $sortDirection + * @param ISqlFilter $filter + * @return PageableData|Schedule[] + */ + public function GetList($pageNumber, $pageSize, $sortField = null, $sortDirection = null, $filter = null); + + /** + * @param int $scheduleId + * @param ScheduleLayout $layout + */ + public function UpdatePeakTimes($scheduleId, ScheduleLayout $layout); + + /** + * @param int $scheduleId + * @param Date $start + * @param Date $end + */ + public function AddCustomLayoutPeriod($scheduleId, Date $start, Date $end); + + /** + * @param int $scheduleId + * @param Date $start + */ + public function DeleteCustomLayoutPeriod($scheduleId, Date $start); + + /** + * @return array all public schedule ids in key value id=>publicid + */ + public function GetPublicScheduleIds(); } interface ILayoutFactory { - /** - * @return IScheduleLayout - */ - public function CreateLayout(); - - /** - * @param IScheduleRepository $repository - * @param int $scheduleId - * @return IScheduleLayout - */ - public function CreateCustomLayout(IScheduleRepository $repository, $scheduleId); + /** + * @return IScheduleLayout + */ + public function CreateLayout(); + + /** + * @param IScheduleRepository $repository + * @param int $scheduleId + * @return IScheduleLayout + */ + public function CreateCustomLayout(IScheduleRepository $repository, $scheduleId); } class ScheduleLayoutFactory implements ILayoutFactory { - private $_targetTimezone; - - /** - * @param string $targetTimezone target timezone of layout - */ - public function __construct($targetTimezone = null) - { - $this->_targetTimezone = $targetTimezone; - } - - public function CreateLayout() - { - return new ScheduleLayout($this->_targetTimezone); - } - - public function CreateCustomLayout(IScheduleRepository $repository, $scheduleId) - { - return new CustomScheduleLayout($this->_targetTimezone, $scheduleId, $repository); - } + private $_targetTimezone; + + /** + * @param string $targetTimezone target timezone of layout + */ + public function __construct($targetTimezone = null) + { + $this->_targetTimezone = $targetTimezone; + } + + public function CreateLayout() + { + return new ScheduleLayout($this->_targetTimezone); + } + + public function CreateCustomLayout(IScheduleRepository $repository, $scheduleId) + { + return new CustomScheduleLayout($this->_targetTimezone, $scheduleId, $repository); + } } class ReservationLayoutFactory implements ILayoutFactory { - private $_targetTimezone; - - /** - * @param string $targetTimezone target timezone of layout - */ - public function __construct($targetTimezone) - { - $this->_targetTimezone = $targetTimezone; - } - - public function CreateLayout() - { - return new ReservationLayout($this->_targetTimezone); - } - - public function CreateCustomLayout(IScheduleRepository $repository, $scheduleId) - { - return new CustomScheduleLayout($this->_targetTimezone, $scheduleId, $repository); - } + private $_targetTimezone; + + /** + * @param string $targetTimezone target timezone of layout + */ + public function __construct($targetTimezone) + { + $this->_targetTimezone = $targetTimezone; + } + + public function CreateLayout() + { + return new ReservationLayout($this->_targetTimezone); + } + + public function CreateCustomLayout(IScheduleRepository $repository, $scheduleId) + { + return new CustomScheduleLayout($this->_targetTimezone, $scheduleId, $repository); + } } class ScheduleRepository implements IScheduleRepository { - /** - * @var DomainCache - */ - private $_cache; - - public function __construct() - { - $this->_cache = new DomainCache(); - } - - public function GetAll() - { - $schedules = array(); - - $reader = ServiceLocator::GetDatabase()->Query(new GetAllSchedulesCommand()); - - while ($row = $reader->GetRow()) { - $schedules[] = Schedule::FromRow($row); - } - - $reader->Free(); - - return $schedules; - } - - public function LoadById($scheduleId) - { - if (!$this->_cache->Exists($scheduleId)) { - $schedule = null; - - $reader = ServiceLocator::GetDatabase()->Query(new GetScheduleByIdCommand($scheduleId)); - - if ($row = $reader->GetRow()) { - $schedule = Schedule::FromRow($row); - } - - $reader->Free(); - - $this->_cache->Add($scheduleId, $schedule); - return $schedule; - } - - return $this->_cache->Get($scheduleId); - } - - public function LoadByPublicId($publicId) - { - $schedule = Schedule::Null(); - - $reader = ServiceLocator::GetDatabase()->Query(new GetScheduleByPublicIdCommand($publicId)); - - if ($row = $reader->GetRow()) { - $schedule = Schedule::FromRow($row); - } - - $reader->Free(); - - return $schedule; - } - - public function Update(Schedule $schedule) - { - ServiceLocator::GetDatabase()->Execute(new UpdateScheduleCommand( - $schedule->GetId(), - $schedule->GetName(), - $schedule->GetIsDefault(), - $schedule->GetWeekdayStart(), - $schedule->GetDaysVisible(), - $schedule->GetIsCalendarSubscriptionAllowed(), - $schedule->GetPublicId(), - $schedule->GetAdminGroupId(), - $schedule->GetAvailabilityBegin(), - $schedule->GetAvailabilityEnd(), - $schedule->GetAllowConcurrentReservations(), - $schedule->GetDefaultStyle())); - - if ($schedule->GetIsDefault()) { - ServiceLocator::GetDatabase()->Execute(new SetDefaultScheduleCommand($schedule->GetId())); - } - } - - public function Add(Schedule $schedule, $copyLayoutFromScheduleId) - { - $source = $this->LoadById($copyLayoutFromScheduleId); - - $db = ServiceLocator::GetDatabase(); - - $scheduleId = $db->ExecuteInsert(new AddScheduleCommand( - $schedule->GetName(), - $schedule->GetIsDefault(), - $schedule->GetWeekdayStart(), - $schedule->GetDaysVisible(), - $source->GetLayoutId(), - $schedule->GetAdminGroupId() - )); - - if ($source->HasCustomLayout()) - { - $layout = $this->GetLayout($scheduleId, new ScheduleLayoutFactory($source->GetTimezone())); - $this->AddScheduleLayout($scheduleId, $layout); - } - - return $scheduleId; - } - - public function Delete(Schedule $schedule) - { - ServiceLocator::GetDatabase()->Execute(new DeleteScheduleCommand($schedule->GetId())); - } - - public function GetLayout($scheduleId, ILayoutFactory $layoutFactory) - { - $reader = ServiceLocator::GetDatabase()->Query(new GetLayoutCommand($scheduleId)); - - /** @var ScheduleLayout $layout */ - $layout = null; - - while ($row = $reader->GetRow()) { - - if ($layout == null) { - if ($row[ColumnNames::LAYOUT_TYPE] == 1) { - $layout = $layoutFactory->CreateCustomLayout($this, $scheduleId); - } - else { - $layout = $layoutFactory->CreateLayout(); - } - } - - $timezone = $row[ColumnNames::BLOCK_TIMEZONE]; - $start = Time::Parse($row[ColumnNames::BLOCK_START], $timezone); - $end = Time::Parse($row[ColumnNames::BLOCK_END], $timezone); - $label = $row[ColumnNames::BLOCK_LABEL]; - $periodType = $row[ColumnNames::BLOCK_CODE]; - $dayOfWeek = $row[ColumnNames::BLOCK_DAY_OF_WEEK]; - - if ($periodType == PeriodTypes::RESERVABLE) { - $layout->AppendPeriod($start, $end, $label, $dayOfWeek); - } - else { - $layout->AppendBlockedPeriod($start, $end, $label, $dayOfWeek); - } - } - $reader->Free(); - - $reader = ServiceLocator::GetDatabase()->Query(new GetPeakTimesCommand($scheduleId)); - if ($row = $reader->GetRow()) { - $layout->ChangePeakTimes(PeakTimes::FromRow($row)); - } + /** + * @var DomainCache + */ + private $_cache; + + public function __construct() + { + $this->_cache = new DomainCache(); + } + + public function GetAll() + { + $schedules = array(); + + $reader = ServiceLocator::GetDatabase()->Query(new GetAllSchedulesCommand()); + + while ($row = $reader->GetRow()) + { + $schedules[] = Schedule::FromRow($row); + } + + $reader->Free(); + + return $schedules; + } + + public function LoadById($scheduleId) + { + if (!$this->_cache->Exists($scheduleId)) + { + $schedule = null; + + $reader = ServiceLocator::GetDatabase()->Query(new GetScheduleByIdCommand($scheduleId)); + + if ($row = $reader->GetRow()) + { + $schedule = Schedule::FromRow($row); + } + + $reader->Free(); + + $this->_cache->Add($scheduleId, $schedule); + return $schedule; + } + + return $this->_cache->Get($scheduleId); + } + + public function LoadByPublicId($publicId) + { + $schedule = Schedule::Null(); + + $reader = ServiceLocator::GetDatabase()->Query(new GetScheduleByPublicIdCommand($publicId)); + + if ($row = $reader->GetRow()) + { + $schedule = Schedule::FromRow($row); + } $reader->Free(); - return $layout; - } - - public function GetCustomLayoutPeriods(Date $date, $scheduleId) - { - $command = new GetCustomLayoutCommand($date, $scheduleId); - $reader = ServiceLocator::GetDatabase()->Query($command); + return $schedule; + } + + public function Update(Schedule $schedule) + { + ServiceLocator::GetDatabase()->Execute(new UpdateScheduleCommand( + $schedule->GetId(), + $schedule->GetName(), + $schedule->GetIsDefault(), + $schedule->GetWeekdayStart(), + $schedule->GetDaysVisible(), + $schedule->GetIsCalendarSubscriptionAllowed(), + $schedule->GetPublicId(), + $schedule->GetAdminGroupId(), + $schedule->GetAvailabilityBegin(), + $schedule->GetAvailabilityEnd(), + $schedule->GetAllowConcurrentReservations(), + $schedule->GetDefaultStyle(), + $schedule->GetTotalConcurrentReservations())); + + if ($schedule->GetIsDefault()) + { + ServiceLocator::GetDatabase()->Execute(new SetDefaultScheduleCommand($schedule->GetId())); + } + } + + public function Add(Schedule $schedule, $copyLayoutFromScheduleId) + { + $source = $this->LoadById($copyLayoutFromScheduleId); + + $db = ServiceLocator::GetDatabase(); + + $scheduleId = $db->ExecuteInsert(new AddScheduleCommand( + $schedule->GetName(), + $schedule->GetIsDefault(), + $schedule->GetWeekdayStart(), + $schedule->GetDaysVisible(), + $source->GetLayoutId(), + $schedule->GetAdminGroupId() + )); + + if ($source->HasCustomLayout()) + { + $layout = $this->GetLayout($scheduleId, new ScheduleLayoutFactory($source->GetTimezone())); + $this->AddScheduleLayout($scheduleId, $layout); + } + + return $scheduleId; + } + + public function Delete(Schedule $schedule) + { + ServiceLocator::GetDatabase()->Execute(new DeleteScheduleCommand($schedule->GetId())); + } + + public function GetLayout($scheduleId, ILayoutFactory $layoutFactory) + { + $reader = ServiceLocator::GetDatabase()->Query(new GetLayoutCommand($scheduleId)); + + /** @var ScheduleLayout $layout */ + $layout = null; + + while ($row = $reader->GetRow()) + { + + if ($layout == null) + { + if ($row[ColumnNames::LAYOUT_TYPE] == 1) + { + $layout = $layoutFactory->CreateCustomLayout($this, $scheduleId); + } + else + { + $layout = $layoutFactory->CreateLayout(); + } + } + + $timezone = $row[ColumnNames::BLOCK_TIMEZONE]; + $start = Time::Parse($row[ColumnNames::BLOCK_START], $timezone); + $end = Time::Parse($row[ColumnNames::BLOCK_END], $timezone); + $label = $row[ColumnNames::BLOCK_LABEL]; + $periodType = $row[ColumnNames::BLOCK_CODE]; + $dayOfWeek = $row[ColumnNames::BLOCK_DAY_OF_WEEK]; + + if ($periodType == PeriodTypes::RESERVABLE) + { + $layout->AppendPeriod($start, $end, $label, $dayOfWeek); + } + else + { + $layout->AppendBlockedPeriod($start, $end, $label, $dayOfWeek); + } + } + $reader->Free(); - $periods = array(); + $reader = ServiceLocator::GetDatabase()->Query(new GetPeakTimesCommand($scheduleId)); + if ($row = $reader->GetRow()) + { + $layout->ChangePeakTimes(PeakTimes::FromRow($row)); + } - while ($row = $reader->GetRow()) { - $timezone = $row[ColumnNames::BLOCK_TIMEZONE]; - $start = Date::FromDatabase($row[ColumnNames::BLOCK_START])->ToTimezone($timezone); - $end = Date::FromDatabase($row[ColumnNames::BLOCK_END])->ToTimezone($timezone); - - $periods[] = new SchedulePeriod($start, $end); - } + $reader->Free(); + + return $layout; + } + + public function GetCustomLayoutPeriods(Date $date, $scheduleId) + { + $command = new GetCustomLayoutCommand($date, $scheduleId); + $reader = ServiceLocator::GetDatabase()->Query($command); + + $periods = array(); + + while ($row = $reader->GetRow()) + { + $timezone = $row[ColumnNames::BLOCK_TIMEZONE]; + $start = Date::FromDatabase($row[ColumnNames::BLOCK_START])->ToTimezone($timezone); + $end = Date::FromDatabase($row[ColumnNames::BLOCK_END])->ToTimezone($timezone); + + $periods[] = new SchedulePeriod($start, $end); + } + + $reader->Free(); + + return $periods; + } + + public function GetCustomLayoutPeriodsInRange(Date $start, Date $end, $scheduleId) + { + $command = new GetCustomLayoutRangeCommand($start, $end, $scheduleId); + $reader = ServiceLocator::GetDatabase()->Query($command); + + $periods = array(); + + while ($row = $reader->GetRow()) + { + $timezone = $row[ColumnNames::BLOCK_TIMEZONE]; + $start = Date::FromDatabase($row[ColumnNames::BLOCK_START])->ToTimezone($timezone); + $end = Date::FromDatabase($row[ColumnNames::BLOCK_END])->ToTimezone($timezone); + + $periods[] = new SchedulePeriod($start, $end); + } + + $reader->Free(); + + return $periods; + } + + public function AddScheduleLayout($scheduleId, ILayoutCreation $layout) + { + $db = ServiceLocator::GetDatabase(); + $timezone = $layout->Timezone(); + + $layoutType = $layout->GetType(); + $addLayoutCommand = new AddLayoutCommand($timezone, $layoutType); + $layoutId = $db->ExecuteInsert($addLayoutCommand); + + if ($layoutType == ScheduleLayout::Standard) + { + $days = array(null); + if ($layout->UsesDailyLayouts()) + { + $days = DayOfWeek::Days(); + } + + foreach ($days as $day) + { + $slots = $layout->GetSlots($day); + + /* @var $slot LayoutPeriod */ + foreach ($slots as $slot) + { + $db->Execute(new AddLayoutTimeCommand($layoutId, $slot->Start, $slot->End, $slot->PeriodType, $slot->Label, $day)); + } + } + } + + $db->Execute(new UpdateScheduleLayoutCommand($scheduleId, $layoutId)); + + $db->Execute(new DeleteOrphanLayoutsCommand()); + } + + /** + * @param int $pageNumber + * @param int $pageSize + * @param string|null $sortField + * @param string|null $sortDirection + * @param ISqlFilter $filter + * @return PageableData|Schedule[] + */ + public function GetList($pageNumber, $pageSize, $sortField = null, $sortDirection = null, $filter = null) + { + $command = new GetAllSchedulesCommand(); + + if ($filter != null) + { + $command = new FilterCommand($command, $filter); + } + + $builder = array('Schedule', 'FromRow'); + return PageableDataStore::GetList($command, $builder, $pageNumber, $pageSize); + } + + public function UpdatePeakTimes($scheduleId, ScheduleLayout $layout) + { + ServiceLocator::GetDatabase()->Execute(new DeletePeakTimesCommand($scheduleId)); + + if ($layout->HasPeakTimesDefined()) + { + $peakTimes = $layout->GetPeakTimes(); + ServiceLocator::GetDatabase()->Execute(new AddPeakTimesCommand($scheduleId, + $peakTimes->IsAllDay(), $peakTimes->GetBeginTime(), $peakTimes->GetEndTime(), + $peakTimes->IsEveryDay(), implode(',', $peakTimes->GetWeekdays()), + $peakTimes->IsAllYear(), $peakTimes->GetBeginDay(), $peakTimes->GetBeginMonth(), + $peakTimes->GetEndDay(), $peakTimes->GetEndMonth())); + } + + } + + public function AddCustomLayoutPeriod($scheduleId, Date $start, Date $end) + { + ServiceLocator::GetDatabase()->Execute(new AddCustomLayoutPeriodCommand($scheduleId, $start, $end)); + } + + public function DeleteCustomLayoutPeriod($scheduleId, Date $start) + { + ServiceLocator::GetDatabase()->Execute(new DeleteCustomLayoutPeriodCommand($scheduleId, $start)); + } + + public function GetPublicScheduleIds() + { + $ids = array(); + $command = new GetSchedulesPublicCommand(); + $reader = ServiceLocator::GetDatabase()->Query($command); + while ($row = $reader->GetRow()) + { + $ids[$row[ColumnNames::SCHEDULE_ID]] = $row[ColumnNames::PUBLIC_ID]; + } + + $reader->Free(); - $reader->Free(); - - return $periods; - } - - public function GetCustomLayoutPeriodsInRange(Date $start, Date $end, $scheduleId) - { - $command = new GetCustomLayoutRangeCommand($start, $end, $scheduleId); - $reader = ServiceLocator::GetDatabase()->Query($command); - - $periods = array(); - - while ($row = $reader->GetRow()) { - $timezone = $row[ColumnNames::BLOCK_TIMEZONE]; - $start = Date::FromDatabase($row[ColumnNames::BLOCK_START])->ToTimezone($timezone); - $end = Date::FromDatabase($row[ColumnNames::BLOCK_END])->ToTimezone($timezone); - - $periods[] = new SchedulePeriod($start, $end); - } - - $reader->Free(); - - return $periods; - } - - public function AddScheduleLayout($scheduleId, ILayoutCreation $layout) - { - $db = ServiceLocator::GetDatabase(); - $timezone = $layout->Timezone(); - - $layoutType = $layout->GetType(); - $addLayoutCommand = new AddLayoutCommand($timezone, $layoutType); - $layoutId = $db->ExecuteInsert($addLayoutCommand); - - if ($layoutType == ScheduleLayout::Standard) { - $days = array(null); - if ($layout->UsesDailyLayouts()) { - $days = DayOfWeek::Days(); - } - - foreach ($days as $day) { - $slots = $layout->GetSlots($day); - - /* @var $slot LayoutPeriod */ - foreach ($slots as $slot) { - $db->Execute(new AddLayoutTimeCommand($layoutId, $slot->Start, $slot->End, $slot->PeriodType, $slot->Label, $day)); - } - } - } - - $db->Execute(new UpdateScheduleLayoutCommand($scheduleId, $layoutId)); - - $db->Execute(new DeleteOrphanLayoutsCommand()); - } - - /** - * @param int $pageNumber - * @param int $pageSize - * @param string|null $sortField - * @param string|null $sortDirection - * @param ISqlFilter $filter - * @return PageableData|Schedule[] - */ - public function GetList($pageNumber, $pageSize, $sortField = null, $sortDirection = null, $filter = null) - { - $command = new GetAllSchedulesCommand(); - - if ($filter != null) { - $command = new FilterCommand($command, $filter); - } - - $builder = array('Schedule', 'FromRow'); - return PageableDataStore::GetList($command, $builder, $pageNumber, $pageSize); - } - - public function UpdatePeakTimes($scheduleId, ScheduleLayout $layout) - { - ServiceLocator::GetDatabase()->Execute(new DeletePeakTimesCommand($scheduleId)); - - if ($layout->HasPeakTimesDefined()) { - $peakTimes = $layout->GetPeakTimes(); - ServiceLocator::GetDatabase()->Execute(new AddPeakTimesCommand($scheduleId, - $peakTimes->IsAllDay(), $peakTimes->GetBeginTime(), $peakTimes->GetEndTime(), - $peakTimes->IsEveryDay(), implode(',', $peakTimes->GetWeekdays()), - $peakTimes->IsAllYear(), $peakTimes->GetBeginDay(), $peakTimes->GetBeginMonth(), - $peakTimes->GetEndDay(), $peakTimes->GetEndMonth())); - } - - } - - public function AddCustomLayoutPeriod($scheduleId, Date $start, Date $end) - { - ServiceLocator::GetDatabase()->Execute(new AddCustomLayoutPeriodCommand($scheduleId, $start, $end)); - } - - public function DeleteCustomLayoutPeriod($scheduleId, Date $start) - { - ServiceLocator::GetDatabase()->Execute(new DeleteCustomLayoutPeriodCommand($scheduleId, $start)); - } - - public function GetPublicScheduleIds() - { - $ids = array(); - $command = new GetSchedulesPublicCommand(); - $reader = ServiceLocator::GetDatabase()->Query($command); - while ($row = $reader->GetRow()) { - $ids[$row[ColumnNames::SCHEDULE_ID]] = $row[ColumnNames::PUBLIC_ID]; - } - - $reader->Free(); - - return $ids; - } + return $ids; + } } diff --git a/Pages/Admin/ManageSchedulesPage.php b/Pages/Admin/ManageSchedulesPage.php index df8b7c2e2..d7bc54560 100644 --- a/Pages/Admin/ManageSchedulesPage.php +++ b/Pages/Admin/ManageSchedulesPage.php @@ -89,6 +89,16 @@ function GetTargetScheduleId(); * @return string */ function GetValue(); + + /** + * @return int + */ + function GetMaximumConcurrentReservations(); + + /** + * @return bool + */ + function GetIsUnlimitedConcurrentReservations(); } interface IManageSchedulesPage extends IUpdateSchedulePage, IActionPage, IPageable @@ -619,4 +629,14 @@ public function GetDefaultStyle() { return $this->GetForm(FormKeys::SCHEDULE_DEFAULT_STYLE); } + + public function GetMaximumConcurrentReservations() + { + return intval($this->GetForm(FormKeys::MAXIMUM_CONCURRENT_RESERVATIONS)); + } + + public function GetIsUnlimitedConcurrentReservations() + { + return $this->GetCheckbox(FormKeys::MAXIMUM_CONCURRENT_UNLIMITED); + } } \ No newline at end of file diff --git a/Presenters/Admin/ManageSchedulesPresenter.php b/Presenters/Admin/ManageSchedulesPresenter.php index cf7173cd3..9e63d4f12 100644 --- a/Presenters/Admin/ManageSchedulesPresenter.php +++ b/Presenters/Admin/ManageSchedulesPresenter.php @@ -26,710 +26,754 @@ class ManageSchedules { - const ActionAdd = 'add'; - const ActionChangeLayout = 'changeLayout'; - const ActionChangeStartDay = 'startDay'; - const ActionChangeDaysVisible = 'daysVisible'; - const ActionMakeDefault = 'makeDefault'; - const ActionRename = 'rename'; - const ActionDelete = 'delete'; - const ActionEnableSubscription = 'enableSubscription'; - const ActionDisableSubscription = 'disableSubscription'; - const ChangeAdminGroup = 'changeAdminGroup'; - const ActionChangePeakTimes = 'ActionChangePeakTimes'; - const ActionChangeAvailability = 'ActionChangeAvailability'; - const ActionToggleConcurrentReservations = 'ToggleConcurrentReservations'; - const ActionSwitchLayoutType = 'ActionSwitchLayoutType'; - const ActionAddLayoutSlot = 'addLayoutSlot'; - const ActionUpdateLayoutSlot = 'updateLayoutSlot'; - const ActionDeleteLayoutSlot = 'deleteLayoutSlot'; - const ActionChangeDefaultStyle = 'changeDefaultStyle'; + const ActionAdd = 'add'; + const ActionChangeLayout = 'changeLayout'; + const ActionChangeStartDay = 'startDay'; + const ActionChangeDaysVisible = 'daysVisible'; + const ActionMakeDefault = 'makeDefault'; + const ActionRename = 'rename'; + const ActionDelete = 'delete'; + const ActionEnableSubscription = 'enableSubscription'; + const ActionDisableSubscription = 'disableSubscription'; + const ChangeAdminGroup = 'changeAdminGroup'; + const ActionChangePeakTimes = 'ActionChangePeakTimes'; + const ActionChangeAvailability = 'ActionChangeAvailability'; + const ActionToggleConcurrentReservations = 'ToggleConcurrentReservations'; + const ActionSwitchLayoutType = 'ActionSwitchLayoutType'; + const ActionAddLayoutSlot = 'addLayoutSlot'; + const ActionUpdateLayoutSlot = 'updateLayoutSlot'; + const ActionDeleteLayoutSlot = 'deleteLayoutSlot'; + const ActionChangeDefaultStyle = 'changeDefaultStyle'; + const ActionChangeMaximumConcurrent = 'changeMaximumConcurrent'; } class ManageScheduleService { - /** - * @var IScheduleRepository - */ - private $scheduleRepository; - - /** - * @var IResourceRepository - */ - private $resourceRepository; - - /** - * @var array|Schedule[] - */ - private $_all; - - public function __construct(IScheduleRepository $scheduleRepository, IResourceRepository $resourceRepository) - { - $this->scheduleRepository = $scheduleRepository; - $this->resourceRepository = $resourceRepository; - } - - /** - * @return array|Schedule[] - */ - public function GetAll() - { - if (is_null($this->_all)) { - $this->_all = $this->scheduleRepository->GetAll(); - } - return $this->_all; - } - - /** - * @return array|Schedule[] - */ - public function GetSourceSchedules() - { - return $this->GetAll(); - } - - /** - * @param Schedule $schedule - * @return IScheduleLayout - */ - public function GetLayout($schedule) - { - return $this->scheduleRepository->GetLayout($schedule->GetId(), new ScheduleLayoutFactory($schedule->GetTimezone())); - } - - /** - * @param string $name - * @param int $daysVisible - * @param int $startDay - * @param int $copyLayoutFromScheduleId - */ - public function Add($name, $daysVisible, $startDay, $copyLayoutFromScheduleId) - { - $schedule = new Schedule(null, $name, false, $startDay, $daysVisible); - $this->scheduleRepository->Add($schedule, $copyLayoutFromScheduleId); - } - - /** - * @param int $scheduleId - * @param string $name - */ - public function Rename($scheduleId, $name) - { - $schedule = $this->scheduleRepository->LoadById($scheduleId); - $schedule->SetName($name); - $this->scheduleRepository->Update($schedule); - } - - /** - * @param int $scheduleId - * @param int|null $startDay - * @param int|null $daysVisible - */ - public function ChangeSettings($scheduleId, $startDay, $daysVisible) - { - Log::Debug('Changing scheduleId %s, WeekdayStart: %s, DaysVisible %s', $scheduleId, $startDay, $daysVisible); - $schedule = $this->scheduleRepository->LoadById($scheduleId); - if (!is_null($startDay)) { - $schedule->SetWeekdayStart($startDay); - } - - if (!is_null($daysVisible)) { - $schedule->SetDaysVisible($daysVisible); - } - - $this->scheduleRepository->Update($schedule); - } - - /** - * @param int $scheduleId - * @param string $timezone - * @param string $reservableSlots - * @param string $blockedSlots - */ - public function ChangeLayout($scheduleId, $timezone, $reservableSlots, $blockedSlots) - { - $layout = ScheduleLayout::Parse($timezone, $reservableSlots, $blockedSlots); - $this->scheduleRepository->AddScheduleLayout($scheduleId, $layout); - } - - /** - * @param int $scheduleId - * @param string $timezone - * @param string[] $reservableSlots - * @param string[] $blockedSlots - */ - public function ChangeDailyLayout($scheduleId, $timezone, $reservableSlots, $blockedSlots) - { - $layout = ScheduleLayout::ParseDaily($timezone, $reservableSlots, $blockedSlots); - $this->scheduleRepository->AddScheduleLayout($scheduleId, $layout); - } - - /** - * @param int $scheduleId - */ - public function MakeDefault($scheduleId) - { - $schedule = $this->scheduleRepository->LoadById($scheduleId); - $schedule->SetIsDefault(true); - - $this->scheduleRepository->Update($schedule); - } - - /** - * @param int $scheduleId - * @param int $moveResourcesToThisScheduleId - */ - public function Delete($scheduleId, $moveResourcesToThisScheduleId) - { - $resources = $this->resourceRepository->GetScheduleResources($scheduleId); - foreach ($resources as $resource) { - $resource->SetScheduleId($moveResourcesToThisScheduleId); - $this->resourceRepository->Update($resource); - } - - $schedule = $this->scheduleRepository->LoadById($scheduleId); - $this->scheduleRepository->Delete($schedule); - } - - /** - * @param int $scheduleId - */ - public function EnableSubscription($scheduleId) - { - $schedule = $this->scheduleRepository->LoadById($scheduleId); - $schedule->EnableSubscription(); - Configuration::Instance()->EnableSubscription(); - $this->scheduleRepository->Update($schedule); - } - - /** - * @param int $scheduleId - */ - public function DisableSubscription($scheduleId) - { - $schedule = $this->scheduleRepository->LoadById($scheduleId); - $schedule->DisableSubscription(); - $this->scheduleRepository->Update($schedule); - } - - /** - * @param int $scheduleId - * @param int $adminGroupId - */ - public function ChangeAdminGroup($scheduleId, $adminGroupId) - { - $schedule = $this->scheduleRepository->LoadById($scheduleId); - $schedule->SetAdminGroupId($adminGroupId); - $this->scheduleRepository->Update($schedule); - } - - /** - * @param int $pageNumber - * @param int $pageSize - * @return PageableData|BookableResource[] - */ - public function GetList($pageNumber, $pageSize) - { - return $this->scheduleRepository->GetList($pageNumber, $pageSize); - } - - /** - * @param int $scheduleId - * @param PeakTimes $peakTimes - * @return IScheduleLayout - */ - public function ChangePeakTimes($scheduleId, PeakTimes $peakTimes) - { - $layout = $this->scheduleRepository->GetLayout($scheduleId, new ScheduleLayoutFactory(null)); - $layout->ChangePeakTimes($peakTimes); - $this->scheduleRepository->UpdatePeakTimes($scheduleId, $layout); - - return $layout; - } - - public function DeletePeakTimes($scheduleId) - { - $layout = $this->scheduleRepository->GetLayout($scheduleId, new ScheduleLayoutFactory(null)); - $layout->RemovePeakTimes(); - $this->scheduleRepository->UpdatePeakTimes($scheduleId, $layout); - - return $layout; - } - - /** - * @return BookableResource[] resources indexed by scheduleId - */ - public function GetResources() - { - $resources = array(); - - $all = $this->resourceRepository->GetResourceList(); - /** @var BookableResource $resource */ - foreach ($all as $resource) { - $resources[$resource->GetScheduleId()][] = $resource; - } - - return $resources; - } - - /** - * @param int $scheduleId - * @return Schedule - */ - public function DeleteAvailability($scheduleId) - { - $schedule = $this->scheduleRepository->LoadById($scheduleId); - $schedule->SetAvailableAllYear(); - $this->scheduleRepository->Update($schedule); - - return $schedule; - } - - /** - * @param int $scheduleId - * @param Date $start - * @param Date $end - * @return Schedule - */ - public function UpdateAvailability($scheduleId, $start, $end) - { - Log::Debug('Updating schedule availability. schedule %s, start %s, end %s', $scheduleId, $start, $end); - $schedule = $this->scheduleRepository->LoadById($scheduleId); - $schedule->SetAvailability($start, $end); - $this->scheduleRepository->Update($schedule); - - return $schedule; - } - - public function ToggleConcurrentReservations($scheduleId) - { - Log::Debug('Toggling concurrent reservations. schedule %s', $scheduleId); - - $schedule = $this->scheduleRepository->LoadById($scheduleId); - $allow = $schedule->GetAllowConcurrentReservations(); - - $schedule->SetAllowConcurrentReservations(!$allow); - - $this->scheduleRepository->Update($schedule); - } - - /** - * @param int $scheduleId - * @param int $layoutType - */ - public function SwitchLayoutType($scheduleId, $layoutType) - { - $schedule = $this->scheduleRepository->LoadById($scheduleId); - $targetTimezone = $schedule->GetTimezone(); - if ($layoutType == ScheduleLayout::Standard) { - $layout = new ScheduleLayout($targetTimezone); - $layout->AppendPeriod(Time::Parse('00:00', $targetTimezone), Time::Parse('00:00', $targetTimezone)); - $this->scheduleRepository->AddScheduleLayout($scheduleId, $layout); - } - else { - $this->scheduleRepository->AddScheduleLayout($scheduleId, new CustomScheduleLayout($targetTimezone, $scheduleId, $this->scheduleRepository)); - } - } - - public function GetCustomLayoutPeriods($start, $end, $scheduleId) - { - return $this->scheduleRepository->GetCustomLayoutPeriodsInRange($start, $end, $scheduleId); - } - - /** - * @param int $scheduleId - * @param Date $start - * @param Date $end - */ - public function AddCustomLayoutPeriod($scheduleId, $start, $end) - { - $overlappingPeriod = $this->CustomLayoutPeriodOverlaps($scheduleId, $start, $end); - if ($overlappingPeriod == null) { - $this->scheduleRepository->AddCustomLayoutPeriod($scheduleId, $start, $end); - } - } - - /** - * @param int $scheduleId - * @param Date $start - * @param Date $end - * @param Date $originalStart - */ - public function UpdateCustomLayoutPeriod($scheduleId, $start, $end, $originalStart) - { + /** + * @var IScheduleRepository + */ + private $scheduleRepository; + + /** + * @var IResourceRepository + */ + private $resourceRepository; + + /** + * @var array|Schedule[] + */ + private $_all; + + public function __construct(IScheduleRepository $scheduleRepository, IResourceRepository $resourceRepository) + { + $this->scheduleRepository = $scheduleRepository; + $this->resourceRepository = $resourceRepository; + } + + /** + * @return array|Schedule[] + */ + public function GetAll() + { + if (is_null($this->_all)) + { + $this->_all = $this->scheduleRepository->GetAll(); + } + return $this->_all; + } + + /** + * @return array|Schedule[] + */ + public function GetSourceSchedules() + { + return $this->GetAll(); + } + + /** + * @param Schedule $schedule + * @return IScheduleLayout + */ + public function GetLayout($schedule) + { + return $this->scheduleRepository->GetLayout($schedule->GetId(), new ScheduleLayoutFactory($schedule->GetTimezone())); + } + + /** + * @param string $name + * @param int $daysVisible + * @param int $startDay + * @param int $copyLayoutFromScheduleId + */ + public function Add($name, $daysVisible, $startDay, $copyLayoutFromScheduleId) + { + $schedule = new Schedule(null, $name, false, $startDay, $daysVisible); + $this->scheduleRepository->Add($schedule, $copyLayoutFromScheduleId); + } + + /** + * @param int $scheduleId + * @param string $name + */ + public function Rename($scheduleId, $name) + { + $schedule = $this->scheduleRepository->LoadById($scheduleId); + $schedule->SetName($name); + $this->scheduleRepository->Update($schedule); + } + + /** + * @param int $scheduleId + * @param int|null $startDay + * @param int|null $daysVisible + */ + public function ChangeSettings($scheduleId, $startDay, $daysVisible) + { + Log::Debug('Changing scheduleId %s, WeekdayStart: %s, DaysVisible %s', $scheduleId, $startDay, $daysVisible); + $schedule = $this->scheduleRepository->LoadById($scheduleId); + if (!is_null($startDay)) + { + $schedule->SetWeekdayStart($startDay); + } + + if (!is_null($daysVisible)) + { + $schedule->SetDaysVisible($daysVisible); + } + + $this->scheduleRepository->Update($schedule); + } + + /** + * @param int $scheduleId + * @param string $timezone + * @param string $reservableSlots + * @param string $blockedSlots + */ + public function ChangeLayout($scheduleId, $timezone, $reservableSlots, $blockedSlots) + { + $layout = ScheduleLayout::Parse($timezone, $reservableSlots, $blockedSlots); + $this->scheduleRepository->AddScheduleLayout($scheduleId, $layout); + } + + /** + * @param int $scheduleId + * @param string $timezone + * @param string[] $reservableSlots + * @param string[] $blockedSlots + */ + public function ChangeDailyLayout($scheduleId, $timezone, $reservableSlots, $blockedSlots) + { + $layout = ScheduleLayout::ParseDaily($timezone, $reservableSlots, $blockedSlots); + $this->scheduleRepository->AddScheduleLayout($scheduleId, $layout); + } + + /** + * @param int $scheduleId + */ + public function MakeDefault($scheduleId) + { + $schedule = $this->scheduleRepository->LoadById($scheduleId); + $schedule->SetIsDefault(true); + + $this->scheduleRepository->Update($schedule); + } + + /** + * @param int $scheduleId + * @param int $moveResourcesToThisScheduleId + */ + public function Delete($scheduleId, $moveResourcesToThisScheduleId) + { + $resources = $this->resourceRepository->GetScheduleResources($scheduleId); + foreach ($resources as $resource) + { + $resource->SetScheduleId($moveResourcesToThisScheduleId); + $this->resourceRepository->Update($resource); + } + + $schedule = $this->scheduleRepository->LoadById($scheduleId); + $this->scheduleRepository->Delete($schedule); + } + + /** + * @param int $scheduleId + */ + public function EnableSubscription($scheduleId) + { + $schedule = $this->scheduleRepository->LoadById($scheduleId); + $schedule->EnableSubscription(); + Configuration::Instance()->EnableSubscription(); + $this->scheduleRepository->Update($schedule); + } + + /** + * @param int $scheduleId + */ + public function DisableSubscription($scheduleId) + { + $schedule = $this->scheduleRepository->LoadById($scheduleId); + $schedule->DisableSubscription(); + $this->scheduleRepository->Update($schedule); + } + + /** + * @param int $scheduleId + * @param int $adminGroupId + */ + public function ChangeAdminGroup($scheduleId, $adminGroupId) + { + $schedule = $this->scheduleRepository->LoadById($scheduleId); + $schedule->SetAdminGroupId($adminGroupId); + $this->scheduleRepository->Update($schedule); + } + + /** + * @param int $pageNumber + * @param int $pageSize + * @return PageableData|BookableResource[] + */ + public function GetList($pageNumber, $pageSize) + { + return $this->scheduleRepository->GetList($pageNumber, $pageSize); + } + + /** + * @param int $scheduleId + * @param PeakTimes $peakTimes + * @return IScheduleLayout + */ + public function ChangePeakTimes($scheduleId, PeakTimes $peakTimes) + { + $layout = $this->scheduleRepository->GetLayout($scheduleId, new ScheduleLayoutFactory(null)); + $layout->ChangePeakTimes($peakTimes); + $this->scheduleRepository->UpdatePeakTimes($scheduleId, $layout); + + return $layout; + } + + public function DeletePeakTimes($scheduleId) + { + $layout = $this->scheduleRepository->GetLayout($scheduleId, new ScheduleLayoutFactory(null)); + $layout->RemovePeakTimes(); + $this->scheduleRepository->UpdatePeakTimes($scheduleId, $layout); + + return $layout; + } + + /** + * @return BookableResource[] resources indexed by scheduleId + */ + public function GetResources() + { + $resources = array(); + + $all = $this->resourceRepository->GetResourceList(); + /** @var BookableResource $resource */ + foreach ($all as $resource) + { + $resources[$resource->GetScheduleId()][] = $resource; + } + + return $resources; + } + + /** + * @param int $scheduleId + * @return Schedule + */ + public function DeleteAvailability($scheduleId) + { + $schedule = $this->scheduleRepository->LoadById($scheduleId); + $schedule->SetAvailableAllYear(); + $this->scheduleRepository->Update($schedule); + + return $schedule; + } + + /** + * @param int $scheduleId + * @param Date $start + * @param Date $end + * @return Schedule + */ + public function UpdateAvailability($scheduleId, $start, $end) + { + Log::Debug('Updating schedule availability. schedule %s, start %s, end %s', $scheduleId, $start, $end); + $schedule = $this->scheduleRepository->LoadById($scheduleId); + $schedule->SetAvailability($start, $end); + $this->scheduleRepository->Update($schedule); + + return $schedule; + } + + public function ToggleConcurrentReservations($scheduleId) + { + Log::Debug('Toggling concurrent reservations. schedule %s', $scheduleId); + + $schedule = $this->scheduleRepository->LoadById($scheduleId); + $allow = $schedule->GetAllowConcurrentReservations(); + + $schedule->SetAllowConcurrentReservations(!$allow); + + $this->scheduleRepository->Update($schedule); + } + + /** + * @param int $scheduleId + * @param int $layoutType + */ + public function SwitchLayoutType($scheduleId, $layoutType) + { + $schedule = $this->scheduleRepository->LoadById($scheduleId); + $targetTimezone = $schedule->GetTimezone(); + if ($layoutType == ScheduleLayout::Standard) + { + $layout = new ScheduleLayout($targetTimezone); + $layout->AppendPeriod(Time::Parse('00:00', $targetTimezone), Time::Parse('00:00', $targetTimezone)); + $this->scheduleRepository->AddScheduleLayout($scheduleId, $layout); + } + else + { + $this->scheduleRepository->AddScheduleLayout($scheduleId, new CustomScheduleLayout($targetTimezone, $scheduleId, $this->scheduleRepository)); + } + } + + public function GetCustomLayoutPeriods($start, $end, $scheduleId) + { + return $this->scheduleRepository->GetCustomLayoutPeriodsInRange($start, $end, $scheduleId); + } + + /** + * @param int $scheduleId + * @param Date $start + * @param Date $end + */ + public function AddCustomLayoutPeriod($scheduleId, $start, $end) + { + $overlappingPeriod = $this->CustomLayoutPeriodOverlaps($scheduleId, $start, $end); + if ($overlappingPeriod == null) + { + $this->scheduleRepository->AddCustomLayoutPeriod($scheduleId, $start, $end); + } + } + + /** + * @param int $scheduleId + * @param Date $start + * @param Date $end + * @param Date $originalStart + */ + public function UpdateCustomLayoutPeriod($scheduleId, $start, $end, $originalStart) + { // $overlappingPeriod = $this->CustomLayoutPeriodOverlaps($scheduleId, $start, $end); // if ($overlappingPeriod != null) { // // if ($overlappingPeriod->BeginDate()->Equals($originalStart)) // { - $this->scheduleRepository->DeleteCustomLayoutPeriod($scheduleId, $originalStart); - $this->scheduleRepository->AddCustomLayoutPeriod($scheduleId, $start, $end); + $this->scheduleRepository->DeleteCustomLayoutPeriod($scheduleId, $originalStart); + $this->scheduleRepository->AddCustomLayoutPeriod($scheduleId, $start, $end); // } // } // else { // $this->scheduleRepository->AddCustomLayoutPeriod($scheduleId, $start, $end); // } - } - - /** - * @param int $scheduleId - * @param Date $start - * @param Date $end - */ - public function DeleteCustomLayoutPeriod($scheduleId, $start, $end) - { - $this->scheduleRepository->DeleteCustomLayoutPeriod($scheduleId, $start); - } - - /** - * @param int $scheduleId - * @param Date $start - * @param Date $end - * @return SchedulePeriod|null - */ - private function CustomLayoutPeriodOverlaps($scheduleId, Date $start, Date $end) - { - $overlaps = null; - $periods = $this->scheduleRepository->GetCustomLayoutPeriodsInRange($start, $end, $scheduleId); - foreach ($periods as $period) { - if ($start->LessThanOrEqual($period->BeginDate()) && $end->GreaterThan($period->BeginDate())) { - $overlaps = $period; - } - } - return $overlaps; - } - - public function ChangeDefaultStyle($scheduleId, $defaultStyle) - { - $schedule = $this->scheduleRepository->LoadById($scheduleId); - $schedule->SetDefaultStyle($defaultStyle); - $this->scheduleRepository->Update($schedule); - } + } + + /** + * @param int $scheduleId + * @param Date $start + * @param Date $end + */ + public function DeleteCustomLayoutPeriod($scheduleId, $start, $end) + { + $this->scheduleRepository->DeleteCustomLayoutPeriod($scheduleId, $start); + } + + /** + * @param int $scheduleId + * @param Date $start + * @param Date $end + * @return SchedulePeriod|null + */ + private function CustomLayoutPeriodOverlaps($scheduleId, Date $start, Date $end) + { + $overlaps = null; + $periods = $this->scheduleRepository->GetCustomLayoutPeriodsInRange($start, $end, $scheduleId); + foreach ($periods as $period) + { + if ($start->LessThanOrEqual($period->BeginDate()) && $end->GreaterThan($period->BeginDate())) + { + $overlaps = $period; + } + } + return $overlaps; + } + + public function ChangeDefaultStyle($scheduleId, $defaultStyle) + { + $schedule = $this->scheduleRepository->LoadById($scheduleId); + $schedule->SetDefaultStyle($defaultStyle); + $this->scheduleRepository->Update($schedule); + } + + public function ChangeConcurrentReservationMaximum($scheduleId, $max, $unlimited) + { + $schedule = $this->scheduleRepository->LoadById($scheduleId); + $schedule->SetTotalConcurrentReservations($unlimited ? 0 : intval($max)); + $this->scheduleRepository->Update($schedule); + } } class ManageSchedulesPresenter extends ActionPresenter { - /** - * @var IManageSchedulesPage - */ - private $page; - - /** - * @var ManageScheduleService - */ - private $manageSchedulesService; - - /** - * @var IGroupViewRepository - */ - private $groupViewRepository; - - public function __construct(IManageSchedulesPage $page, - ManageScheduleService $manageSchedulesService, - IGroupViewRepository $groupViewRepository) - { - parent::__construct($page); - $this->page = $page; - $this->manageSchedulesService = $manageSchedulesService; - $this->groupViewRepository = $groupViewRepository; - - $this->AddAction(ManageSchedules::ActionAdd, 'Add'); - $this->AddAction(ManageSchedules::ActionChangeLayout, 'ChangeLayout'); - $this->AddAction(ManageSchedules::ActionChangeStartDay, 'ChangeStartDay'); - $this->AddAction(ManageSchedules::ActionChangeDaysVisible, 'ChangeDaysVisible'); - $this->AddAction(ManageSchedules::ActionMakeDefault, 'MakeDefault'); - $this->AddAction(ManageSchedules::ActionRename, 'Rename'); - $this->AddAction(ManageSchedules::ActionDelete, 'Delete'); - $this->AddAction(ManageSchedules::ActionEnableSubscription, 'EnableSubscription'); - $this->AddAction(ManageSchedules::ActionDisableSubscription, 'DisableSubscription'); - $this->AddAction(ManageSchedules::ChangeAdminGroup, 'ChangeAdminGroup'); - $this->AddAction(ManageSchedules::ActionChangePeakTimes, 'ChangePeakTimes'); - $this->AddAction(ManageSchedules::ActionChangeAvailability, 'ChangeAvailability'); - $this->AddAction(ManageSchedules::ActionToggleConcurrentReservations, 'ToggleConcurrentReservations'); - $this->AddAction(ManageSchedules::ActionSwitchLayoutType, 'SwitchLayoutType'); - $this->AddAction(ManageSchedules::ActionAddLayoutSlot, 'AddLayoutSlot'); - $this->AddAction(ManageSchedules::ActionUpdateLayoutSlot, 'UpdateLayoutSlot'); - $this->AddAction(ManageSchedules::ActionDeleteLayoutSlot, 'DeleteLayoutSlot'); - $this->AddAction(ManageSchedules::ActionChangeDefaultStyle, 'ChangeDefaultStyle'); - } - - public function PageLoad() - { - $results = $this->manageSchedulesService->GetList($this->page->GetPageNumber(), $this->page->GetPageSize()); - $schedules = $results->Results(); - - $sourceSchedules = $this->manageSchedulesService->GetSourceSchedules(); - $resources = $this->manageSchedulesService->GetResources(); - - $layouts = array(); - /* @var $schedule Schedule */ - foreach ($schedules as $schedule) { - $layout = $this->manageSchedulesService->GetLayout($schedule); - $layouts[$schedule->GetId()] = $layout; - } - - $this->page->BindGroups($this->groupViewRepository->GetGroupsByRole(RoleLevel::SCHEDULE_ADMIN)); - - $this->page->BindSchedules($schedules, $layouts, $sourceSchedules); - $this->page->BindPageInfo($results->PageInfo()); - $this->page->BindResources($resources); - $this->PopulateTimezones(); - - } - - private function PopulateTimezones() - { - $timezoneValues = array(); - $timezoneOutput = array(); - - foreach ($GLOBALS['APP_TIMEZONES'] as $timezone) { - $timezoneValues[] = $timezone; - $timezoneOutput[] = $timezone; - } - - $this->page->SetTimezones($timezoneValues, $timezoneOutput); - } - - /** - * @internal should only be used for testing - */ - public function Add() - { - $copyLayoutFromScheduleId = $this->page->GetSourceScheduleId(); - $name = $this->page->GetScheduleName(); - $weekdayStart = $this->page->GetStartDay(); - $daysVisible = $this->page->GetDaysVisible(); - - Log::Debug('Adding schedule with name %s', $name); - - $this->manageSchedulesService->Add($name, $daysVisible, $weekdayStart, $copyLayoutFromScheduleId); - } - - /** - * @internal should only be used for testing - */ - public function Rename() - { - $this->manageSchedulesService->Rename($this->page->GetScheduleId(), $this->page->GetValue()); - } - - /** - * @internal should only be used for testing - */ - public function ChangeStartDay() - { - $this->manageSchedulesService->ChangeSettings($this->page->GetScheduleId(), $this->page->GetValue(), null); - } - - /** - * @internal should only be used for testing - */ - public function ChangeDaysVisible() - { - $this->manageSchedulesService->ChangeSettings($this->page->GetScheduleId(), null, $this->page->GetValue()); - } - - /** - * @internal should only be used for testing - */ - public function ChangeLayout() - { - $scheduleId = $this->page->GetScheduleId(); - $timezone = $this->page->GetLayoutTimezone(); - $usingSingleLayout = $this->page->GetUsingSingleLayout(); - - Log::Debug('Changing layout for scheduleId=%s. timezone=%s, usingSingleLayout=%s', $scheduleId, $timezone, - $usingSingleLayout); - if ($usingSingleLayout) { - $reservableSlots = $this->page->GetReservableSlots(); - $blockedSlots = $this->page->GetBlockedSlots(); - $this->manageSchedulesService->ChangeLayout($scheduleId, $timezone, $reservableSlots, $blockedSlots); - } - else { - $reservableSlots = $this->page->GetDailyReservableSlots(); - $blockedSlots = $this->page->GetDailyBlockedSlots(); - $this->manageSchedulesService->ChangeDailyLayout($scheduleId, $timezone, $reservableSlots, $blockedSlots); - } - } - - /** - * @internal should only be used for testing - */ - public function ChangeAdminGroup() - { - $this->manageSchedulesService->ChangeAdminGroup($this->page->GetScheduleId(), $this->page->GetValue()); - } - - /** - * @internal should only be used for testing - */ - public function MakeDefault() - { - $this->manageSchedulesService->MakeDefault($this->page->GetScheduleId()); - } - - /** - * @internal should only be used for testing - */ - public function Delete() - { - $this->manageSchedulesService->Delete($this->page->GetScheduleId(), $this->page->GetTargetScheduleId()); - } - - public function EnableSubscription() - { - $this->manageSchedulesService->EnableSubscription($this->page->GetScheduleId()); - } - - public function DisableSubscription() - { - $this->manageSchedulesService->DisableSubscription($this->page->GetScheduleId()); - } - - public function ChangePeakTimes() - { - $scheduleId = $this->page->GetScheduleId(); - $deletePeak = $this->page->GetDeletePeakTimes(); - - if ($deletePeak) { - $layout = $this->manageSchedulesService->DeletePeakTimes($scheduleId); - } - else { - $allDay = $this->page->GetPeakAllDay(); - $beginTime = $this->page->GetPeakBeginTime(); - $endTime = $this->page->GetPeakEndTime(); - - $everyDay = $this->page->GetPeakEveryDay(); - $peakDays = $this->page->GetPeakWeekdays(); - - $allYear = $this->page->GetPeakAllYear(); - $beginDay = $this->page->GetPeakBeginDay(); - $beginMonth = $this->page->GetPeakBeginMonth(); - $endDay = $this->page->GetPeakEndDay(); - $endMonth = $this->page->GetPeakEndDMonth(); - - $peakTimes = new PeakTimes($allDay, $beginTime, $endTime, $everyDay, $peakDays, $allYear, $beginDay, $beginMonth, $endDay, $endMonth); - $layout = $this->manageSchedulesService->ChangePeakTimes($scheduleId, $peakTimes); - } - $this->page->DisplayPeakTimes($layout); - } - - public function ChangeAvailability() - { - $availableAllYear = $this->page->GetAvailableAllYear(); - $start = $this->page->GetAvailabilityBegin(); - $end = $this->page->GetAvailabilityEnd(); - $scheduleId = $this->page->GetScheduleId(); - $timezone = ServiceLocator::GetServer()->GetUserSession()->Timezone; - - if ($availableAllYear || empty($start) || empty($end)) { - $schedule = $this->manageSchedulesService->DeleteAvailability($scheduleId); - } - else { - $schedule = $this->manageSchedulesService->UpdateAvailability($scheduleId, Date::Parse($start, $timezone), Date::Parse($end, $timezone)); - } - - $this->page->DisplayAvailability($schedule, $timezone); - } - - public function ToggleConcurrentReservations() - { - $scheduleId = $this->page->GetScheduleId(); - $this->manageSchedulesService->ToggleConcurrentReservations($scheduleId); - } - - public function SwitchLayoutType() - { - $scheduleId = $this->page->GetScheduleId(); - $layoutType = $this->page->GetLayoutType(); - $this->manageSchedulesService->SwitchLayoutType($scheduleId, $layoutType); - } - - public function ProcessDataRequest($dataRequest) - { - if ($dataRequest == 'events') { - $timezone = ServiceLocator::GetServer()->GetUserSession()->Timezone; - $start = Date::Parse($this->page->GetCustomLayoutStartRange(), $timezone); - $end = Date::Parse($this->page->GetCustomLayoutEndRange(), $timezone); - $scheduleId = $this->page->GetScheduleId(); - - $periods = $this->manageSchedulesService->GetCustomLayoutPeriods($start, $end, $scheduleId); - - $events = array(); - $dateFormat = Resources::GetInstance()->GetDateFormat('fullcalendar'); - foreach ($periods as $period) { - $events[] = array( - 'id' => $period->BeginDate()->Format(Date::SHORT_FORMAT), - 'start' => $period->BeginDate()->Format($dateFormat), - 'end' => $period->EndDate()->Format($dateFormat), - 'allDay' => false, - 'startEditable' => true, - ); - } - - $this->page->BindEvents($events); - } - } - - public function AddLayoutSlot() - { - $timezone = ServiceLocator::GetServer()->GetUserSession()->Timezone; - $start = Date::Parse($this->page->GetSlotStart(), $timezone); - $end = Date::Parse($this->page->GetSlotEnd(), $timezone); - $scheduleId = $this->page->GetScheduleId(); - - Log::Debug('Adding custom layout slot. Start %s, End %s, Schedule %s', $start, $end, $scheduleId); - $this->manageSchedulesService->AddCustomLayoutPeriod($scheduleId, $start, $end); - } - - public function UpdateLayoutSlot() - { - $timezone = ServiceLocator::GetServer()->GetUserSession()->Timezone; - $start = Date::Parse($this->page->GetSlotStart(), $timezone); - $end = Date::Parse($this->page->GetSlotEnd(), $timezone); - $originalStart = Date::Parse($this->page->GetSlotId(), $timezone); - $scheduleId = $this->page->GetScheduleId(); - - Log::Debug('Updating custom layout slot. Start %s, End %s, Schedule %s', $start, $end, $scheduleId); - $this->manageSchedulesService->UpdateCustomLayoutPeriod($scheduleId, $start, $end, $originalStart); - } - - public function DeleteLayoutSlot() - { - $timezone = ServiceLocator::GetServer()->GetUserSession()->Timezone; - $start = Date::Parse($this->page->GetSlotStart(), $timezone); - $end = Date::Parse($this->page->GetSlotEnd(), $timezone); - $scheduleId = $this->page->GetScheduleId(); - - Log::Debug('Deleting custom layout slot. Start %s, End %s, Schedule %s', $start, $end, $scheduleId); - $this->manageSchedulesService->DeleteCustomLayoutPeriod($scheduleId, $start, $end); - } - - public function ChangeDefaultStyle() - { - $scheduleId = $this->page->GetScheduleId(); - $style = $this->page->GetValue(); - - Log::Debug('Changing default style. Schedule %s, Style %s', $scheduleId, $style); - - $this->manageSchedulesService->ChangeDefaultStyle($scheduleId, $style); - } - - protected function LoadValidators($action) - { - if ($action == ManageSchedules::ActionChangeLayout) { - $validateSingle = $this->page->GetUsingSingleLayout(); - if ($validateSingle) { - $reservableSlots = $this->page->GetReservableSlots(); - $blockedSlots = $this->page->GetBlockedSlots(); - } - else { - $reservableSlots = $this->page->GetDailyReservableSlots(); - $blockedSlots = $this->page->GetDailyBlockedSlots(); - } - $this->page->RegisterValidator('layoutValidator', - new LayoutValidator($reservableSlots, $blockedSlots, $validateSingle)); - } - } + /** + * @var IManageSchedulesPage + */ + private $page; + + /** + * @var ManageScheduleService + */ + private $manageSchedulesService; + + /** + * @var IGroupViewRepository + */ + private $groupViewRepository; + + public function __construct(IManageSchedulesPage $page, + ManageScheduleService $manageSchedulesService, + IGroupViewRepository $groupViewRepository) + { + parent::__construct($page); + $this->page = $page; + $this->manageSchedulesService = $manageSchedulesService; + $this->groupViewRepository = $groupViewRepository; + + $this->AddAction(ManageSchedules::ActionAdd, 'Add'); + $this->AddAction(ManageSchedules::ActionChangeLayout, 'ChangeLayout'); + $this->AddAction(ManageSchedules::ActionChangeStartDay, 'ChangeStartDay'); + $this->AddAction(ManageSchedules::ActionChangeDaysVisible, 'ChangeDaysVisible'); + $this->AddAction(ManageSchedules::ActionMakeDefault, 'MakeDefault'); + $this->AddAction(ManageSchedules::ActionRename, 'Rename'); + $this->AddAction(ManageSchedules::ActionDelete, 'Delete'); + $this->AddAction(ManageSchedules::ActionEnableSubscription, 'EnableSubscription'); + $this->AddAction(ManageSchedules::ActionDisableSubscription, 'DisableSubscription'); + $this->AddAction(ManageSchedules::ChangeAdminGroup, 'ChangeAdminGroup'); + $this->AddAction(ManageSchedules::ActionChangePeakTimes, 'ChangePeakTimes'); + $this->AddAction(ManageSchedules::ActionChangeAvailability, 'ChangeAvailability'); + $this->AddAction(ManageSchedules::ActionToggleConcurrentReservations, 'ToggleConcurrentReservations'); + $this->AddAction(ManageSchedules::ActionSwitchLayoutType, 'SwitchLayoutType'); + $this->AddAction(ManageSchedules::ActionAddLayoutSlot, 'AddLayoutSlot'); + $this->AddAction(ManageSchedules::ActionUpdateLayoutSlot, 'UpdateLayoutSlot'); + $this->AddAction(ManageSchedules::ActionDeleteLayoutSlot, 'DeleteLayoutSlot'); + $this->AddAction(ManageSchedules::ActionChangeDefaultStyle, 'ChangeDefaultStyle'); + $this->AddAction(ManageSchedules::ActionChangeMaximumConcurrent, 'ChangeMaximumConcurrentReservations'); + } + + public function PageLoad() + { + $results = $this->manageSchedulesService->GetList($this->page->GetPageNumber(), $this->page->GetPageSize()); + $schedules = $results->Results(); + + $sourceSchedules = $this->manageSchedulesService->GetSourceSchedules(); + $resources = $this->manageSchedulesService->GetResources(); + + $layouts = array(); + /* @var $schedule Schedule */ + foreach ($schedules as $schedule) + { + $layout = $this->manageSchedulesService->GetLayout($schedule); + $layouts[$schedule->GetId()] = $layout; + } + + $this->page->BindGroups($this->groupViewRepository->GetGroupsByRole(RoleLevel::SCHEDULE_ADMIN)); + + $this->page->BindSchedules($schedules, $layouts, $sourceSchedules); + $this->page->BindPageInfo($results->PageInfo()); + $this->page->BindResources($resources); + $this->PopulateTimezones(); + + } + + private function PopulateTimezones() + { + $timezoneValues = array(); + $timezoneOutput = array(); + + foreach ($GLOBALS['APP_TIMEZONES'] as $timezone) + { + $timezoneValues[] = $timezone; + $timezoneOutput[] = $timezone; + } + + $this->page->SetTimezones($timezoneValues, $timezoneOutput); + } + + /** + * @internal should only be used for testing + */ + public function Add() + { + $copyLayoutFromScheduleId = $this->page->GetSourceScheduleId(); + $name = $this->page->GetScheduleName(); + $weekdayStart = $this->page->GetStartDay(); + $daysVisible = $this->page->GetDaysVisible(); + + Log::Debug('Adding schedule with name %s', $name); + + $this->manageSchedulesService->Add($name, $daysVisible, $weekdayStart, $copyLayoutFromScheduleId); + } + + /** + * @internal should only be used for testing + */ + public function Rename() + { + $this->manageSchedulesService->Rename($this->page->GetScheduleId(), $this->page->GetValue()); + } + + /** + * @internal should only be used for testing + */ + public function ChangeStartDay() + { + $this->manageSchedulesService->ChangeSettings($this->page->GetScheduleId(), $this->page->GetValue(), null); + } + + /** + * @internal should only be used for testing + */ + public function ChangeDaysVisible() + { + $this->manageSchedulesService->ChangeSettings($this->page->GetScheduleId(), null, $this->page->GetValue()); + } + + /** + * @internal should only be used for testing + */ + public function ChangeLayout() + { + $scheduleId = $this->page->GetScheduleId(); + $timezone = $this->page->GetLayoutTimezone(); + $usingSingleLayout = $this->page->GetUsingSingleLayout(); + + Log::Debug('Changing layout for scheduleId=%s. timezone=%s, usingSingleLayout=%s', $scheduleId, $timezone, + $usingSingleLayout); + if ($usingSingleLayout) + { + $reservableSlots = $this->page->GetReservableSlots(); + $blockedSlots = $this->page->GetBlockedSlots(); + $this->manageSchedulesService->ChangeLayout($scheduleId, $timezone, $reservableSlots, $blockedSlots); + } + else + { + $reservableSlots = $this->page->GetDailyReservableSlots(); + $blockedSlots = $this->page->GetDailyBlockedSlots(); + $this->manageSchedulesService->ChangeDailyLayout($scheduleId, $timezone, $reservableSlots, $blockedSlots); + } + } + + /** + * @internal should only be used for testing + */ + public function ChangeAdminGroup() + { + $this->manageSchedulesService->ChangeAdminGroup($this->page->GetScheduleId(), $this->page->GetValue()); + } + + /** + * @internal should only be used for testing + */ + public function MakeDefault() + { + $this->manageSchedulesService->MakeDefault($this->page->GetScheduleId()); + } + + /** + * @internal should only be used for testing + */ + public function Delete() + { + $this->manageSchedulesService->Delete($this->page->GetScheduleId(), $this->page->GetTargetScheduleId()); + } + + public function EnableSubscription() + { + $this->manageSchedulesService->EnableSubscription($this->page->GetScheduleId()); + } + + public function DisableSubscription() + { + $this->manageSchedulesService->DisableSubscription($this->page->GetScheduleId()); + } + + public function ChangePeakTimes() + { + $scheduleId = $this->page->GetScheduleId(); + $deletePeak = $this->page->GetDeletePeakTimes(); + + if ($deletePeak) + { + $layout = $this->manageSchedulesService->DeletePeakTimes($scheduleId); + } + else + { + $allDay = $this->page->GetPeakAllDay(); + $beginTime = $this->page->GetPeakBeginTime(); + $endTime = $this->page->GetPeakEndTime(); + + $everyDay = $this->page->GetPeakEveryDay(); + $peakDays = $this->page->GetPeakWeekdays(); + + $allYear = $this->page->GetPeakAllYear(); + $beginDay = $this->page->GetPeakBeginDay(); + $beginMonth = $this->page->GetPeakBeginMonth(); + $endDay = $this->page->GetPeakEndDay(); + $endMonth = $this->page->GetPeakEndDMonth(); + + $peakTimes = new PeakTimes($allDay, $beginTime, $endTime, $everyDay, $peakDays, $allYear, $beginDay, $beginMonth, $endDay, $endMonth); + $layout = $this->manageSchedulesService->ChangePeakTimes($scheduleId, $peakTimes); + } + $this->page->DisplayPeakTimes($layout); + } + + public function ChangeAvailability() + { + $availableAllYear = $this->page->GetAvailableAllYear(); + $start = $this->page->GetAvailabilityBegin(); + $end = $this->page->GetAvailabilityEnd(); + $scheduleId = $this->page->GetScheduleId(); + $timezone = ServiceLocator::GetServer()->GetUserSession()->Timezone; + + if ($availableAllYear || empty($start) || empty($end)) + { + $schedule = $this->manageSchedulesService->DeleteAvailability($scheduleId); + } + else + { + $schedule = $this->manageSchedulesService->UpdateAvailability($scheduleId, Date::Parse($start, $timezone), Date::Parse($end, $timezone)); + } + + $this->page->DisplayAvailability($schedule, $timezone); + } + + public function ToggleConcurrentReservations() + { + $scheduleId = $this->page->GetScheduleId(); + $this->manageSchedulesService->ToggleConcurrentReservations($scheduleId); + } + + public function SwitchLayoutType() + { + $scheduleId = $this->page->GetScheduleId(); + $layoutType = $this->page->GetLayoutType(); + $this->manageSchedulesService->SwitchLayoutType($scheduleId, $layoutType); + } + + public function ProcessDataRequest($dataRequest) + { + if ($dataRequest == 'events') + { + $timezone = ServiceLocator::GetServer()->GetUserSession()->Timezone; + $start = Date::Parse($this->page->GetCustomLayoutStartRange(), $timezone); + $end = Date::Parse($this->page->GetCustomLayoutEndRange(), $timezone); + $scheduleId = $this->page->GetScheduleId(); + + $periods = $this->manageSchedulesService->GetCustomLayoutPeriods($start, $end, $scheduleId); + + $events = array(); + $dateFormat = Resources::GetInstance()->GetDateFormat('fullcalendar'); + foreach ($periods as $period) + { + $events[] = array( + 'id' => $period->BeginDate()->Format(Date::SHORT_FORMAT), + 'start' => $period->BeginDate()->Format($dateFormat), + 'end' => $period->EndDate()->Format($dateFormat), + 'allDay' => false, + 'startEditable' => true, + ); + } + + $this->page->BindEvents($events); + } + } + + public function AddLayoutSlot() + { + $timezone = ServiceLocator::GetServer()->GetUserSession()->Timezone; + $start = Date::Parse($this->page->GetSlotStart(), $timezone); + $end = Date::Parse($this->page->GetSlotEnd(), $timezone); + $scheduleId = $this->page->GetScheduleId(); + + Log::Debug('Adding custom layout slot. Start %s, End %s, Schedule %s', $start, $end, $scheduleId); + $this->manageSchedulesService->AddCustomLayoutPeriod($scheduleId, $start, $end); + } + + public function UpdateLayoutSlot() + { + $timezone = ServiceLocator::GetServer()->GetUserSession()->Timezone; + $start = Date::Parse($this->page->GetSlotStart(), $timezone); + $end = Date::Parse($this->page->GetSlotEnd(), $timezone); + $originalStart = Date::Parse($this->page->GetSlotId(), $timezone); + $scheduleId = $this->page->GetScheduleId(); + + Log::Debug('Updating custom layout slot. Start %s, End %s, Schedule %s', $start, $end, $scheduleId); + $this->manageSchedulesService->UpdateCustomLayoutPeriod($scheduleId, $start, $end, $originalStart); + } + + public function DeleteLayoutSlot() + { + $timezone = ServiceLocator::GetServer()->GetUserSession()->Timezone; + $start = Date::Parse($this->page->GetSlotStart(), $timezone); + $end = Date::Parse($this->page->GetSlotEnd(), $timezone); + $scheduleId = $this->page->GetScheduleId(); + + Log::Debug('Deleting custom layout slot. Start %s, End %s, Schedule %s', $start, $end, $scheduleId); + $this->manageSchedulesService->DeleteCustomLayoutPeriod($scheduleId, $start, $end); + } + + public function ChangeDefaultStyle() + { + $scheduleId = $this->page->GetScheduleId(); + $style = $this->page->GetValue(); + + Log::Debug('Changing default style. Schedule %s, Style %s', $scheduleId, $style); + + $this->manageSchedulesService->ChangeDefaultStyle($scheduleId, $style); + } + + public function ChangeMaximumConcurrentReservations() + { + $scheduleId = $this->page->GetScheduleId(); + $max = $this->page->GetMaximumConcurrentReservations(); + $unlimited = $this->page->GetIsUnlimitedConcurrentReservations(); + + Log::Debug('Changing maximum number of concurrent reservations. Schedule %s, Max %s, Unlimited %s', $scheduleId, $max, $unlimited); + + $this->manageSchedulesService->ChangeConcurrentReservationMaximum($scheduleId, $max, $unlimited); + } + + + protected function LoadValidators($action) + { + if ($action == ManageSchedules::ActionChangeLayout) + { + $validateSingle = $this->page->GetUsingSingleLayout(); + if ($validateSingle) + { + $reservableSlots = $this->page->GetReservableSlots(); + $blockedSlots = $this->page->GetBlockedSlots(); + } + else + { + $reservableSlots = $this->page->GetDailyReservableSlots(); + $blockedSlots = $this->page->GetDailyBlockedSlots(); + } + $this->page->RegisterValidator('layoutValidator', + new LayoutValidator($reservableSlots, $blockedSlots, $validateSingle)); + } + } } \ No newline at end of file diff --git a/Web/scripts/admin/schedule.js b/Web/scripts/admin/schedule.js index b3d8482cd..2d1b60ead 100644 --- a/Web/scripts/admin/schedule.js +++ b/Web/scripts/admin/schedule.js @@ -1,668 +1,710 @@ function ScheduleManagement(opts) { - var options = opts; - - var elements = { - activeId: $('#activeId'), - - layoutDialog: $('#changeLayoutDialog'), - deleteDialog: $('#deleteDialog'), - addDialog: $('#addDialog'), - - changeLayoutForm: $('#changeLayoutForm'), - placeholderForm: $('#placeholderForm'), - deleteForm: $('#deleteForm'), - - addForm: $('#addScheduleForm'), - addName: $('#addName'), - - reservableEdit: $('#reservableEdit'), - blockedEdit: $('#blockedEdit'), - layoutTimezone: $('#layoutTimezone'), - quickLayoutConfig: $('#quickLayoutConfig'), - quickLayoutStart: $('#quickLayoutStart'), - quickLayoutEnd: $('#quickLayoutEnd'), - createQuickLayout: $('#createQuickLayout'), - - daysVisible: $('#daysVisible'), - dayOfWeek: $('#dayOfWeek'), - deleteDestinationScheduleId: $('#targetScheduleId'), - usesSingleLayout: $('#usesSingleLayout'), - - addScheduleButton: $('#add-schedule'), - - peakTimesDialog: $('#peakTimesDialog'), - peakTimesForm: $('#peakTimesForm'), - peakEveryDay: $('#peakEveryDay'), - peakDayList: $('#peakDayList'), - peakAllYear: $('#peakAllYear'), - peakDateRange: $('#peakDateRange'), - peakAllDay: $('#peakAllDay'), - peakTimes: $('#peakTimes'), - deletePeakTimesButton: $('#deletePeakBtn'), - deletePeakTimes: $('#deletePeakTimes'), - - availabilityDialog: $('#availabilityDialog'), - availableStartDateTextbox: $('#availabilityStartDate'), - availableStartDate: $('#formattedBeginDate'), - availableEndDateTextbox: $('#availabilityEndDate'), - availableEndDate: $('#formattedEndDate'), - availableAllYear: $('#availableAllYear'), - availabilityForm: $('#availabilityForm'), - - concurrentForm: $('#concurrentForm'), - - switchLayoutButton: $('.switchLayout'), - switchLayoutForm: $('#switchLayoutForm'), - switchLayoutDialog: $('#switchLayoutDialog'), - - layoutSlotForm: $('#layoutSlotForm'), - slotStartDate: $('#slotStartDate'), - slotEndDate: $('#slotEndDate'), - slotId: $('#slotId'), - deleteCustomLayoutDialog: $('#deleteCustomLayoutDialog'), - deleteSlotStartDate: $('#deleteSlotStartDate'), - deleteSlotEndDate: $('#deleteSlotEndDate'), - cancelDeleteSlot: $('#cancelDeleteSlot'), - deleteCustomTimeSlotForm: $('#deleteCustomTimeSlotForm'), - deleteSlot: $('#deleteSlot'), - confirmCreateSlotDialog: $('#confirmCreateSlotDialog'), - cancelCreateSlot: $('#cancelCreateSlot') - }; - - ScheduleManagement.prototype.init = function () { - $('.scheduleDetails').each(function () { - var details = $(this); - var id = details.find(':hidden.id').val(); - var reservable = details.find('.reservableSlots'); - var blocked = details.find('.blockedSlots'); - var timezone = details.find('.timezone'); - var daysVisible = details.find('.daysVisible'); - var dayOfWeek = details.find('.dayOfWeek'); - var usesDailyLayouts = details.find('.usesDailyLayouts'); - - details.find('a.update').click(function () { - setActiveScheduleId(id); - }); - - details.find('.renameButton').click(function (e) { - e.stopPropagation(); - details.find('.scheduleName').editable('toggle'); - }); - - details.find('.dayName').click(function (e) { - e.stopPropagation(); - $(this).editable('toggle'); - }); - - details.find('.daysVisible').click(function (e) { - e.stopPropagation(); - $(this).editable('toggle'); - }); - - details.find('.changeScheduleAdmin').click(function (e) { - e.stopPropagation(); - details.find('.scheduleAdmin').editable('toggle'); - }); - - details.find('.changeLayoutButton').click(function (e) { - if ($(e.target).data('layout-type') == 0) { - showChangeLayout(e, reservable, blocked, timezone, (usesDailyLayouts.val() == 'false')); - } - else { - showChangeCustomLayout(id); - } - return false; - }); - - details.find('.makeDefaultButton').click(function (e) { - PerformAsyncAction($(this), getSubmitCallback(options.makeDefaultAction), $('#action-indicator')); - }); - - details.find('.enableSubscription').click(function (e) { - PerformAsyncAction($(this), getSubmitCallback(options.enableSubscriptionAction), $('#action-indicator')); - }); - - details.find('.disableSubscription').click(function (e) { - PerformAsyncAction($(this), getSubmitCallback(options.disableSubscriptionAction), $('#action-indicator')); - }); - - details.find('.deleteScheduleButton').click(function (e) { - showDeleteDialog(e); - return false; - }); - - details.find('.showAllDailyLayouts').click(function (e) { - e.preventDefault(); - $(this).next('.allDailyLayouts').toggle(); - }); - - details.find('.changePeakTimes').click(function (e) { - e.preventDefault(); - showPeakTimesDialog(getActiveScheduleId()); - }); - - details.find('.changeAvailability').click(function (e) { - e.preventDefault(); - showAvailabilityDialog(getActiveScheduleId()); - }); - - details.find('.toggleConcurrent').click(function (e) { - e.preventDefault(); - var toggle = $(e.target); - var container = toggle.parent('.concurrentContainer'); - toggleConcurrentReservations(getActiveScheduleId(), toggle, container); - }); - - details.find('.defaultScheduleStyle').click(function (e) { - e.stopPropagation(); - $(this).editable('toggle'); - }); - - details.find('.switchLayout').click(function (e) { - e.preventDefault(); - $('#switchLayoutTypeId').val($(e.target).data('switch-to')); - elements.switchLayoutDialog.modal('show'); - }); - }); - - elements.deletePeakTimesButton.click(function (e) { - e.preventDefault(); - elements.deletePeakTimes.val('1'); - }); - - elements.availableAllYear.on('click', function (e) { - if ($(e.target).is(':checked')) { - elements.availableStartDateTextbox.prop('disabled', true); - elements.availableEndDateTextbox.prop('disabled', true); - } - else { - elements.availableStartDateTextbox.prop('disabled', false); - elements.availableEndDateTextbox.prop('disabled', false); - } - }); - - $(".save").click(function (e) { - e.preventDefault(); - e.stopPropagation(); - $(this).closest('form').submit(); - }); - - $(".cancel").click(function () { - $(this).closest('.dialog').dialog("close"); - }); - - elements.quickLayoutConfig.change(function () { - createQuickLayout(); - }); - - elements.quickLayoutStart.change(function () { - createQuickLayout(); - }); - - elements.quickLayoutEnd.change(function () { - createQuickLayout(); - }); - - elements.createQuickLayout.click(function (e) { - e.preventDefault(); - createQuickLayout(); - }); - - elements.usesSingleLayout.change(function () { - toggleLayoutChange($(this).is(':checked')); - }); - - elements.addScheduleButton.click(function (e) { - e.preventDefault(); - elements.addDialog.modal('show'); - }); - - elements.addDialog.on('shown.bs.modal', function () { - elements.addName.focus(); - }); - - elements.cancelDeleteSlot.click(function(e) { - elements.deleteCustomLayoutDialog.hide(); - }); - - elements.cancelCreateSlot.click(function (e) { - elements.confirmCreateSlotDialog.hide(); - }); - - $('.autofillBlocked').click(function (e) { - e.preventDefault(); - autoFillBlocked(); - }); - - wireUpPeakTimeToggles(); - - ConfigureAsyncForm(elements.changeLayoutForm, getSubmitCallback(options.changeLayoutAction)); - ConfigureAsyncForm(elements.addForm, getSubmitCallback(options.addAction), null, handleAddError); - ConfigureAsyncForm(elements.deleteForm, getSubmitCallback(options.deleteAction)); - ConfigureAsyncForm(elements.peakTimesForm, getSubmitCallback(options.peakTimesAction), refreshPeakTimes); - ConfigureAsyncForm(elements.availabilityForm, getSubmitCallback(options.availabilityAction), refreshAvailability); - ConfigureAsyncForm(elements.concurrentForm, getSubmitCallback(options.toggleConcurrentReservations), function () { - }, function () { - }); - ConfigureAsyncForm(elements.switchLayoutForm, getSubmitCallback(options.switchLayout)); - ConfigureAsyncForm(elements.deleteCustomTimeSlotForm, getSubmitCallback(options.deleteLayoutSlot), afterDeleteSlot); - }; - - var getSubmitCallback = function (action) { - return function () { - return options.submitUrl + "?sid=" + elements.activeId.val() + "&action=" + action; - }; - }; - - var createQuickLayout = function () { - var intervalMinutes = elements.quickLayoutConfig.val(); - var startTime = elements.quickLayoutStart.val(); - var endTime = elements.quickLayoutEnd.val(); - - if (intervalMinutes != '' && startTime != '' && endTime != '') { - var layout = ''; - var blocked = ''; - - if (startTime != '00:00') { - blocked += '00:00 - ' + startTime + "\n"; - } - - if (endTime != '00:00') { - blocked += endTime + ' - 00:00'; - } - - var startTimes = startTime.split(":"); - var endTimes = endTime.split(":"); - - var currentTime = new Date(); - currentTime.setHours(startTimes[0]); - currentTime.setMinutes(startTimes[1]); - - var endDateTime = new Date(); - endDateTime.setHours(endTimes[0]); - endDateTime.setMinutes(endTimes[1]); - - var nextTime = new Date(currentTime); - - var intervalMilliseconds = 60 * 1000 * intervalMinutes; - while (currentTime.getTime() < endDateTime.getTime()) { - nextTime.setTime(nextTime.getTime() + intervalMilliseconds); - - layout += getFormattedTime(currentTime) + ' - '; - layout += getFormattedTime(nextTime) + '\n'; - - currentTime.setTime(currentTime.getTime() + intervalMilliseconds); - } - - $('.reservableEdit:visible', elements.layoutDialog).val(layout); - $('.blockedEdit:visible', elements.layoutDialog).val(blocked); - } - }; - - var getFormattedTime = function (date) { - var hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(); - var minute = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(); - return hour + ":" + minute; - }; - - var autoFillBlocked = function () { - - function splitAndTrim(line) { - return _.map(_.split(line, '-'), _.trim); - } - - var blocked = ''; - - var reservableText = _.trim($('.reservableEdit:visible', elements.layoutDialog).val()); - var reservable = _.split(reservableText, "\n"); - if (reservable.length === 0) { - $('.blockedEdit:visible', elements.layoutDialog).val("00:00 - 00:00"); - return; - } - - var startIndex = 0; - if (!_.startsWith(reservable[0], '00:00') && !_.startsWith(reservable[0], '0:00')) { - blocked += "00:00 - " + splitAndTrim(reservable)[0] + "\n"; - startIndex = 1; - } - - for (var i = startIndex; i < reservable.length; i++) { - var firstIteration = i === 0; - var lastIteration = i + 1 === reservable.length; - - if (_.isEmpty(_.trim(reservable[i]))) { - continue; - } - - var current = splitAndTrim(reservable[i]); - var previous = null; - if (!firstIteration) { - previous = splitAndTrim(reservable[i - 1]); - } - - if (!firstIteration && !lastIteration && current[0] != previous[1]) { - blocked += previous[1] + " - " + current[0] + "\n"; - } - - if (lastIteration && current[1] != '00:00') { - blocked += current[1] + ' - 00:00' + "\n"; - } - } - - $('.blockedEdit:visible', elements.layoutDialog).val(blocked); - }; - - var handleAddError = function (responseText) { - $('#addScheduleResults').text(responseText); - $('#addScheduleResults').show(); - }; - - var setActiveScheduleId = function (scheduleId) { - elements.activeId.val(scheduleId); - }; - - var getActiveScheduleId = function () { - return elements.activeId.val(); - }; - - var showChangeLayout = function (e, reservableDiv, blockedDiv, timezone, usesSingleLayout) { - elements.changeLayoutForm.find('.validationSummary ').addClass('no-show'); - $.each(reservableDiv, function (index, val) { - var slots = reformatTimeSlots($(val)); - $('#' + $(val).attr('ref')).val(slots); - }); - - $.each(blockedDiv, function (index, val) { - var slots = reformatTimeSlots($(val)); - $('#' + $(val).attr('ref')).val(slots); - }); - - elements.layoutTimezone.val(timezone.val()); - elements.usesSingleLayout.prop('checked', false); - - if (usesSingleLayout) { - elements.usesSingleLayout.prop('checked', true); - } - elements.usesSingleLayout.trigger('change'); - - elements.layoutDialog.modal("show"); - }; - - var toggleLayoutChange = function (useSingleLayout) { - if (useSingleLayout) { - $('#dailySlots').hide(); - $('#staticSlots').show(); - } - else { - $('#staticSlots').hide(); - $('#dailySlots').show(); - } - }; - - var showDeleteDialog = function (e) { - var scheduleId = getActiveScheduleId(); - elements.deleteDestinationScheduleId.children().removeAttr('disabled'); - elements.deleteDestinationScheduleId.children('option[value="' + scheduleId + '"]').attr('disabled', 'disabled'); - elements.deleteDestinationScheduleId.val(''); - - elements.deleteDialog.modal('show'); - }; - - var reformatTimeSlots = function (div) { - var text = $.trim(div.text()); - text = text.replace(/\s\s+/g, ' '); - text = text.replace(/\s*,\s*/g, '\n'); - return text; - }; - - var showPeakTimesDialog = function (scheduleId) { - var peakPlaceHolder = $('[data-schedule-id=' + scheduleId + ']').find('.peakPlaceHolder'); - - var times = peakPlaceHolder.find('.peakTimes'); - var days = peakPlaceHolder.find('.peakDays'); - var months = peakPlaceHolder.find('.peakMonths'); - - if (times.length > 0) { - var allDay = times.data('all-day'); - var startTime = times.data('start-time'); - var endTime = times.data('end-time'); - - var everyday = days.data('everyday'); - var days = days.data('weekdays').split(","); - - var allYear = months.data('all-year'); - var beginMonth = months.data('begin-month'); - var beginDay = months.data('begin-day'); - var endMonth = months.data('end-month'); - var endDay = months.data('end-day'); - - if (allDay == 1) { - elements.peakAllDay.prop('checked', true); - } - else { - elements.peakAllDay.prop('checked', false); - $('#peakStartTime').val(startTime); - $('#peakEndTime').val(endTime); - } - - elements.peakEveryDay.attr('checked', everyday == 1); - - _.each($('#peakDayList').find(':checked'), function (e) { - $(e).closest('label').button('toggle'); - }); - - _.each(days, function (day) { - $('#peakDay' + day).closest('label').button('toggle'); - }); - - if (allYear == 1) { - elements.peakAllYear.prop('checked', true); - } - else { - elements.peakAllYear.prop('checked', false); - $('#peakBeginMonth').val(beginMonth); - $('#peakBeginDay').val(beginDay); - $('#peakEndMonth').val(endMonth); - $('#peakEndDay').val(endDay); - } - - peakOnAllDayChanged(); - peakOnEveryDayChanged(); - peakOnAllYearChanged(); - } - - elements.deletePeakTimes.val(''); - elements.peakTimesDialog.modal('show'); - }; - - var peakOnEveryDayChanged = function () { - if ((elements.peakEveryDay).is(':checked')) { - elements.peakDayList.addClass('no-show'); - } - else { - elements.peakDayList.removeClass('no-show'); - } - }; - - var peakOnAllYearChanged = function () { - if ((elements.peakAllYear).is(':checked')) { - elements.peakDateRange.addClass('no-show'); - } - else { - elements.peakDateRange.removeClass('no-show'); - } - }; - - var peakOnAllDayChanged = function () { - if ((elements.peakAllDay).is(':checked')) { - elements.peakTimes.addClass('no-show'); - } - else { - elements.peakTimes.removeClass('no-show'); - } - }; - - var refreshPeakTimes = function (resultHtml) { - $('[data-schedule-id=' + getActiveScheduleId() + ']').find('.peakPlaceHolder').html(resultHtml); - elements.peakTimesDialog.modal('hide'); - }; - - var wireUpPeakTimeToggles = function () { - elements.peakEveryDay.on('click', function (e) { - peakOnEveryDayChanged(); - }); - - elements.peakAllYear.on('click', function (e) { - peakOnAllYearChanged(); - }); - - elements.peakAllDay.on('click', function (e) { - peakOnAllDayChanged(); - }); - }; - - var showAvailabilityDialog = function (scheduleId) { - var placeholder = $('[data-schedule-id=' + scheduleId + ']').find('.availabilityPlaceHolder'); - var dates = placeholder.find('.availableDates'); - - var hasAvailability = dates.data('has-availability') == '1'; - - // elements.availableAllYear.prop('checked', !hasAvailability); - elements.availableStartDateTextbox.datepicker("setDate", dates.data('start-date')); - elements.availableStartDate.trigger('change'); - - elements.availableEndDateTextbox.datepicker("setDate", dates.data('end-date')); - elements.availableEndDate.trigger('change'); - - if (!hasAvailability) { - elements.availableAllYear.trigger('click'); - } - - elements.availabilityDialog.modal('show'); - }; - - var refreshAvailability = function (resultHtml) { - $('[data-schedule-id=' + getActiveScheduleId() + ']').find('.availabilityPlaceHolder').html(resultHtml); - elements.availabilityDialog.modal('hide'); - }; - - var toggleConcurrentReservations = function (scheduleId, toggle, container) { - var allow = toggle.data('allow') == 1; - if (allow) { - container.find('.allowConcurrentYes').addClass('no-show'); - container.find('.allowConcurrentNo').removeClass('no-show'); - } - else { - container.find('.allowConcurrentYes').removeClass('no-show'); - container.find('.allowConcurrentNo').addClass('no-show'); - } - elements.concurrentForm.submit(); - - toggle.data('allow', allow ? '0' : '1'); - }; - - var _fullCalendar = null; - var showChangeCustomLayout = function (scheduleId) { - var customLayoutScheduleId = scheduleId; - - $('#customLayoutDialog').unbind(); - - function updateEvent(event) { - elements.slotStartDate.val(event.start.format('YYYY-MM-DD HH:mm')); - elements.slotEndDate.val(event.end.format('YYYY-MM-DD HH:mm')); - elements.slotId.val(event.id); - ajaxPost(elements.layoutSlotForm, - options.submitUrl + '?action=' + options.updateLayoutSlot + '&sid=' + getActiveScheduleId(), - null, - function (data) { - _fullCalendar.fullCalendar('refetchEvents'); - } - ); - } - - $('#customLayoutDialog').unbind('shown.bs.modal'); - $('#customLayoutDialog').on('shown.bs.modal', function () { - if (_fullCalendar != null) { - _fullCalendar.fullCalendar('destroy'); - } - var calendar = $('#calendar'); - _fullCalendar = calendar.fullCalendar({ - header: { - left: 'prev,next,today', - center: 'title', - right: 'month,agendaWeek,agendaDay' - }, - buttonText: opts.calendarOptions.buttonText, - allDaySlot: false, - defaultDate: opts.calendarOptions.defaultDate, - defaultView: 'month', - eventSources: [{ - url: opts.calendarOptions.eventsUrl, - type: 'GET', - data: { - dr: 'events', - sid: scheduleId - } - }], - dayClick: function (date, jsEvent, view) { - if (view.name == 'month') { - calendar.fullCalendar('changeView', 'agendaDay'); - calendar.fullCalendar('gotoDate', date); - } - }, - selectable: true, - selectHelper: true, - editable: true, - droppable: true, - eventOverlap: false, - select: function (start, end, jsEvent, view) { - if (view.name != 'month') - { - elements.confirmCreateSlotDialog.show(); - elements.confirmCreateSlotDialog.position({ - my: 'left bottom', - at: 'left top', - of: jsEvent - }); - $('#confirmCreateOK').unbind('click'); - $('#confirmCreateOK').click(function (e) { - elements.slotStartDate.val(start.format('YYYY-MM-DD HH:mm')); - elements.slotEndDate.val(end.format('YYYY-MM-DD HH:mm')); - ajaxPost(elements.layoutSlotForm, - options.submitUrl + '?action=' + options.addLayoutSlot + '&sid=' + getActiveScheduleId(), - null, - function () { - _fullCalendar.fullCalendar('refetchEvents'); - elements.confirmCreateSlotDialog.hide(); - } - ); - }); - } - }, - eventClick: function (event, jsEvent, view) { - elements.deleteSlotStartDate.val(event.start.format('YYYY-MM-DD HH:mm')); - elements.deleteSlotEndDate.val(event.end.format('YYYY-MM-DD HH:mm')); - elements.deleteCustomLayoutDialog.show(); - elements.deleteCustomLayoutDialog.position({ - my: 'left bottom', - at: 'left top', - of: jsEvent - }); - }, - eventDrop: function (event, delta, revertFunc) { - updateEvent(event); - }, - eventResize: function (event, delta, revertFunc, jsEvent, ui, view) { - updateEvent(event); - } - }); - }); - - $('#customLayoutDialog').modal('show'); - }; - - function afterDeleteSlot() { - elements.deleteCustomLayoutDialog.hide(); - _fullCalendar.fullCalendar('refetchEvents'); - } + var options = opts; + + var elements = { + activeId: $('#activeId'), + + layoutDialog: $('#changeLayoutDialog'), + deleteDialog: $('#deleteDialog'), + addDialog: $('#addDialog'), + + changeLayoutForm: $('#changeLayoutForm'), + placeholderForm: $('#placeholderForm'), + deleteForm: $('#deleteForm'), + + addForm: $('#addScheduleForm'), + addName: $('#addName'), + + reservableEdit: $('#reservableEdit'), + blockedEdit: $('#blockedEdit'), + layoutTimezone: $('#layoutTimezone'), + quickLayoutConfig: $('#quickLayoutConfig'), + quickLayoutStart: $('#quickLayoutStart'), + quickLayoutEnd: $('#quickLayoutEnd'), + createQuickLayout: $('#createQuickLayout'), + + daysVisible: $('#daysVisible'), + dayOfWeek: $('#dayOfWeek'), + deleteDestinationScheduleId: $('#targetScheduleId'), + usesSingleLayout: $('#usesSingleLayout'), + + addScheduleButton: $('#add-schedule'), + + peakTimesDialog: $('#peakTimesDialog'), + peakTimesForm: $('#peakTimesForm'), + peakEveryDay: $('#peakEveryDay'), + peakDayList: $('#peakDayList'), + peakAllYear: $('#peakAllYear'), + peakDateRange: $('#peakDateRange'), + peakAllDay: $('#peakAllDay'), + peakTimes: $('#peakTimes'), + deletePeakTimesButton: $('#deletePeakBtn'), + deletePeakTimes: $('#deletePeakTimes'), + + availabilityDialog: $('#availabilityDialog'), + availableStartDateTextbox: $('#availabilityStartDate'), + availableStartDate: $('#formattedBeginDate'), + availableEndDateTextbox: $('#availabilityEndDate'), + availableEndDate: $('#formattedEndDate'), + availableAllYear: $('#availableAllYear'), + availabilityForm: $('#availabilityForm'), + + concurrentForm: $('#concurrentForm'), + + switchLayoutButton: $('.switchLayout'), + switchLayoutForm: $('#switchLayoutForm'), + switchLayoutDialog: $('#switchLayoutDialog'), + + switchConcurrentMaximum: $('.changeScheduleConcurrentMaximum'), + concurrentMaximumForm: $('#concurrentMaximumForm'), + concurrentMaximumDialog: $('#concurrentMaximumDialog'), + maximumConcurrentUnlimited: $('#maximumConcurrentUnlimited'), + maximumConcurrent: $('#maximumConcurrent'), + + layoutSlotForm: $('#layoutSlotForm'), + slotStartDate: $('#slotStartDate'), + slotEndDate: $('#slotEndDate'), + slotId: $('#slotId'), + deleteCustomLayoutDialog: $('#deleteCustomLayoutDialog'), + deleteSlotStartDate: $('#deleteSlotStartDate'), + deleteSlotEndDate: $('#deleteSlotEndDate'), + cancelDeleteSlot: $('#cancelDeleteSlot'), + deleteCustomTimeSlotForm: $('#deleteCustomTimeSlotForm'), + deleteSlot: $('#deleteSlot'), + confirmCreateSlotDialog: $('#confirmCreateSlotDialog'), + cancelCreateSlot: $('#cancelCreateSlot') + }; + + ScheduleManagement.prototype.init = function () { + $('.scheduleDetails').each(function () { + var details = $(this); + var id = details.find(':hidden.id').val(); + var reservable = details.find('.reservableSlots'); + var blocked = details.find('.blockedSlots'); + var timezone = details.find('.timezone'); + var daysVisible = details.find('.daysVisible'); + var dayOfWeek = details.find('.dayOfWeek'); + var usesDailyLayouts = details.find('.usesDailyLayouts'); + + details.find('a.update').click(function () { + setActiveScheduleId(id); + }); + + details.find('.renameButton').click(function (e) { + e.stopPropagation(); + details.find('.scheduleName').editable('toggle'); + }); + + details.find('.dayName').click(function (e) { + e.stopPropagation(); + $(this).editable('toggle'); + }); + + details.find('.daysVisible').click(function (e) { + e.stopPropagation(); + $(this).editable('toggle'); + }); + + details.find('.changeScheduleAdmin').click(function (e) { + e.stopPropagation(); + details.find('.scheduleAdmin').editable('toggle'); + }); + + details.find('.changeLayoutButton').click(function (e) { + if ($(e.target).data('layout-type') == 0) + { + showChangeLayout(e, reservable, blocked, timezone, (usesDailyLayouts.val() == 'false')); + } + else + { + showChangeCustomLayout(id); + } + return false; + }); + + details.find('.makeDefaultButton').click(function (e) { + PerformAsyncAction($(this), getSubmitCallback(options.makeDefaultAction), $('#action-indicator')); + }); + + details.find('.enableSubscription').click(function (e) { + PerformAsyncAction($(this), getSubmitCallback(options.enableSubscriptionAction), $('#action-indicator')); + }); + + details.find('.disableSubscription').click(function (e) { + PerformAsyncAction($(this), getSubmitCallback(options.disableSubscriptionAction), $('#action-indicator')); + }); + + details.find('.deleteScheduleButton').click(function (e) { + showDeleteDialog(e); + return false; + }); + + details.find('.showAllDailyLayouts').click(function (e) { + e.preventDefault(); + $(this).next('.allDailyLayouts').toggle(); + }); + + details.find('.changePeakTimes').click(function (e) { + e.preventDefault(); + showPeakTimesDialog(getActiveScheduleId()); + }); + + details.find('.changeAvailability').click(function (e) { + e.preventDefault(); + showAvailabilityDialog(getActiveScheduleId()); + }); + + details.find('.toggleConcurrent').click(function (e) { + e.preventDefault(); + var toggle = $(e.target); + var container = toggle.parent('.concurrentContainer'); + toggleConcurrentReservations(getActiveScheduleId(), toggle, container); + }); + + details.find('.defaultScheduleStyle').click(function (e) { + e.stopPropagation(); + $(this).editable('toggle'); + }); + + details.find('.switchLayout').click(function (e) { + e.preventDefault(); + $('#switchLayoutTypeId').val($(e.target).data('switch-to')); + elements.switchLayoutDialog.modal('show'); + }); + + details.find('.changeScheduleConcurrentMaximum').click(function (e) { + e.preventDefault(); + var concurrent = $(e.target).closest('.maximumConcurrentContainer').data('concurrent'); + elements.maximumConcurrentUnlimited.attr('checked', concurrent == "0"); + elements.maximumConcurrent.val(concurrent); + elements.maximumConcurrent.attr('disabled', concurrent == "0"); + elements.concurrentMaximumDialog.modal('show'); + }); + }); + + elements.maximumConcurrentUnlimited.on('click', function (e) { + if (elements.maximumConcurrentUnlimited.is(":checked")) { + elements.maximumConcurrent.attr('disabled', true); + } + else { + elements.maximumConcurrent.attr('disabled', false); + } + }); + + elements.deletePeakTimesButton.click(function (e) { + e.preventDefault(); + elements.deletePeakTimes.val('1'); + }); + + elements.availableAllYear.on('click', function (e) { + if ($(e.target).is(':checked')) + { + elements.availableStartDateTextbox.prop('disabled', true); + elements.availableEndDateTextbox.prop('disabled', true); + } + else + { + elements.availableStartDateTextbox.prop('disabled', false); + elements.availableEndDateTextbox.prop('disabled', false); + } + }); + + $(".save").click(function (e) { + e.preventDefault(); + e.stopPropagation(); + $(this).closest('form').submit(); + }); + + $(".cancel").click(function () { + $(this).closest('.dialog').dialog("close"); + }); + + elements.quickLayoutConfig.change(function () { + createQuickLayout(); + }); + + elements.quickLayoutStart.change(function () { + createQuickLayout(); + }); + + elements.quickLayoutEnd.change(function () { + createQuickLayout(); + }); + + elements.createQuickLayout.click(function (e) { + e.preventDefault(); + createQuickLayout(); + }); + + elements.usesSingleLayout.change(function () { + toggleLayoutChange($(this).is(':checked')); + }); + + elements.addScheduleButton.click(function (e) { + e.preventDefault(); + elements.addDialog.modal('show'); + }); + + elements.addDialog.on('shown.bs.modal', function () { + elements.addName.focus(); + }); + + elements.cancelDeleteSlot.click(function (e) { + elements.deleteCustomLayoutDialog.hide(); + }); + + elements.cancelCreateSlot.click(function (e) { + elements.confirmCreateSlotDialog.hide(); + }); + + $('.autofillBlocked').click(function (e) { + e.preventDefault(); + autoFillBlocked(); + }); + + wireUpPeakTimeToggles(); + + ConfigureAsyncForm(elements.changeLayoutForm, getSubmitCallback(options.changeLayoutAction)); + ConfigureAsyncForm(elements.addForm, getSubmitCallback(options.addAction), null, handleAddError); + ConfigureAsyncForm(elements.deleteForm, getSubmitCallback(options.deleteAction)); + ConfigureAsyncForm(elements.peakTimesForm, getSubmitCallback(options.peakTimesAction), refreshPeakTimes); + ConfigureAsyncForm(elements.availabilityForm, getSubmitCallback(options.availabilityAction), refreshAvailability); + ConfigureAsyncForm(elements.concurrentForm, getSubmitCallback(options.toggleConcurrentReservations), function () { + }, function () { + }); + ConfigureAsyncForm(elements.switchLayoutForm, getSubmitCallback(options.switchLayout)); + ConfigureAsyncForm(elements.deleteCustomTimeSlotForm, getSubmitCallback(options.deleteLayoutSlot), afterDeleteSlot); + ConfigureAsyncForm(elements.concurrentMaximumForm, getSubmitCallback(options.maximumConcurrentAction)); + }; + + var getSubmitCallback = function (action) { + return function () { + return options.submitUrl + "?sid=" + elements.activeId.val() + "&action=" + action; + }; + }; + + var createQuickLayout = function () { + var intervalMinutes = elements.quickLayoutConfig.val(); + var startTime = elements.quickLayoutStart.val(); + var endTime = elements.quickLayoutEnd.val(); + + if (intervalMinutes != '' && startTime != '' && endTime != '') + { + var layout = ''; + var blocked = ''; + + if (startTime != '00:00') + { + blocked += '00:00 - ' + startTime + "\n"; + } + + if (endTime != '00:00') + { + blocked += endTime + ' - 00:00'; + } + + var startTimes = startTime.split(":"); + var endTimes = endTime.split(":"); + + var currentTime = new Date(); + currentTime.setHours(startTimes[0]); + currentTime.setMinutes(startTimes[1]); + + var endDateTime = new Date(); + endDateTime.setHours(endTimes[0]); + endDateTime.setMinutes(endTimes[1]); + + var nextTime = new Date(currentTime); + + var intervalMilliseconds = 60 * 1000 * intervalMinutes; + while (currentTime.getTime() < endDateTime.getTime()) + { + nextTime.setTime(nextTime.getTime() + intervalMilliseconds); + + layout += getFormattedTime(currentTime) + ' - '; + layout += getFormattedTime(nextTime) + '\n'; + + currentTime.setTime(currentTime.getTime() + intervalMilliseconds); + } + + $('.reservableEdit:visible', elements.layoutDialog).val(layout); + $('.blockedEdit:visible', elements.layoutDialog).val(blocked); + } + }; + + var getFormattedTime = function (date) { + var hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(); + var minute = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(); + return hour + ":" + minute; + }; + + var autoFillBlocked = function () { + + function splitAndTrim(line) { + return _.map(_.split(line, '-'), _.trim); + } + + var blocked = ''; + + var reservableText = _.trim($('.reservableEdit:visible', elements.layoutDialog).val()); + var reservable = _.split(reservableText, "\n"); + if (reservable.length === 0) + { + $('.blockedEdit:visible', elements.layoutDialog).val("00:00 - 00:00"); + return; + } + + var startIndex = 0; + if (!_.startsWith(reservable[0], '00:00') && !_.startsWith(reservable[0], '0:00')) + { + blocked += "00:00 - " + splitAndTrim(reservable)[0] + "\n"; + startIndex = 1; + } + + for (var i = startIndex; i < reservable.length; i++) + { + var firstIteration = i === 0; + var lastIteration = i + 1 === reservable.length; + + if (_.isEmpty(_.trim(reservable[i]))) + { + continue; + } + + var current = splitAndTrim(reservable[i]); + var previous = null; + if (!firstIteration) + { + previous = splitAndTrim(reservable[i - 1]); + } + + if (!firstIteration && !lastIteration && current[0] != previous[1]) + { + blocked += previous[1] + " - " + current[0] + "\n"; + } + + if (lastIteration && current[1] != '00:00') + { + blocked += current[1] + ' - 00:00' + "\n"; + } + } + + $('.blockedEdit:visible', elements.layoutDialog).val(blocked); + }; + + var handleAddError = function (responseText) { + $('#addScheduleResults').text(responseText); + $('#addScheduleResults').show(); + }; + + var setActiveScheduleId = function (scheduleId) { + elements.activeId.val(scheduleId); + }; + + var getActiveScheduleId = function () { + return elements.activeId.val(); + }; + + var showChangeLayout = function (e, reservableDiv, blockedDiv, timezone, usesSingleLayout) { + elements.changeLayoutForm.find('.validationSummary ').addClass('no-show'); + $.each(reservableDiv, function (index, val) { + var slots = reformatTimeSlots($(val)); + $('#' + $(val).attr('ref')).val(slots); + }); + + $.each(blockedDiv, function (index, val) { + var slots = reformatTimeSlots($(val)); + $('#' + $(val).attr('ref')).val(slots); + }); + + elements.layoutTimezone.val(timezone.val()); + elements.usesSingleLayout.prop('checked', false); + + if (usesSingleLayout) + { + elements.usesSingleLayout.prop('checked', true); + } + elements.usesSingleLayout.trigger('change'); + + elements.layoutDialog.modal("show"); + }; + + var toggleLayoutChange = function (useSingleLayout) { + if (useSingleLayout) + { + $('#dailySlots').hide(); + $('#staticSlots').show(); + } + else + { + $('#staticSlots').hide(); + $('#dailySlots').show(); + } + }; + + var showDeleteDialog = function (e) { + var scheduleId = getActiveScheduleId(); + elements.deleteDestinationScheduleId.children().removeAttr('disabled'); + elements.deleteDestinationScheduleId.children('option[value="' + scheduleId + '"]').attr('disabled', 'disabled'); + elements.deleteDestinationScheduleId.val(''); + + elements.deleteDialog.modal('show'); + }; + + var reformatTimeSlots = function (div) { + var text = $.trim(div.text()); + text = text.replace(/\s\s+/g, ' '); + text = text.replace(/\s*,\s*/g, '\n'); + return text; + }; + + var showPeakTimesDialog = function (scheduleId) { + var peakPlaceHolder = $('[data-schedule-id=' + scheduleId + ']').find('.peakPlaceHolder'); + + var times = peakPlaceHolder.find('.peakTimes'); + var days = peakPlaceHolder.find('.peakDays'); + var months = peakPlaceHolder.find('.peakMonths'); + + if (times.length > 0) + { + var allDay = times.data('all-day'); + var startTime = times.data('start-time'); + var endTime = times.data('end-time'); + + var everyday = days.data('everyday'); + var days = days.data('weekdays').split(","); + + var allYear = months.data('all-year'); + var beginMonth = months.data('begin-month'); + var beginDay = months.data('begin-day'); + var endMonth = months.data('end-month'); + var endDay = months.data('end-day'); + + if (allDay == 1) + { + elements.peakAllDay.prop('checked', true); + } + else + { + elements.peakAllDay.prop('checked', false); + $('#peakStartTime').val(startTime); + $('#peakEndTime').val(endTime); + } + + elements.peakEveryDay.attr('checked', everyday == 1); + + _.each($('#peakDayList').find(':checked'), function (e) { + $(e).closest('label').button('toggle'); + }); + + _.each(days, function (day) { + $('#peakDay' + day).closest('label').button('toggle'); + }); + + if (allYear == 1) + { + elements.peakAllYear.prop('checked', true); + } + else + { + elements.peakAllYear.prop('checked', false); + $('#peakBeginMonth').val(beginMonth); + $('#peakBeginDay').val(beginDay); + $('#peakEndMonth').val(endMonth); + $('#peakEndDay').val(endDay); + } + + peakOnAllDayChanged(); + peakOnEveryDayChanged(); + peakOnAllYearChanged(); + } + + elements.deletePeakTimes.val(''); + elements.peakTimesDialog.modal('show'); + }; + + var peakOnEveryDayChanged = function () { + if ((elements.peakEveryDay).is(':checked')) + { + elements.peakDayList.addClass('no-show'); + } + else + { + elements.peakDayList.removeClass('no-show'); + } + }; + + var peakOnAllYearChanged = function () { + if ((elements.peakAllYear).is(':checked')) + { + elements.peakDateRange.addClass('no-show'); + } + else + { + elements.peakDateRange.removeClass('no-show'); + } + }; + + var peakOnAllDayChanged = function () { + if ((elements.peakAllDay).is(':checked')) + { + elements.peakTimes.addClass('no-show'); + } + else + { + elements.peakTimes.removeClass('no-show'); + } + }; + + var refreshPeakTimes = function (resultHtml) { + $('[data-schedule-id=' + getActiveScheduleId() + ']').find('.peakPlaceHolder').html(resultHtml); + elements.peakTimesDialog.modal('hide'); + }; + + var wireUpPeakTimeToggles = function () { + elements.peakEveryDay.on('click', function (e) { + peakOnEveryDayChanged(); + }); + + elements.peakAllYear.on('click', function (e) { + peakOnAllYearChanged(); + }); + + elements.peakAllDay.on('click', function (e) { + peakOnAllDayChanged(); + }); + }; + + var showAvailabilityDialog = function (scheduleId) { + var placeholder = $('[data-schedule-id=' + scheduleId + ']').find('.availabilityPlaceHolder'); + var dates = placeholder.find('.availableDates'); + + var hasAvailability = dates.data('has-availability') == '1'; + + // elements.availableAllYear.prop('checked', !hasAvailability); + elements.availableStartDateTextbox.datepicker("setDate", dates.data('start-date')); + elements.availableStartDate.trigger('change'); + + elements.availableEndDateTextbox.datepicker("setDate", dates.data('end-date')); + elements.availableEndDate.trigger('change'); + + if (!hasAvailability) + { + elements.availableAllYear.trigger('click'); + } + + elements.availabilityDialog.modal('show'); + }; + + var refreshAvailability = function (resultHtml) { + $('[data-schedule-id=' + getActiveScheduleId() + ']').find('.availabilityPlaceHolder').html(resultHtml); + elements.availabilityDialog.modal('hide'); + }; + + var toggleConcurrentReservations = function (scheduleId, toggle, container) { + var allow = toggle.data('allow') == 1; + if (allow) + { + container.find('.allowConcurrentYes').addClass('no-show'); + container.find('.allowConcurrentNo').removeClass('no-show'); + } + else + { + container.find('.allowConcurrentYes').removeClass('no-show'); + container.find('.allowConcurrentNo').addClass('no-show'); + } + elements.concurrentForm.submit(); + + toggle.data('allow', allow ? '0' : '1'); + }; + + var _fullCalendar = null; + var showChangeCustomLayout = function (scheduleId) { + var customLayoutScheduleId = scheduleId; + + $('#customLayoutDialog').unbind(); + + function updateEvent(event) { + elements.slotStartDate.val(event.start.format('YYYY-MM-DD HH:mm')); + elements.slotEndDate.val(event.end.format('YYYY-MM-DD HH:mm')); + elements.slotId.val(event.id); + ajaxPost(elements.layoutSlotForm, options.submitUrl + '?action=' + options.updateLayoutSlot + '&sid=' + getActiveScheduleId(), null, function (data) { + _fullCalendar.fullCalendar('refetchEvents'); + }); + } + + $('#customLayoutDialog').unbind('shown.bs.modal'); + $('#customLayoutDialog').on('shown.bs.modal', function () { + if (_fullCalendar != null) + { + _fullCalendar.fullCalendar('destroy'); + } + var calendar = $('#calendar'); + _fullCalendar = calendar.fullCalendar({ + header: { + left: 'prev,next,today', center: 'title', right: 'month,agendaWeek,agendaDay' + }, + buttonText: opts.calendarOptions.buttonText, + allDaySlot: false, + defaultDate: opts.calendarOptions.defaultDate, + defaultView: 'month', + eventSources: [{ + url: opts.calendarOptions.eventsUrl, type: 'GET', data: { + dr: 'events', sid: scheduleId + } + }], + dayClick: function (date, jsEvent, view) { + if (view.name == 'month') + { + calendar.fullCalendar('changeView', 'agendaDay'); + calendar.fullCalendar('gotoDate', date); + } + }, + selectable: true, + selectHelper: true, + editable: true, + droppable: true, + eventOverlap: false, + select: function (start, end, jsEvent, view) { + if (view.name != 'month') + { + elements.confirmCreateSlotDialog.show(); + elements.confirmCreateSlotDialog.position({ + my: 'left bottom', at: 'left top', of: jsEvent + }); + $('#confirmCreateOK').unbind('click'); + $('#confirmCreateOK').click(function (e) { + elements.slotStartDate.val(start.format('YYYY-MM-DD HH:mm')); + elements.slotEndDate.val(end.format('YYYY-MM-DD HH:mm')); + ajaxPost(elements.layoutSlotForm, options.submitUrl + '?action=' + options.addLayoutSlot + '&sid=' + getActiveScheduleId(), null, function () { + _fullCalendar.fullCalendar('refetchEvents'); + elements.confirmCreateSlotDialog.hide(); + }); + }); + } + }, + eventClick: function (event, jsEvent, view) { + elements.deleteSlotStartDate.val(event.start.format('YYYY-MM-DD HH:mm')); + elements.deleteSlotEndDate.val(event.end.format('YYYY-MM-DD HH:mm')); + elements.deleteCustomLayoutDialog.show(); + elements.deleteCustomLayoutDialog.position({ + my: 'left bottom', at: 'left top', of: jsEvent + }); + }, + eventDrop: function (event, delta, revertFunc) { + updateEvent(event); + }, + eventResize: function (event, delta, revertFunc, jsEvent, ui, view) { + updateEvent(event); + } + }); + }); + + $('#customLayoutDialog').modal('show'); + }; + + function afterDeleteSlot() { + elements.deleteCustomLayoutDialog.hide(); + _fullCalendar.fullCalendar('refetchEvents'); + } } \ No newline at end of file diff --git a/build.xml b/build.xml index 78c8e1c7a..20f84c3ba 100644 --- a/build.xml +++ b/build.xml @@ -1,6 +1,6 @@ - + diff --git a/lang/en_us.php b/lang/en_us.php index 2baf321f8..19aefad62 100755 --- a/lang/en_us.php +++ b/lang/en_us.php @@ -783,6 +783,10 @@ protected function _LoadStrings() $strings['Custom'] = 'Custom'; $strings['AddDate'] = 'Add Date'; $strings['RepeatOn'] = 'Repeat On'; + $strings['ScheduleConcurrentMaximum'] = 'A total of %s resources may be reserved concurrently'; + $strings['ScheduleConcurrentMaximumNone'] = 'There is no limit to the number of concurrent reserved resources'; + $strings['ScheduleMaximumConcurrent'] = 'Maximum number of resources reserved concurrently'; + $strings['ScheduleMaximumConcurrentNote'] = 'When set, the total number of resources that can be reserved concurrently for this schedule will be limited.'; // End Strings // Install diff --git a/lib/Config/Configuration.php b/lib/Config/Configuration.php index c4e17fc40..a1dfe1a0b 100644 --- a/lib/Config/Configuration.php +++ b/lib/Config/Configuration.php @@ -99,7 +99,7 @@ class Configuration implements IConfiguration const DEFAULT_CONFIG_ID = 'booked'; const DEFAULT_CONFIG_FILE_PATH = 'config/config.php'; - const VERSION = '2.7.8'; + const VERSION = '2.8.1'; protected function __construct() { diff --git a/lib/Database/Commands/Commands.php b/lib/Database/Commands/Commands.php index 96f982f19..daa8e0d9f 100644 --- a/lib/Database/Commands/Commands.php +++ b/lib/Database/Commands/Commands.php @@ -2663,7 +2663,9 @@ public function __construct($scheduleId, Date $availabilityBegin, Date $availabilityEnd, $allowConcurrentReservations, - $defaultStyle) + $defaultStyle, + $totalConcurrentReservations +) { parent::__construct(Queries::UPDATE_SCHEDULE); @@ -2679,6 +2681,7 @@ public function __construct($scheduleId, $this->AddParameter(new Parameter(ParameterNames::SCHEDULE_AVAILABILITY_END, $availabilityEnd->ToDatabase())); $this->AddParameter(new Parameter(ParameterNames::SCHEDULE_ALLOW_CONCURRENT_RESERVATIONS, (int)$allowConcurrentReservations)); $this->AddParameter(new Parameter(ParameterNames::SCHEDULE_DEFAULT_STYLE, (int)$defaultStyle)); + $this->AddParameter(new Parameter(ParameterNames::SCHEDULE_TOTAL_CONCURRENT_RESERVATIONS, (int)$totalConcurrentReservations)); } } diff --git a/lib/Database/Commands/ParameterNames.php b/lib/Database/Commands/ParameterNames.php index 18eb7c2ff..88af38905 100644 --- a/lib/Database/Commands/ParameterNames.php +++ b/lib/Database/Commands/ParameterNames.php @@ -241,6 +241,7 @@ private function __construct() const SCHEDULE_AVAILABILITY_END = '@end_date'; const SCHEDULE_ALLOW_CONCURRENT_RESERVATIONS = '@allow_concurrent_bookings'; const SCHEDULE_DEFAULT_STYLE = '@default_layout'; + const SCHEDULE_TOTAL_CONCURRENT_RESERVATIONS = '@total_concurrent_reservations'; const SERIES_ID = '@seriesid'; const SESSION_TOKEN = '@session_token'; const START_DATE = '@startDate'; diff --git a/lib/Database/Commands/Queries.php b/lib/Database/Commands/Queries.php index 9df0d97b2..c5086d122 100644 --- a/lib/Database/Commands/Queries.php +++ b/lib/Database/Commands/Queries.php @@ -1140,7 +1140,8 @@ private function __construct() `start_date` = @start_date, `end_date` = @end_date, `allow_concurrent_bookings` = @allow_concurrent_bookings, - `default_layout` = @default_layout + `default_layout` = @default_layout, + `total_concurrent_reservations` = @total_concurrent_reservations WHERE `schedule_id` = @scheduleid'; diff --git a/lib/Server/FormKeys.php b/lib/Server/FormKeys.php index 2fc4b9919..5658fb090 100644 --- a/lib/Server/FormKeys.php +++ b/lib/Server/FormKeys.php @@ -151,6 +151,8 @@ private function __construct() const MIN_NOTICE_NONE_DELETE = 'minNoticeNoneDelete'; const MAX_NOTICE = 'maxNotice'; const MAX_NOTICE_NONE = 'maxNoticeNone'; + const MAXIMUM_CONCURRENT_UNLIMITED = 'MAXIMUM_CONCURRENT_UNLIMITED'; + const MAXIMUM_CONCURRENT_RESERVATIONS = 'MAXIMUM_CONCURRENT_RESERVATIONS'; const NAME = 'name'; const NOTES = 'notes'; diff --git a/readme.html b/readme.html index b48a90f9b..dac3abf88 100644 --- a/readme.html +++ b/readme.html @@ -25,6 +25,11 @@

General Application Help

Release Notes

+

2.8.1

+
    +
  • Added ability to limit the total number of concurrent reservations for a schedule
  • +
+

2.7.8

  • Added ability to repeat a reservation on non-sequential dates
  • diff --git a/tests/Application/Reservation/ScheduleTotalConcurrentReservationsRuleTests.php b/tests/Application/Reservation/ScheduleTotalConcurrentReservationsRuleTests.php index 0cd4a8525..dbab366cd 100644 --- a/tests/Application/Reservation/ScheduleTotalConcurrentReservationsRuleTests.php +++ b/tests/Application/Reservation/ScheduleTotalConcurrentReservationsRuleTests.php @@ -43,7 +43,7 @@ public function setUp(): void $this->scheduleRepository->_Schedule = new FakeSchedule(); $this->reservationViewRepository = new FakeReservationViewRepository(); - $this->rule = new ScheduleTotalConcurrentReservationsRule($this->scheduleRepository, $this->reservationViewRepository); + $this->rule = new ScheduleTotalConcurrentReservationsRule($this->scheduleRepository, $this->reservationViewRepository, "UTC"); } public function testValidWhenScheduleIsUnlimited() @@ -73,7 +73,7 @@ public function testValidWhenUpdating() $series = (new ExistingReservationSeriesBuilder())->WithPrimaryResource(new FakeBookableResource($resourceId)) ->WithCurrentInstance(new TestReservation($refNum, new DateRange($start, $end)))->Build(); - $this->scheduleRepository->_Schedule->SetTotalConcurrentReservations(1); + $this->scheduleRepository->_Schedule->SetTotalConcurrentReservations(count($series->AllResourceIds())); $result = $this->rule->Validate($series, null); diff --git a/tests/Domain/Schedule/ScheduleRepositoryTests.php b/tests/Domain/Schedule/ScheduleRepositoryTests.php index 2c10b6a8c..23c067929 100644 --- a/tests/Domain/Schedule/ScheduleRepositoryTests.php +++ b/tests/Domain/Schedule/ScheduleRepositoryTests.php @@ -315,6 +315,7 @@ public function testCanUpdateSchedule() $end = Date::Now()->AddDays(1); $allowConcurrent = true; $style = ScheduleStyle::CondensedWeek; + $maxConcurrent = 10; $schedule = new Schedule($id, $name, @@ -328,10 +329,11 @@ public function testCanUpdateSchedule() $schedule->SetAvailability($begin, $end); $schedule->SetAllowConcurrentReservations($allowConcurrent); $schedule->SetDefaultStyle($style); + $schedule->SetTotalConcurrentReservations($maxConcurrent); $this->scheduleRepository->Update($schedule); - $this->assertEquals(new UpdateScheduleCommand($id, $name, $isDefault, $weekdayStart, $daysVisible, $subscriptionEnabled, $publicId, $adminGroupId, $begin, $end, $allowConcurrent, $style), + $this->assertEquals(new UpdateScheduleCommand($id, $name, $isDefault, $weekdayStart, $daysVisible, $subscriptionEnabled, $publicId, $adminGroupId, $begin, $end, $allowConcurrent, $style, $maxConcurrent), $this->db->_LastCommand); } diff --git a/tpl/Admin/Schedules/manage_schedules.tpl b/tpl/Admin/Schedules/manage_schedules.tpl index b820f195e..9501c9e09 100644 --- a/tpl/Admin/Schedules/manage_schedules.tpl +++ b/tpl/Admin/Schedules/manage_schedules.tpl @@ -20,90 +20,104 @@ along with Booked Scheduler. If not, see .
    -

    {translate key=ManageSchedules}

    - -
    -
    {translate key="AllSchedules"} - {translate key="AddSchedule"} - - -
    -
    +

    {translate key=ManageSchedules}

    + +
    +
    {translate key="AllSchedules"} + {translate key="AddSchedule"} + + +
    +
    {foreach from=$Schedules item=schedule} {assign var=id value=$schedule->GetId()} {capture name=daysVisible}{$schedule->GetDaysVisible()}{/capture} + data-pk='{$id}' + data-name='{FormKeys::SCHEDULE_DAYS_VISIBLE}' + data-min='0'>{$schedule->GetDaysVisible()}{/capture} {assign var=dayOfWeek value=$schedule->GetWeekdayStart()} {capture name=dayName}{if $dayOfWeek == Schedule::Today}{$Today}{else}{$DayNames[$dayOfWeek]}{/if}{/capture} -
    -
    - - - - -
    + data-pk='{$id}' + data-name='{FormKeys::SCHEDULE_WEEKDAY_START}' + data-value='{$dayOfWeek}'>{if $dayOfWeek == Schedule::Today}{$Today}{else}{$DayNames[$dayOfWeek]}{/if}{/capture} +
    +
    + + + + +
    {$schedule->GetName()} - {translate key=Rename} -
    + data-name="{FormKeys::SCHEDULE_NAME}">{$schedule->GetName()} + {translate key=Rename} +
    -
    {translate key="LayoutDescription" args="{$smarty.capture.dayName}, {$smarty.capture.daysVisible}"}
    +
    {translate key="LayoutDescription" args="{$smarty.capture.dayName}, {$smarty.capture.daysVisible}"}
    -
    {translate key='ScheduleAdministrator'} - {$GroupLookup[$schedule->GetAdminGroupId()]->Name} +
    {translate key='ScheduleAdministrator'} + {$GroupLookup[$schedule->GetAdminGroupId()]->Name} {if $AdminGroups|count > 0} - - {translate key='ScheduleAdministrator'} - + + {translate key='ScheduleAdministrator'} + + {/if} -
    +
    -
    -
    +
    +
    {include file="Admin/Schedules/manage_availability.tpl" schedule=$schedule timezone=$Timezone} -
    - - Change Availability - - -
    - -
    - {translate key=ConcurrentYes} - {translate key=ConcurrentNo} - {translate key=Change} -
    - -
    +
    + + Change Availability + + +
    + +
    + {translate key=ConcurrentYes} + {translate key=ConcurrentNo} + {translate key=Change} +
    + + +
    + {if $schedule->EnforceConcurrentReservationMaximum()} + {translate key=ScheduleConcurrentMaximum args=$schedule->GetTotalConcurrentReservations()} + {else} + {translate key=ScheduleConcurrentMaximumNone} + {/if} + + {translate key='ScheduleMaximumConcurrent'} + + +
    + +
    {translate key=DefaultStyle} - {$StyleNames[$schedule->GetDefaultStyle()]} -
    + {$StyleNames[$schedule->GetDefaultStyle()]} +
    {if $CreditsEnabled} - {translate key=PeakTimes} - - {translate key=PeakTimes} - - -
    + {translate key=PeakTimes} + + {translate key=PeakTimes} + + +
    {include file="Admin/Schedules/manage_peak_times.tpl" Layout=$Layouts[$id] Months=$Months DayNames=$DayNames} -
    +
    {/if} -
    {translate key=Resources} - +
    {translate key=Resources} + {if array_key_exists($id, $Resources)} {foreach from=$Resources[$id] item=r name=resources_loop} {$r->GetName()|escape}{if !$smarty.foreach.resources_loop.last}, {/if} @@ -112,18 +126,18 @@ along with Booked Scheduler. If not, see . {translate key=None} {/if} -
    +
    {if $schedule->GetIsCalendarSubscriptionAllowed()} -
    - {translate key=PublicId} - {$schedule->GetPublicId()} -
    +
    + {translate key=PublicId} + {$schedule->GetPublicId()} +
    {/if} -
    +
    -
    +
    {function name="display_periods"} {foreach from=$Layouts[$id]->GetSlots($day) item=period name=layouts} {if $period->IsReservable() == $showReservable} @@ -138,545 +152,579 @@ along with Booked Scheduler. If not, see . {/foreach} {/function} -
    +
    {translate key=ScheduleLayout args=$schedule->GetTimezone()} - + - {translate key=ChangeLayout} - -
    - + data-layout-type="{$Layouts[$id]->GetType()}"> + {translate key=ChangeLayout} + +
    + {if $Layouts[$id]->UsesDailyLayouts()} - + {translate key=LayoutVariesByDay} - - {translate key=ShowHide} -
    + {translate key=ShowHide} +
    {foreach from=DayOfWeek::Days() item=day} {$DayNames[$day]} -
    +
    {display_periods showReservable=true day=$day} -
    -
    +
    +
    {display_periods showReservable=false day=$day} -
    +
    {/foreach} -
    -
    {translate key=ThisScheduleUsesAStandardLayout} -
    - +
    +
    {translate key=ThisScheduleUsesAStandardLayout} +
    + {elseif $Layouts[$id]->UsesCustomLayout()} -
    {translate key=ThisScheduleUsesACustomLayout}
    - +
    {translate key=ThisScheduleUsesACustomLayout}
    + {else} - + {translate key=ReservableTimeSlots} -
    +
    {display_periods showReservable=true day=null} -
    +
    {translate key=BlockedTimeSlots} -
    +
    {display_periods showReservable=false day=null} -
    -
    {translate key=ThisScheduleUsesAStandardLayout} -
    - +
    +
    {translate key=ThisScheduleUsesAStandardLayout} +
    + {/if} -
    -
    +
    +
    {if $schedule->GetIsDefault()} - {translate key=ThisIsTheDefaultSchedule} - | - {translate key=DefaultScheduleCannotBeDeleted} - | + {translate key=ThisIsTheDefaultSchedule} + | + {translate key=DefaultScheduleCannotBeDeleted} + | {else} - {translate key=MakeDefault} - | - {translate key=Delete} - | + {translate key=MakeDefault} + | + {translate key=Delete} + | {/if} {if $schedule->GetIsCalendarSubscriptionAllowed()} - {translate key=TurnOffSubscription} - | + {translate key=TurnOffSubscription} + | {else} - {translate key=TurnOnSubscription} + {translate key=TurnOnSubscription} {/if} {if $schedule->GetIsCalendarSubscriptionAllowed()} {html_image src="feed.png"} - Atom - | - iCalendar + Atom + | + iCalendar {/if} {indicator id="action-indicator"} -
    -
    -
    +
    +
    +
    {/foreach} -
    -
    +
    +
    {pagination pageInfo=$PageInfo} - - -
    - - - - -
    +
    {cancel_button id="cancelCreateSlot"} {add_button id="confirmCreateOK"} -
    +
    + + {control type="DatePickerSetupControl" ControlId="availabilityStartDate" AltId="formattedBeginDate" DefaultDate=$StartDate} {control type="DatePickerSetupControl" ControlId="availabilityEndDate" AltId="formattedEndDate" DefaultDate=$EndDate} @@ -687,111 +735,108 @@ along with Booked Scheduler. If not, see . {jsfile src="admin/schedule.js"} {jsfile src="js/jquery.form-3.09.min.js"} - + ] + }); + } + + $(document).ready(function () { + setUpEditables(); + + var opts = { + submitUrl: '{$smarty.server.SCRIPT_NAME}', + saveRedirect: '{$smarty.server.SCRIPT_NAME}', + changeLayoutAction: '{ManageSchedules::ActionChangeLayout}', + addAction: '{ManageSchedules::ActionAdd}', + peakTimesAction: '{ManageSchedules::ActionChangePeakTimes}', + makeDefaultAction: '{ManageSchedules::ActionMakeDefault}', + deleteAction: '{ManageSchedules::ActionDelete}', + availabilityAction: '{ManageSchedules::ActionChangeAvailability}', + enableSubscriptionAction: '{ManageSchedules::ActionEnableSubscription}', + disableSubscriptionAction: '{ManageSchedules::ActionDisableSubscription}', + toggleConcurrentReservations: '{ManageSchedules::ActionToggleConcurrentReservations}', + switchLayout: '{ManageSchedules::ActionSwitchLayoutType}', + addLayoutSlot: '{ManageSchedules::ActionAddLayoutSlot}', + updateLayoutSlot: '{ManageSchedules::ActionUpdateLayoutSlot}', + deleteLayoutSlot: '{ManageSchedules::ActionDeleteLayoutSlot}', + maximumConcurrentAction: '{ManageSchedules::ActionChangeMaximumConcurrent}', + calendarOptions: { + buttonText: { + today: '{{translate key=Today}|escape:'javascript'}', + month: '{{translate key=Month}|escape:'javascript'}', + week: '{{translate key=Week}|escape:'javascript'}', + day: '{{translate key=Day}|escape:'javascript'}' + }, defaultDate: moment('{Date::Now()->ToTimezone({$Timezone})->Format('Y-m-d')}', 'YYYY-MM-DD'), eventsUrl: '{$smarty.server.SCRIPT_NAME}' + } + }; + + var scheduleManagement = new ScheduleManagement(opts); + scheduleManagement.init(); + + $('.timepicker').timepicker({ + timeFormat: '{$TimeFormat}' + }); + + + }); + + {include file='globalfooter.tpl'} \ No newline at end of file