├── .gitignore ├── README.md ├── css ├── rooms_date_range_fields.css ├── rooms_modal.css ├── rooms_options_widget.css └── rooms_ui.css ├── images ├── bg-shade-light.png └── close.png ├── includes ├── rooms.calendar.inc ├── rooms.calendar_interface.inc ├── rooms.event.inc └── rooms.event_interface.inc ├── js ├── rooms_date_popup.js └── rooms_fullcalendar_singlerowmonth.js ├── modules ├── rooms_availability │ ├── css │ │ ├── fullcalendar.theme.css │ │ ├── rooms_availability.css │ │ └── rooms_three_month_calendar.css │ ├── includes │ │ ├── rooms_availability.booking_event.inc │ │ ├── rooms_availability.booking_event_interface.inc │ │ ├── rooms_availability.unit_calendar.inc │ │ └── rooms_availability.unit_calendar_interface.inc │ ├── js │ │ ├── rooms_availability.js │ │ ├── rooms_availability_full_day.js │ │ ├── rooms_color.js │ │ ├── rooms_unit_management.js │ │ └── rooms_unit_management_full_day.js │ ├── rooms_availability.api.php │ ├── rooms_availability.info │ ├── rooms_availability.install │ └── rooms_availability.module ├── rooms_availability_reference │ ├── css │ │ └── rooms_availability_reference_calendar.css │ ├── js │ │ ├── rooms_availability_reference.js │ │ └── rooms_availability_reference_full_day.js │ ├── rooms_availability_field_calendar.tpl.php │ ├── rooms_availability_reference.info │ ├── rooms_availability_reference.install │ └── rooms_availability_reference.module ├── rooms_booking │ ├── css │ │ └── rooms_booking.css │ ├── includes │ │ ├── rooms_booking.availability_agent.inc │ │ └── rooms_booking.availability_agent_filter.inc │ ├── js │ │ └── rooms_booking.js │ ├── rooms_booking-extra-data.tpl.php │ ├── rooms_booking.admin.inc │ ├── rooms_booking.api.php │ ├── rooms_booking.info │ ├── rooms_booking.install │ ├── rooms_booking.module │ ├── rooms_booking.tpl.php │ ├── rooms_booking_type.admin.inc │ └── views │ │ ├── rooms_booking.views.inc │ │ ├── rooms_booking_handler_booking_operations_field.inc │ │ ├── rooms_booking_handler_booking_type_field.inc │ │ ├── rooms_booking_handler_date_field.inc │ │ ├── rooms_booking_handler_date_filter.inc │ │ ├── rooms_booking_handler_delete_link_field.inc │ │ ├── rooms_booking_handler_edit_link_field.inc │ │ ├── rooms_booking_handler_field_booking_status.inc │ │ ├── rooms_booking_handler_filter_booking_status.inc │ │ ├── rooms_booking_handler_link_field.inc │ │ ├── rooms_booking_handler_nights_field.inc │ │ ├── rooms_booking_handler_unit_field.inc │ │ └── rooms_booking_handler_unit_filter.inc ├── rooms_booking_manager │ ├── css │ │ ├── rooms_booking_search.css │ │ └── rooms_sticky_booking_legend.css │ ├── includes │ │ └── rooms_booking_manager.fields.inc │ ├── js │ │ ├── rooms_booking_manager_checked.js │ │ └── rooms_sticky_booking_legend.js │ ├── rooms_booking_email.tpl.php │ ├── rooms_booking_enquiry_confirmation.tpl.php │ ├── rooms_booking_manager.api.php │ ├── rooms_booking_manager.availability_search.inc │ ├── rooms_booking_manager.commerce.inc │ ├── rooms_booking_manager.confirmation_override.inc │ ├── rooms_booking_manager.info │ ├── rooms_booking_manager.install │ ├── rooms_booking_manager.module │ ├── rooms_booking_manager.rules.inc │ ├── rooms_booking_manager.rules_defaults.inc │ ├── rooms_booking_manager.units_per_type_form.inc │ ├── rooms_booking_manager.variable.inc │ ├── rooms_booking_override_confirmation_page.tpl.php │ ├── rooms_booking_results.tpl.php │ └── views │ │ └── rooms_booking_manager.views.inc ├── rooms_pricing │ ├── css │ │ └── rooms_pricing.css │ ├── includes │ │ ├── rooms_pricing.pricing_event.inc │ │ ├── rooms_pricing.pricing_event_interface.inc │ │ ├── rooms_pricing.unit_pricing_calendar.inc │ │ └── rooms_pricing.unit_pricing_calendar_interface.inc │ ├── js │ │ ├── rooms_pricing.js │ │ └── rooms_pricing_management.js │ ├── modules │ │ └── rooms_periodic_pricing │ │ │ ├── css │ │ │ └── rooms_periodic_pricing.css │ │ │ ├── rooms_periodic_pricing.info │ │ │ ├── rooms_periodic_pricing.install │ │ │ └── rooms_periodic_pricing.module │ ├── rooms_pricing.api.php │ ├── rooms_pricing.info │ ├── rooms_pricing.install │ └── rooms_pricing.module └── rooms_unit │ ├── css │ ├── rooms_unit.css │ └── rooms_unit_type.css │ ├── js │ └── rooms_unit.js │ ├── rooms_unit-extra-data.tpl.php │ ├── rooms_unit.admin.inc │ ├── rooms_unit.features.inc │ ├── rooms_unit.info │ ├── rooms_unit.install │ ├── rooms_unit.migrate.inc │ ├── rooms_unit.module │ ├── rooms_unit.tpl.php │ ├── rooms_unit_type.admin.inc │ └── views │ ├── rooms_unit.views.inc │ ├── rooms_unit_handler_delete_link_field.inc │ ├── rooms_unit_handler_edit_link_field.inc │ ├── rooms_unit_handler_link_field.inc │ ├── rooms_unit_handler_unit_operations_field.inc │ └── rooms_unit_handler_unit_type_field.inc ├── rooms.api.php ├── rooms.info ├── rooms.install ├── rooms.make ├── rooms.module ├── rooms.variable.inc ├── rooms_three_month_calendar.tpl.php ├── rooms_ui.info ├── rooms_ui.module ├── test ├── composer.json ├── default.behat.yml └── features │ ├── availability.feature │ ├── availability_constraints.feature │ ├── availability_filter.feature │ ├── availability_reference.feature │ ├── booking.feature │ ├── bootstrap │ └── FeatureContext.php │ ├── example.feature │ ├── installation.feature │ ├── package.feature │ ├── pricing.feature │ ├── unit.feature │ └── unit_options.feature └── translations └── it.po /.gitignore: -------------------------------------------------------------------------------- 1 | #ignore environment specific behat files 2 | test/behat.yml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ROOMS 7.x-1.x 2 | ------------- 3 | Rooms is a family of modules that allow you to manage bookings primarily for the accommodation industry it is suitable for hotels, B&Bs, vacation rentals and agencies that manage multiple properties. 4 | 5 | If you are looking to create a fully customized booking solution for scenarios such as the ones above from scratch install Rooms on a vanilla Drupal site with all its dependencies. You will be able to use standard Drupal techniques to customize further. 6 | 7 | We have updated documetation on Rooms 1.9 and over here: 8 | [General Documentation](http://docs.roomify.us) 9 | 10 | 11 | If instead you are looking to get a head start checkout out roomify.us for access to our open-source ready made solutions for: 12 | 13 | Vacation Rentals - [Casa](https://roomify.us/casa-vacation-rental-websites) 14 | 15 | Hotels and B&Bs - [Locanda](https://roomify.us/locanda-hotel-website) 16 | 17 | Multiple Properties - [Agency](https://roomify.us/roomify-agency-for-multiple-property-rentals) 18 | -------------------------------------------------------------------------------- /css/rooms_date_range_fields.css: -------------------------------------------------------------------------------- 1 | /* Theming for rooms date range fields. */ 2 | 3 | .rooms-date-range .start-date, 4 | .rooms-date-range .end-date { 5 | display: inline-block; 6 | clear: none; 7 | margin: 0; 8 | } 9 | 10 | .rooms-date-range .start-date { 11 | margin-right: 20px; /* LTR */ 12 | } 13 | 14 | 15 | /* Start and end date fields */ 16 | .js .rooms-date-range .description { 17 | display: none; 18 | } 19 | .rooms-date-range .form-text { 20 | width: 10em; 21 | } 22 | .rooms-date-range .form-item { 23 | margin-right: 0; 24 | } 25 | .rooms-date-range .container-inline-date > .form-item { 26 | margin-right: 0; 27 | display: block; 28 | } 29 | .rooms-date-range .container-inline-date .form-item .form-item { 30 | float: none; 31 | } 32 | .rooms-date-range .container-inline-date .form-item input { 33 | margin-right: 0; 34 | width: 10em; 35 | } 36 | .rooms-date-range .form-type-textfield label { 37 | display: none; 38 | } 39 | .rooms-date-range .date-padding, 40 | .rooms-date-range .container-inline-date .date-padding { 41 | padding: 0; 42 | float: none; 43 | } 44 | -------------------------------------------------------------------------------- /css/rooms_modal.css: -------------------------------------------------------------------------------- 1 | /* When modals are enabled, add hover effect to calendar */ 2 | .fc-event:hover { 3 | cursor: pointer; 4 | } 5 | .fc-widget-content:hover { 6 | cursor: default; 7 | background: rgba(230,230,230, 0.2); 8 | } 9 | 10 | /* Make modals responsive */ 11 | .page-admin-rooms #modalContent, 12 | .page-admin-rooms #modalContent .ctools-modal-content, 13 | .page-admin-rooms #modalContent .ctools-modal-content .modal-content { 14 | max-width: 100% !important; 15 | } 16 | 17 | /* Account for the extra div coming from ctools_modal_form_render() */ 18 | .page-admin-rooms .messages div.messages { 19 | background: none; 20 | border: none; 21 | margin: 0; 22 | padding: 0; 23 | -webkit-border-radius: 0; 24 | -moz-border-radius: 0; 25 | border-radius: 0; 26 | } 27 | .page-admin-rooms .ctools-modal-content { 28 | font-size: 12px; 29 | border: solid 1px #ddd; 30 | -webkit-border-radius: 0.5em; 31 | -moz-border-radius: 0.5em; 32 | border-radius: 0.5em; 33 | -webkit-box-shadow: -1em 1em 1em rgba(0, 0, 0, 0.5); 34 | -moz-box-shadow: -1em 1em 1em rgba(0, 0, 0, 0.5); 35 | box-shadow: -1em 1em 1em rgba(0, 0, 0, 0.5); 36 | background: #FFF; 37 | } 38 | .page-admin-rooms .ctools-modal-content .modal-header { 39 | background: #fff url('../images/bg-shade-light.png') repeat-x bottom left; 40 | color: #777; 41 | display: block; 42 | font-weight: 700; 43 | letter-spacing: normal; 44 | padding: 0.25em 1em; 45 | -webkit-border-radius: 0.5em 0.5em 0 0; 46 | -moz-border-radius: 0.5em 0.5em 0 0; 47 | border-radius: 0.5em 0.5em 0 0; 48 | } 49 | .page-admin-rooms .ctools-modal-content .modal-title { 50 | font-size: 120%; 51 | font-weight: bold; 52 | text-shadow: #fff 1px 1px 1px; 53 | color: #555; 54 | } 55 | .page-admin-rooms .ctools-modal-content .modal-content { 56 | width: 96% !important; 57 | padding: 15px 2%; 58 | -webkit-box-sizing: content-box; 59 | -moz-box-sizing: content-box; 60 | box-sizing: content-box; 61 | } 62 | .page-admin-rooms .ctools-modal-content a.close { 63 | color: #666; 64 | font-weight: normal; 65 | padding-left: 1.6em; 66 | background: url('../images/close.png') no-repeat 0 50%; 67 | } 68 | .page-admin-rooms .ctools-modal-content a.close img { 69 | display: none; 70 | } 71 | /** modal forms CSS **/ 72 | .page-admin-rooms .ctools-modal-content .form-item label { 73 | width: 100%; 74 | float: none; 75 | clear: both; 76 | } 77 | .page-admin-rooms .ctools-modal-content .resizable-textarea { 78 | width: 100%; 79 | margin-left: 0; 80 | margin-right: 0; 81 | } 82 | 83 | /* Override rooms form layouts when they appear inside CTools modals */ 84 | .page-admin-rooms .modal-content .rooms-management-form fieldset, 85 | .page-admin-rooms .modal-content .rooms-management-form .form-wrapper { 86 | float: none !important; 87 | width: 100% !important; 88 | max-width: 100% !important; 89 | margin-right: 0 !important; 90 | } 91 | .page-admin-rooms .modal-content .rooms-management-form .form-item label { 92 | float: none; 93 | width: auto; 94 | } 95 | .page-admin-rooms .modal-content .rooms-management-form .form-type-select, 96 | .page-admin-rooms .modal-content .rooms-management-form .form-item-operation, 97 | .page-admin-rooms .modal-content .rooms-management-form .form-item-amount, 98 | .page-admin-rooms .modal-content .rooms-management-form .form-actions { 99 | float: none; 100 | margin: 0; 101 | clear: both; 102 | } 103 | .page-admin-rooms .modal-content .rooms-management-form .rooms-date-range, 104 | .page-admin-rooms .modal-content .rooms-management-form .form-item-data-group-size, 105 | .page-admin-rooms .modal-content .rooms-management-form .rooms-booking-group-size-children-container { 106 | float: left !important; 107 | margin-right: 4% !important; 108 | width: 45% !important; 109 | max-width: 45% !important; 110 | min-width: 125px !important; 111 | clear: none; 112 | } 113 | -------------------------------------------------------------------------------- /css/rooms_options_widget.css: -------------------------------------------------------------------------------- 1 | .field-widget-rooms-options-combined .field-multiple-table .form-item { 2 | float: left; 3 | margin-right: 20px; 4 | } 5 | 6 | .field-widget-rooms-options-combined .rooms-option--remove-button { 7 | clear: both; 8 | float: right; 9 | } 10 | 11 | .field-widget-rooms-options-combined .ajax-progress { 12 | float: right; 13 | margin: 5px 10px 0 0; 14 | } 15 | -------------------------------------------------------------------------------- /css/rooms_ui.css: -------------------------------------------------------------------------------- 1 | /* Some Rooms admin pages use tables for layout, so let's kill this default. */ 2 | tbody { 3 | border-top: none; 4 | } 5 | 6 | /* General styles for all Rooms management forms */ 7 | .rooms-management-form:after { 8 | content: "."; 9 | display: block; 10 | height: 0; 11 | clear: both; 12 | visibility: hidden; 13 | } 14 | 15 | .rooms-management-form .form-item { 16 | padding: 0; 17 | margin-top: 0; 18 | margin-bottom: 0; 19 | } 20 | .rooms-management-form .form-item label { 21 | margin: 0; 22 | display: block; 23 | } 24 | .rooms-management-form .form-type-item label { 25 | display: inline; 26 | } 27 | 28 | .rooms-management-form .form-type-radio label, 29 | .rooms-management-form .form-type-checkbox label { 30 | display: inline; 31 | } 32 | 33 | .rooms-management-form .form-item-name .form-text { 34 | width: 100%; 35 | } 36 | 37 | .rooms-management-form .form-type-select, 38 | .rooms-management-form .form-item-operation, 39 | .rooms-management-form .form-item-amount, 40 | .rooms-management-form .rooms-date-range, 41 | .rooms-management-form .form-actions { 42 | float: left; /* LTR */ 43 | margin: 0 20px 0 0; /* LTR */ 44 | clear: none; 45 | } 46 | 47 | .rooms-management-form .form-actions { 48 | padding-top: 1.6em; 49 | } 50 | 51 | /* Fieldsets are floated by default, but if its collapsible, dont float it*/ 52 | .rooms-management-form fieldset { 53 | padding: 0; 54 | margin: 0; 55 | border: none; 56 | float: left; /* LTR */ 57 | width: auto; 58 | } 59 | .rooms-management-form fieldset.collapsible { 60 | float: none; 61 | clear: both; 62 | padding: 0; 63 | margin: 20px 0; 64 | } 65 | .rooms-management-form .fieldset-legend, 66 | .rooms-management-form .fieldset-wrapper { 67 | margin: 0; 68 | padding: 0; 69 | position: relative; 70 | text-transform: none; 71 | } 72 | .rooms-management-form .fieldset-description { 73 | padding: 15px 0; 74 | } 75 | 76 | /* Fix for floated fieldsets around vertical tabs */ 77 | .rooms-management-form .vertical-tabs, 78 | .rooms-management-form .field-group-tab-wrapper { 79 | clear: both; 80 | } 81 | 82 | .rooms-month-manager .unit-name, 83 | .rooms-month-manager .unit-days { 84 | border-bottom: 1px solid #ccc !important; 85 | } 86 | 87 | /* Booking */ 88 | .view-booking-cart-form table, 89 | .view-booking-checkout-form table, 90 | .commerce-checkout-form-review table { 91 | width: 100%; 92 | } 93 | .views-field-line-item-title, 94 | .views-field-line-item-label { 95 | width: 67%; 96 | } 97 | .page-bookings .views-field-commerce-total { 98 | text-align: right; 99 | padding-right: 1em; 100 | } 101 | .page-bookings .component-total { 102 | text-align: right; 103 | padding-right: 1em; 104 | } 105 | .page-bookings .views-field-commerce-unit-price, 106 | .page-bookings .component-title { 107 | padding-left: 1em; 108 | padding-right: 1em; 109 | } 110 | .commerce-line-item-actions { 111 | margin: 20px 1em 20px 0; 112 | } 113 | -------------------------------------------------------------------------------- /images/bg-shade-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roomify/rooms/a51d50c2547b7d683af121917b144775c09c9723/images/bg-shade-light.png -------------------------------------------------------------------------------- /images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roomify/rooms/a51d50c2547b7d683af121917b144775c09c9723/images/close.png -------------------------------------------------------------------------------- /includes/rooms.calendar.inc: -------------------------------------------------------------------------------- 1 | monthDefined($event->startMonth(), $event->startYear())) { 41 | $partial_month_row = $this->preparePartialMonthArray($event); 42 | $update = db_update($this->base_table) 43 | ->condition('unit_id', $this->unit_id) 44 | ->condition('month', $event->startMonth()) 45 | ->condition('year', $event->startYear()) 46 | ->fields($partial_month_row) 47 | ->execute(); 48 | } 49 | // Do an insert for a new month 50 | else { 51 | // Prepare the days array 52 | $days = $this->prepareFullMonthArray($event); 53 | $month_row = array( 54 | 'unit_id' => $this->unit_id, 55 | 'year' => $event->startYear(), 56 | 'month' => $event->startMonth(), 57 | ); 58 | $month_row = array_merge($month_row, $days); 59 | $insert = db_insert($this->base_table)->fields($month_row); 60 | $insert->execute(); 61 | } 62 | } 63 | 64 | /** 65 | * Given an event it prepares the entire month array for it 66 | * assuming no other events in the month and days where there 67 | * is no event get set to the default state. 68 | * 69 | * @param RoomsEventInterface $event 70 | * The event to process. 71 | * 72 | * @return array 73 | * The days of the month states processed array. 74 | */ 75 | protected abstract function prepareFullMonthArray(RoomsEventInterface $event); 76 | 77 | /** 78 | * Given an event it prepares a partial array covering just the days 79 | * for which the event is involved 80 | * 81 | * @param RoomsEventInterface $event 82 | * The event to process. 83 | * 84 | * @return array 85 | * The days of the month states processed array. 86 | */ 87 | protected abstract function preparePartialMonthArray(RoomsEventInterface $event); 88 | 89 | /** 90 | * {@inheritdoc} 91 | */ 92 | public abstract function getEvents(DateTime $start_date, DateTime $end_date); 93 | 94 | /** 95 | * {@inheritdoc} 96 | */ 97 | public abstract function getRawDayData(DateTime $start_date, DateTime $end_date); 98 | 99 | /** 100 | * {@inheritdoc} 101 | */ 102 | public function monthDefined($month, $year) { 103 | $query = db_select($this->base_table, 'a'); 104 | $query->addField('a', 'unit_id'); 105 | $query->addField('a', 'year'); 106 | $query->addField('a', 'month'); 107 | $query->condition('a.unit_id', $this->unit_id); 108 | $query->condition('a.year', $year); 109 | $query->condition('a.month', $month); 110 | $result = $query->execute()->fetchAll(PDO::FETCH_ASSOC); 111 | if (count($result) > 0) { 112 | return TRUE; 113 | } 114 | return FALSE; 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /includes/rooms.calendar_interface.inc: -------------------------------------------------------------------------------- 1 | start_date->format($format); 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function startMonth($format = 'n') { 36 | return $this->start_date->format($format); 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function startYear($format = 'Y') { 43 | return $this->start_date->format($format); 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function endDay($format = 'j') { 50 | return $this->end_date->format($format); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function endMonth($format = 'n') { 57 | return $this->end_date->format($format); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function endYear($format = 'Y') { 64 | return $this->end_date->format($format); 65 | } 66 | 67 | /** 68 | * {@inheritdoc} 69 | */ 70 | public function diff() { 71 | $interval = $this->start_date->diff($this->end_date); 72 | return $interval; 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function sameMonth() { 79 | if (($this->startMonth() == $this->endMonth()) && ($this->startYear() == $this->endYear())) { 80 | return TRUE; 81 | } 82 | return FALSE; 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function sameYear() { 89 | if ($this->startYear() == $this->endYear()) { 90 | return TRUE; 91 | } 92 | return FALSE; 93 | } 94 | 95 | /** 96 | * {@inheritdoc} 97 | */ 98 | public function transformToYearlyEvents() { 99 | // If same year return the event 100 | if ($this->sameYear()) { 101 | $sd = new DateTime(); 102 | $sd->setDate($this->startYear(), $this->startMonth(), $this->startDay()); 103 | $ed = new DateTime(); 104 | $ed->setDate($this->endYear(), $this->endMonth(), $this->endDay()); 105 | $be = $this->createEvent($sd, $ed); 106 | return array($be); 107 | } 108 | 109 | // Else split into years 110 | $events = array(); 111 | for ($i = $this->startYear(); $i <= $this->endYear(); $i++) { 112 | $sd = new DateTime(); 113 | $ed = new DateTime(); 114 | if ($i == $this->startYear()) { 115 | $sd->setDate($i, $this->startMonth(), $this->startDay()); 116 | $ed->setDate($i, 12, 31); 117 | $events[$i] = $this->createEvent($sd, $ed); 118 | } 119 | elseif ($i == $this->endYear()) { 120 | $sd->setDate($i, 1, 1); 121 | $ed->setDate($i, $this->endMonth(), $this->endDay()); 122 | $events[$i] = $this->createEvent($sd, $ed); 123 | } 124 | else { 125 | $sd->setDate($i, 1, 1); 126 | $ed->setDate($i, 12, 31); 127 | $events[$i] = $this->createEvent($sd, $ed); 128 | } 129 | } 130 | 131 | return $events; 132 | } 133 | 134 | /** 135 | * {@inheritdoc} 136 | */ 137 | public function transformToMonthlyEvents() { 138 | $events = array(); 139 | //First we need to split into events in separate years 140 | if (!$this->sameYear()) { 141 | return FALSE; 142 | } 143 | if ($this->sameMonth()) { 144 | $sd = new DateTime(); 145 | $sd->setDate($this->startYear(), $this->startMonth(), $this->startDay()); 146 | $ed = new DateTime(); 147 | $ed->setDate($this->endYear(), $this->endMonth(), $this->endDay()); 148 | $be = $this->createEvent($sd, $ed); 149 | return array($be); 150 | } 151 | $months = rooms_end_of_month_dates($this->startYear()); 152 | 153 | for ($i = $this->startMonth(); $i <= $this->endMonth(); $i++) { 154 | $sd = new DateTime(); 155 | $ed = new DateTime(); 156 | if ($i == $this->startMonth()) { 157 | $sd->setDate($this->startYear() , $i, $this->startDay()); 158 | $ed->setDate($this->startYear(), $i, $months[$i]); 159 | $events[$i] = $this->createEvent($sd, $ed); 160 | } 161 | elseif ($i == $this->endMonth()) { 162 | $sd->setDate($this->startYear(), $i, 1); 163 | $ed->setDate($this->startYear(), $i, $this->endDay()); 164 | $events[$i] = $this->createEvent($sd, $ed); 165 | } 166 | else{ 167 | $sd->setDate($this->startYear(), $i, 1); 168 | $ed->setDate($this->startYear(), $i, $months[$i]); 169 | $events[$i] = $this->createEvent($sd, $ed); 170 | } 171 | } 172 | return $events; 173 | } 174 | 175 | /** 176 | * Creates a new event 177 | * 178 | * @param DateTime $start_date 179 | * The new event start date. 180 | * @param DateTime $end_date 181 | * The new event end date. 182 | * 183 | * @return RoomsEventInterface 184 | * The new event created. 185 | */ 186 | protected abstract function createEvent(DateTime $start_date, DateTime $end_date); 187 | 188 | } 189 | -------------------------------------------------------------------------------- /includes/rooms.event_interface.inc: -------------------------------------------------------------------------------- 1 | ').appendTo(dayGridContainerEl); 26 | this.el.find('.fc-body > tr > td').append(dayGridContainerEl); 27 | 28 | this.dayGrid.setElement(dayGridEl); 29 | this.dayGrid.renderDates(this.hasRigidRows()); 30 | }, 31 | }); 32 | 33 | FC.views.singleRowMonth = singleRowMonth; 34 | 35 | })(jQuery); 36 | -------------------------------------------------------------------------------- /modules/rooms_availability/css/fullcalendar.theme.css: -------------------------------------------------------------------------------- 1 | .fc table { 2 | margin: 0; 3 | } 4 | .fc td, .fc th { 5 | width: auto; 6 | } 7 | 8 | /* Header styles */ 9 | .fc-header-title { 10 | text-align: center; 11 | } 12 | .fc-header-title h2 { 13 | font-size: 18px; 14 | font-weight: normal; 15 | margin: 0; 16 | } 17 | .fc-header { 18 | border: none; 19 | } 20 | .fc-header tbody, 21 | .fc-header tr, 22 | .fc-header th, 23 | .fc-header td { 24 | background-color: transparent; 25 | border: none !important; 26 | } 27 | .fc-header td { 28 | padding: 8px 0; 29 | } 30 | .fc-header .fc-state-default, 31 | .fc-header .ui-state-default { 32 | margin-bottom: 0; 33 | } 34 | 35 | /* Days of week */ 36 | .fc-widget-header { 37 | font-weight: 100; 38 | font-size: 0.9em; 39 | color: #666; 40 | text-transform: none; 41 | background: transparent; 42 | } 43 | 44 | /* Table borders */ 45 | .fc-border-separate { 46 | border: none; 47 | } 48 | .fc-border-separate th, 49 | .fc-border-separate th.fc-last { 50 | border-width: 0; 51 | } 52 | .fc-border-separate td.fc-last { 53 | border-width: 0 0 1px 1px; 54 | } 55 | .fc-border-separate tr.fc-last td { 56 | border-width: 0 0 0 1px; 57 | padding-bottom: 0; 58 | } 59 | .fc-border-separate td.fc-first { 60 | border-left: none; 61 | } 62 | 63 | /* Calendar content */ 64 | .fc-widget-content { 65 | border-color: #f5f5f5; 66 | background: transparent; 67 | } 68 | 69 | /* Events */ 70 | .fc-event, 71 | .fc-agenda .fc-event-time, 72 | .fc-event a { 73 | border-style: none; 74 | background-color: transparent; 75 | color: inherit; 76 | } 77 | .fc-event-default, 78 | .fc-event-default .fc-event-time, 79 | .fc-event-default a { 80 | border-color: #36c; 81 | border-style: solid; 82 | background-color: #36c; 83 | color: #fff; 84 | } 85 | .fc-event-skin { 86 | opacity: 0.8; 87 | } 88 | .fc-event-title { 89 | padding: 0 2px; 90 | display: block; 91 | } 92 | 93 | .fc-corner-left, 94 | .fc-corner-left .fc-event-inner { 95 | border-left-width: 0; 96 | } 97 | .fc-corner-right, 98 | .fc-corner-right .fc-event-inner { 99 | border-right-width: 0; 100 | } 101 | .fc-corner-left.fc-corner-right, 102 | .fc-corner-left.fc-corner-right .fc-event-inner { 103 | border-left-width: 0; 104 | border-right-width: 0; 105 | } 106 | 107 | /* Updated message */ 108 | .fullcalendar-status { 109 | background: #ecf8f4; 110 | border: 1px solid #fff; 111 | color: #687; 112 | display: none; 113 | margin: 0 0 10px 0; 114 | padding: 15px; 115 | text-align: center; 116 | } 117 | 118 | /* Undo Drupal default theme link colors */ 119 | .fc-content a:link, 120 | .fc-content a:visited { 121 | color: #fff; 122 | text-decoration: none; 123 | } 124 | 125 | /* Grid whitespace */ 126 | .fc-grid .fc-day-number { 127 | text-align: right; 128 | padding: 1px 8% 0; 129 | cursor: default; 130 | } 131 | .fc-grid .fc-day-content { 132 | padding: 0 5% 0; 133 | } 134 | 135 | /* Visual separator between weeks in single-row-month */ 136 | .fc-view-singleRowMonth th.fc-sun { 137 | border-left-width: 1px; 138 | } 139 | .fc-view-singleRowMonth th.fc-sun.fc-first { 140 | border-left-width: 0; 141 | } 142 | 143 | .fc-cell-overlay { 144 | background: rgb(0, 122, 255); 145 | opacity: .7; 146 | filter: alpha(opacity=70); 147 | } 148 | 149 | /* Hide plain text events when javascript is enabled */ 150 | html.js .fullcalendar-content { 151 | display: none; 152 | } 153 | -------------------------------------------------------------------------------- /modules/rooms_availability/css/rooms_availability.css: -------------------------------------------------------------------------------- 1 | /* Hide header on single-row month form display */ 2 | .rooms-bulk-availability-form .fc-header { 3 | display: none; 4 | } 5 | 6 | /* Filter month form */ 7 | .rooms-filter-month-form .form-item-month, 8 | .rooms-filter-month-form .form-item-year, 9 | .rooms-filter-month-form .form-item-type { 10 | float: left; 11 | margin-right: 20px; 12 | } 13 | .rooms-filter-month-form .form-item-month label, 14 | .rooms-filter-month-form .form-item-year label, 15 | .rooms-filter-month-form .form-item-type label { 16 | display: inline; 17 | margin-right: 4px; 18 | } 19 | .rooms-bulk-availability-form .fc-row:first-child .fc-content-skeleton table { 20 | margin-bottom: 0; 21 | } 22 | 23 | .fc-title { 24 | color: white; 25 | } 26 | 27 | .rooms-bulk-availability-form .fc-content-skeleton .fc-event-container a { 28 | border-radius: 0px; 29 | } 30 | 31 | .rooms-bulk-availability-form .fc-content-skeleton .fc-event-container .event-start { 32 | content: ""; 33 | position: absolute; 34 | width: 0; 35 | height: 0; 36 | border-style: solid; 37 | border-color: transparent; 38 | border-bottom: 0; 39 | bottom: -8px; 40 | left: -1px; 41 | border-width: 7px; 42 | } 43 | 44 | .rooms-bulk-availability-form .fc-content-skeleton .fc-event-container .event-end { 45 | content: ""; 46 | position: absolute; 47 | width: 0; 48 | height: 0; 49 | border-style: solid; 50 | border-color: transparent; 51 | border-bottom: 0; 52 | bottom: -10px; 53 | left: 0px; 54 | border-width: 7px; 55 | } 56 | 57 | .rooms-bulk-availability-form .fc-content-skeleton .fc-event-container .event-end { 58 | margin-bottom: 23px; 59 | transform: rotate(180deg); 60 | -ms-transform: rotate(180deg); /* IE 9 */ 61 | -webkit-transform: rotate(180deg); 62 | } 63 | .rooms-month-manager .rooms-calendar-second-row-events { 64 | margin-top: -15px; 65 | } 66 | 67 | .page-admin-rooms-units{ 68 | font: normal 81.3%/1.538em "Lucida Grande", "Lucida Sans Unicode", sans-serif; 69 | } 70 | 71 | /* Ensure that datepicker is visible in the modal popup. */ 72 | #ui-datepicker-div { 73 | z-index: 1005 !important; 74 | } 75 | -------------------------------------------------------------------------------- /modules/rooms_availability/css/rooms_three_month_calendar.css: -------------------------------------------------------------------------------- 1 | .rooms-three-month-calendar { 2 | margin-bottom: 20px; 3 | position: relative; 4 | overflow-x: hidden; 5 | } 6 | .rooms-three-month-calendar ::-moz-selection { 7 | color: inherit; 8 | background: inherit; 9 | } 10 | .rooms-three-month-calendar ::selection { 11 | color: inherit; 12 | background: inherit; 13 | } 14 | .rooms-three-month-calendar .ajax-progress { 15 | position: absolute; 16 | top: 0; 17 | right: 0; 18 | } 19 | .calendar-set, 20 | .calendar-navigation, 21 | .calendar-management-form { 22 | margin-bottom: 20px; 23 | } 24 | .rooms-three-month-calendar .month1, 25 | .rooms-three-month-calendar .month2, 26 | .rooms-three-month-calendar .month3 { 27 | float: left; /* LTR */ 28 | width: 33%; 29 | padding: 0 15px; 30 | -webkit-box-sizing: border-box; 31 | -moz-box-sizing: border-box; 32 | box-sizing: border-box; 33 | } 34 | .rooms-three-month-calendar .month1 { 35 | padding-left: 0; /* LTR */ 36 | } 37 | .rooms-three-month-calendar .month3 { 38 | padding-right: 0; /* LTR */ 39 | } 40 | @media (max-width: 570px) { 41 | .rooms-three-month-calendar .month1, 42 | .rooms-three-month-calendar .month2, 43 | .rooms-three-month-calendar .month3 { 44 | width: 100%; 45 | padding: 0 0 15px; 46 | -webkit-box-sizing: border-box; 47 | -moz-box-sizing: border-box; 48 | box-sizing: border-box; 49 | } 50 | } 51 | 52 | .calendar-back, 53 | .calendar-current, 54 | .calendar-forward { 55 | float: left; /* LTR */ 56 | width: 33%; 57 | -webkit-box-sizing: border-box; 58 | -moz-box-sizing: border-box; 59 | box-sizing: border-box; 60 | } 61 | .calendar-back { 62 | text-align: left; /* LTR */ 63 | } 64 | .calendar-current { 65 | text-align: center; 66 | } 67 | .calendar-forward { 68 | text-align: right; /* LTR */ 69 | } 70 | 71 | .page-admin-rooms-units{ 72 | font: normal 81.3%/1.538em "Lucida Grande", "Lucida Sans Unicode", sans-serif; 73 | } 74 | .rooms-three-month-calendar .fc-content-skeleton .fc-event-container a { 75 | border-radius: 0px; 76 | } 77 | 78 | .rooms-three-month-calendar .fc-content-skeleton .fc-event-container .event-start { 79 | content: ""; 80 | position: absolute; 81 | width: 0; 82 | height: 0; 83 | border-style: solid; 84 | border-color: transparent; 85 | border-bottom: 0; 86 | bottom: -8px; 87 | left: 0px; 88 | border-width: 8px; 89 | } 90 | 91 | .rooms-three-month-calendar .fc-content-skeleton .fc-event-container .event-end { 92 | content: ""; 93 | position: absolute; 94 | width: 0; 95 | height: 0; 96 | border-style: solid; 97 | border-color: transparent; 98 | border-bottom: 0; 99 | bottom: -12px; 100 | left: 0px; 101 | border-width: 8px; 102 | } 103 | 104 | .rooms-three-month-calendar .fc-content-skeleton .fc-event-container .event-end { 105 | margin-bottom: 26px; 106 | transform: rotate(180deg); 107 | -ms-transform: rotate(180deg); /* IE 9 */ 108 | -webkit-transform: rotate(180deg); 109 | } 110 | 111 | .rooms-three-month-calendar .rooms-calendar-second-row-events { 112 | margin-top: -15px; 113 | } 114 | -------------------------------------------------------------------------------- /modules/rooms_availability/includes/rooms_availability.booking_event.inc: -------------------------------------------------------------------------------- 1 | unit_id = $unit_id; 31 | $this->id = $event_id; 32 | $this->start_date = $start_date; 33 | $this->end_date = $end_date; 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | protected function createEvent(DateTime $start_date, DateTime $end_date) { 40 | return new BookingEvent($this->unit_id, $this->id, $start_date, $end_date); 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function lock() { 47 | // Check that the event is not already locked. 48 | $query = db_select('rooms_booking_locks', 'l'); 49 | $query->addField('l', 'unit_id'); 50 | $query->addField('l', 'state'); 51 | $query->addField('l', 'locked'); 52 | $query->condition('l.unit_id', $this->unit_id); 53 | $query->condition('l.state', $this->id); 54 | $query->condition('l.locked', 1); 55 | $result = $query->execute()->fetchAll(PDO::FETCH_ASSOC); 56 | 57 | if (count($result) == 1) { 58 | return FALSE; 59 | } 60 | else { 61 | $fields = array( 62 | 'unit_id' => $this->unit_id, 63 | 'state' => $this->id, 64 | 'locked' => 1, 65 | ); 66 | $lock = db_insert('rooms_booking_locks')->fields($fields); 67 | $lock->execute(); 68 | } 69 | return TRUE; 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function unlock() { 76 | db_delete('rooms_booking_locks') 77 | ->condition(db_or()->condition('state', $this->id)->condition('state', -($this->id))) 78 | ->execute(); 79 | } 80 | 81 | /** 82 | * {@inheritdoc} 83 | */ 84 | public function formatJson($style = ROOMS_AVAILABILITY_ADMIN_STYLE, $unit_name = '') { 85 | $event = array( 86 | 'id' => $this->id, 87 | 'start' => $this->startYear() . '-' . $this->startMonth('m') . '-' . $this->startDay('d') . 'T13:00:00', 88 | 'end' => $this->endYear() . '-' . $this->endMonth('m') . '-' . $this->endDay('d') . 'T13:00:00', 89 | 'title' => $this->id, 90 | ); 91 | 92 | // Check if we are dealing with a booking. 93 | if ($this->id > 10 || $this->id < -10) { 94 | // Get the actual booking id. 95 | $booking_id = rooms_availability_return_id($this->id); 96 | $booking = rooms_booking_load($booking_id); 97 | if ($style == ROOMS_AVAILABILITY_ADMIN_STYLE) { 98 | $name = (isset($booking->name) && !empty($booking->name)) ? $booking->name : t('Booking') . ': ' . $booking->booking_id; 99 | $interval = $this->diff(); 100 | if ((strlen($name) > 7) && ($interval->d < 1)) { 101 | $event['title'] = substr($name, 0, 6) . '...'; 102 | } 103 | else { 104 | $event['title'] = $name; 105 | } 106 | } 107 | elseif ($style == ROOMS_AVAILABILITY_GENERIC_STYLE) { 108 | $this->id = ROOMS_NOT_AVAILABLE; 109 | $event['id'] = ROOMS_NOT_AVAILABLE; 110 | } 111 | } 112 | 113 | $view_unit_name = array_filter(variable_get('rooms_view_unit_name', array(''))); 114 | 115 | // Set the color. 116 | switch ($this->id) { 117 | case ROOMS_NOT_AVAILABLE: 118 | $event['color'] = variable_get('rooms_not_available_color', '#CC2727'); 119 | $event['borderColor'] = variable_get('rooms_not_available_color', 120 | '#CC2727'); 121 | $event['title'] = variable_get('rooms_not_available_text', 'N/A'); 122 | break; 123 | 124 | case ROOMS_AVAILABLE: 125 | $event['color'] = variable_get('rooms_available_color', '#8BA175'); 126 | $event['borderColor'] = '#8BA175'; 127 | $event['title'] = variable_get('rooms_available_text', 'AV'); 128 | break; 129 | 130 | case ROOMS_ON_REQUEST: 131 | $event['color'] = variable_get('rooms_on_request_color', '#C5C5C5'); 132 | $event['title'] = variable_get('rooms_on_request_text', 'ON-REQ'); 133 | break; 134 | 135 | case ($this->id < 0): 136 | $event['color'] = variable_get('rooms_unconfirmed_booking_color', '#6D8C9C'); 137 | $event['title'] = variable_get('rooms_unconfirmed_booking_text', 'UNCONF'); 138 | break; 139 | 140 | case ROOMS_ANON_BOOKED: 141 | if ($style == ROOMS_AVAILABILITY_ADMIN_STYLE) { 142 | $event['color'] = variable_get('rooms_anon_booking_color', '#8C6A5A'); 143 | $event['title'] = variable_get('rooms_anon_booking_text', 'A-B'); 144 | } 145 | elseif ($style == ROOMS_AVAILABILITY_GENERIC_STYLE) { 146 | $event['color'] = variable_get('rooms_not_available_color', 147 | '#910a1c'); 148 | $event['title'] = variable_get('rooms_not_available_text', 'N/A'); 149 | } 150 | break; 151 | 152 | default: 153 | $event['color'] = '#017eba'; 154 | break; 155 | } 156 | 157 | if (!empty($view_unit_name)) { 158 | $event['title'] = $unit_name; 159 | } 160 | 161 | return $event; 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /modules/rooms_availability/includes/rooms_availability.booking_event_interface.inc: -------------------------------------------------------------------------------- 1 | id] == ROOMS_UPDATED) { 26 | $unit_affected = rooms_unit_load($event->unit_id); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modules/rooms_availability/rooms_availability.info: -------------------------------------------------------------------------------- 1 | name = Rooms Availability 2 | description = Manages availability for Rooms. 3 | core = 7.x 4 | 5 | package = Rooms 6 | 7 | files[] = includes/rooms_availability.booking_event.inc 8 | files[] = includes/rooms_availability.booking_event_interface.inc 9 | files[] = includes/rooms_availability.unit_calendar.inc 10 | files[] = includes/rooms_availability.unit_calendar_interface.inc 11 | 12 | 13 | dependencies[] = rooms_unit 14 | dependencies[] = rooms 15 | dependencies[] = rooms_ui 16 | dependencies[] = date 17 | dependencies[] = date_api 18 | dependencies[] = date_popup 19 | dependencies[] = ctools 20 | dependencies[] = bat_api 21 | -------------------------------------------------------------------------------- /modules/rooms_availability_reference/css/rooms_availability_reference_calendar.css: -------------------------------------------------------------------------------- 1 | 2 | .rooms-availability-field-calendar .fc-content-skeleton .fc-event-container a { 3 | border-radius: 0px; 4 | } 5 | 6 | .rooms-availability-field-calendar .fc-content-skeleton .fc-event-container .event-start { 7 | content: ""; 8 | position: absolute; 9 | width: 0; 10 | height: 0; 11 | border-style: solid; 12 | border-color: transparent; 13 | border-bottom: 0; 14 | bottom: -8px; 15 | left: 0px; 16 | border-width: 8px; 17 | } 18 | 19 | .rooms-availability-field-calendar .fc-content-skeleton .fc-event-container .event-end { 20 | content: ""; 21 | position: absolute; 22 | width: 0; 23 | height: 0; 24 | border-style: solid; 25 | border-color: transparent; 26 | border-bottom: 0; 27 | bottom: -11px; 28 | left: 0px; 29 | border-width: 8px; 30 | } 31 | 32 | .rooms-availability-field-calendar .fc-content-skeleton .fc-event-container .event-end { 33 | margin-bottom: 26px; 34 | transform: rotate(180deg); 35 | -ms-transform: rotate(180deg); /* IE 9 */ 36 | -webkit-transform: rotate(180deg); 37 | } 38 | 39 | .rooms-availability-field-calendar .fc-event .fc-title { 40 | color:white; 41 | } 42 | 43 | .rooms-availability-field-calendar .fc-event .fc-title:hover { 44 | color:white; 45 | } 46 | 47 | .rooms-availability-field-calendar .fc-unthemed .fc-popover, 48 | .rooms-availability-field-calendar .fc-unthemed .fc-row, 49 | .rooms-availability-field-calendar .fc-unthemed hr, 50 | .rooms-availability-field-calendar .fc-unthemed tbody, 51 | .rooms-availability-field-calendar .fc-unthemed td, .fc-unthemed th, 52 | .rooms-availability-field-calendar .fc-unthemed thead { 53 | border-color:white; 54 | } 55 | 56 | .rooms-availability-field-calendar .fc-event-container { 57 | padding-top: 2px; 58 | padding-bottom: 4px; 59 | } -------------------------------------------------------------------------------- /modules/rooms_availability_reference/js/rooms_availability_reference.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | 3 | 4 | Drupal.behaviors.rooms_availability_reference = { 5 | attach: function(context) { 6 | var today = moment(); 7 | 8 | $('.cal').once('cal', function() { 9 | var lastSource; 10 | var cal_id = $(this).siblings('.availability-title').attr('id'); 11 | 12 | $(this).fullCalendar({ 13 | editable: false, 14 | displayEventTime: false, 15 | dayNamesShort:[Drupal.t("Sun"), Drupal.t("Mon"), Drupal.t("Tue"), Drupal.t("Wed"), Drupal.t("Thu"), Drupal.t("Fri"), Drupal.t("Sat")], 16 | monthNames:[Drupal.t("January"), Drupal.t("February"), Drupal.t("March"), Drupal.t("April"), Drupal.t("May"), Drupal.t("June"), Drupal.t("July"), Drupal.t("August"), Drupal.t("September"), Drupal.t("October"), Drupal.t("November"), Drupal.t("December")], 17 | defaultDate: today, 18 | firstDay: Drupal.settings.roomsAvailabilityRef[cal_id].firstDay, 19 | header:{ 20 | left: 'today', 21 | center: 'title', 22 | right: 'prev, next' 23 | }, 24 | viewRender: function(view, element) { 25 | if (view.name == 'month') { 26 | view.calendar.removeEvents(); 27 | 28 | var url = '?q=bat/v1/availability&units=' + Drupal.settings.roomsAvailabilityRef[cal_id].unitID.join() + '&start_date=' + view.intervalStart.get('year') + '-' + (view.intervalStart.get('month') + 1) + '-01&duration=1M'; 29 | $.ajax({ 30 | url: url, 31 | success: function(data) { 32 | events = data['events']; 33 | 34 | for (var index = 0; index < Drupal.settings.roomsAvailabilityRef[cal_id].unitID.length; index++) { 35 | events_array = events[Drupal.settings.roomsAvailabilityRef[cal_id].unitID[index]]; 36 | 37 | for (i in events_array) { 38 | events_array[i].end = moment(events_array[i].end).subtract(1, 'days').format(); 39 | } 40 | view.calendar.addEventSource(events_array); 41 | } 42 | } 43 | }); 44 | } 45 | }, 46 | eventRender: function(event, el, view) { 47 | // Add a class if the event start it is not "AV" or "N/A". 48 | if (el.hasClass('fc-start') && this.id != 1 && this.id != 0) { 49 | el.append('
'); 50 | el.find('.event-start').css('border-top-color', this.color); 51 | } 52 | 53 | // Add a class if the event end and it is not "AV" or "N/A". 54 | if (el.hasClass('fc-end') && this.id != 1 && this.id != 0) { 55 | el.append('
'); 56 | el.find('.event-end').css('border-top-color', this.color); 57 | } 58 | }, 59 | eventAfterRender: function(event, element, view) { 60 | // Hide events that are outside this month. 61 | if (event.start.month() != view.intervalStart.month()) { 62 | element.css('visibility', 'hidden'); 63 | return; 64 | } 65 | 66 | // Event width. 67 | var width = element.parent().width() 68 | // Event colspan number. 69 | var colspan = element.parent().get(0).colSpan; 70 | // Single cell width. 71 | var cell_width = width/colspan; 72 | var half_cell_width = cell_width/2; 73 | 74 | // Move events between table margins. 75 | element.css('margin-left', half_cell_width); 76 | element.css('margin-right', -(half_cell_width)); 77 | 78 | // Calculate width event to add end date triangle. 79 | width_event = element.children('.fc-content').width(); 80 | 81 | // Add a margin left to the top triangle. 82 | element.children().closest('.event-end').css('margin-left', width_event-14); 83 | 84 | if (element.parent().index() == 0) { 85 | if (element.hasClass('fc-start')) { 86 | var row_index = element.closest('.fc-row').index('.fc-row'); 87 | if (row_index >= 2) { 88 | var event_color = $(view.dayGrid.rowEls[row_index - 2]).find('.fc-day-grid-event').last().css('background-color'); 89 | var event_text = $(view.dayGrid.rowEls[row_index - 2]).find('.fc-day-grid-event').last().find('.fc-title').text(); 90 | 91 | var new_event = $('
' + event_text + '
'); 92 | element.parent().append(new_event); 93 | 94 | var event_end = $(view.dayGrid.rowEls[row_index - 2]).find('.fc-day-grid-event').last().find('.event-end').prop('outerHTML'); 95 | if (event_end != undefined) { 96 | $(view.dayGrid.rowEls[row_index - 2]).find('.fc-day-grid-event').last().find('.event-end').remove(); 97 | 98 | new_event.append(event_end); 99 | new_event.find('.event-end').css('margin-left', half_cell_width - 16); 100 | } 101 | } 102 | } 103 | } 104 | 105 | if (element.parent().index() == element.parent().parent().children('td').length - 1) { 106 | element.css('margin-right', 0); 107 | } 108 | 109 | // If the event end in a next row. 110 | if (element.hasClass('fc-not-end')) { 111 | element.css('margin-right', 0); 112 | } 113 | // If the event start in a previous row. 114 | if (element.hasClass('fc-not-start')) { 115 | element.css('margin-left', 0); 116 | element.children().closest('.event-end').css('margin-left', (colspan * cell_width) + half_cell_width - 14); 117 | } 118 | } 119 | }); 120 | 121 | }); 122 | 123 | 124 | // Resize takes care of some quirks on occasion 125 | $(window).resize(); 126 | 127 | } 128 | }; 129 | })(jQuery); 130 | -------------------------------------------------------------------------------- /modules/rooms_availability_reference/js/rooms_availability_reference_full_day.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | 3 | Drupal.behaviors.rooms_availability_reference = { 4 | attach: function(context) { 5 | var today = moment(); 6 | 7 | $('.cal').once('cal', function() { 8 | var lastSource; 9 | var cal_id = $(this).siblings('.availability-title').attr('id'); 10 | 11 | $(this).fullCalendar({ 12 | editable: false, 13 | displayEventTime: false, 14 | dayNamesShort:[Drupal.t("Sun"), Drupal.t("Mon"), Drupal.t("Tue"), Drupal.t("Wed"), Drupal.t("Thu"), Drupal.t("Fri"), Drupal.t("Sat")], 15 | monthNames:[Drupal.t("January"), Drupal.t("February"), Drupal.t("March"), Drupal.t("April"), Drupal.t("May"), Drupal.t("June"), Drupal.t("July"), Drupal.t("August"), Drupal.t("September"), Drupal.t("October"), Drupal.t("November"), Drupal.t("December")], 16 | defaultDate: today, 17 | firstDay: Drupal.settings.roomsAvailabilityRef[cal_id].firstDay, 18 | header:{ 19 | left: 'today', 20 | center: 'title', 21 | right: 'prev, next' 22 | }, 23 | viewRender: function(view, element) { 24 | if (view.name == 'month') { 25 | view.calendar.removeEvents(); 26 | 27 | var url = '?q=bat/v1/availability&units=' + Drupal.settings.roomsAvailabilityRef[cal_id].unitID.join() + '&start_date=' + view.intervalStart.get('year') + '-' + (view.intervalStart.get('month') + 1) + '-01&duration=1M'; 28 | $.ajax({ 29 | url: url, 30 | success: function(data) { 31 | events = data['events']; 32 | 33 | for (var index = 0; index < Drupal.settings.roomsAvailabilityRef[cal_id].unitID.length; index++) { 34 | events_array = events[Drupal.settings.roomsAvailabilityRef[cal_id].unitID[index]]; 35 | 36 | view.calendar.addEventSource(events_array); 37 | } 38 | } 39 | }); 40 | } 41 | }, 42 | eventAfterRender: function(event, element, view) { 43 | // Hide events that are outside this month. 44 | if (event.start.month() != view.intervalStart.month()) { 45 | element.css('visibility', 'hidden'); 46 | return; 47 | } 48 | } 49 | }); 50 | 51 | }); 52 | 53 | 54 | // Resize takes care of some quirks on occasion 55 | $(window).resize(); 56 | 57 | } 58 | }; 59 | })(jQuery); 60 | -------------------------------------------------------------------------------- /modules/rooms_availability_reference/rooms_availability_field_calendar.tpl.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 |
6 | 7 |
8 | -------------------------------------------------------------------------------- /modules/rooms_availability_reference/rooms_availability_reference.info: -------------------------------------------------------------------------------- 1 | name = Rooms Availability Reference 2 | description = Provides a field to reference and display availability information on any fieldable entity. 3 | core = 7.x 4 | 5 | package = Rooms 6 | 7 | dependencies[] = rooms_availability 8 | -------------------------------------------------------------------------------- /modules/rooms_availability_reference/rooms_availability_reference.install: -------------------------------------------------------------------------------- 1 | array( 15 | 'type' => 'int', 16 | 'unsigned' => TRUE, 17 | 'not null' => FALSE, 18 | ), 19 | ); 20 | return array( 21 | 'columns' => $columns, 22 | 'indexes' => array('unit_id' => array('unit_id')), 23 | 'foreign keys' => array( 24 | 'unit_id' => array( 25 | 'table' => 'rooms_units', 26 | 'columns' => array('unit_id' => 'unit_id'), 27 | ), 28 | ), 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /modules/rooms_booking/css/rooms_booking.css: -------------------------------------------------------------------------------- 1 | /* Booking edit form */ 2 | .rooms-booking-form .form-actions, 3 | .rooms-booking-form .rooms-booking-status-wrapper { 4 | float: none; 5 | clear: both; 6 | } 7 | .rooms-booking-form .rooms-booking-status-wrapper, 8 | .rooms-booking-form .rooms-booking-availability-wrapper, 9 | .rooms-booking-form .rooms-booking-unit-type-wrapper, 10 | .rooms-booking-form .rooms-booking-order-wrapper { 11 | margin-bottom: 15px; 12 | } 13 | .rooms-booking-form .rooms-booking-customer-wrapper, 14 | .rooms-booking-form .rooms-booking-data-wrapper { 15 | float: left; 16 | max-width: 400px; 17 | margin-right: 20px; 18 | } 19 | .rooms-booking-form .rooms-booking-customer-wrapper .form-item-client .form-text { 20 | width: 62%; 21 | min-width: 250px; 22 | } 23 | .rooms-booking-form .rooms-booking-customer-wrapper .form-item-client .field-suffix { 24 | width: 30%; 25 | display: block; 26 | } 27 | .rooms-booking-form .rooms-booking-date-range-wrapper { 28 | clear: both; 29 | padding: 0; 30 | margin: 0 0 1em; 31 | } 32 | .rooms-booking-form .rooms-booking-availability-button-wrapper { 33 | clear: both; 34 | } 35 | .rooms-booking-form .rooms-booking-availability-wrapper fieldset { 36 | background: none; 37 | float: none; 38 | clear: both; 39 | margin-bottom: 0; 40 | } 41 | .rooms-booking-form .rooms-booking-availability-wrapper .fieldset-description { 42 | padding: 0; 43 | } 44 | .rooms-booking-form .form-item-data-group-size, 45 | .rooms-booking-form .rooms-booking-group-size-children-container { 46 | float: left; 47 | margin-right: 20px; 48 | max-width: 175px; 49 | } 50 | .rooms-booking-form .form-item-data-group-size, 51 | .rooms-booking-form .form-item-data-group-size-children { 52 | padding: 0; 53 | } 54 | .rooms-booking-form .rooms-booking-options-wrapper .form-wrapper { 55 | margin: 0; 56 | } 57 | .rooms-booking-form .rooms-booking-options-wrapper .form-type-select { 58 | float: none; 59 | } 60 | .rooms-booking-form .rooms-booking-options-wrapper .form-type-select label { 61 | display: inline; 62 | font-weight: normal; 63 | } 64 | .rooms-booking-form .rooms-booking-availability-wrapper .form-item-unit-type { 65 | float: none; 66 | margin-bottom: 15px; 67 | } 68 | 69 | /* Children discount form */ 70 | .children-discount-form .child-discount { 71 | clear: both; 72 | float: none; 73 | } 74 | .children-discount-form .child-discount__age-start, 75 | .children-discount-form .child-discount__age-end { 76 | width: 2em; 77 | text-align: right; 78 | } 79 | .children-discount-form .child-discount__amount { 80 | width: 3em; 81 | text-align: right; 82 | } 83 | -------------------------------------------------------------------------------- /modules/rooms_booking/js/rooms_booking.js: -------------------------------------------------------------------------------- 1 | 2 | (function ($) { 3 | 4 | Drupal.behaviors.bookingFieldsetSummaries = { 5 | attach: function (context) { 6 | $('fieldset#edit-user', context).drupalSetSummary(function (context) { 7 | var name = $('#edit-owner-name').val() || Drupal.settings.anonymous; 8 | return Drupal.t('Owned by @name', { '@name': name }); 9 | }); 10 | 11 | $('fieldset#edit-booking-history', context).drupalSetSummary(function (context) { 12 | var summary = $('#edit-created', context).val() ? 13 | Drupal.t('Created @date', { '@date' : $('#edit-created').val() }) : 14 | Drupal.t('New order'); 15 | 16 | // Add the changed date to the summary if it's different from the created. 17 | if ($('#edit-created', context).val() != $('#edit-changed', context).val()) { 18 | summary += '
' + Drupal.t('Updated @date', { '@date' : $('#edit-changed').val() }); 19 | } 20 | 21 | return summary; 22 | }); 23 | } 24 | }; 25 | 26 | })(jQuery); 27 | -------------------------------------------------------------------------------- /modules/rooms_booking/rooms_booking-extra-data.tpl.php: -------------------------------------------------------------------------------- 1 | 14 | 15 |
16 | Unit Sample Data: ' . $rooms_unit_sample_data = ($rooms_unit_sample_data) ? 'Switch On' : 'Switch Off' ?> 17 |
18 | -------------------------------------------------------------------------------- /modules/rooms_booking/rooms_booking.api.php: -------------------------------------------------------------------------------- 1 | ROOMS_DYNAMIC_MODIFIER, 32 | '#quantity' => 1, 33 | '#op_type' => ROOMS_SUB, 34 | '#amount' => 10 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /modules/rooms_booking/rooms_booking.info: -------------------------------------------------------------------------------- 1 | name = Rooms Booking 2 | description = Create and administer bookings for Rooms. 3 | core = 7.x 4 | 5 | package = Rooms 6 | 7 | dependencies[] = rooms_unit 8 | dependencies[] = rooms_availability 9 | dependencies[] = rooms_pricing 10 | dependencies[] = views 11 | dependencies[] = commerce 12 | dependencies[] = commerce_ui 13 | dependencies[] = commerce_customer 14 | dependencies[] = commerce_customer_ui 15 | dependencies[] = commerce_cart 16 | dependencies[] = commerce_cart_expiration 17 | dependencies[] = commerce_order 18 | 19 | files[] = rooms_booking.admin.inc 20 | files[] = rooms_booking_type.admin.inc 21 | files[] = includes/rooms_booking.availability_agent.inc 22 | files[] = includes/rooms_booking.availability_agent_filter.inc 23 | files[] = views/rooms_booking.views.inc 24 | files[] = views/rooms_booking_handler_link_field.inc 25 | files[] = views/rooms_booking_handler_date_field.inc 26 | files[] = views/rooms_booking_handler_delete_link_field.inc 27 | files[] = views/rooms_booking_handler_edit_link_field.inc 28 | files[] = views/rooms_booking_handler_unit_field.inc 29 | files[] = views/rooms_booking_handler_booking_operations_field.inc 30 | files[] = views/rooms_booking_handler_booking_type_field.inc 31 | files[] = views/rooms_booking_handler_date_filter.inc 32 | files[] = views/rooms_booking_handler_field_booking_status.inc 33 | files[] = views/rooms_booking_handler_filter_booking_status.inc 34 | files[] = views/rooms_booking_handler_unit_filter.inc 35 | files[] = views/rooms_booking_handler_nights_field.inc 36 | -------------------------------------------------------------------------------- /modules/rooms_booking/rooms_booking.tpl.php: -------------------------------------------------------------------------------- 1 | 31 |
> 32 | 33 | > 34 | 35 | 36 | 37 | 38 |
> 39 | 46 |
47 |
48 | -------------------------------------------------------------------------------- /modules/rooms_booking/rooms_booking_type.admin.inc: -------------------------------------------------------------------------------- 1 | path]['description'] = 'Manage booking types, including adding and removing fields and the display of fields.'; 23 | $items[$this->path]['weight'] = '3'; 24 | $items[$this->path]['type'] = MENU_LOCAL_TASK; 25 | return $items; 26 | } 27 | } 28 | 29 | /** 30 | * Generates the Booking type editing form. 31 | */ 32 | function rooms_booking_type_form($form, &$form_state, $booking_type, $op = 'edit') { 33 | 34 | $form['#attributes']['class'][] = 'rooms-management-form rooms-booking-type-form'; 35 | 36 | if ($op == 'clone') { 37 | $booking_type->label .= ' (cloned)'; 38 | $booking_type->type = ''; 39 | } 40 | 41 | $form['label'] = array( 42 | '#title' => t('Label'), 43 | '#type' => 'textfield', 44 | '#default_value' => $booking_type->label, 45 | '#description' => t('The human-readable name of this booking type.'), 46 | '#required' => TRUE, 47 | '#size' => 30, 48 | ); 49 | // Machine-readable type name. 50 | $form['type'] = array( 51 | '#type' => 'machine_name', 52 | '#default_value' => isset($booking_type->type) ? $booking_type->type : '', 53 | '#maxlength' => 32, 54 | '#machine_name' => array( 55 | 'exists' => 'rooms_booking_get_types', 56 | 'source' => array('label'), 57 | ), 58 | '#description' => t('A unique machine-readable name for this booking type. It must only contain lowercase letters, numbers, and underscores.'), 59 | ); 60 | 61 | $form['actions'] = array( 62 | '#type' => 'actions', 63 | '#tree' => FALSE, 64 | ); 65 | $form['actions']['submit'] = array( 66 | '#type' => 'submit', 67 | '#value' => t('Save Booking type'), 68 | '#weight' => 40, 69 | ); 70 | 71 | return $form; 72 | } 73 | 74 | /** 75 | * Form API submit callback for the type form. 76 | */ 77 | function rooms_booking_type_form_submit(&$form, &$form_state) { 78 | $booking_type = entity_ui_form_submit_build_entity($form, $form_state); 79 | // Save and go back. 80 | $booking_type->save(); 81 | $form_state['redirect'] = 'admin/rooms/bookings/booking-types'; 82 | } 83 | 84 | /** 85 | * Form API submit callback for the delete button. 86 | */ 87 | function rooms_booking_type_form_submit_delete(&$form, &$form_state) { 88 | $form_state['redirect'] = 'admin/rooms/bookings/booking_types/manage/' . $form_state['rooms_booking_type']->type . '/delete'; 89 | } 90 | -------------------------------------------------------------------------------- /modules/rooms_booking/views/rooms_booking_handler_booking_operations_field.inc: -------------------------------------------------------------------------------- 1 | additional_fields['booking_id'] = 'booking_id'; 12 | } 13 | 14 | function query() { 15 | $this->ensure_my_table(); 16 | $this->add_additional_fields(); 17 | } 18 | 19 | function render($values) { 20 | $links = menu_contextual_links('rooms_booking', 'admin/rooms/bookings/booking', array($this->get_value($values, 'booking_id'))); 21 | if (!empty($links)) { 22 | return theme('links', array( 23 | 'links' => $links, 24 | 'attributes' => array( 25 | 'class' => array('links', 26 | 'inline', 27 | 'operations' 28 | ), 29 | ), 30 | )); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /modules/rooms_booking/views/rooms_booking_handler_booking_type_field.inc: -------------------------------------------------------------------------------- 1 | get_value($values)); 17 | return $booking_type->label; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /modules/rooms_booking/views/rooms_booking_handler_date_field.inc: -------------------------------------------------------------------------------- 1 | get_value($values); 17 | $date = new DateTime($value); 18 | $value = $date->getTimestamp(); 19 | $format = $this->options['date_format']; 20 | if (in_array($format, array('custom', 'raw time ago', 'time ago', 'raw time span', 'time span', 'raw time span', 'inverse time span', 'time span'))) { 21 | $custom_format = $this->options['custom_date_format']; 22 | } 23 | 24 | if ($value) { 25 | $time_diff = REQUEST_TIME - $value; 26 | switch ($format) { 27 | case 'raw time ago': 28 | return format_interval($time_diff, is_numeric($custom_format) ? $custom_format : 2); 29 | 30 | case 'time ago': 31 | return t('%time ago', array('%time' => format_interval($time_diff, is_numeric($custom_format) ? $custom_format : 2))); 32 | 33 | case 'raw time hence': 34 | return format_interval(-$time_diff, is_numeric($custom_format) ? $custom_format : 2); 35 | 36 | case 'time hence': 37 | return t('%time hence', array('%time' => format_interval(-$time_diff, is_numeric($custom_format) ? $custom_format : 2))); 38 | 39 | case 'raw time span': 40 | return ($time_diff < 0 ? '-' : '') . format_interval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2); 41 | 42 | case 'inverse time span': 43 | return ($time_diff > 0 ? '-' : '') . format_interval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2); 44 | 45 | case 'time span': 46 | return t(($time_diff < 0 ? '%time hence' : '%time ago'), array('%time' => format_interval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2))); 47 | 48 | case 'custom': 49 | if ($custom_format == 'r') { 50 | return format_date($value, $format, $custom_format, NULL, 'en'); 51 | } 52 | return format_date($value, $format, $custom_format); 53 | 54 | default: 55 | return format_date($value, $format); 56 | } 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /modules/rooms_booking/views/rooms_booking_handler_date_filter.inc: -------------------------------------------------------------------------------- 1 | view); 12 | 13 | $value = date('Y-m-d', intval(strtotime($this->value['value'], $query_substitutions['***CURRENT_TIME***']))); 14 | 15 | $this->query->add_where_expression($this->options['group'], "$field $this->operator '$value'"); 16 | } 17 | 18 | function op_between($field) { 19 | // Use the substitutions to ensure a consistent timestamp. 20 | $query_substitutions = views_views_query_substitutions($this->view); 21 | $a = date('Y-m-d', intval(strtotime($this->value['min'], $query_substitutions['***CURRENT_TIME***']))); 22 | $b = date('Y-m-d', intval(strtotime($this->value['max'], $query_substitutions['***CURRENT_TIME***']))); 23 | 24 | // This is safe because we are manually scrubbing the values. 25 | // It is necessary to do it this way because $a and $b are formulas when using an offset. 26 | $operator = strtoupper($this->operator); 27 | $this->query->add_where_expression($this->options['group'], "$field $operator '$a' AND '$b'"); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /modules/rooms_booking/views/rooms_booking_handler_delete_link_field.inc: -------------------------------------------------------------------------------- 1 | additional_fields['type'] = 'type'; 14 | } 15 | 16 | 17 | function render($values) { 18 | $type = $values->{$this->aliases['type']}; 19 | 20 | // Creating a dummy room to check access against. 21 | $dummy_booking = (object) array('type' => $type); 22 | if (!rooms_booking_access('delete', $dummy_booking)) { 23 | return; 24 | } 25 | 26 | $text = !empty($this->options['text']) ? $this->options['text'] : t('delete'); 27 | $booking_id = $values->{$this->aliases['booking_id']}; 28 | 29 | return l($text, 'admin/rooms/bookings/booking/' . $booking_id . '/delete'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /modules/rooms_booking/views/rooms_booking_handler_edit_link_field.inc: -------------------------------------------------------------------------------- 1 | additional_fields['type'] = 'type'; 14 | } 15 | 16 | 17 | function render($values) { 18 | $type = $values->{$this->aliases['type']}; 19 | 20 | //Creating a dummy booking to check access against 21 | $dummy_booking = (object) array('type' => $type); 22 | if (!rooms_booking_access('update', $dummy_booking)) { 23 | return; 24 | } 25 | $text = !empty($this->options['text']) ? $this->options['text'] : t('edit'); 26 | $booking_id = $values->{$this->aliases['booking_id']}; 27 | 28 | return l($text, 'admin/rooms/bookings/booking/' . $booking_id . '/edit'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /modules/rooms_booking/views/rooms_booking_handler_field_booking_status.inc: -------------------------------------------------------------------------------- 1 | get_value($values); 12 | 13 | if ($value == 0) { 14 | return 'Unconfirmed'; 15 | } 16 | elseif ($value == 1) { 17 | return 'Confirmed'; 18 | } 19 | 20 | return ''; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /modules/rooms_booking/views/rooms_booking_handler_filter_booking_status.inc: -------------------------------------------------------------------------------- 1 | 'Unconfirmed', 12 | '1' => 'Confirmed', 13 | ); 14 | 15 | $this->value_options = $options; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /modules/rooms_booking/views/rooms_booking_handler_link_field.inc: -------------------------------------------------------------------------------- 1 | additional_fields['booking_id'] = 'booking_id'; 14 | $this->additional_fields['type'] = 'type'; 15 | } 16 | 17 | function option_definition() { 18 | $options = parent::option_definition(); 19 | 20 | $options['text'] = array('default' => '', 'translatable' => TRUE); 21 | 22 | return $options; 23 | } 24 | 25 | function options_form(&$form, &$form_state) { 26 | parent::options_form($form, $form_state); 27 | 28 | $form['text'] = array( 29 | '#type' => 'textfield', 30 | '#title' => t('Text to display'), 31 | '#default_value' => $this->options['text'], 32 | ); 33 | } 34 | 35 | function query() { 36 | $this->ensure_my_table(); 37 | $this->add_additional_fields(); 38 | } 39 | 40 | function render($values) { 41 | $text = !empty($this->options['text']) ? $this->options['text'] : t('view'); 42 | $booking_id = $values->{$this->aliases['booking_id']}; 43 | 44 | return l($text, 'booking/' . $booking_id); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /modules/rooms_booking/views/rooms_booking_handler_nights_field.inc: -------------------------------------------------------------------------------- 1 | options['group_type'] != 'group' ? array('function' => $this->options['group_type']) : array(); 14 | $this->query->add_orderby(NULL, NULL, $order, $this->field_alias, $params); 15 | } 16 | 17 | function query() { 18 | $this->ensure_my_table(); 19 | 20 | $this->field_alias = $this->table_alias . '_nights'; 21 | 22 | $alias = $this->field_alias; 23 | $counter = 0; 24 | while (!empty($this->query->fields[$this->field_alias])) { 25 | $this->field_alias = $alias . '_' . ++$counter; 26 | } 27 | 28 | // Add the field. 29 | $params = $this->options['group_type'] != 'group' ? array('function' => $this->options['group_type']) : array(); 30 | $this->query->add_field(NULL, '(TO_DAYS(' . $this->table_alias . '.end_date) - TO_DAYS(' . $this->table_alias . '.start_date))', $this->field_alias, $params); 31 | 32 | $this->add_additional_fields(); 33 | } 34 | 35 | function render($values) { 36 | $value = number_format($values->{$this->field_alias}, $this->options['precision'], $this->options['decimal'], $this->options['separator']); 37 | return $this->sanitize_value($value); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /modules/rooms_booking/views/rooms_booking_handler_unit_field.inc: -------------------------------------------------------------------------------- 1 | additional_fields['unit_id'] = 'unit_id'; 15 | } 16 | 17 | function option_definition() { 18 | $options = parent::option_definition(); 19 | 20 | $options['text'] = array('default' => '', 'translatable' => TRUE); 21 | 22 | return $options; 23 | } 24 | 25 | function options_form(&$form, &$form_state) { 26 | parent::options_form($form, $form_state); 27 | 28 | $form['text'] = array( 29 | '#type' => 'textfield', 30 | '#title' => t('Text to display'), 31 | '#default_value' => $this->options['text'], 32 | ); 33 | } 34 | 35 | function query() { 36 | $this->ensure_my_table(); 37 | $this->add_additional_fields(); 38 | } 39 | 40 | function render($values) { 41 | $unit_id = $values->{$this->aliases['unit_id']}; 42 | $unit = rooms_unit_load($unit_id); 43 | if (!is_object($unit)) { 44 | return ''; 45 | } 46 | 47 | $text = !empty($this->options['text']) ? $this->options['text'] : $unit->name; 48 | 49 | return l($text, 'admin/rooms/units/unit/' . $unit_id . '/availability'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /modules/rooms_booking_manager/css/rooms_booking_search.css: -------------------------------------------------------------------------------- 1 | /* Make availability search fields layout look decent out of the box. */ 2 | 3 | .rooms-availability-search { 4 | position: relative; 5 | } 6 | .rooms-availability-search .ajax-progress { 7 | position: absolute; 8 | top: 0; 9 | width: 100%; 10 | background: rgba(255, 255, 255, 0.8); 11 | height: 100%; 12 | left: 0; 13 | } 14 | 15 | .rooms-availability-search .form-item-group-size-children, 16 | .rooms-availability-search .form-item-group-size-adults, 17 | .rooms-availability-search .form-item-unit-type, 18 | .rooms-availability-search .form-item-rooms, 19 | .rooms-availability-search .form-wrapper { 20 | float: left; 21 | margin: 0 1em .6em 0; 22 | } 23 | 24 | .rooms-availability-search .rooms-date-range .form-text { 25 | width: 100%; 26 | } 27 | 28 | /* Group size field */ 29 | .rooms-group-size, 30 | .rooms-group-size tr, 31 | .rooms-group-size th, 32 | .rooms-group-size td { 33 | background: none; 34 | border: none; 35 | margin: 0; 36 | padding: 0; 37 | color: inherit; 38 | font: inherit; 39 | } 40 | 41 | /* Labels */ 42 | .rooms-group-size th, 43 | .rooms-group-size td, 44 | .rooms-availability-search .form-item label { 45 | font-weight: normal; 46 | font-size: 1em; 47 | margin: 0; 48 | padding-top: 0; 49 | padding-bottom: 0.2em; 50 | } 51 | .rooms-group-size tr:last-child td { 52 | padding-bottom: 0; 53 | } 54 | .rooms-group-size .rooms-group-size__children { 55 | padding-left: 20px; 56 | } 57 | .rooms-group-size .form-item { 58 | margin: 0; 59 | } 60 | 61 | .rooms-availability-search .form-item-adults, 62 | .rooms-availability-search .form-item-children, 63 | .rooms-availability-search-results .form-item-adults, 64 | .rooms-availability-search-results .form-item-children { 65 | padding-left: 1.1em; 66 | } 67 | 68 | .rooms-availability-search .rooms-search-result__actions { 69 | padding: 0; 70 | margin: 1.2em 0 0; 71 | } 72 | 73 | 74 | /* Current Search Info */ 75 | .rooms-current-search__info, 76 | .rooms-management-form .rooms-current-search__info { 77 | float: left; /* or right */ 78 | margin-bottom: 20px; 79 | } 80 | .rooms-current-search__info .form-item, 81 | .rooms-current-search__actions { 82 | font-size: 0.9em; 83 | clear: both; 84 | margin: 0; 85 | } 86 | .rooms-current-search__info .fieldset-legend { 87 | position: relative; 88 | text-transform: none; 89 | } 90 | .rooms-current-search__change-search-button { 91 | float: left; 92 | margin-left: 10em; 93 | } 94 | .rooms-current-search__info .form-item label { 95 | display: block; 96 | float: left; 97 | width: 9.5em; 98 | text-align: right; 99 | margin-right: .5em; 100 | } 101 | .rooms-current-search__info .form-type-item .info { 102 | display: block; 103 | float: left; 104 | width: 8em; 105 | } 106 | 107 | .rooms-search-result__unit-type { 108 | clear: both; 109 | margin-bottom: 20px; 110 | } 111 | .rooms-search-result__unit-type-name { 112 | margin-top: 20px; 113 | padding-top: 20px; 114 | border-top: 1px solid #ccc; 115 | } 116 | .rooms-search-result__booking-form { 117 | vertical-align: top; 118 | } 119 | .rooms-search-result__booking-form { 120 | position: relative; 121 | width: 100%; 122 | } 123 | .rooms-search-result__booking-form .ajax-progress { 124 | position: absolute; 125 | top: 0; 126 | width: 100%; 127 | background: rgba(255, 255, 255, 0.8); 128 | height: 100%; 129 | left: 0; 130 | } 131 | .rooms-search-result__unit-type-name { 132 | font-weight: bold; 133 | } 134 | .rooms-search-result__unit-price { 135 | text-align: right; 136 | } 137 | .rooms-search-result__unit-base-price label, 138 | .rooms-search-result__unit-price label { 139 | font-weight: bold; 140 | display: inline; 141 | } 142 | .rooms-search-result__booking-legend, 143 | .rooms-search-result__select-units { 144 | text-align: left; 145 | width: 15%; 146 | } 147 | .rooms-search-result__select-units .form-item label { 148 | display: inline; 149 | } 150 | 151 | /* Individual (embedded) unit search results layout */ 152 | .rooms-search-result__unit-embedded { 153 | float: left; 154 | width: 100%; 155 | } 156 | .rooms-search-result__unit-embedded .rooms-search-result__select-guests, 157 | .rooms-search-result__unit-embedded .rooms-search-result__select-children, 158 | .rooms-search-result__unit-embedded .rooms-search-result__select-childrenage { 159 | float: left; 160 | margin-right: 20px; 161 | } 162 | .rooms-search-result__unit-embedded .rooms-search-result__new-price, 163 | .rooms-search-result__unit-embedded .rooms-search-result__actions { 164 | float: right; 165 | clear: right; 166 | } 167 | .rooms-search-result__new-price { 168 | font-weight: bold; 169 | } 170 | .rooms-search-result__unit_options .no-unit-fieldset { 171 | border: 0px; 172 | } 173 | .rooms-search-result__unit_options .no-unit-fieldset .fieldset-legend { 174 | display: none; 175 | } 176 | .rooms-search-result__unit_options .no-unit-fieldset legend{ 177 | background:none; 178 | border:none; 179 | } 180 | -------------------------------------------------------------------------------- /modules/rooms_booking_manager/css/rooms_sticky_booking_legend.css: -------------------------------------------------------------------------------- 1 | .booking-legend.is-fixed { 2 | position: fixed; 3 | top: 80px; 4 | } 5 | -------------------------------------------------------------------------------- /modules/rooms_booking_manager/js/rooms_booking_manager_checked.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $(document).ready(function() { 3 | $('#edit-perc-textfield').focus(function() 4 | { 5 | $('input[name=rooms_payment_options][value=11]').attr('checked', true); 6 | }); 7 | }); 8 | })(jQuery); 9 | -------------------------------------------------------------------------------- /modules/rooms_booking_manager/js/rooms_sticky_booking_legend.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | 3 | /** 4 | * Similar to sticky-enabled tables from core Drupal, this allows the legend 5 | * on the far right side of the booking form to stay in place as the user 6 | * scrolls down the page to view the options. 7 | */ 8 | Drupal.behaviors.rooms_sticky_booking_legend = { 9 | attach: function(context) { 10 | 11 | if ($('.booking-legend').offset() !== null) { 12 | var top = $('.booking-legend').offset().top - parseFloat($('.booking-legend').css('marginTop').replace(/auto/, 0)); 13 | $(window).scroll(function (event) { 14 | // How far has the user scrolled vertically. 15 | var y = $(this).scrollTop(); 16 | 17 | // When the user scrolls past the top of the booking legend. 18 | if (y >= top) { 19 | // Fix the legend in place on the page. 20 | $('.booking-legend').addClass('is-fixed'); 21 | } else { 22 | // Otherwise un-fix it. 23 | $('.booking-legend').removeClass('is-fixed'); 24 | } 25 | }); 26 | } 27 | } 28 | }; 29 | 30 | })(jQuery); 31 | -------------------------------------------------------------------------------- /modules/rooms_booking_manager/rooms_booking_email.tpl.php: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 |

6 |

7 |

8 |

9 |

10 |

11 | 12 |

13 | 14 |

15 | 16 |

17 | 18 | -------------------------------------------------------------------------------- /modules/rooms_booking_manager/rooms_booking_enquiry_confirmation.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /modules/rooms_booking_manager/rooms_booking_manager.api.php: -------------------------------------------------------------------------------- 1 | $value) { 27 | if ($key > 200) { 28 | unset($units_per_type['villa'][$key]); 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * Allows you to change the line item before it is added to the cart - this gives 35 | * lots of flexibility to add information to a line item or change the label. 36 | * 37 | * 38 | * @param $line_item 39 | * @param array $context 40 | */ 41 | function hook_rooms_create_line_item_alter(&$line_item, $context) { 42 | // No example. 43 | } 44 | -------------------------------------------------------------------------------- /modules/rooms_booking_manager/rooms_booking_manager.commerce.inc: -------------------------------------------------------------------------------- 1 | unit_id; 28 | $status = $unit_to_book['state']; 29 | 30 | $unit = rooms_unit_load($unit_id); 31 | 32 | $unit_type = rooms_unit_type_load($unit->type); 33 | 34 | // Set the correct departure date for the product name (checkout day). 35 | $dd = new DateTime($agent->end_date->format('Y-m-d')); 36 | $dd->add(new DateInterval('P1D')); 37 | 38 | $product_id = variable_get('rooms_booking_manager_booking_product_id', 0); 39 | $room_product = commerce_product_load($product_id); 40 | $line_item = commerce_product_line_item_new($room_product, 1, 0, array(), 'rooms_booking'); 41 | 42 | // Product name - we set something that will make sense to the user here. 43 | $nights = $dd->diff($agent->start_date)->days; 44 | 45 | $line_item_label = rooms_string( 46 | t('Booking for') . ' ' . $unit_type->label . ' (' . 47 | format_plural($nights, '1 Night', '@count Nights') . '; ' . 48 | t('Arrival') . ': ' . $agent->start_date->format($date_format) . ' ' . 49 | t('Departure') . ': ' . $dd->format($date_format) . ')' , 50 | $context = array( 51 | '#component' => 'rooms_booking_manager', 52 | '#purpose' => 'rooms_create_line_item', 53 | '#data' => array( 54 | 'unit' => $unit_to_book, 55 | 'unit_id' => $unit_id, 56 | 'unit_type' => $unit_type->label, 57 | 'arrival' => $agent->start_date, 58 | 'departure' => $dd, 59 | 'nights' => $nights, 60 | ) 61 | ) 62 | ); 63 | 64 | $line_item->line_item_label = $line_item_label; 65 | 66 | $unit_info = $agent->checkAvailabilityForUnit($unit_id, $price_modifiers); 67 | 68 | // Check $unit_info to make sure we still have availability. 69 | if ($unit_info == ROOMS_NO_ROOMS) { 70 | form_set_error('', t('We apologize for the inconvenience; this unit is no longer available.')); 71 | } 72 | else { 73 | // If available set price and create line item. 74 | $unit_first = array_pop($unit_info); 75 | $price = $unit_first['price']; 76 | 77 | // Convert to integer value as this is what Commerce expects. 78 | $price = commerce_currency_decimal_to_amount($price, commerce_default_currency()); 79 | 80 | // We do not need to setup the line item's commerce_unit_price here, as it 81 | // will be overwritten with the values from the product entity anyway. 82 | // Setting commerce unit price to the correct value is now done completely 83 | // by the function rooms_booking_manager_price_apply() 84 | // in rooms_booking_manager.module. 85 | $commerce_dates = array( 86 | LANGUAGE_NONE => array( 87 | array( 88 | 'value' => $agent->start_date->format('Y-m-d') . 'T00:00:00', 89 | 'value2' => $dd->format('Y-m-d') . 'T00:00:00', 90 | ), 91 | ), 92 | ); 93 | 94 | $commerce_unit = array( 95 | LANGUAGE_NONE => array( 96 | array( 97 | 'value' => $unit->unit_id, 98 | ), 99 | ), 100 | ); 101 | 102 | $commerce_status = array( 103 | LANGUAGE_NONE => array( 104 | array( 105 | 'value' => $status, 106 | ), 107 | ), 108 | ); 109 | 110 | $commerce_price = array( 111 | LANGUAGE_NONE => array( 112 | array( 113 | 'value' => $price, 114 | ), 115 | ), 116 | ); 117 | 118 | $booking_price = array( 119 | LANGUAGE_NONE => array( 120 | array( 121 | 'value' => $unit_first['booking_price'], 122 | ), 123 | ), 124 | ); 125 | 126 | $group_size = array( 127 | LANGUAGE_NONE => array( 128 | array( 129 | 'value' => isset($booking_size['adults']) ? $booking_size['adults'] : 0, 130 | ), 131 | array( 132 | 'value' => isset($booking_size['children']) ? $booking_size['children'] : 0, 133 | ), 134 | ), 135 | ); 136 | 137 | $childrens_age = array(LANGUAGE_NONE => array()); 138 | if (isset($booking_size['childrens_age'])) { 139 | foreach ($booking_size['childrens_age'] as $age) { 140 | $childrens_age[LANGUAGE_NONE][]['value'] = $age; 141 | } 142 | } 143 | 144 | $price_options = array(); 145 | foreach ($price_modifiers as $mod) { 146 | $price_options[LANGUAGE_NONE][] = array( 147 | 'name' => $mod['#name'], 148 | 'quantity' => $mod['#quantity'], 149 | 'operation' => $mod['#op_type'], 150 | 'value' => $mod['#amount'], 151 | ); 152 | } 153 | 154 | $line_item->rooms_booking_children_ages = $childrens_age; 155 | $line_item->rooms_booking_number_people = $group_size; 156 | $line_item->rooms_booking_dates = $commerce_dates; 157 | $line_item->rooms_booked_unit_id = $commerce_unit; 158 | $line_item->rooms_booked_status = $commerce_status; 159 | $line_item->rooms_booked_price = $commerce_price; 160 | $line_item->rooms_booked_bookingprice = $booking_price; 161 | $line_item->rooms_booking_options = $price_options; 162 | 163 | $context = array( 164 | 'unit' => $unit_to_book, 165 | 'unit_id' => $unit_id, 166 | 'unit_type' => $unit_type->label, 167 | 'arrival' => $agent->start_date, 168 | 'departure' => $dd, 169 | 'nights' => $nights, 170 | ); 171 | drupal_alter('rooms_create_line_item', $line_item, $context); 172 | 173 | return $line_item; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /modules/rooms_booking_manager/rooms_booking_manager.info: -------------------------------------------------------------------------------- 1 | name = Rooms Booking Manager 2 | description = Manages booking for Rooms - handling main integration with Commerce 3 | core = 7.x 4 | 5 | package = Rooms 6 | 7 | files[] = views/rooms_booking_manager.views.inc 8 | 9 | dependencies[] = number 10 | 11 | dependencies[] = rooms 12 | dependencies[] = rooms_unit 13 | dependencies[] = rooms_booking 14 | dependencies[] = commerce_product 15 | dependencies[] = commerce_product_ui 16 | dependencies[] = commerce_price 17 | dependencies[] = commerce_line_item 18 | dependencies[] = commerce_order 19 | dependencies[] = commerce_order_ui 20 | dependencies[] = commerce_checkout 21 | dependencies[] = entityreference 22 | -------------------------------------------------------------------------------- /modules/rooms_booking_manager/rooms_booking_manager.rules.inc: -------------------------------------------------------------------------------- 1 | t('Cancel the booking related to an order'), 16 | 'parameter' => array( 17 | 'commerce_order' => array( 18 | 'type' => 'commerce_order', 19 | 'label' => t('Order from which to extract info for booking cancellation'), 20 | ), 21 | ), 22 | 'group' => t('Rooms'), 23 | 'callbacks' => array( 24 | 'execute' => 'rooms_booking_cancel_order_booking', 25 | ), 26 | ); 27 | 28 | $actions['rooms_booking_manager_price_apply'] = array( 29 | 'label' => t('Apply price to a booking line item'), 30 | 'parameter' => array( 31 | 'commerce_line_item' => array( 32 | 'type' => 'commerce_line_item', 33 | 'label' => t('Line item'), 34 | ), 35 | ), 36 | 'group' => t('Rooms'), 37 | 'callbacks' => array( 38 | 'execute' => 'rooms_booking_manager_price_apply', 39 | ), 40 | ); 41 | 42 | $actions['rooms_booking_manager_booked_price'] = array( 43 | 'label' => t('Set the line item price to the booked price'), 44 | 'parameter' => array( 45 | 'commerce_line_item' => array( 46 | 'type' => 'commerce_line_item', 47 | 'label' => t('Line item'), 48 | ), 49 | ), 50 | 'group' => t('Rooms'), 51 | 'callbacks' => array( 52 | 'execute' => 'rooms_booking_manager_booked_price', 53 | ), 54 | ); 55 | 56 | return $actions; 57 | } 58 | -------------------------------------------------------------------------------- /modules/rooms_booking_manager/rooms_booking_manager.rules_defaults.inc: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /modules/rooms_booking_manager/rooms_booking_results.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | $units_per_price_level): ?> 16 |
17 | 18 | 19 | 20 | $units) : ?> 21 | $unit) : ?> 22 |
23 | 28 |
29 | 30 | 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /modules/rooms_pricing/css/rooms_pricing.css: -------------------------------------------------------------------------------- 1 | /* Styles for the rooms_pricing edit form */ 2 | .rooms-bulk-pricing-form .fc-header { 3 | display: none; 4 | } 5 | 6 | /* Filter month form */ 7 | .rooms-filter-month-form .form-item-month, 8 | .rooms-filter-month-form .form-item-year, 9 | .rooms-management-form .form-item, 10 | .rooms-filter-month-form .form-item-type { 11 | float: left; 12 | margin-right: 20px; 13 | } 14 | .rooms-filter-month-form .form-item-month label, 15 | .rooms-filter-month-form .form-item-year label, 16 | .rooms-filter-month-form .form-item-type label { 17 | display: inline; 18 | margin-right: 4px; 19 | } 20 | 21 | /* Bulk pricing form */ 22 | .rooms-bulk-pricing-form .fc-day-grid-container { 23 | overflow-y: hidden; 24 | height: 60px; 25 | } 26 | .rooms-bulk-pricing-form .form-item-day-options { 27 | float: left; 28 | margin-right: 20px; 29 | } 30 | .rooms-bulk-pricing-form .form-item-day-options .form-type-checkbox { 31 | display: inline; 32 | } 33 | .rooms-bulk-pricing-form .form-item-day-options .form-checkboxes:after { 34 | content: "."; 35 | display: block; 36 | height: 0; 37 | clear: both; 38 | visibility: hidden; 39 | } 40 | .fc-day-grid-container { 41 | overflow-y: hidden; 42 | } 43 | .fc-title { 44 | color: white; 45 | } 46 | -------------------------------------------------------------------------------- /modules/rooms_pricing/includes/rooms_pricing.pricing_event.inc: -------------------------------------------------------------------------------- 1 | unit_id = $unit_id; 40 | $this->amount = $amount; 41 | $this->start_date = $start_date; 42 | $this->end_date = $end_date; 43 | $this->operation = $operation; 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | protected function createEvent(DateTime $start_date, DateTime $end_date) { 50 | return new PricingEvent($this->unit_id, $this->amount, $start_date, $end_date, $this->operation); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function applyOperation($amount, $operation) { 57 | switch ($operation) { 58 | case ROOMS_REPLACE: 59 | $this->amount = $amount; 60 | break; 61 | 62 | case ROOMS_ADD: 63 | $this->amount = $this->amount + $amount; 64 | break; 65 | 66 | case ROOMS_SUB: 67 | $this->amount = $this->amount - $amount; 68 | break; 69 | 70 | case ROOMS_INCREASE: 71 | $this->amount = $this->amount + (($this->amount) * ($amount / 100)); 72 | break; 73 | 74 | case ROOMS_DECREASE: 75 | $this->amount = $this->amount - (($this->amount) * ($amount / 100)); 76 | break; 77 | 78 | default: 79 | break; 80 | } 81 | 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | public function formatJson() { 88 | $amount = $this->amount; 89 | 90 | $event = array( 91 | "id" => $amount, 92 | "start" => $this->startYear() . '-' . $this->startMonth('m') . '-' . $this->startDay('d') . 'T13:00:00', 93 | "end" => $this->endYear() . '-' . $this->endMonth('m') . '-' . $this->endDay('d') . 'T13:00:00', 94 | ); 95 | 96 | // Set the color. 97 | if ($amount < 100) { 98 | $event['color'] = variable_get('rooms_price_low_value_color', '#F3C776'); 99 | $event['title'] = $amount; 100 | } 101 | elseif ($amount >= 100) { 102 | $event['color'] = variable_get('rooms_price_high_value_color', '#9DDC9D'); 103 | $event['title'] = $amount; 104 | } 105 | 106 | return $event; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /modules/rooms_pricing/includes/rooms_pricing.pricing_event_interface.inc: -------------------------------------------------------------------------------- 1 | $t('Rooms periodic Pricing'), 15 | 'description' => $t('You need to run @url', array('@url' => 'update.php')), 16 | 'value' => 'Weekly, Monthly fields missing', 17 | 'severity' => REQUIREMENT_ERROR, 18 | ); 19 | } 20 | } 21 | 22 | return $requirements; 23 | } 24 | -------------------------------------------------------------------------------- /modules/rooms_pricing/modules/rooms_periodic_pricing/rooms_periodic_pricing.module: -------------------------------------------------------------------------------- 1 | 'fieldset', 11 | '#title' => t('Weekly/Monthly discount'), 12 | '#tree' => FALSE, 13 | '#weight' => -97, 14 | '#collapsible' => TRUE, 15 | '#collapsed' => TRUE, 16 | ); 17 | 18 | $form['weekly_monthly_discount']['weekly_discount'] = array( 19 | '#type' => 'textfield', 20 | '#title' => t('Weekly discount'), 21 | '#default_value' => isset($unit->weekly_discount) ? $unit->weekly_discount : '', 22 | '#size' => '5', 23 | '#field_suffix' => t('% (> 7 days)'), 24 | '#description' => t('You can provide here a percentage that will be applied as a discount to the standard cost for bookings that exceed 7 days.'), 25 | '#maxlength' => 10, 26 | ); 27 | 28 | $form['weekly_monthly_discount']['monthly_discount'] = array( 29 | '#type' => 'textfield', 30 | '#title' => t('Monthly discount'), 31 | '#default_value' => isset($unit->monthly_discount) ? $unit->monthly_discount : '', 32 | '#size' => '5', 33 | '#field_suffix' => t('% (> 28 days)'), 34 | '#description' => t('You can provide here a percentage that will be applied as a discount to the standard cost for bookings that exceed 28 days.'), 35 | '#maxlength' => 10, 36 | ); 37 | 38 | if (isset($unit->weekly_discount) && $unit->weekly_discount == '0.00') { 39 | $form['weekly_monthly_discount']['weekly_discount']['#default_value'] = ''; 40 | } 41 | if (isset($unit->monthly_discount) && $unit->monthly_discount == '0.00') { 42 | $form['weekly_monthly_discount']['monthly_discount']['#default_value'] = ''; 43 | } 44 | 45 | $form['#validate'][] = 'rooms_periodic_pricing_form_rooms_unit_edit_form_validate'; 46 | 47 | $form['#attached']['css'][] = drupal_get_path('module', 'rooms_periodic_pricing') . '/css/rooms_periodic_pricing.css'; 48 | } 49 | 50 | /** 51 | * Validate 'Weekly discount' and 'Monthly discount'. 52 | */ 53 | function rooms_periodic_pricing_form_rooms_unit_edit_form_validate(&$form, &$form_state) { 54 | if (empty($form_state['values']['weekly_discount'])) { 55 | $form_state['values']['weekly_discount'] = '0'; 56 | } 57 | elseif (!empty($form_state['values']['weekly_discount'])) { 58 | if (!is_numeric($form_state['values']['weekly_discount'])) { 59 | form_set_error('weekly_discount', t('%name: you must enter a numeric value for the weekly percentage.', array('%name' => t('Weekly discount')))); 60 | } 61 | elseif ($form_state['values']['weekly_discount'] < 0 || $form_state['values']['weekly_discount'] > 100) { 62 | form_set_error('weekly_discount', t('%name: you must enter a valid value for the weekly percentage.', array('%name' => t('Weekly discount')))); 63 | } 64 | } 65 | 66 | if (empty($form_state['values']['monthly_discount'])) { 67 | $form_state['values']['monthly_discount'] = '0'; 68 | } 69 | elseif (!empty($form_state['values']['monthly_discount'])) { 70 | if (!is_numeric($form_state['values']['monthly_discount'])) { 71 | form_set_error('monthly_discount', t('%name: you must enter a numeric value for the monthly percentage.', array('%name' => t('Monthly discount')))); 72 | } 73 | elseif ($form_state['values']['monthly_discount'] < 0 || $form_state['values']['monthly_discount'] > 100) { 74 | form_set_error('weekly_discount', t('%name: you must enter a valid value for the monthly percentage.', array('%name' => t('Monthly discount')))); 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * Implements hook_rooms_string_alter(). 81 | */ 82 | function rooms_periodic_pricing_rooms_string_alter(&$string_suggestions, $context) { 83 | if ($context['#purpose'] == 'display_base_price') { 84 | $string_suggestion = reset($string_suggestions); 85 | 86 | if (module_exists('commerce_multicurrency')) { 87 | $currency_code = commerce_multicurrency_get_user_currency_code(); 88 | } 89 | else { 90 | $currency_code = commerce_default_currency(); 91 | } 92 | 93 | $price = $context['#data']['price']; 94 | 95 | if ($context['#component'] == 'units_per_type_form') { 96 | $units = reset($context['#data']['units'][$price]); 97 | $unit = $units['unit']; 98 | 99 | $price_log = $units['price_log']; 100 | } 101 | elseif ($context['#component'] == 'book_unit_form_builder') { 102 | $unit = $context['#data']['unit']; 103 | $price_log = $context['#data']['price_log']; 104 | } 105 | 106 | $start_date = $context['#data']['arrival']; 107 | $end_date = $context['#data']['departure']; 108 | 109 | if (isset($price_log['modifiers']['rooms_periodic_pricing']['dynamic_modifier']['pre'])) { 110 | $pre_price = $price_log['modifiers']['rooms_periodic_pricing']['dynamic_modifier']['pre']; 111 | $post_price = $price_log['modifiers']['rooms_periodic_pricing']['dynamic_modifier']['post']; 112 | 113 | $price_amount = ($pre_price - $post_price) * 100; 114 | if (module_exists('commerce_multicurrency')) { 115 | $price_amount = commerce_currency_convert($price_amount, commerce_default_currency(), $currency_code); 116 | } 117 | 118 | $string_suggestion .= ' ' . t('(Reduction of @amount)', array('@amount' => commerce_currency_format($price_amount, $currency_code))); 119 | } 120 | 121 | $string_suggestions[] = $string_suggestion; 122 | } 123 | } 124 | 125 | /** 126 | * Implements hook_rooms_price_modifier_alter(). 127 | */ 128 | function rooms_periodic_pricing_rooms_price_modifier_alter(&$price_modifiers, $booking_info) { 129 | if (!isset($price_modifiers['rooms_periodic_pricing'])) { 130 | $unit = $booking_info['unit']; 131 | $end_date = $booking_info['end_date']; 132 | $start_date = $booking_info['start_date']; 133 | 134 | $weeks_months_days = rooms_periodic_pricing_calculate_weeks_months_days($start_date, $end_date); 135 | 136 | // If "Monthly discount" is set and the current period is greater than or equal to 28 days. 137 | if ($weeks_months_days['months'] > 0 && $unit->monthly_discount > 0) { 138 | // Add a new price modifier to decrease old price with monthly discount. 139 | $price_modifiers['rooms_periodic_pricing'] = array( 140 | '#type' => ROOMS_DYNAMIC_MODIFIER, 141 | '#quantity' => 1, 142 | '#op_type' => ROOMS_DECREASE, 143 | '#amount' => $unit->monthly_discount, 144 | ); 145 | } 146 | // If "Weekly discount" is set and the current period is greater than or equal to 7 days. 147 | elseif ($weeks_months_days['total_weeks'] > 0 && $unit->weekly_discount > 0) { 148 | // Add a new price modifier to decrease old price with weekly discount. 149 | $price_modifiers['rooms_periodic_pricing'] = array( 150 | '#type' => ROOMS_DYNAMIC_MODIFIER, 151 | '#quantity' => 1, 152 | '#op_type' => ROOMS_DECREASE, 153 | '#amount' => $unit->weekly_discount, 154 | ); 155 | } 156 | } 157 | } 158 | 159 | /** 160 | * Calculate number of days, weeks and months in a given period. 161 | * 162 | * @param DateTime $start_date 163 | * The starting date 164 | * 165 | * @param DateTime $end_date 166 | * The end date of our range 167 | * 168 | * @return array 169 | */ 170 | function rooms_periodic_pricing_calculate_weeks_months_days($start_date, $end_date) { 171 | $days = $end_date->diff($start_date)->days; 172 | 173 | $weeks = floor($days / 7); 174 | $months = floor($weeks / 4); 175 | 176 | $more_weeks = $weeks % 4; 177 | $more_days = $days % 7; 178 | 179 | return array( 180 | 'total_days' => $days, 181 | 'total_weeks' => $weeks, 182 | 'more_days' => $more_days, 183 | 'more_weeks' => $more_weeks, 184 | 'months' => $months, 185 | ); 186 | } 187 | -------------------------------------------------------------------------------- /modules/rooms_pricing/rooms_pricing.api.php: -------------------------------------------------------------------------------- 1 | diff($booking_data['end_date'])->days + 1; 33 | 34 | if ($period > 5) { 35 | $price = 100; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /modules/rooms_pricing/rooms_pricing.info: -------------------------------------------------------------------------------- 1 | name = Rooms Pricing 2 | description = Manages pricing information for Rooms. 3 | core = 7.x 4 | 5 | package = Rooms 6 | 7 | files[] = includes/rooms_pricing.unit_pricing_calendar.inc 8 | files[] = includes/rooms_pricing.unit_pricing_calendar_interface.inc 9 | files[] = includes/rooms_pricing.pricing_event.inc 10 | files[] = includes/rooms_pricing.pricing_event_interface.inc 11 | 12 | dependencies[] = rooms_unit 13 | dependencies[] = commerce 14 | dependencies[] = bat_api 15 | -------------------------------------------------------------------------------- /modules/rooms_unit/css/rooms_unit.css: -------------------------------------------------------------------------------- 1 | /* Bookable unit edit form */ 2 | .rooms-unit-edit-form .form-wrapper { 3 | max-width: 400px; 4 | margin: 0 20px 0.4em 0; 5 | } 6 | .rooms-unit-edit-form .form-item { 7 | margin-bottom: 1em; 8 | } 9 | .rooms-unit-edit-form .form-item label, 10 | .rooms-unit-edit-form .form-wrapper legend { 11 | margin-bottom: 0.2em; 12 | } 13 | .rooms-unit-edit-form .form-item label, 14 | .rooms-unit-edit-form .form-wrapper .fieldset-legend { 15 | font-size: 1em; 16 | font-weight: bold; 17 | } 18 | .rooms-unit-edit-form .form-checkbox + label { 19 | font-weight: normal; 20 | } 21 | .rooms-unit-edit-form .form-checkbox:checked + label { 22 | font-weight: bold; 23 | } 24 | .rooms-unit-edit-form .field-widget-rooms-options-combined { 25 | max-width: inherit; 26 | margin-right: 0; 27 | clear: both; 28 | } 29 | 30 | .rooms-unit-edit-form .field-widget-rooms-availability-constraints-range { 31 | max-width: inherit; 32 | margin-right: 0; 33 | clear: both; 34 | } 35 | 36 | .rooms-unit-edit-form .form-item-name, 37 | .rooms-unit-edit-form .form-item-base-price, 38 | .rooms-unit-edit-form .rooms-unit-guest-capacity, 39 | .rooms-unit-edit-form .rooms-unit-child-capacity, 40 | .rooms-unit-edit-form .rooms-unit-bed-arrangement { 41 | float: left; 42 | width: 15em; 43 | margin-right: 20px; 44 | } 45 | 46 | .rooms-unit-edit-form .rooms-unit-guest-capacity { 47 | clear: left; 48 | } 49 | 50 | .rooms-unit-edit-form .rooms-unit-guest-capacity .form-text, 51 | .rooms-unit-edit-form .rooms-unit-child-capacity .form-text, 52 | .rooms-unit-edit-form .rooms-unit-data-bed-arrangement .form-text { 53 | width: 2em; 54 | text-align: right; 55 | } 56 | 57 | .rooms-unit-edit-form .form-item-base-price .form-text { 58 | width: 6em; 59 | text-align: right; 60 | } 61 | 62 | .rooms-unit-edit-form .form-actions { 63 | clear: both; 64 | float: none; 65 | } 66 | -------------------------------------------------------------------------------- /modules/rooms_unit/css/rooms_unit_type.css: -------------------------------------------------------------------------------- 1 | /* Bookable unit type edit form */ 2 | .rooms-unit-type-edit-form .form-wrapper { 3 | max-width: 400px; 4 | margin: 0 20px 0.4em 0; 5 | } 6 | .rooms-unit-type-edit-form .form-item { 7 | margin-bottom: 1em; 8 | } 9 | .rooms-unit-type-edit-form .form-item label, 10 | .rooms-unit-type-edit-form .form-wrapper legend { 11 | margin-bottom: 0.2em; 12 | } 13 | .rooms-unit-type-edit-form .form-item label, 14 | .rooms-unit-type-edit-form .form-wrapper .fieldset-legend { 15 | font-size: 1em; 16 | font-weight: bold; 17 | } 18 | .rooms-unit-type-edit-form .form-checkbox + label { 19 | font-weight: normal; 20 | } 21 | .rooms-unit-type-edit-form .form-checkbox:checked + label { 22 | font-weight: bold; 23 | } 24 | .rooms-unit-type-edit-form .field-widget-rooms-options-combined { 25 | max-width: inherit; 26 | margin-right: 0; 27 | clear: both; 28 | } 29 | .rooms-unit-type-edit-form .field-widget-rooms-availability-constraints-range { 30 | max-width: inherit; 31 | margin-right: 0; 32 | clear: both; 33 | } 34 | .rooms-unit-type-edit-form .form-actions { 35 | clear: both; 36 | } 37 | 38 | .rooms-unit-type-edit-form .form-item-name, 39 | .rooms-unit-type-edit-form .form-item-data-base-price, 40 | .rooms-unit-type-edit-form .rooms-unit-type-guest-capacity, 41 | .rooms-unit-type-edit-form .rooms-unit-type-child-capacity { 42 | float: left; 43 | width: 15em; 44 | margin-right: 20px; 45 | } 46 | .rooms-unit-type-edit-form .rooms-unit-type-reference, 47 | .rooms-unit-type-edit-form .rooms-unit-type-defaults { 48 | float: none; 49 | width: 100%; 50 | max-width: 100%; 51 | } 52 | -------------------------------------------------------------------------------- /modules/rooms_unit/js/rooms_unit.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | 3 | Drupal.behaviors.nodeFieldsetSummaries = { 4 | attach: function (context) { 5 | $('fieldset.unit-form-availability', context).drupalSetSummary(function (context) { 6 | var vals = []; 7 | 8 | if ($('.form-item-bookable input', context).is(':checked')) { 9 | vals.push(Drupal.t('Bookable')); 10 | } 11 | else { 12 | vals.push(Drupal.t('Not bookable')); 13 | } 14 | 15 | vals.push($('#edit-default-state option:selected').text()); 16 | 17 | return vals.join(', '); 18 | }); 19 | 20 | $('fieldset.unit-form-multiple', context).drupalSetSummary(function (context) { 21 | 22 | if ($('.form-item-multiple input', context).val() > 1) { 23 | return (Drupal.t('Multiple: @units units', { '@units': $('.form-item-multiple input', context).val()})); 24 | } 25 | else { 26 | return (Drupal.t('Not multiple')); 27 | } 28 | }); 29 | 30 | $('fieldset.unit-form-author', context).drupalSetSummary(function (context) { 31 | var name = $('.form-item-author-name input', context).val() || Drupal.settings.anonymous, 32 | date = $('.form-item-date input', context).val(); 33 | return date ? 34 | Drupal.t('By @name on @date', { '@name': name, '@date': date }) : 35 | Drupal.t('By @name', { '@name': name }); 36 | }); 37 | 38 | $('fieldset.unit-form-published', context).drupalSetSummary(function (context) { 39 | 40 | if ($('.form-item-status input', context).is(':checked')) { 41 | return (Drupal.t('Published')); 42 | } 43 | else { 44 | return (Drupal.t('Not published')); 45 | } 46 | }); 47 | } 48 | }; 49 | 50 | })(jQuery); 51 | -------------------------------------------------------------------------------- /modules/rooms_unit/rooms_unit-extra-data.tpl.php: -------------------------------------------------------------------------------- 1 | 14 | 15 |
16 | Unit Sample Data: ' . $rooms_unit_sample_data = ($rooms_unit_sample_data) ? 'Switch On' : 'Switch Off' ?> 17 |
18 | -------------------------------------------------------------------------------- /modules/rooms_unit/rooms_unit.features.inc: -------------------------------------------------------------------------------- 1 | 31 |
> 32 | 33 | > 34 | 35 | 36 | 37 | 38 |
> 39 | 46 |
47 |
48 | -------------------------------------------------------------------------------- /modules/rooms_unit/views/rooms_unit_handler_delete_link_field.inc: -------------------------------------------------------------------------------- 1 | additional_fields['type'] = 'type'; 13 | } 14 | 15 | 16 | function render($values) { 17 | $type = $values->{$this->aliases['type']}; 18 | 19 | // Creating a dummy room to check access against. 20 | $dummy_unit = (object) array('type' => $type); 21 | if (!rooms_unit_access('delete', $dummy_unit)) { 22 | return; 23 | } 24 | 25 | $text = !empty($this->options['text']) ? $this->options['text'] : t('delete'); 26 | $unit_id = $values->{$this->aliases['unit_id']}; 27 | 28 | return l($text, 'admin/rooms/units/unit/' . $unit_id . '/delete'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /modules/rooms_unit/views/rooms_unit_handler_edit_link_field.inc: -------------------------------------------------------------------------------- 1 | additional_fields['type'] = 'type'; 13 | } 14 | 15 | 16 | function render($values) { 17 | $type = $values->{$this->aliases['type']}; 18 | 19 | // Creating a dummy unit to check access against. 20 | $dummy_unit = (object) array('type' => $type); 21 | if (!rooms_unit_access('update', $dummy_unit)) { 22 | return; 23 | } 24 | $text = !empty($this->options['text']) ? $this->options['text'] : t('edit'); 25 | $unit_id = $values->{$this->aliases['unit_id']}; 26 | 27 | return l($text, 'admin/rooms/units/unit/' . $unit_id . '/edit'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modules/rooms_unit/views/rooms_unit_handler_link_field.inc: -------------------------------------------------------------------------------- 1 | additional_fields['unit_id'] = 'unit_id'; 14 | $this->additional_fields['type'] = 'type'; 15 | } 16 | 17 | function option_definition() { 18 | $options = parent::option_definition(); 19 | 20 | $options['text'] = array('default' => '', 'translatable' => TRUE); 21 | 22 | return $options; 23 | } 24 | 25 | function options_form(&$form, &$form_state) { 26 | parent::options_form($form, $form_state); 27 | 28 | $form['text'] = array( 29 | '#type' => 'textfield', 30 | '#title' => t('Text to display'), 31 | '#default_value' => $this->options['text'], 32 | ); 33 | } 34 | 35 | function query() { 36 | $this->ensure_my_table(); 37 | $this->add_additional_fields(); 38 | } 39 | 40 | function render($values) { 41 | $text = !empty($this->options['text']) ? $this->options['text'] : t('view'); 42 | $unit_id = $values->{$this->aliases['unit_id']}; 43 | 44 | return l($text, 'unit/' . $unit_id); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /modules/rooms_unit/views/rooms_unit_handler_unit_operations_field.inc: -------------------------------------------------------------------------------- 1 | additional_fields['unit_id'] = 'unit_id'; 14 | } 15 | 16 | function query() { 17 | $this->ensure_my_table(); 18 | $this->add_additional_fields(); 19 | } 20 | 21 | function render($values) { 22 | $links = menu_contextual_links('rooms_unit', 'admin/rooms/units/unit', array($this->get_value($values, 'unit_id'))); 23 | 24 | // Unset the "view" link, as this is provided separately. 25 | if (array_key_exists('rooms_unit-view', $links)) { 26 | unset($links['rooms_unit-view']); 27 | } 28 | 29 | if (!empty($links)) { 30 | return theme('links', array( 31 | 'links' => $links, 32 | 'attributes' => array( 33 | 'class' => array( 34 | 'links', 35 | 'inline', 36 | 'operations', 37 | ), 38 | ), 39 | )); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /modules/rooms_unit/views/rooms_unit_handler_unit_type_field.inc: -------------------------------------------------------------------------------- 1 | get_value($values)); 17 | return $unit_type->label; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /rooms.api.php: -------------------------------------------------------------------------------- 1 | max_sleeps; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /rooms.info: -------------------------------------------------------------------------------- 1 | name = Rooms 2 | description = Provides basic underlying functionality and configuration options used by all Rooms modules. 3 | core = 7.x 4 | php = 5.3.9 5 | 6 | files[] = includes/rooms.calendar.inc 7 | files[] = includes/rooms.calendar_interface.inc 8 | files[] = includes/rooms.event.inc 9 | files[] = includes/rooms.event_interface.inc 10 | 11 | package = Rooms 12 | 13 | dependencies[] = date 14 | dependencies[] = date_api 15 | dependencies[] = date_popup 16 | dependencies[] = jquery_update 17 | dependencies[] = libraries (>=2.x) 18 | dependencies[] = variable 19 | -------------------------------------------------------------------------------- /rooms.install: -------------------------------------------------------------------------------- 1 | array( 15 | 'name' => array( 16 | 'type' => 'varchar', 17 | 'length' => 255, 18 | 'not null' => TRUE, 19 | ), 20 | 'quantity' => array( 21 | 'type' => 'int', 22 | 'not null' => FALSE, 23 | ), 24 | 'operation' => array( 25 | 'type' => 'varchar', 26 | 'length' => 255, 27 | 'not null' => FALSE, 28 | ), 29 | 'value' => array( 30 | 'type' => 'float', 31 | 'not null' => FALSE, 32 | ), 33 | 'type' => array( 34 | 'type' => 'varchar', 35 | 'length' => 255, 36 | 'not null' => FALSE, 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | 43 | /** 44 | * Implements hook_requirements(). 45 | */ 46 | function rooms_requirements($phase) { 47 | $requirements = array(); 48 | $t = get_t(); 49 | switch ($phase) { 50 | case 'runtime': 51 | if (!rooms_library_loaded('fullcalendar', 'minified')) { 52 | $description = array(); 53 | $fullcalendar_library = libraries_detect('fullcalendar'); 54 | if (!$fullcalendar_library['installed']) { 55 | $description[] = $t('Could not load the FullCalendar Library'); 56 | } 57 | $requirements['fullcalendar'] = array( 58 | 'title' => $t('FullCalendar'), 59 | 'description' => implode('
', $description), 60 | 'value' => 'FullCalendar Missing', 61 | 'severity' => REQUIREMENT_ERROR, 62 | ); 63 | } 64 | else { 65 | $requirements['fullcalendar'] = array( 66 | 'title' => $t('FullCalendar'), 67 | 'description' => $t('The FullCalendar Library is installed'), 68 | 'value' => 'FullCalendar Installed', 69 | 'severity' => REQUIREMENT_OK, 70 | ); 71 | } 72 | } 73 | 74 | return $requirements; 75 | } 76 | 77 | /** 78 | * Utility function: rename a set of permissions. 79 | */ 80 | function rooms_update_rename_permissions($map) { 81 | // Easy part: rename the permissions in {role_permission}. 82 | foreach ($map as $old_name => $new_name) { 83 | db_update('role_permission') 84 | ->fields(array('permission' => $new_name)) 85 | ->condition('permission', $old_name) 86 | ->execute(); 87 | } 88 | 89 | // Trickier: rename the permission for the in-database Views. 90 | foreach (views_get_all_views() as $view) { 91 | if ($view->type == t('Default')) { 92 | continue; 93 | } 94 | 95 | $save_view = FALSE; 96 | foreach ($view->display as $display_name => $display) { 97 | if (!empty($display->display_options['access']['type']) && $display->display_options['access']['type'] == 'perm') { 98 | $permission_name = $display->display_options['access']['perm']; 99 | if (isset($map[$permission_name])) { 100 | $display->display_options['access']['perm'] = $map[$permission_name]; 101 | $save_view = TRUE; 102 | } 103 | } 104 | } 105 | 106 | if ($save_view) { 107 | $view->save(); 108 | } 109 | } 110 | } 111 | 112 | /** 113 | * Converts the booking option price value columns from integer to float. 114 | */ 115 | function rooms_update_7001() { 116 | $spec = array( 117 | 'type' => 'float', 118 | 'not null' => FALSE, 119 | ); 120 | 121 | $fields = array_filter(field_info_fields(), function($item) { 122 | return $item['type'] == 'rooms_options'; 123 | }); 124 | 125 | foreach ($fields as $field_definition) { 126 | $data_table = 'field_data_' . $field_definition['field_name']; 127 | $revision_table = 'field_revision_' . $field_definition['field_name']; 128 | $field = $field_definition['field_name'] . '_value'; 129 | 130 | db_change_field($data_table, $field, $field, $spec); 131 | db_change_field($revision_table, $field, $field, $spec); 132 | } 133 | return t('Field structure updated.'); 134 | } 135 | 136 | /** 137 | * Add the booking option Type column. 138 | */ 139 | function rooms_update_7002() { 140 | $fields = field_info_fields(); 141 | 142 | foreach ($fields as $field) { 143 | if ($field['type'] == 'rooms_options') { 144 | $revision_table = key($field['storage']['details']['sql']['FIELD_LOAD_REVISION']); 145 | $field_name = $field['storage']['details']['sql']['FIELD_LOAD_REVISION'][$revision_table]['type']; 146 | if (!db_field_exists($revision_table, $field_name)) { 147 | $field_spec = $field['columns']['type']; 148 | $field_spec['initial'] = 'optional'; 149 | db_add_field($revision_table, $field_name, $field_spec); 150 | } 151 | 152 | $current_table = key($field['storage']['details']['sql']['FIELD_LOAD_CURRENT']); 153 | $field_name = $field['storage']['details']['sql']['FIELD_LOAD_CURRENT'][$current_table]['type']; 154 | if (!db_field_exists($current_table, $field_name)) { 155 | $field_spec = $field['columns']['type']; 156 | $field_spec['initial'] = 'optional'; 157 | db_add_field($current_table, $field_name, $field_spec); 158 | } 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /rooms.make: -------------------------------------------------------------------------------- 1 | api = 2 2 | core = 7.x 3 | 4 | ; @see https://github.com/drush-ops/drush 5 | 6 | ; FullCalendar 7 | libraries[fullcalendar][directory_name] = fullcalendar 8 | libraries[fullcalendar][type] = library 9 | libraries[fullcalendar][destination] = libraries 10 | libraries[fullcalendar][download][type] = get 11 | libraries[fullcalendar][download][url] = https://github.com/arshaw/fullcalendar/releases/download/v3.1.0/fullcalendar-3.1.0.zip 12 | -------------------------------------------------------------------------------- /rooms.variable.inc: -------------------------------------------------------------------------------- 1 | 'string', 13 | 'title' => t('Arrival Date'), 14 | 'default' => 'Arrival Date', 15 | 'description' => t('Change text : Arrival Date'), 16 | 'localize' => TRUE, 17 | 'group' => 'rooms', 18 | ); 19 | 20 | $variables['rooms_departure_date'] = array( 21 | 'type' => 'string', 22 | 'title' => t('Departure Date'), 23 | 'default' => 'Departure Date', 24 | 'description' => t('Change text : Departure Date'), 25 | 'localize' => TRUE, 26 | 'group' => 'rooms', 27 | ); 28 | 29 | return $variables; 30 | } 31 | 32 | /** 33 | * Implements hook_variable_group_info(). 34 | */ 35 | 36 | function rooms_variable_group_info() { 37 | $groups['rooms'] = array( 38 | 'title' => t('Rooms'), 39 | 'description' => t('Variable examples of different types.'), 40 | 'access' => 'configure room settings', 41 | 'path' => array('admin/rooms/'), 42 | ); 43 | return $groups; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /rooms_three_month_calendar.tpl.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 |
8 |
9 |
10 |
11 |
12 | 13 |
14 |
15 |
16 |
17 |
18 | 19 |
20 | -------------------------------------------------------------------------------- /rooms_ui.info: -------------------------------------------------------------------------------- 1 | name = Rooms UI 2 | description = Interface support to the Rooms family of modules. 3 | core = 7.x 4 | version = 1.x-dev 5 | 6 | package = Rooms 7 | 8 | dependencies[] = rooms 9 | dependencies[] = variable 10 | -------------------------------------------------------------------------------- /rooms_ui.module: -------------------------------------------------------------------------------- 1 | 'Rooms', 18 | 'description' => 'Administer Rooms.', 19 | 'page callback' => 'system_admin_menu_block_page', 20 | 'access arguments' => array('access administration pages'), 21 | 'file path' => drupal_get_path('module', 'system'), 22 | 'file' => 'system.admin.inc', 23 | 'weight' => -7, 24 | ); 25 | $items['admin/rooms/config'] = array( 26 | 'title' => 'Configuration', 27 | 'description' => 'Configure settings and business rules for Rooms management.', 28 | 'page callback' => 'system_admin_menu_block_page', 29 | 'access arguments' => array('configure room settings'), 30 | 'type' => MENU_NORMAL_ITEM, 31 | 'weight' => 10, 32 | 'file path' => drupal_get_path('module', 'system'), 33 | 'file' => 'system.admin.inc', 34 | ); 35 | $items['admin/rooms/config/bookings'] = array( 36 | 'title' => 'Booking Settings', 37 | 'description' => 'Configure settings and business rules for Bookings.', 38 | 'page callback' => 'drupal_get_form', 39 | 'page arguments' => array('rooms_booking_settings'), 40 | 'access arguments' => array('configure room settings'), 41 | 'type' => MENU_NORMAL_ITEM, 42 | ); 43 | $items['admin/rooms/config/terminology'] = array( 44 | 'title' => 'Rooms Terminology', 45 | 'description' => 'Change Rooms Terminology', 46 | 'page callback' => 'drupal_get_form', 47 | 'page arguments' => array('variable_group_form', 'rooms'), 48 | 'access arguments' => array('configure room settings'), 49 | ); 50 | 51 | return $items; 52 | } 53 | 54 | /** 55 | * Implements hook_page_build(). 56 | */ 57 | function rooms_ui_page_build(&$page) { 58 | drupal_add_css(drupal_get_path('module', 'rooms_ui') . '/css/rooms_ui.css'); 59 | } 60 | 61 | /** 62 | * Main booking settings form. 63 | */ 64 | function rooms_booking_settings($form, &$form_state) { 65 | // Management vertical tabs. 66 | $form['rooms_settings'] = array( 67 | '#type' => 'vertical_tabs', 68 | '#weight' => 99, 69 | '#attached' => array( 70 | 'css' => array( 71 | array( 72 | 'data' => '#rooms-booking-settings .vertical-tabs .form-type-textfield input { width: auto; }', 73 | 'type' => 'inline', 74 | ), 75 | ), 76 | ), 77 | ); 78 | 79 | $form['rooms_booking_settings'] = array( 80 | '#type' => 'fieldset', 81 | '#group' => 'rooms_settings', 82 | '#title' => t('Rooms booking settings'), 83 | ); 84 | 85 | $form['rooms_booking_settings']['rooms_booking_start_date'] = array( 86 | '#type' => 'select', 87 | '#title' => t('How soon a booking can start'), 88 | '#description' => t('Users with the "Make same-day bookings" permission will not be affected by this setting.'), 89 | '#options' => array( 90 | '0' => t('Same day bookings'), 91 | '1' => t('1 day in advance'), 92 | '2' => t('@count days in advance', array('@count' => 2)), 93 | '3' => t('@count days in advance', array('@count' => 3)), 94 | '4' => t('@count days in advance', array('@count' => 4)), 95 | '5' => t('@count days in advance', array('@count' => 5)), 96 | '6' => t('@count days in advance', array('@count' => 6)), 97 | '7' => t('@count days in advance', array('@count' => 7)), 98 | ), 99 | '#default_value' => variable_get('rooms_booking_start_date', 1), 100 | ); 101 | 102 | return system_settings_form($form); 103 | } 104 | 105 | /** 106 | * Implements hook_variable_info_alter(). 107 | * 108 | * Group all rooms-related variables under a single 'rooms' group. 109 | * This simplifies access control and the Rooms Terminology form. 110 | */ 111 | function rooms_ui_variable_info_alter(&$info) { 112 | foreach ($info as $name => $variable) { 113 | if (0 === strpos($name, 'rooms_')) { 114 | $info[$name]['group'] = 'rooms'; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /test/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "drupal/drupal-extension": "~3.1" 4 | }, 5 | "config": { 6 | "bin-dir": "bin/" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/default.behat.yml: -------------------------------------------------------------------------------- 1 | default: 2 | suites: 3 | default: 4 | contexts: 5 | - FeatureContext 6 | - Drupal\DrupalExtension\Context\DrupalContext 7 | - Drupal\DrupalExtension\Context\MessageContext 8 | - Drupal\DrupalExtension\Context\MinkContext 9 | - Drupal\DrupalExtension\Context\MarkupContext 10 | extensions: 11 | Behat\MinkExtension: 12 | goutte: ~ 13 | base_url: http://roomsdemo.local 14 | default_session: goutte 15 | selenium2: 16 | browser: chrome 17 | capabilities: { "browserName": "chrome", "browser": "chrome", 'chrome': {'switches':['--no-sandbox']} } 18 | Drupal\DrupalExtension: 19 | blackbox: ~ 20 | api_driver: 'drupal' 21 | drush_driver: 'drush' 22 | drush: 23 | alias: 'rooms.local' 24 | drupal: 25 | drupal_root: '/path/to/drupal' 26 | selectors: 27 | message_selector: '.messages' 28 | error_message_selector: '.messages.error' 29 | success_message_selector: '.messages.status' 30 | -------------------------------------------------------------------------------- /test/features/availability.feature: -------------------------------------------------------------------------------- 1 | Feature: Once rooms_availability is installed 2 | In order to create bookable units 3 | As a site administrator 4 | I should be able to manage the bookable units availability options 5 | 6 | @api @javascript 7 | Scenario: Availability manager user should be able to modify the availability state of units both in bulk availability page and per unit. 8 | # Creating a unit type and some units. 9 | Given unit types: 10 | | type | label | base_price | min_sleeps | max_sleeps | min_children | max_children | 11 | | standard | Standard | 100 | 2 | 3 | 0 | 1 | 12 | 13 | # Creating a bunch of units programmatically. 14 | Given "standard" units: 15 | | name | default_state | 16 | | Unavailable | 0 | 17 | | Available | 1 | 18 | | On Request | 2 | 19 | | An. Booking | 3 | 20 | 21 | # Checking the initial conditions. 22 | Given the cache has been cleared 23 | And I am logged in as a user with the "access administration pages,view any rooms_unit entity of bundle standard,administer rooms_unit availability,update availability any rooms_unit entity of bundle standard,access availability index service" permissions 24 | Then the state for "Unavailable" between "2016-01-05" and "2016-03-07" should be "0" 25 | And the state for "Available" between "2016-01-05" and "2016-03-07" should be "1" 26 | And the state for "On Request" between "2016-01-05" and "2016-03-07" should be "2" 27 | And the state for "An. Booking" between "2016-01-05" and "2016-03-07" should be "3" 28 | 29 | Given I am on "admin/rooms/units/bulk_unit_management/2016/1" 30 | Then I click "Update Availability" 31 | And I fill in "rooms_start_date[date]" with "19/01/2016" 32 | And I fill in "rooms_end_date[date]" with "23/01/2016" 33 | And I select "On Request" from "change_event_status" 34 | And I select "All (this page)" from "select-all" 35 | And I press the "Update Unit Availability" button 36 | 37 | Then the state for "Available" between "2016-01-19" and "2016-01-23" should be "2" 38 | And the state for "Unavailable" between "2016-01-19" and "2016-01-23" should be "2" 39 | And the state for "On Request" between "2016-01-19" and "2016-01-23" should be "2" 40 | And the state for "An. Booking" between "2016-01-19" and "2016-01-23" should be "2" 41 | 42 | # Checking the single unit availability management page. 43 | Given I am managing the "Unavailable" unit availability 44 | Then I click "Update Unit Availability" 45 | And I fill in "rooms_start_date[date]" with "29/01/2016" 46 | And I fill in "rooms_end_date[date]" with "23/02/2017" 47 | And I select "Available" from "unit_state" 48 | And I press the "Update Availability" button 49 | 50 | Then the state for "Unavailable" between "2016-01-29" and "2017-02-23" should be "1" 51 | -------------------------------------------------------------------------------- /test/features/availability_constraints.feature: -------------------------------------------------------------------------------- 1 | Feature: Once rooms_availability_constraints is installed 2 | I should be able to create bookable units availability range constrains 3 | 4 | @api @javascript 5 | Scenario: I can create bookable units range constraints. 6 | Given I am logged in as a user with the "administrator" role 7 | 8 | # Creating a unit type and some units. 9 | Given unit types: 10 | | type | label | base_price | min_sleeps | max_sleeps | min_children | max_children | 11 | | standard | Standard | 100 | 2 | 3 | 0 | 1 | 12 | 13 | # Creating a bunch of units programmatically. 14 | Given "standard" units: 15 | | name | base_price | 16 | | Normal | 120 | 17 | | Special | 130 | 18 | 19 | Then I am on "admin/rooms/units/unit-types/manage/standard" 20 | And I check the box "rooms_availability_range_unit" 21 | And I check the box "rooms_availability_range_type" 22 | And I press the "Save unit type" button 23 | 24 | Then I am editing the "Normal" unit 25 | And I add a constraint from "2016-01-03" to "2016-03-03" "must" start on "1" 26 | And I add a constraint from "2016-01-03" to "2016-03-03" "" start on "" and the minimum is "" and the maximum is "5" 27 | And I add a constraint from "2016-03-04" to "2016-06-03" "if" start on "2" and the minimum is "3" and the maximum is "" 28 | And I add a constraint from "2016-03-04" to "2016-06-03" "" start on "" and the minimum is "" and the maximum is "5" 29 | And I add an always constraint where "" start on "" and the minimum is "2" and the maximum is "" 30 | And I press the "Save Unit" button 31 | 32 | Then I will be able to make a booking for "Normal" unit from "2016-01-04" to "2016-01-07" 33 | And I won't be able to make a booking for "Normal" unit from "2016-01-04" to "2016-01-15" 34 | And I won't be able to make a booking for "Normal" unit from "2016-01-05" to "2016-01-08" 35 | And I won't be able to make a booking for "Normal" unit from "2016-03-08" to "2016-03-10" 36 | And I won't be able to make a booking for "Normal" unit from "2016-03-08" to "2016-03-16" 37 | And I will be able to make a booking for "Normal" unit from "2016-03-08" to "2016-03-12" 38 | And I will be able to make a booking for "Normal" unit from "2016-03-09" to "2016-03-12" 39 | And I won't be able to make a booking for "Normal" unit from "2016-10-08" to "2016-10-09" 40 | And I will be able to make a booking for "Normal" unit from "2016-10-08" to "2016-10-19" 41 | 42 | Then I am on "admin/rooms/units/unit-types/manage/standard" 43 | 44 | And I add a constraint from "2016-06-04" to "2016-09-03" "must" start on "3" 45 | And I add a constraint from "2016-06-04" to "2016-09-03" "" start on "" and the minimum is "" and the maximum is "5" 46 | And I add a constraint from "2016-09-04" to "2016-12-03" "if" start on "2" and the minimum is "3" and the maximum is "" 47 | And I add a constraint from "2016-09-04" to "2016-12-03" "" start on "" and the minimum is "" and the maximum is "5" 48 | And I press the "Save unit type" button 49 | 50 | Then I will be able to make a booking for "Special" unit from "2016-08-03" to "2016-08-06" 51 | And I won't be able to make a booking for "Special" unit from "2016-08-03" to "2016-08-14" 52 | And I won't be able to make a booking for "Special" unit from "2016-08-04" to "2016-08-07" 53 | And I will be able to make a booking for "Special" unit from "2016-10-18" to "2016-10-22" 54 | And I won't be able to make a booking for "Special" unit from "2016-10-18" to "2016-10-26" 55 | And I will be able to make a booking for "Special" unit from "2016-10-19" to "2016-10-20" 56 | And I will be able to make a booking for "Special" unit from "2016-10-08" to "2016-10-09" 57 | 58 | Then I won't be able to make a booking for "Normal" unit from "2016-10-08" to "2016-10-19" 59 | -------------------------------------------------------------------------------- /test/features/availability_filter.feature: -------------------------------------------------------------------------------- 1 | Feature: Availability filter 2 | In order to test overlapping confirmation message 3 | 4 | @api @javascript 5 | Scenario: Placing bookings in cart with the same dates 6 | Given unit types: 7 | | type | label | base_price | min_sleeps | max_sleeps | min_children | max_children | 8 | | standard | Standard | 100 | 2 | 3 | 0 | 1 | 9 | 10 | Given "standard" units: 11 | | name | 12 | | Normal | 13 | 14 | Given CommerceCart Availability Filter is not active 15 | 16 | Given the cache has been cleared 17 | 18 | Given I am logged in as a user with the "access checkout" permission 19 | Then I will be able to make a booking for "Normal" unit from "2016-01-05" to "2016-01-08" 20 | 21 | When I am on "booking/2016-01-05/2016-01-08/1/?rooms_group_size1=2" 22 | Then I select "1" from "standard[300][quantity]" 23 | And I wait for AJAX to finish 24 | And I press "Place Booking" 25 | Then I should see "Booking for Standard (3 Nights; Arrival: 05-01-2016 Departure: 08-01-2016)" 26 | And I should see "$300.00" 27 | 28 | When I am on "booking/2016-01-05/2016-01-08/1/?rooms_group_size1=2" 29 | Then I select "1" from "standard[300][quantity]" 30 | And I wait for AJAX to finish 31 | And I press "Place Booking" 32 | Then I should see "You have a booking request with overlapping dates, continuing will replace it." 33 | And I should see "Go Back to Search" 34 | And I press "Continue" 35 | Then I should see "Booking for Standard (3 Nights; Arrival: 05-01-2016 Departure: 08-01-2016)" 36 | And I should see "$300.00" 37 | 38 | @api @javascript 39 | Scenario: Placing bookings in cart with overlapping dates 40 | Given unit types: 41 | | type | label | base_price | min_sleeps | max_sleeps | min_children | max_children | 42 | | standard | Standard | 100 | 2 | 3 | 0 | 1 | 43 | 44 | Given "standard" units: 45 | | name | 46 | | Normal | 47 | 48 | Given CommerceCart Availability Filter is not active 49 | 50 | Given the cache has been cleared 51 | 52 | Given I am logged in as a user with the "access checkout" permission 53 | Then I will be able to make a booking for "Normal" unit from "2016-01-05" to "2016-01-08" 54 | 55 | When I am on "booking/2016-01-05/2016-01-08/1/?rooms_group_size1=2" 56 | Then I select "1" from "standard[300][quantity]" 57 | And I wait for AJAX to finish 58 | And I press "Place Booking" 59 | Then I should see "Booking for Standard (3 Nights; Arrival: 05-01-2016 Departure: 08-01-2016)" 60 | And I should see "$300.00" 61 | 62 | When I am on "booking/2016-01-03/2016-01-11/1/?rooms_group_size1=2" 63 | Then I select "1" from "standard[800][quantity]" 64 | And I wait for AJAX to finish 65 | And I press "Place Booking" 66 | Then I should see "You have a booking request with overlapping dates, continuing will replace it." 67 | And I should see "Go Back to Search" 68 | And I press "Continue" 69 | 70 | @api @javascript 71 | Scenario: Replacing overlapping bookings 72 | Given unit types: 73 | | type | label | base_price | min_sleeps | max_sleeps | min_children | max_children | 74 | | standard | Standard | 100 | 2 | 3 | 0 | 1 | 75 | 76 | Given "standard" units: 77 | | name | 78 | | Normal | 79 | 80 | Given CommerceCart Availability Filter is not active 81 | 82 | Given the cache has been cleared 83 | 84 | Given I am logged in as a user with the "access checkout" permission 85 | Then I will be able to make a booking for "Normal" unit from "2016-01-05" to "2016-01-08" 86 | 87 | When I am on "booking/2016-01-05/2016-01-08/1/?rooms_group_size1=2" 88 | Then I select "1" from "standard[300][quantity]" 89 | And I wait for AJAX to finish 90 | And I press "Place Booking" 91 | Then I should see "Booking for Standard (3 Nights; Arrival: 05-01-2016 Departure: 08-01-2016)" 92 | And I should see "$300.00" 93 | 94 | When I am on "booking/2016-01-03/2016-01-11/1/?rooms_group_size1=2" 95 | Then I select "1" from "standard[800][quantity]" 96 | And I wait for AJAX to finish 97 | And I press "Place Booking" 98 | Then I should see "You have a booking request with overlapping dates, continuing will replace it." 99 | And I should see "Go Back to Search" 100 | And I press "Continue" 101 | Then I should see "Booking for Standard (8 Nights; Arrival: 03-01-2016 Departure: 11-01-2016)" 102 | And I should see "$800.00" 103 | And I should not see "Booking for Standard (3 Nights; Arrival: 05-01-2016 Departure: 08-01-2016)" 104 | And I should not see "$300.00" 105 | -------------------------------------------------------------------------------- /test/features/availability_reference.feature: -------------------------------------------------------------------------------- 1 | Feature: Once rooms_availability_reference is installed 2 | In order to create bookable units 3 | As a site administrator 4 | I should be able to add Availability calendars in any entity. 5 | 6 | @api @javascript 7 | Scenario: Admin user should be able to add an availability_calendar in nodes. 8 | Given I am logged in as a user with the "administrator" role 9 | 10 | # Creating a unit type and some units. 11 | Given unit types: 12 | | type | label | base_price | min_sleeps | max_sleeps | min_children | max_children | 13 | | standard | Standard | 100 | 2 | 3 | 0 | 1 | 14 | 15 | # Creating a bunch of units programmatically. 16 | Given "standard" units: 17 | | name | base_price | 18 | | Normal | 120 | 19 | | Special | 130 | 20 | 21 | # Creating a customer profile. 22 | Given customer profiles: 23 | | profile_id | name | country | locality | address | postal_code | 24 | | profile1 | User test 1 | US | Austin | 1900 David St | 78705 | 25 | | profile2 | User test 2 | US | Austin | 1900 David St | 78705 | 26 | 27 | # Creating a booking. 28 | Given "standard_booking" bookings: 29 | | profile_id | guests | children | start_date | end_date | unit | status | 30 | | profile2 | 1 | 1 | 2016-05-19 | 2016-05-23 | Normal | 1 | 31 | 32 | Then the "Normal" unit should be Confirmed by the last booking between "2016-05-19" and "2016-05-22" 33 | 34 | # Creating a booking. 35 | Given "standard_booking" bookings: 36 | | profile_id | guests | children | start_date | end_date | unit | status | 37 | | profile1 | 3 | 0 | 2016-05-19 | 2016-05-23 | Special | 0 | 38 | 39 | Then the "Special" unit should be Unconfirmed by the last booking between "2016-05-19" and "2016-05-22" 40 | 41 | When I add the "availability_ref" availability reference field referencing to "standard" units in "page2" content 42 | Then I should be able to edit a "page2" 43 | And reference units "Normal,Special" in the "availability_ref" field 44 | And I fill in "title" with "asdf" 45 | And I press the "Save" button 46 | 47 | Then I navigate in the fullCalendar to "2016-05" 48 | And I should see the text "User test 2" 49 | And I should see the text "UNCONF" 50 | -------------------------------------------------------------------------------- /test/features/booking.feature: -------------------------------------------------------------------------------- 1 | Feature: Once rooms_booking correctly installed 2 | In order to create bookings 3 | As a site administrator 4 | I need to be able to access all the Rooms booking configuration screens 5 | And create bookings from scratch 6 | 7 | @api @javascript 8 | Scenario: I can create a booking 9 | Given I am logged in as a user with the "administrator" role 10 | 11 | # Creating a unit type and some units. 12 | Given unit types: 13 | | type | label | base_price | min_sleeps | max_sleeps | min_children | max_children | 14 | | standard | Standard | 100 | 2 | 3 | 0 | 1 | 15 | 16 | # Creating a bunch of units programmatically. 17 | Given "standard" units: 18 | | name | base_price | min_sleeps | max_sleeps | 19 | | Normal | 120 | 1 | 2 | 20 | | Special | 130 | 2 | 3 | 21 | 22 | # Checking that Standard booking type exists. 23 | When I am on "admin/rooms/bookings/booking-types" 24 | Then I should see the text "Standard Booking" 25 | 26 | # Creating a booking and acustomer profile in modal click by click. 27 | When I am on "admin/rooms/bookings" 28 | Then I should see the text "Add a Booking" 29 | 30 | When I click "Add a Booking" 31 | Then I should see the text "Add Standard Booking" 32 | 33 | When I click "create a new profile" 34 | And I wait for AJAX to finish 35 | 36 | Then I fill in "commerce_customer_address[und][0][name_line]" with "User test" 37 | And select "United States" from "commerce_customer_address[und][0][country]" 38 | And I wait for AJAX to finish 39 | 40 | Then I fill in "commerce_customer_address[und][0][thoroughfare]" with "1900 David St" 41 | And I fill in "commerce_customer_address[und][0][locality]" with "Austin" 42 | And select "Texas" from "commerce_customer_address[und][0][administrative_area]" 43 | And I fill in "commerce_customer_address[und][0][postal_code]" with "78705" 44 | And I press the "Save profile" button 45 | 46 | Given I wait for AJAX to finish 47 | Then I fill in "rooms_start_date[date]" with "19/06/2015" 48 | And I fill in "rooms_end_date[date]" with "23/06/2015" 49 | And I press the "Check availability" button 50 | And I wait for AJAX to finish 51 | 52 | Then select "Standard" from "unit_type" 53 | And I wait for AJAX to finish 54 | 55 | Then I should see the text "Normal - Cost: $ 480" 56 | And I should see the text "Special - Cost: $ 520" 57 | 58 | #Playing with the number of guests to check restrictions. 59 | Then select "1" from "data[group_size]" 60 | And I wait for AJAX to finish 61 | 62 | Then I should see the text "Normal - Cost: $ 480" 63 | And I should see the text "Special - Cost: $ 520" 64 | 65 | Then select "3" from "data[group_size]" 66 | And I wait for AJAX to finish 67 | 68 | Then I should not see the text "Normal - Cost: $ 480" 69 | And I should see the text "Special - Cost: $ 520" 70 | 71 | Then select "2" from "data[group_size]" 72 | And I wait for AJAX to finish 73 | 74 | Then I should see the text "Normal - Cost: $ 480" 75 | And I should see the text "Special - Cost: $ 520" 76 | 77 | # Selecting the desired unit. 78 | Then I select the radio button "Normal - Cost: $ 480" 79 | And I wait for AJAX to finish 80 | 81 | Then the "price" field should contain "480.00" 82 | And I press the "Save Booking" button 83 | 84 | # Checking that the unit has been locked. 85 | Then the "Normal" unit should be Unconfirmed by the last booking between "2015-06-19" and "2015-06-23" 86 | 87 | # Editing the booking to mark it as confirmed. 88 | When I am on "admin/rooms/bookings" 89 | Then I should see the text "19-06-2015" 90 | And I should see the text "23-06-2015" 91 | And I should see the text "User test" 92 | And I click "Edit" 93 | 94 | Then the "rooms_start_date[date]" field should contain "19/06/2015" 95 | And the "rooms_end_date[date]" field should contain "23/06/2015" 96 | 97 | Then I check the box "Booking Confirmed" 98 | And I press the "Save Booking" button 99 | 100 | # Checking that the unit has been locked and the booking confirmed.. 101 | Then the "Normal" unit should be Confirmed by the last booking between "2015-06-19" and "2015-06-23" 102 | 103 | # Editing the booking and changing the unit assigned. 104 | When I am on "admin/rooms/bookings" 105 | And I click "Edit" 106 | 107 | Then I press the "Re-assign Unit" button 108 | And I wait for AJAX to finish 109 | 110 | Then select "Standard" from "unit_type" 111 | And I wait for AJAX to finish 112 | 113 | Then I select the radio button "Special - Cost: $ 520" 114 | And I wait for AJAX to finish 115 | 116 | Then the "price" field should contain "520.00" 117 | And I press the "Save Booking" button 118 | 119 | # Checking that the prior unit has been released and the new one locked. 120 | Then the "Special" unit should be Confirmed by the last booking between "2015-06-19" and "2015-06-23" 121 | And the state for "Normal" between "2015-06-19" and "2015-06-23" should be "1" 122 | 123 | # Deleting created entities to keep installation clean. 124 | When I am on "admin/rooms/bookings" 125 | And I click "Delete" 126 | Then I press the "Delete" button 127 | 128 | When I am on "admin/commerce/customer-profiles" 129 | And I click "delete" 130 | When I press the "Delete" button 131 | Then I should see the success message "The profile has been deleted." 132 | 133 | @api @javascript 134 | Scenario: I can create a booking programmatically 135 | Given I am logged in as a user with the "administrator" role 136 | 137 | # Creating a unit type and some units. 138 | Given unit types: 139 | | type | label | base_price | min_sleeps | max_sleeps | min_children | max_children | 140 | | standard | Standard | 100 | 2 | 3 | 0 | 1 | 141 | 142 | # Creating a bunch of units programmatically. 143 | Given "standard" units: 144 | | name | base_price | 145 | | Normal | 120 | 146 | | Special | 130 | 147 | 148 | # Creating a customer profile. 149 | Given customer profiles: 150 | | profile_id | name | country | locality | address | postal_code | 151 | | profile1 | User test 1 | US | Austin | 1900 David St | 78705 | 152 | | profile2 | User test 2 | US | Austin | 1900 David St | 78705 | 153 | 154 | # Creating a booking. 155 | Given "standard_booking" bookings: 156 | | profile_id | guests | children | start_date | end_date | unit | status | 157 | | profile2 | 1 | 1 | 2015-05-19 | 2015-05-23 | Normal | 1 | 158 | 159 | Then the "Normal" unit should be Confirmed by the last booking between "2015-05-19" and "2015-05-22" 160 | 161 | # Creating a booking. 162 | Given "standard_booking" bookings: 163 | | profile_id | guests | children | start_date | end_date | unit | status | 164 | | profile1 | 3 | 0 | 2015-05-19 | 2015-05-23 | Special | 0 | 165 | 166 | Then the "Special" unit should be Unconfirmed by the last booking between "2015-05-19" and "2015-05-22" 167 | -------------------------------------------------------------------------------- /test/features/example.feature: -------------------------------------------------------------------------------- 1 | Feature: Create Example Bookings 2 | In order to create bookings 3 | As a site administrator 4 | I need to be able to access all the Rooms booking configuration screens 5 | And create bookings from scratch 6 | 7 | @api @javascript 8 | Scenario: Bulk creation of units and bookings 9 | 10 | Given customer profiles: 11 | | profile_id | name | country | locality | address | postal_code | 12 | | profile1 | User test 1 | US | Austin | 1900 David St | 78705 | 13 | 14 | Given unit types: 15 | | type | label | base_price | min_sleeps | max_sleeps | min_children | max_children | 16 | | standard | Standard | 100 | 2 | 3 | 0 | 1 | 17 | | special | Special | 120 | 2 | 3 | 0 | 1 | 18 | | deluxe | Deluxe | 150 | 2 | 3 | 0 | 1 | 19 | | suite | Suite | 200 | 2 | 3 | 0 | 1 | 20 | | single | Single | 140 | 1 | 1 | 0 | 1 | 21 | 22 | Given 2 units of type "standard" 23 | Given 2 units of type "special" 24 | Given 2 units of type "deluxe" 25 | Given 2 units of type "suite" 26 | Given 2 units of type "single" 27 | 28 | Given 2 bookings of type "standard_booking" for all "standard" units 29 | Given 2 bookings of type "standard_booking" for all "special" units 30 | Given 2 bookings of type "standard_booking" for all "deluxe" units 31 | Given 2 bookings of type "standard_booking" for all "suite" units 32 | Given 2 bookings of type "standard_booking" for all "single" units 33 | -------------------------------------------------------------------------------- /test/features/installation.feature: -------------------------------------------------------------------------------- 1 | Feature: Have Rooms correctly installed 2 | In order to use Rooms 3 | As a site administrator 4 | I need to be able to access all the Rooms configuration screens 5 | 6 | @api 7 | Scenario: I can access the Bookable Units interface 8 | Given I am logged in as a user with the "administrator" role 9 | When I am on "admin/rooms" 10 | Then I should see the text "Bookable Units" 11 | -------------------------------------------------------------------------------- /test/features/pricing.feature: -------------------------------------------------------------------------------- 1 | Feature: Once rooms_pricing is installed 2 | In order to create bookable units 3 | As a site administrator 4 | I should be able to manage the bookable units pricing options 5 | 6 | @api @javascript 7 | Scenario: Availability manager user should be able to modify the pricing of units both in bulk pricing page and per unit. 8 | # Creating a unit type and some units. 9 | Given unit types: 10 | | type | label | base_price | min_sleeps | max_sleeps | min_children | max_children | 11 | | standard | Standard | 100 | 2 | 3 | 0 | 1 | 12 | 13 | # Creating a bunch of units programmatically. 14 | Given "standard" units: 15 | | name | base_price | 16 | | Normal | 120 | 17 | | Special | 130 | 18 | 19 | # Check the initial conditions. 20 | Given the cache has been cleared 21 | And I am logged in as a user with the "access administration pages,view any rooms_unit entity of bundle standard,administer rooms_unit pricing,update pricing any rooms_unit entity of bundle standard,book in advance without limitation,access pricing index service" permissions 22 | Then the price for "Normal" between "2016-05-05" and "2016-07-07" should be "120" 23 | And the price for "Special" between "2016-05-05" and "2016-07-07" should be "130" 24 | 25 | # Check the Add to price operation. 26 | Given I am on "admin/rooms/units/bulk_pricing_management/2016/5" 27 | Then I click "Update Pricing" 28 | And I fill in "rooms_start_date[date]" with "19/05/2016" 29 | And I fill in "rooms_end_date[date]" with "23/05/2016" 30 | And I select "Add to price" from "operation" 31 | And I fill in "amount" with "30" 32 | And I select "All (this page)" from "select-all" 33 | And I press the "Update Unit Pricing" button 34 | 35 | Then the price for "Normal" between "2016-05-19" and "2016-05-23" should be "150" 36 | And the price for "Special" between "2016-05-19" and "2016-05-23" should be "160" 37 | 38 | # Check the Add to price per night operation. 39 | Given I am on "admin/rooms/units/bulk_pricing_management/2016/5" 40 | Then I click "Update Pricing" 41 | And I fill in "rooms_start_date[date]" with "19/05/2016" 42 | And I fill in "rooms_end_date[date]" with "23/05/2016" 43 | And I select "Add to price" from "operation" 44 | And I fill in "amount" with "50" 45 | And I select "All (this page)" from "select-all" 46 | And I press the "Update Unit Pricing" button 47 | 48 | Then the price for "Normal" between "2016-05-19" and "2016-05-23" should be "200" 49 | And the price for "Special" between "2016-05-19" and "2016-05-23" should be "210" 50 | 51 | # Check the Subtract from price operation. 52 | Given I am on "admin/rooms/units/bulk_pricing_management/2016/5" 53 | Then I click "Update Pricing" 54 | And I fill in "rooms_start_date[date]" with "19/05/2016" 55 | And I fill in "rooms_end_date[date]" with "23/05/2016" 56 | And I select "Subtract from price" from "operation" 57 | And I fill in "amount" with "30" 58 | And I select "All (this page)" from "select-all" 59 | And I press the "Update Unit Pricing" button 60 | 61 | Then the price for "Normal" between "2016-05-19" and "2016-05-23" should be "170" 62 | And the price for "Special" between "2016-05-19" and "2016-05-23" should be "180" 63 | 64 | # Check the Subtract from price per night operation. 65 | Given I am on "admin/rooms/units/bulk_pricing_management/2016/5" 66 | Then I click "Update Pricing" 67 | And I fill in "rooms_start_date[date]" with "19/05/2016" 68 | And I fill in "rooms_end_date[date]" with "23/05/2016" 69 | And I select "Subtract from price" from "operation" 70 | And I fill in "amount" with "50" 71 | And I select "All (this page)" from "select-all" 72 | And I press the "Update Unit Pricing" button 73 | 74 | Then the price for "Normal" between "2016-05-19" and "2016-05-23" should be "120" 75 | And the price for "Special" between "2016-05-19" and "2016-05-23" should be "130" 76 | 77 | # Check the Replace price operation. 78 | Given I am on "admin/rooms/units/bulk_pricing_management/2016/5" 79 | Then I click "Update Pricing" 80 | And I fill in "rooms_start_date[date]" with "19/05/2016" 81 | And I fill in "rooms_end_date[date]" with "23/05/2016" 82 | And I select "Replace price" from "operation" 83 | And I fill in "amount" with "150" 84 | And I select "All (this page)" from "select-all" 85 | And I press the "Update Unit Pricing" button 86 | 87 | Then the price for "Normal" between "2016-05-19" and "2016-05-23" should be "150" 88 | And the price for "Special" between "2016-05-19" and "2016-05-23" should be "150" 89 | 90 | # Check the Increase price by % amount operation. 91 | Given I am on "admin/rooms/units/bulk_pricing_management/2016/5" 92 | Then I click "Update Pricing" 93 | And I fill in "rooms_start_date[date]" with "19/05/2016" 94 | And I fill in "rooms_end_date[date]" with "23/05/2016" 95 | And I select "Increase price by % amount" from "operation" 96 | And I fill in "amount" with "20" 97 | And I select "All (this page)" from "select-all" 98 | And I press the "Update Unit Pricing" button 99 | 100 | Then the price for "Normal" between "2016-05-19" and "2016-05-23" should be "180" 101 | And the price for "Special" between "2016-05-19" and "2016-05-23" should be "180" 102 | 103 | # Check the Decrease price by % amount operation. 104 | Given I am on "admin/rooms/units/bulk_pricing_management/2016/5" 105 | Then I click "Update Pricing" 106 | And I fill in "rooms_start_date[date]" with "19/05/2016" 107 | And I fill in "rooms_end_date[date]" with "23/05/2016" 108 | And I select "Decrease price by % amount" from "operation" 109 | And I fill in "amount" with "20" 110 | And I select "All (this page)" from "select-all" 111 | And I press the "Update Unit Pricing" button 112 | 113 | Then the price for "Normal" between "2016-05-19" and "2016-05-23" should be "144" 114 | And the price for "Special" between "2016-05-19" and "2016-05-23" should be "144" 115 | 116 | # Check the day of the week select boxes. 117 | Given I am on "admin/rooms/units/bulk_pricing_management/2016/5" 118 | Then I click "Update Pricing" 119 | And I fill in "rooms_start_date[date]" with "19/05/2016" 120 | And I fill in "rooms_end_date[date]" with "21/05/2016" 121 | And I select "Replace price" from "operation" 122 | And I fill in "amount" with "120" 123 | And I check the box "day_options[2]" 124 | And I check the box "day_options[3]" 125 | And I check the box "day_options[4]" 126 | And I select "All (this page)" from "select-all" 127 | And I press the "Update Unit Pricing" button 128 | 129 | Then the price for "Normal" between "2016-05-19" and "2016-05-21" should be "120" 130 | And the price for "Special" between "2016-05-19" and "2016-05-21" should be "120" 131 | And the price for "Normal" between "2016-05-22" and "2016-05-23" should be "144" 132 | And the price for "Special" between "2016-05-22" and "2016-05-23" should be "144" 133 | 134 | # Check the single unit price management page. 135 | Given I am managing the "Normal" unit pricing 136 | Then I click "Update Unit Pricing" 137 | And I fill in "rooms_start_date[date]" with "29/05/2016" 138 | And I fill in "rooms_end_date[date]" with "23/06/2017" 139 | And I select "Replace price" from "operation" 140 | And I fill in "amount" with "150" 141 | And I press the "Update Unit Pricing" button 142 | 143 | Then the price for "Normal" between "2016-05-29" and "2017-06-23" should be "150" 144 | -------------------------------------------------------------------------------- /test/features/unit.feature: -------------------------------------------------------------------------------- 1 | Feature: Once rooms_unit is installed 2 | In order to create bookable units 3 | As a site administrator 4 | I should be able to access the unit type and unit edition pages 5 | 6 | @api @javascript 7 | Scenario: Unit type manager can access the Bookable unit types page and create, edit and delete Unit types 8 | Given I am logged in as a user with the "administer rooms_unit_type entities" permission 9 | When I am on "admin/rooms/units/unit-types" 10 | Then I should see the text "Add bookable unit type" 11 | When I click "Add bookable unit type" 12 | Then I am on "admin/rooms/units/unit-types/add" 13 | When I fill in "label" with "Standard" 14 | And I fill in "data[base_price]" with "100" 15 | And I fill in "data[min_sleeps]" with "2" 16 | And I fill in "data[max_sleeps]" with "3" 17 | And I fill in "data[min_children]" with "0" 18 | And I fill in "data[max_children]" with "1" 19 | When I press the "Save unit type" button 20 | Then I should be on "admin/rooms/units/unit-types" 21 | And I should see the text "Standard" 22 | And I should see the text "(Machine name: standard)" 23 | Then I am on "admin/rooms/units/unit-types/manage/standard" 24 | And the "label" field should contain "Standard" 25 | And the "data[base_price]" field should contain "100" 26 | And the "data[min_sleeps]" field should contain "2" 27 | And the "data[max_sleeps]" field should contain "3" 28 | And the "data[min_children]" field should contain "0" 29 | And the "data[max_children]" field should contain "1" 30 | Then I fill in "label" with "Modified value" 31 | And I fill in "data[base_price]" with "1200" 32 | And I fill in "data[min_sleeps]" with "1" 33 | And I fill in "data[max_sleeps]" with "4" 34 | And I fill in "data[min_children]" with "1" 35 | And I fill in "data[max_children]" with "3" 36 | When I press the "Save unit type" button 37 | Then I should be on "admin/rooms/units/unit-types" 38 | When I am on "admin/rooms/units/unit-types/manage/standard" 39 | And the "label" field should contain "Modified value" 40 | And the "data[base_price]" field should contain "1200" 41 | And the "data[min_sleeps]" field should contain "1" 42 | And the "data[max_sleeps]" field should contain "4" 43 | And the "data[min_children]" field should contain "1" 44 | And the "data[max_children]" field should contain "3" 45 | When I am on "admin/rooms/units/unit-types/manage/standard/delete" 46 | And I press the "Confirm" button 47 | Then I should see the message "Deleted Bookable Unit Type Modified value." 48 | 49 | @api @javascript 50 | Scenario: Unit type manager creates two unit types and two unit admin creates and edit it 51 | # Creating a unit type programmatically. 52 | Given unit types: 53 | | type | label | base_price | min_sleeps | max_sleeps | min_children | max_children | 54 | | standard | Standard | 100 | 2 | 3 | 0 | 1 | 55 | | deluxe | Deluxe | 150 | 1 | 2 | 0 | 1 | 56 | 57 | # Standard unit manager manages the unit type. 58 | Given the cache has been cleared 59 | And I am logged in as a user with the "access administration pages,view any rooms_unit entity of bundle standard,create rooms_unit entities of bundle standard,update any rooms_unit entity of bundle standard,delete any rooms_unit entity of bundle standard" permissions 60 | When I am on "admin/rooms/units/add/standard" 61 | Then the "base_price" field should contain "100" 62 | And the "min_sleeps" field should contain "2" 63 | And the "max_sleeps" field should contain "3" 64 | And the "min_children" field should contain "0" 65 | And the "max_children" field should contain "1" 66 | Then I fill in "name" with "Standard one" 67 | And I press the "Save Unit" button 68 | Then I should see the message "Bookable unit Standard one saved" 69 | When I am editing the "Standard one" unit 70 | Then I fill in "name" with "Standard one edited" 71 | And I fill in "base_price" with "150" 72 | And I fill in "min_sleeps" with "3" 73 | And I fill in "max_sleeps" with "8" 74 | And I fill in "min_children" with "1" 75 | And I fill in "max_children" with "4" 76 | And I press the "Save Unit" button 77 | Then I should see the message "Bookable unit Standard one edited saved" 78 | When I am on the "Standard one edited" unit 79 | Then I should see the text "Standard one edited" 80 | When I am editing the "Standard one edited" unit 81 | Then the "base_price" field should contain "150.00" 82 | And the "min_sleeps" field should contain "3" 83 | And the "max_sleeps" field should contain "8" 84 | And the "min_children" field should contain "1" 85 | And the "max_children" field should contain "4" 86 | When I am deleting the "Standard one edited" unit 87 | And I press the "Delete" button 88 | Then I should see the message "The unit Standard one edited has been deleted." 89 | 90 | # Deluxe unit manager manages the unit type. 91 | Given I am logged in as a user with the "access administration pages,view any rooms_unit entity of bundle deluxe,create rooms_unit entities of bundle deluxe,update any rooms_unit entity of bundle deluxe,delete any rooms_unit entity of bundle deluxe" permissions 92 | When I am on "admin/rooms/units/add/deluxe" 93 | Then the "base_price" field should contain "150" 94 | And the "min_sleeps" field should contain "1" 95 | And the "max_sleeps" field should contain "2" 96 | And the "min_children" field should contain "0" 97 | And the "max_children" field should contain "1" 98 | Then I fill in "name" with "Deluxe one" 99 | And I press the "Save Unit" button 100 | Then I should see the message "Bookable unit Deluxe one saved" 101 | When I am editing the "Deluxe one" unit 102 | Then I fill in "name" with "Deluxe one edited" 103 | And I fill in "base_price" with "150" 104 | And I fill in "min_sleeps" with "3" 105 | And I fill in "max_sleeps" with "8" 106 | And I fill in "min_children" with "1" 107 | And I fill in "max_children" with "4" 108 | And I press the "Save Unit" button 109 | Then I should see the message "Bookable unit Deluxe one edited saved" 110 | When I am on the "Deluxe one edited" unit 111 | Then I should see the text "Deluxe one edited" 112 | When I am editing the "Deluxe one edited" unit 113 | Then the "base_price" field should contain "150.00" 114 | And the "min_sleeps" field should contain "3" 115 | And the "max_sleeps" field should contain "8" 116 | And the "min_children" field should contain "1" 117 | And the "max_children" field should contain "4" 118 | When I am deleting the "Deluxe one edited" unit 119 | And I press the "Delete" button 120 | Then I should see the message "The unit Deluxe one edited has been deleted." 121 | 122 | @api @javascript 123 | Scenario: Creating a bunch of units for testing purposes. 124 | # Creating a unit type programmatically. 125 | Given unit types: 126 | | type | label | base_price | min_sleeps | max_sleeps | min_children | max_children | 127 | | standard | Standard | 100 | 2 | 3 | 0 | 1 | 128 | 129 | # Creating a bunch of units programmatically. 130 | Given "standard" units: 131 | | name | base_price | min_sleeps | max_sleeps | 132 | | test1 | 120 | 1 | 2 | 133 | | test2 | 140 | 2 | 4 | 134 | 135 | Given I am logged in as a user with the "administrator" role 136 | When I am on the "test1" unit 137 | Then I should see the text "test1" 138 | When I am on the "test2" unit 139 | Then I should see the text "test2" 140 | --------------------------------------------------------------------------------