Booking Rooms Model

* model changes

* Endpoint to get all avaible booking rooms per summit

GET /api/v1/summits/{id}/locations/bookable-rooms

query string params

page ( int ) current page number
per_page ( int) max amount of items per page
filter ( allowed fields: name, description, cost, capacity, availability_day[epoch], attribute[string|number])
order ( allowed fields id,name,capacity)
expand ( allowed relations venue,floor,attribute_type)

scopes
%s/bookable-rooms/read
%s/summits/read/all

* Endpoint to get slot availability  per room

GET /api/v1/summits/{id}/locations/bookable-rooms/{room_id}/availability/{day}

where id is summit id (integer)
room_id ( integer)
and day (epoch timestamp)

scopes
%s/bookable-rooms/read
%s/summits/read/all

* endpoint create reservation

POST /api/v1/summits/{id}/locations/bookable-rooms/{room_id}/reservations

payload

'currency'       => 'required|string|currency_iso',
'amount'         => 'required|integer',
'start_datetime' => 'required|date_format:U',
'end_datetime'   => 'required|date_format:U|after:start_datetime',

scopes
%s/bookable-rooms/my-reservations/write

* endpoint to get all my reservations

GET /api/v1/summits/{id}/locations/bookable-rooms/all/reservations/me

query string params

expand [owner, room, type]

scopes
%s/bookable-rooms/my-reservations/read

* endpoint to cancel/ask for refund a reservation

DELETE /api/v1/summits/{id}/locations/bookable-rooms/all/reservations/{reservation_id}

scopes
%s/bookable-rooms/my-reservations/write

Change-Id: I741878c6ffc833ba23fca40f09f4664b42c8edd4
This commit is contained in:
smarcet 2019-05-29 14:32:14 -03:00
parent 6a674a6dd8
commit 9f01b9e3b3
55 changed files with 3194 additions and 93 deletions

View File

@ -133,7 +133,7 @@ abstract class AbstractSerializer implements IModelSerializer
* @param array $params
* @return array
*/
public function serialize($expand = null, array $fields = array(), array $relations = array(), array $params = array() )
public function serialize($expand = null, array $fields = [], array $relations = [], array $params = [])
{
$values = [];
$method_prefix = ['get', 'is'];

View File

@ -0,0 +1,36 @@
<?php namespace App\Http\Controllers;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use models\exceptions\ValidationException;
/**
* Class SummitRoomReservationValidationRulesFactory
* @package App\Http\Controllers
*/
final class SummitRoomReservationValidationRulesFactory
{
/**
* @param array $data
* @param bool $update
* @return array
* @throws ValidationException
*/
public static function build(array $data, $update = false){
return [
'currency' => 'required|string|currency_iso',
'amount' => 'required|integer',
'start_datetime' => 'required|date_format:U',
'end_datetime' => 'required|date_format:U|after:start_datetime',
];
}
}

View File

@ -211,6 +211,28 @@ final class OAuth2SummitApiController extends OAuth2ProtectedController
}
}
public function getAllSummitByIdOrSlug($id){
$expand = Request::input('expand', '');
try {
$summit = $this->repository->getById(intval($id));
if(is_null($summit))
$summit = $this->repository->getBySlug(trim($id));
if (is_null($summit)) return $this->error404();
$serializer_type = $this->serializer_type_selector->getSerializerType();
return $this->ok(SerializerRegistry::getInstance()->getSerializer($summit, $serializer_type)->serialize($expand));
}
catch(HTTP403ForbiddenException $ex1){
Log::warning($ex1);
return $this->error403();
}
catch (Exception $ex) {
Log::error($ex);
return $this->error500($ex);
}
}
/**
* @return mixed
*/

View File

@ -16,8 +16,10 @@ use App\Models\Foundation\Summit\Locations\Banners\SummitLocationBannerConstants
use App\Models\Foundation\Summit\Locations\SummitLocationConstants;
use App\Models\Foundation\Summit\Repositories\ISummitLocationBannerRepository;
use App\Models\Foundation\Summit\Repositories\ISummitLocationRepository;
use App\Services\Apis\IPaymentGatewayAPI;
use App\Services\Model\ILocationService;
use Exception;
use Illuminate\Http\Request as LaravelRequest;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Request;
@ -25,6 +27,7 @@ use Illuminate\Support\Facades\Validator;
use libs\utils\HTMLCleaner;
use models\exceptions\EntityNotFoundException;
use models\exceptions\ValidationException;
use models\main\IMemberRepository;
use models\oauth2\IResourceServerContext;
use models\summit\IEventFeedbackRepository;
use models\summit\ISpeakerRepository;
@ -45,9 +48,6 @@ use utils\FilterParserException;
use utils\OrderParser;
use utils\PagingInfo;
use utils\PagingResponse;
use Illuminate\Http\Request as LaravelRequest;
use utils\ParseMultiPartFormDataInputStream;
/**
* Class OAuth2SummitLocationsApiController
* @package App\Http\Controllers
@ -79,6 +79,11 @@ final class OAuth2SummitLocationsApiController extends OAuth2ProtectedController
*/
private $location_repository;
/**
* @var IMemberRepository
*/
private $member_repository;
/**
* @var ILocationService
*/
@ -89,6 +94,11 @@ final class OAuth2SummitLocationsApiController extends OAuth2ProtectedController
*/
private $location_banners_repository;
/**
* @var IPaymentGatewayAPI
*/
private $payment_gateway;
/**
* OAuth2SummitLocationsApiController constructor.
* @param ISummitRepository $summit_repository
@ -97,8 +107,10 @@ final class OAuth2SummitLocationsApiController extends OAuth2ProtectedController
* @param IEventFeedbackRepository $event_feedback_repository
* @param ISummitLocationRepository $location_repository
* @param ISummitLocationBannerRepository $location_banners_repository
* @param IMemberRepository $member_repository
* @param ISummitService $summit_service
* @param ILocationService $location_service
* @param IPaymentGatewayAPI $payment_gateway
* @param IResourceServerContext $resource_server_context
*/
public function __construct
@ -109,19 +121,23 @@ final class OAuth2SummitLocationsApiController extends OAuth2ProtectedController
IEventFeedbackRepository $event_feedback_repository,
ISummitLocationRepository $location_repository,
ISummitLocationBannerRepository $location_banners_repository,
IMemberRepository $member_repository,
ISummitService $summit_service,
ILocationService $location_service,
IPaymentGatewayAPI $payment_gateway,
IResourceServerContext $resource_server_context
) {
parent::__construct($resource_server_context);
$this->repository = $summit_repository;
$this->speaker_repository = $speaker_repository;
$this->event_repository = $event_repository;
$this->member_repository = $member_repository;
$this->event_feedback_repository = $event_feedback_repository;
$this->location_repository = $location_repository;
$this->location_banners_repository = $location_banners_repository;
$this->location_service = $location_service;
$this->summit_service = $summit_service;
$this->payment_gateway = $payment_gateway;
}
/**
@ -2211,4 +2227,8 @@ final class OAuth2SummitLocationsApiController extends OAuth2ProtectedController
}
}
// bookable rooms
use SummitBookableVenueRoomApi;
}

View File

@ -0,0 +1,384 @@
<?php namespace App\Http\Controllers;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use App\Http\Utils\PagingConstants;
use Exception;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Validator;
use models\exceptions\EntityNotFoundException;
use models\exceptions\ValidationException;
use models\summit\SummitBookableVenueRoom;
use models\summit\SummitBookableVenueRoomAvailableSlot;
use ModelSerializers\SerializerRegistry;
use utils\Filter;
use utils\FilterParser;
use utils\OrderParser;
use utils\PagingInfo;
use utils\PagingResponse;
use Illuminate\Http\Request as LaravelRequest;
/**
* Trait SummitBookableVenueRoomApi
* @package App\Http\Controllers
*/
trait SummitBookableVenueRoomApi
{
/**
* @param $summit_id
* @return \Illuminate\Http\JsonResponse|mixed
*/
public function getBookableVenueRooms($summit_id){
$values = Input::all();
$rules = [
'page' => 'integer|min:1',
'per_page' => sprintf('required_with:page|integer|min:%s|max:%s', PagingConstants::MinPageSize, PagingConstants::MaxPageSize),
];
try {
$summit = $summit_id === 'current' ? $this->repository->getCurrent() : $this->repository->getById(intval($summit_id));
if (is_null($summit)) return $this->error404();
$validation = Validator::make($values, $rules);
if ($validation->fails()) {
$ex = new ValidationException();
throw $ex->setMessages($validation->messages()->toArray());
}
// default values
$page = 1;
$per_page = PagingConstants::DefaultPageSize;
if (Input::has('page')) {
$page = intval(Input::get('page'));
$per_page = intval(Input::get('per_page'));
}
$filter = null;
if (Input::has('filter')) {
$filter = FilterParser::parse(Input::get('filter'), [
'name' => ['==', '=@'],
'description' => ['=@'],
'capacity' => ['>', '<', '<=', '>=', '=='],
'availability_day' => ['=='],
'attribute' => ['=='],
]);
}
if(is_null($filter)) $filter = new Filter();
$filter->validate([
'name' => 'sometimes|string',
'description' => 'sometimes|string',
'capacity' => 'sometimes|integer',
'availability_day' => 'sometimes|date_format:U',
'attribute' => 'sometimes|string',
]);
$order = null;
if (Input::has('order'))
{
$order = OrderParser::parse(Input::get('order'), [
'id',
'name',
'capacity',
]);
}
$filter->addFilterCondition(FilterParser::buildFilter('class_name','==', SummitBookableVenueRoom::ClassName));
$data = $this->location_repository->getBySummit($summit, new PagingInfo($page, $per_page), $filter, $order);
return $this->ok
(
$data->toArray
(
Request::input('expand', ''),
[],
[],
[]
)
);
}
catch (ValidationException $ex1)
{
Log::warning($ex1);
return $this->error412([$ex1->getMessage()]);
}
catch (EntityNotFoundException $ex2)
{
Log::warning($ex2);
return $this->error404(['message' => $ex2->getMessage()]);
}
catch(\HTTP401UnauthorizedException $ex3)
{
Log::warning($ex3);
return $this->error401();
}
catch (Exception $ex) {
Log::error($ex);
return $this->error500($ex);
}
}
public function getBookableVenueRoom($summit_id, $room_id){
}
/**
* @param $summit_id
* @param $room_id
* @param $day
* @return \Illuminate\Http\JsonResponse|mixed
*/
public function getBookableVenueRoomAvailability($summit_id, $room_id, $day){
try {
$day = intval($day);
$summit = $summit_id === 'current' ? $this->repository->getCurrent() : $this->repository->getById(intval($summit_id));
if (is_null($summit)) return $this->error404();
$room = $summit->getLocation($room_id);
if(!$room instanceof SummitBookableVenueRoom)
return $this->error404();
$slots_definitions = $room->getAvailableSlots(new \DateTime("@$day"));
$list = [];
foreach($slots_definitions as $slot_label => $is_free){
$dates = explode('|', $slot_label);
$list[] = new SummitBookableVenueRoomAvailableSlot
(
$room,
$summit->convertDateFromTimeZone2UTC(new \DateTime($dates[0], $summit->getTimeZone())),
$summit->convertDateFromTimeZone2UTC(new \DateTime($dates[1], $summit->getTimeZone())),
$is_free
);
}
$response = new PagingResponse
(
count($list),
count($list),
1,
1,
$list
);
return $this->ok(
$response->toArray()
);
}
catch (ValidationException $ex1)
{
Log::warning($ex1);
return $this->error412([$ex1->getMessage()]);
}
catch (EntityNotFoundException $ex2)
{
Log::warning($ex2);
return $this->error404(['message' => $ex2->getMessage()]);
}
catch(\HTTP401UnauthorizedException $ex3)
{
Log::warning($ex3);
return $this->error401();
}
catch (Exception $ex) {
Log::error($ex);
return $this->error500($ex);
}
}
/**
* @param $summit_id
* @param $room_id
* @return \Illuminate\Http\JsonResponse|mixed
*/
public function createBookableVenueRoomReservation($summit_id, $room_id){
try {
if(!Request::isJson()) return $this->error400();
$current_member_id = $this->resource_server_context->getCurrentUserExternalId();
if (is_null($current_member_id))
return $this->error403();
$summit = $summit_id === 'current' ? $this->repository->getCurrent() : $this->repository->getById(intval($summit_id));
if (is_null($summit)) return $this->error404();
$room = $summit->getLocation($room_id);
if(!$room instanceof SummitBookableVenueRoom)
return $this->error404();
$payload = Input::json()->all();
$payload['owner_id'] = $current_member_id;
$rules = SummitRoomReservationValidationRulesFactory::build($payload);
// Creates a Validator instance and validates the data.
$validation = Validator::make($payload, $rules);
if ($validation->fails()) {
$messages = $validation->messages()->toArray();
return $this->error412
(
$messages
);
}
$reservation = $this->location_service->addBookableRoomReservation($summit, $room_id, $payload);
return $this->created(SerializerRegistry::getInstance()->getSerializer($reservation)->serialize());
}
catch (ValidationException $ex1)
{
Log::warning($ex1);
return $this->error412([$ex1->getMessage()]);
}
catch (EntityNotFoundException $ex2)
{
Log::warning($ex2);
return $this->error404(['message' => $ex2->getMessage()]);
}
catch(\HTTP401UnauthorizedException $ex3)
{
Log::warning($ex3);
return $this->error401();
}
catch (Exception $ex) {
Log::error($ex);
return $this->error500($ex);
}
}
/**
* @param $summit_id
* @return mixed
*/
public function getMyBookableVenueRoomReservations($summit_id){
try{
$current_member_id = $this->resource_server_context->getCurrentUserExternalId();
if (is_null($current_member_id))
return $this->error403();
$summit = $summit_id === 'current' ? $this->repository->getCurrent() : $this->repository->getById(intval($summit_id));
if (is_null($summit)) return $this->error404();
$member = $this->member_repository->getById($current_member_id);
if(is_null($member))
return $this->error403();
$reservations = $member->getReservations()->toArray();
$response = new PagingResponse
(
count($reservations),
count($reservations),
1,
1,
$reservations
);
return $this->ok(
$response->toArray(
Request::input('expand', '')
)
);
}
catch (ValidationException $ex1)
{
Log::warning($ex1);
return $this->error412([$ex1->getMessage()]);
}
catch (EntityNotFoundException $ex2)
{
Log::warning($ex2);
return $this->error404(['message' => $ex2->getMessage()]);
}
catch(\HTTP401UnauthorizedException $ex3)
{
Log::warning($ex3);
return $this->error401();
}
catch (Exception $ex) {
Log::error($ex);
return $this->error500($ex);
}
}
/**
* @param $summit_id
* @param $reservation_id
* @return mixed
*/
public function cancelMyBookableVenueRoomReservation($summit_id, $reservation_id){
try{
$current_member_id = $this->resource_server_context->getCurrentUserExternalId();
if (is_null($current_member_id))
return $this->error403();
$summit = $summit_id === 'current' ? $this->repository->getCurrent() : $this->repository->getById(intval($summit_id));
if (is_null($summit)) return $this->error404();
$member = $this->member_repository->getById($current_member_id);
if(is_null($member))
return $this->error403();
$this->location_service->cancelReservation($summit, $member, $reservation_id);
return $this->deleted();
}
catch (ValidationException $ex1)
{
Log::warning($ex1);
return $this->error412([$ex1->getMessage()]);
}
catch (EntityNotFoundException $ex2)
{
Log::warning($ex2);
return $this->error404(['message' => $ex2->getMessage()]);
}
catch(\HTTP401UnauthorizedException $ex3)
{
Log::warning($ex3);
return $this->error401();
}
catch (Exception $ex) {
Log::error($ex);
return $this->error500($ex);
}
}
/**
* @param LaravelRequest $request
* @return mixed
*/
public function confirmBookableVenueRoomReservation(LaravelRequest $request){
if(!Request::isJson())
return $this->error400();
try {
$response = $this->payment_gateway->processCallback($request);
$this->location_service->processBookableRoomPayment($response);
return $this->ok();
}
catch (Exception $ex){
Log::error($ex);
return $this->error400(["error" => 'payload error']);
}
return $this->error400(["error" => 'invalid event type']);
}
}

View File

@ -51,6 +51,15 @@ Route::group([
Route::group(['prefix' => 'selection-plans'], function () {
Route::get('current/{status}', 'OAuth2SummitSelectionPlansApiController@getCurrentSelectionPlanByStatus')->where('status', 'submission|selection|voting');
});
Route::group(['prefix' => 'bookable-rooms'], function () {
Route::group(['prefix' => 'all'], function () {
Route::group(['prefix' => 'reservations'], function () {
// api/public/v1/summits/all/bookable-rooms/all/reservations/confirm ( open endpoint for payment gateway callbacks)
Route::post("confirm", "OAuth2SummitLocationsApiController@confirmBookableVenueRoomReservation");
});
});
});
});
Route::group(['prefix' => '{id}'], function () {

View File

@ -109,11 +109,7 @@ final class PagingResponse
$items = [];
foreach($this->items as $i)
{
if($i instanceof IEntity)
{
$i = SerializerRegistry::getInstance()->getSerializer($i, $serializer_type)->serialize($expand, $fields, $relations, $params);
}
$items[] = $i;
$items[] = SerializerRegistry::getInstance()->getSerializer($i, $serializer_type)->serialize($expand, $fields, $relations, $params);;
}
return

View File

@ -86,12 +86,15 @@ Route::group([
Route::group(array('prefix' => 'summits'), function () {
Route::get('', [ 'middleware' => 'cache:'.Config::get('cache_api_response.get_summits_response_lifetime', 600), 'uses' => 'OAuth2SummitApiController@getSummits']);
Route::group(['prefix' => 'all'], function () {
Route::get('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators|summit-room-administrators', 'uses' => 'OAuth2SummitApiController@getAllSummits']);
Route::get('{id}', 'OAuth2SummitApiController@getAllSummitByIdOrSlug');
Route::group(['prefix' => 'selection-plans'], function () {
Route::get('current/{status}', ['uses' => 'OAuth2SummitSelectionPlansApiController@getCurrentSelectionPlanByStatus'])->where('status', 'submission|selection|voting');
});
});
Route::post('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitApiController@addSummit']);
Route::group(['prefix' => '{id}'], function () {
@ -312,10 +315,43 @@ Route::group([
Route::post('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@addLocation']);
Route::get('metadata', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@getMetadata']);
// bookable-rooms
Route::group(['prefix' => 'bookable-rooms'], function () {
// GET /api/v1/summits/{id}/bookable-rooms
Route::get('', 'OAuth2SummitLocationsApiController@getBookableVenueRooms');
Route::group(['prefix' => 'all'], function () {
Route::group(['prefix' => 'reservations'], function () {
// GET /api/v1/summits/{id}/bookable-rooms/all/reservations/me
Route::get('me', 'OAuth2SummitLocationsApiController@getMyBookableVenueRoomReservations');
Route::group(['prefix' => '{reservation_id}'], function () {
// DELETE /api/v1/summits/{id}/bookable-rooms/all/reservations/{reservation_id}
Route::delete('', 'OAuth2SummitLocationsApiController@cancelMyBookableVenueRoomReservation');
});
});
});
Route::group(['prefix' => '{room_id}'], function () {
// GET /api/v1/summits/{id}/locations/bookable-rooms/{room_id}
Route::get('', 'OAuth2SummitLocationsApiController@getBookableVenueRoom');
// GET /api/v1/summits/{id}/locations/bookable-rooms/{room_id}/availability/{day}
Route::get('availability/{day}', 'OAuth2SummitLocationsApiController@getBookableVenueRoomAvailability');
Route::group(['prefix' => 'reservations'], function () {
// POST /api/v1/summits/{id}/locations/bookable-rooms/{room_id}/reservations
Route::post('', 'OAuth2SummitLocationsApiController@createBookableVenueRoomReservation');
});
});
});
// venues
Route::group(['prefix' => 'venues'], function () {
Route::get('', 'OAuth2SummitLocationsApiController@getVenues');
Route::post('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@addVenue']);
Route::group(['prefix' => '{venue_id}'], function () {
Route::put('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@updateVenue']);
@ -328,6 +364,20 @@ Route::group([
});
});
// bookable-rooms
Route::group(['prefix' => 'bookable-rooms'], function () {
// POST /api/v1/summits/{id}/locations/venues/{venue_id}/bookable-rooms
Route::post('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@addBookableVenueRoom']);
Route::group(['prefix' => '{room_id}'], function () {
// GET /api/v1/summits/{id}/locations/venues/{venue_id}/bookable-rooms/{room_id}
Route::get('', 'OAuth2SummitLocationsApiController@getBookableVenueRoom');
// PUT /api/v1/summits/{id}/locations/venues/{venue_id}/bookable-rooms/{room_id}
Route::put('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@updateBookableVenueRoom']);
// DELETE /api/v1/summits/{id}/locations/venues/{venue_id}/bookable-rooms/{room_id}
Route::delete('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@deleteBookableVenueRoom']);
});
});
Route::group(['prefix' => 'floors'], function () {
Route::post('', [ 'middleware' => 'auth.user:administrators|summit-front-end-administrators', 'uses' => 'OAuth2SummitLocationsApiController@addVenueFloor']);
Route::group(['prefix' => '{floor_id}'], function () {

View File

@ -0,0 +1,25 @@
<?php namespace App\ModelSerializers\Locations;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use ModelSerializers\SilverStripeSerializer;
/**
* Class SummitBookableVenueRoomAttributeTypeSerializer
* @package App\ModelSerializers\Locations
*/
class SummitBookableVenueRoomAttributeTypeSerializer extends SilverStripeSerializer
{
protected static $array_mappings = [
'Type' => 'type:json_string',
'SummitId' => 'summit_id:json_int',
];
}

View File

@ -0,0 +1,52 @@
<?php namespace App\ModelSerializers\Locations;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use models\summit\SummitBookableVenueRoomAttributeValue;
use ModelSerializers\SerializerRegistry;
use ModelSerializers\SilverStripeSerializer;
/**
* Class SummitBookableVenueRoomAttributeValueSerializer
* @package App\ModelSerializers\Locations
*/
class SummitBookableVenueRoomAttributeValueSerializer extends SilverStripeSerializer
{
protected static $array_mappings = [
'Value' => 'value:json_string',
'TypeId' => 'type_id:json_int',
];
public function serialize($expand = null, array $fields = array(), array $relations = array(), array $params = array() )
{
$attr_value = $this->object;
if(!$attr_value instanceof SummitBookableVenueRoomAttributeValue)
return [];
$values = parent::serialize($expand, $fields, $relations, $params);
if (!empty($expand)) {
$exp_expand = explode(',', $expand);
foreach ($exp_expand as $relation) {
switch (trim($relation)) {
case 'attribute_type': {
unset($values['type_id']);
$values['type'] = SerializerRegistry::getInstance()->getSerializer($attr_value->getType())->serialize();
}
break;
}
}
}
return $values;
}
}

View File

@ -0,0 +1,28 @@
<?php namespace App\ModelSerializers\Locations;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Libs\ModelSerializers\AbstractSerializer;
/**
* Class SummitBookableVenueRoomAvailableSlotSerializer
* @package App\ModelSerializers\Locations
*/
final class SummitBookableVenueRoomAvailableSlotSerializer extends AbstractSerializer
{
protected static $array_mappings = [
'StartDate' => 'start_date:datetime_epoch',
'EndDate' => 'end_date:datetime_epoch',
'LocalStartDate' => 'local_start_date:datetime_epoch',
'LocalEndDate' => 'local_end_date:datetime_epoch',
'Free' => 'is_free:json_boolean',
];
}

View File

@ -0,0 +1,43 @@
<?php namespace App\ModelSerializers\Locations;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use models\summit\SummitBookableVenueRoom;
use ModelSerializers\Locations\SummitVenueRoomSerializer;
use ModelSerializers\SerializerRegistry;
/**
* Class SummitBookableVenueRoomSerializer
* @package App\ModelSerializers\Locations
*/
class SummitBookableVenueRoomSerializer extends SummitVenueRoomSerializer
{
protected static $array_mappings = [
'TimeSlotCost' => 'time_slot_cost:json_float',
'Currency' => 'currency:json_string',
];
public function serialize($expand = null, array $fields = array(), array $relations = array(), array $params = array() )
{
$room = $this->object;
if(!$room instanceof SummitBookableVenueRoom)
return [];
$values = parent::serialize($expand, $fields, $relations, $params);
$attributes = [];
foreach ($room->getAttributes() as $attribute){
$attributes[] = SerializerRegistry::getInstance()->getSerializer($attribute)->serialize($expand);
}
$values['attributes'] = $attributes;
return $values;
}
}

View File

@ -0,0 +1,73 @@
<?php namespace App\ModelSerializers\Locations;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use models\summit\SummitRoomReservation;
use ModelSerializers\SerializerRegistry;
use ModelSerializers\SilverStripeSerializer;
/**
* Class SummitRoomReservationSerializer
* @package App\ModelSerializers\Locations
*/
final class SummitRoomReservationSerializer extends SilverStripeSerializer
{
protected static $array_mappings = [
'RoomId' => 'room_id:json_int',
'OwnerId' => 'owner_id:json_int',
'Amount' => 'amount:json_int',
'Currency' => 'currency:json_string',
'Status' => 'status:json_string',
'StartDatetime' => 'start_datetime:datetime_epoch',
'EndDatetime' => 'end_datetime:datetime_epoch',
'LocalStartDatetime' => 'local_start_datetime:datetime_epoch',
'LocalEndDatetime' => 'local_end_datetime:datetime_epoch',
'ApprovedPaymentDate' => 'approved_payment_date:datetime_epoch',
'LastError' => 'last_error:json_string',
'PaymentGatewayClientToken' => 'payment_gateway_client_token:json_string'
];
/**
* @param null $expand
* @param array $fields
* @param array $relations
* @param array $params
* @return array
*/
public function serialize($expand = null, array $fields = [], array $relations = [], array $params = [])
{
$reservation = $this->object;
if(!$reservation instanceof SummitRoomReservation)
return [];
$values = parent::serialize($expand, $fields, $relations, $params);
if (!empty($expand)) {
$exp_expand = explode(',', $expand);
foreach ($exp_expand as $relation) {
switch (trim($relation)) {
case 'room': {
unset($values['room_id']);
$values['room'] = SerializerRegistry::getInstance()->getSerializer($reservation->getRoom())->serialize($expand);
}
break;
case 'owner': {
unset($values['owner_id']);
$values['owner'] = SerializerRegistry::getInstance()->getSerializer($reservation->getOwner())->serialize($expand);
}
break;
}
}
}
return $values;
}
}

View File

@ -12,12 +12,11 @@
* limitations under the License.
**/
use ModelSerializers\SerializerRegistry;
/**
* Class SummitVenueRoomSerializer
* @package ModelSerializers\Locations
*/
final class SummitVenueRoomSerializer extends SummitAbstractLocationSerializer
class SummitVenueRoomSerializer extends SummitAbstractLocationSerializer
{
protected static $array_mappings = [
'VenueId' => 'venue_id:json_int',

View File

@ -14,6 +14,11 @@
use App\ModelSerializers\CCLA\TeamSerializer;
use App\ModelSerializers\FileSerializer;
use App\ModelSerializers\LanguageSerializer;
use App\ModelSerializers\Locations\SummitBookableVenueRoomAttributeTypeSerializer;
use App\ModelSerializers\Locations\SummitBookableVenueRoomAttributeValueSerializer;
use App\ModelSerializers\Locations\SummitBookableVenueRoomAvailableSlotSerializer;
use App\ModelSerializers\Locations\SummitBookableVenueRoomSerializer;
use App\ModelSerializers\Locations\SummitRoomReservationSerializer;
use App\ModelSerializers\Marketplace\CloudServiceOfferedSerializer;
use App\ModelSerializers\Marketplace\ConfigurationManagementTypeSerializer;
use App\ModelSerializers\Marketplace\ConsultantClientSerializer;
@ -191,15 +196,20 @@ final class SerializerRegistry
$this->registry['SponsorSummitRegistrationPromoCode'] = SponsorSummitRegistrationPromoCodeSerializer::class;
$this->registry['PresentationSpeakerSummitAssistanceConfirmationRequest'] = PresentationSpeakerSummitAssistanceConfirmationRequestSerializer::class;
// locations
$this->registry['SummitVenue'] = SummitVenueSerializer::class;
$this->registry['SummitVenueRoom'] = SummitVenueRoomSerializer::class;
$this->registry['SummitVenueFloor'] = SummitVenueFloorSerializer::class;
$this->registry['SummitExternalLocation'] = SummitExternalLocationSerializer::class;
$this->registry['SummitHotel'] = SummitHotelSerializer::class;
$this->registry['SummitAirport'] = SummitAirportSerializer::class;
$this->registry['SummitLocationImage'] = SummitLocationImageSerializer::class;
$this->registry['SummitLocationBanner'] = SummitLocationBannerSerializer::class;
$this->registry['ScheduledSummitLocationBanner'] = ScheduledSummitLocationBannerSerializer::class;
$this->registry['SummitVenue'] = SummitVenueSerializer::class;
$this->registry['SummitVenueRoom'] = SummitVenueRoomSerializer::class;
$this->registry['SummitVenueFloor'] = SummitVenueFloorSerializer::class;
$this->registry['SummitExternalLocation'] = SummitExternalLocationSerializer::class;
$this->registry['SummitHotel'] = SummitHotelSerializer::class;
$this->registry['SummitAirport'] = SummitAirportSerializer::class;
$this->registry['SummitLocationImage'] = SummitLocationImageSerializer::class;
$this->registry['SummitLocationBanner'] = SummitLocationBannerSerializer::class;
$this->registry['ScheduledSummitLocationBanner'] = ScheduledSummitLocationBannerSerializer::class;
$this->registry['SummitBookableVenueRoom'] = SummitBookableVenueRoomSerializer::class;
$this->registry['SummitBookableVenueRoomAttributeType'] = SummitBookableVenueRoomAttributeTypeSerializer::class;
$this->registry['SummitBookableVenueRoomAttributeValue'] = SummitBookableVenueRoomAttributeValueSerializer::class;
$this->registry['SummitBookableVenueRoomAvailableSlot'] = SummitBookableVenueRoomAvailableSlotSerializer::class;
$this->registry['SummitRoomReservation'] = SummitRoomReservationSerializer::class;
// track tag groups
$this->registry['TrackTagGroup'] = TrackTagGroupSerializer::class;

View File

@ -52,6 +52,10 @@ class SummitSerializer extends SilverStripeSerializer
'SecondaryRegistrationLink' => 'secondary_registration_link:json_string',
'SecondaryRegistrationLabel' => 'secondary_registration_label:json_string',
'RawSlug' => 'slug:json_string',
'MeetingRoomBookingStartTime' => 'meeting_room_booking_start_time:datetime_epoch',
'MeetingRoomBookingEndTime' => 'meeting_room_booking_end_time:datetime_epoch',
'MeetingRoomBookingSlotLength' => 'meeting_room_booking_slot_length:json_int',
'MeetingRoomBookingMaxAllowed' => 'meeting_room_booking_max_allowed:json_int',
];
protected static $allowed_relations = [
@ -59,6 +63,7 @@ class SummitSerializer extends SilverStripeSerializer
'locations',
'wifi_connections',
'selection_plans',
'meeting_booking_room_allowed_attributes',
];
/**
@ -110,7 +115,16 @@ class SummitSerializer extends SilverStripeSerializer
$values['ticket_types'] = $ticket_types;
}
//locations
// meeting_booking_room_allowed_attributes
if(in_array('meeting_booking_room_allowed_attributes', $relations)) {
$meeting_booking_room_allowed_attributes = [];
foreach ($summit->getMeetingBookingRoomAllowedAttributes() as $attr) {
$meeting_booking_room_allowed_attributes[] = SerializerRegistry::getInstance()->getSerializer($attr)->serialize($expand);
}
$values['meeting_booking_room_allowed_attributes'] = $meeting_booking_room_allowed_attributes;
}
// locations
if(in_array('locations', $relations)) {
$locations = [];
foreach ($summit->getLocations() as $location) {

View File

@ -24,6 +24,7 @@ use models\summit\RSVP;
use models\summit\Summit;
use models\summit\SummitEvent;
use models\summit\SummitEventFeedback;
use models\summit\SummitRoomReservation;
use models\utils\SilverstripeBaseModel;
use Doctrine\ORM\Mapping AS ORM;
/**
@ -207,6 +208,12 @@ class Member extends SilverstripeBaseModel
*/
private $favorites;
/**
* @ORM\OneToMany(targetEntity="models\summit\SummitRoomReservation", mappedBy="owner", cascade={"persist"}, orphanRemoval=true)
* @var ArrayCollection
*/
private $reservations;
/**
* Member constructor.
*/
@ -223,6 +230,7 @@ class Member extends SilverstripeBaseModel
$this->rsvp = new ArrayCollection();
$this->calendars_sync = new ArrayCollection();
$this->schedule_sync_info = new ArrayCollection();
$this->reservations = new ArrayCollection();
}
/**
@ -1123,4 +1131,33 @@ SQL;
}
return $photoUrl;
}
/**
* @param SummitRoomReservation $reservation
* @return $this
*/
public function addReservation(SummitRoomReservation $reservation){
if($this->reservations->contains($reservation)) return $this;
$this->reservations->add($reservation);
$reservation->setOwner($this);
return $this;
}
/**
* @return ArrayCollection
*/
public function getReservations(){
return $this->reservations;
}
/**
* @param int $reservation_id
* @return SummitRoomReservation
*/
public function getReservationById(int $reservation_id): ?SummitRoomReservation {
$criteria = Criteria::create()
->where(Criteria::expr()->eq("id", $reservation_id));
return $this->reservations->matching($criteria)->first();
}
}

View File

@ -0,0 +1,53 @@
<?php namespace App\Models\Foundation\Summit\Factories;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use models\summit\Summit;
use models\summit\SummitRoomReservation;
/**
* Class SummitRoomReservationFactory
* @package App\Models\Foundation\Summit\Factories
*/
final class SummitRoomReservationFactory
{
/**
* @param Summit $summit
* @param array $data
* @return SummitRoomReservation
*/
public static function build(Summit $summit, array $data){
$reservation = new SummitRoomReservation;
if(isset($data['owner']))
$reservation->setOwner($data['owner']);
if(isset($data['currency']))
$reservation->setCurrency(trim($data['currency']));
if(isset($data['amount']))
$reservation->setAmount(floatval($data['amount']));
// dates ( they came on local time epoch , so must be converted to utc using
// summit timezone
if(isset($data['start_datetime'])) {
$val = intval($data['start_datetime']);
$val = new \DateTime("@$val", $summit->getTimeZone());
$reservation->setStartDatetime($summit->convertDateFromTimeZone2UTC($val));
}
if(isset($data['end_datetime'])){
$val = intval($data['end_datetime']);
$val = new \DateTime("@$val", $summit->getTimeZone());
$reservation->setEndDatetime($summit->convertDateFromTimeZone2UTC($val));
}
return $reservation;
}
}

View File

@ -40,7 +40,16 @@ use Doctrine\ORM\Mapping AS ORM;
* @ORM\Table(name="SummitAbstractLocation")
* @ORM\InheritanceType("JOINED")
* @ORM\DiscriminatorColumn(name="ClassName", type="string")
* @ORM\DiscriminatorMap({"SummitAbstractLocation" = "SummitAbstractLocation", "SummitGeoLocatedLocation" = "SummitGeoLocatedLocation", "SummitExternalLocation" = "SummitExternalLocation", "SummitVenue" = "SummitVenue", "SummitHotel" = "SummitHotel", "SummitAirport" = "SummitAirport", "SummitVenueRoom" = "SummitVenueRoom"})
* @ORM\DiscriminatorMap({
* "SummitAbstractLocation" = "SummitAbstractLocation",
* "SummitGeoLocatedLocation" = "SummitGeoLocatedLocation",
* "SummitExternalLocation" = "SummitExternalLocation",
* "SummitVenue" = "SummitVenue",
* "SummitHotel" = "SummitHotel",
* "SummitAirport" = "SummitAirport",
* "SummitVenueRoom" = "SummitVenueRoom",
* "SummitBookableVenueRoom" = "SummitBookableVenueRoom"
* })
* Class SummitAbstractLocation
* @package models\summit
*/

View File

@ -0,0 +1,280 @@
<?php namespace models\summit;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping AS ORM;
use models\exceptions\ValidationException;
/**
* @ORM\Entity
* @ORM\Table(name="SummitBookableVenueRoom")
* Class SummitBookableVenueRoom
* @package models\summit
*/
class SummitBookableVenueRoom extends SummitVenueRoom
{
const ClassName = 'SummitBookableVenueRoom';
/**
* @ORM\Column(name="TimeSlotCost", type="decimal")
* @var float
*/
private $time_slot_cost;
/**
* @var string
* @ORM\Column(name="Currency", type="string")
*/
private $currency;
/**
* @ORM\OneToMany(targetEntity="models\summit\SummitRoomReservation", mappedBy="room", cascade={"persist"}, orphanRemoval=true)
* @var ArrayCollection
*/
private $reservations;
/**
* @ORM\ManyToMany(targetEntity="models\summit\SummitBookableVenueRoomAttributeValue")
* @ORM\JoinTable(name="SummitBookableVenueRoom_Attributes",
* joinColumns={@ORM\JoinColumn(name="SummitBookableVenueRoomID", referencedColumnName="ID")},
* inverseJoinColumns={@ORM\JoinColumn(name="SummitBookableVenueRoomAttributeValueID", referencedColumnName="ID", unique=true)}
* )
*/
private $attributes;
public function __construct()
{
parent::__construct();
$this->reservations = new ArrayCollection();
$this->attributes = new ArrayCollection();
}
/**
* @param SummitRoomReservation $reservation
* @return $this
* @throws ValidationException
*/
public function addReservation(SummitRoomReservation $reservation){
$criteria = Criteria::create();
$start_date = $reservation->getStartDatetime();
$end_date = $reservation->getEndDatetime();
$criteria
->where(Criteria::expr()->eq('start_datetime', $start_date))
->andWhere(Criteria::expr()->eq('end_datetime',$end_date))
->andWhere(Criteria::expr()->notIn("status", [SummitRoomReservation::RequestedRefundStatus, SummitRoomReservation::RefundedStatus]));
if($this->reservations->matching($criteria)->count() > 0)
throw new ValidationException(sprintf("reservation overlaps an existent reservation"));
$criteria
->where(Criteria::expr()->lte('start_datetime', $end_date))
->andWhere(Criteria::expr()->gte('end_datetime', $start_date))
->andWhere(Criteria::expr()->notIn("status", [SummitRoomReservation::RequestedRefundStatus, SummitRoomReservation::RefundedStatus]));
if($this->reservations->matching($criteria)->count() > 0)
throw new ValidationException(sprintf("reservation overlaps an existent reservation"));
$summit = $this->summit;
$local_start_date = $summit->convertDateFromUTC2TimeZone($start_date);
$local_end_date = $summit->convertDateFromUTC2TimeZone($end_date);
$start_time = $summit->getMeetingRoomBookingStartTime();
$end_time = $summit->getMeetingRoomBookingEndTime();
if(!$summit->isTimeFrameInsideSummitDuration($local_start_date, $local_end_date))
throw new ValidationException("requested reservation period does not belong to summit period");
$local_start_time = new \DateTime("now", $this->summit->getTimeZone());
$local_start_time->setTime(
intval($start_time->format("H")),
intval($start_time->format("i")),
intval($start_time->format("s"))
);
$local_end_time = new \DateTime("now", $this->summit->getTimeZone());
$local_end_time->setTime(
intval($end_time->format("H")),
intval($end_time->format("i")),
intval($end_time->format("s"))
);
$local_start_time->setDate
(
intval($start_date->format("Y")),
intval($start_date->format("m")),
intval($start_date->format("d"))
);
$local_end_time->setDate
(
intval($start_date->format("Y")),
intval($start_date->format("m")),
intval($start_date->format("d"))
);
if(!($local_start_time <= $local_start_date
&& $local_end_date <= $local_end_time))
throw new ValidationException("requested booking time slot is not allowed!");
$interval = $end_date->diff($start_date);
$minutes = ($interval->d * 24 * 60) + ($interval->h * 60) + $interval->i;
if($minutes != $summit->getMeetingRoomBookingSlotLength())
throw new ValidationException("requested booking time slot is not allowed!");
$this->reservations->add($reservation);
$reservation->setRoom($this);
return $this;
}
/**
* @return float
*/
public function getTimeSlotCost(): float
{
return floatval($this->time_slot_cost);
}
/**
* @param float $time_slot_cost
*/
public function setTimeSlotCost(float $time_slot_cost): void
{
$this->time_slot_cost = $time_slot_cost;
}
/**
* @return ArrayCollection
*/
public function getReservations(): ArrayCollection
{
return $this->reservations;
}
public function clearReservations(){
$this->reservations->clear();
}
/**
* @return string
*/
public function getClassName()
{
return self::ClassName;
}
/**
* @param \DateTime $day should be on local summit day
* @return array
* @throws ValidationException
*/
public function getAvailableSlots(\DateTime $day):array{
$availableSlots = [];
$summit = $this->summit;
$day = $day->setTimezone($summit->getTimeZone())->setTime(0, 0,0);
$booking_start_time = $summit->getMeetingRoomBookingStartTime();
if(is_null($booking_start_time))
throw new ValidationException("MeetingRoomBookingStartTime is null!");
$booking_end_time = $summit->getMeetingRoomBookingEndTime();
if(is_null($booking_end_time))
throw new ValidationException("MeetingRoomBookingEndTime is null!");
$booking_slot_len = $summit->getMeetingRoomBookingSlotLength();
$start_datetime = clone $day;
$end_datetime = clone $day;
$start_datetime->setTime(
intval($booking_start_time->format("H")),
intval($booking_start_time->format("i")),
0);
$start_datetime->setTimezone($summit->getTimeZone());
$end_datetime->setTime(
intval($booking_end_time->format("H")),
intval($booking_end_time->format("i")),
00);
$end_datetime->setTimezone($summit->getTimeZone());
$criteria = Criteria::create();
if(!$summit->isTimeFrameInsideSummitDuration($start_datetime, $end_datetime))
throw new ValidationException("requested day does not belong to summit period");
$criteria
->where(Criteria::expr()->gte('start_datetime', $summit->convertDateFromTimeZone2UTC($start_datetime)))
->andWhere(Criteria::expr()->lte('end_datetime', $summit->convertDateFromTimeZone2UTC($end_datetime)))
->andWhere(Criteria::expr()->notIn("status", [SummitRoomReservation::RequestedRefundStatus, SummitRoomReservation::RefundedStatus]));
$reservations = $this->reservations->matching($criteria);
while($start_datetime <= $end_datetime) {
$current_time_slot_end = clone $start_datetime;
$current_time_slot_end->add(new \DateInterval("PT" . $booking_slot_len . 'M'));
if($current_time_slot_end<=$end_datetime)
$availableSlots[$start_datetime->format('Y-m-d H:i:s').'|'.$current_time_slot_end->format('Y-m-d H:i:s')] = true;
$start_datetime = $current_time_slot_end;
}
foreach ($reservations as $reservation){
if(!$reservation instanceof SummitRoomReservation) continue;
$availableSlots[
$summit->convertDateFromUTC2TimeZone($reservation->getStartDatetime())->format("Y-m-d H:i:s")
.'|'.
$summit->convertDateFromUTC2TimeZone($reservation->getEndDatetime())->format("Y-m-d H:i:s")
] = false;
}
return $availableSlots;
}
/**
* @param \DateTime $day
* @return array
* @throws ValidationException
*/
public function getFreeSlots(\DateTime $day):array{
$slots = $this->getAvailableSlots($day);
$free_slots = [];
foreach ($slots as $label => $status){
if(!$status) continue;
$free_slots[] = $label;
}
return $free_slots;
}
/**
* @return string
*/
public function getCurrency(): string
{
return $this->currency;
}
/**
* @param string $currency
*/
public function setCurrency(string $currency): void
{
$this->currency = $currency;
}
/**
* @return mixed
*/
public function getAttributes()
{
return $this->attributes;
}
}

View File

@ -0,0 +1,85 @@
<?php namespace models\summit;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping AS ORM;
use models\exceptions\ValidationException;
use models\summit\SummitOwned;
use models\utils\SilverstripeBaseModel;
/**
* @ORM\Entity
* @ORM\Table(name="SummitBookableVenueRoomAttributeType")
* @ORM\AssociationOverrides({
* @ORM\AssociationOverride(
* name="summit",
* inversedBy="booking_room_allowed_attributes"
* )
* })
* Class SummitBookableVenueRoomAttributeType
* @package models\summit
*/
class SummitBookableVenueRoomAttributeType extends SilverstripeBaseModel
{
/**
* @ORM\Column(name="Type", type="string")
* @var string
*/
private $type;
use SummitOwned;
/**
* @ORM\OneToMany(targetEntity="models\summit\SummitBookableVenueRoomAttributeValue", mappedBy="type", cascade={"persist"}, orphanRemoval=true)
* @var ArrayCollection
*/
private $values;
public function __construct()
{
parent::__construct();
$this->values = new ArrayCollection();
}
/**
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* @param string $type
*/
public function setType(string $type): void
{
$this->type = $type;
}
/**
* @return ArrayCollection
*/
public function getValues(): ArrayCollection
{
return $this->values;
}
/**
* @param ArrayCollection $values
*/
public function setValues(ArrayCollection $values): void
{
$this->values = $values;
}
}

View File

@ -0,0 +1,84 @@
<?php namespace models\summit;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping AS ORM;
use models\exceptions\ValidationException;
use models\utils\SilverstripeBaseModel;
/**
* @ORM\Entity
* @ORM\Table(name="SummitBookableVenueRoomAttributeValue")
* Class SummitBookableVenueRoomAttributeValue
* @package models\summit
*/
class SummitBookableVenueRoomAttributeValue extends SilverstripeBaseModel
{
/**
* @ORM\Column(name="Value", type="string")
* @var string
*/
private $value;
/**
* @ORM\ManyToOne(targetEntity="models\summit\SummitBookableVenueRoomAttributeType", inversedBy="values")
* @ORM\JoinColumn(name="TypeID", referencedColumnName="ID")
* @var SummitBookableVenueRoomAttributeType
*/
private $type;
/**
* @return string
*/
public function getValue(): string
{
return $this->value;
}
/**
* @param string $value
*/
public function setValue(string $value): void
{
$this->value = $value;
}
/**
* @return SummitBookableVenueRoomAttributeType
*/
public function getType(): SummitBookableVenueRoomAttributeType
{
return $this->type;
}
/**
* @param SummitBookableVenueRoomAttributeType $type
*/
public function setType(SummitBookableVenueRoomAttributeType $type): void
{
$this->type = $type;
}
/**
* @return int
*/
public function getTypeId():int{
try {
return is_null($this->type) ? 0 : $this->type->getId();
}
catch(\Exception $ex){
return 0;
}
}
}

View File

@ -0,0 +1,88 @@
<?php namespace models\summit;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
final class SummitBookableVenueRoomAvailableSlot
{
/**
* @var \DateTime
*/
private $start_date;
/**
* @var \DateTime
*/
private $end_date;
/**
* @var bool
*/
private $is_free;
/**
* @var SummitBookableVenueRoom
*/
private $room;
/**
* SummitBookableVenueRoomAvailableSlot constructor.
* @param SummitBookableVenueRoom $room
* @param \DateTime $start_date
* @param \DateTime $end_date
* @param bool $is_free
*/
public function __construct(SummitBookableVenueRoom $room, \DateTime $start_date, \DateTime $end_date, bool $is_free)
{
$this->room = $room;
$this->start_date = $start_date;
$this->end_date = $end_date;
$this->is_free = $is_free;
}
/**
* @return \DateTime
*/
public function getStartDate(): \DateTime
{
return $this->start_date;
}
/**
* @return \DateTime
*/
public function getEndDate(): \DateTime
{
return $this->end_date;
}
/**
* @return \DateTime
*/
public function getLocalStartDate(): \DateTime
{
return $this->room->getSummit()->convertDateFromUTC2TimeZone($this->start_date);
}
/**
* @return \DateTime
*/
public function getLocalEndDate(): \DateTime
{
return $this->room->getSummit()->convertDateFromUTC2TimeZone($this->end_date);
}
public function isFree():bool{
return $this->is_free;
}
}

View File

@ -0,0 +1,329 @@
<?php namespace models\summit;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Doctrine\ORM\Mapping AS ORM;
use models\main\Member;
use models\utils\SilverstripeBaseModel;
/**
* @ORM\Entity(repositoryClass="App\Repositories\Summit\DoctrineSummitRoomReservationRepository")
* @ORM\Table(name="SummitRoomReservation")
* Class SummitRoomReservation
* @package models\summit
*/
class SummitRoomReservation extends SilverstripeBaseModel
{
/**
* @var \DateTime
* @ORM\Column(name="StartDateTime", type="datetime")
*/
private $start_datetime;
/**
* @var \DateTime
* @ORM\Column(name="EndDateTime", type="datetime")
*/
private $end_datetime;
/**
* @var \DateTime
* @ORM\Column(name="ApprovedPaymentDate", type="datetime")
*/
private $approved_payment_date;
/**
* @var string
* @ORM\Column(name="Status", type="string")
*/
private $status;
/**
* @var string
* @ORM\Column(name="LastError", type="string")
*/
private $last_error;
/**
* @var string
* @ORM\Column(name="PaymentGatewayCartId", type="string")
*/
private $payment_gateway_cart_id;
/**
* @var string
*/
private $payment_gateway_client_token;
/**
* @var string
* @ORM\Column(name="Currency", type="string")
*/
private $currency;
/**
* @var float
* @ORM\Column(name="Amount", type="float")
*/
private $amount;
/**
* @ORM\ManyToOne(targetEntity="models\main\Member", inversedBy="reservations")
* @ORM\JoinColumn(name="OwnerID", referencedColumnName="ID")
* @var Member
*/
private $owner;
/**
* @ORM\ManyToOne(targetEntity="models\summit\SummitBookableVenueRoom", inversedBy="reservations")
* @ORM\JoinColumn(name="RoomID", referencedColumnName="ID")
* @var SummitBookableVenueRoom
*/
private $room;
const ReservedStatus = "Reserved";
const ErrorStatus = "Error";
const PayedStatus = "Payed";
const RequestedRefundStatus = "RequestedRefund";
const RefundedStatus = "Refunded";
public static $valid_status = [
self::ReservedStatus,
self::PayedStatus,
self::RequestedRefundStatus,
self::RefundedStatus,
self::ErrorStatus,
];
/**
* @return \DateTime
*/
public function getStartDatetime(): \DateTime
{
return $this->start_datetime;
}
/**
* @return \DateTime
*/
public function getLocalStartDatetime(): \DateTime
{
return $this->room->getSummit()->convertDateFromUTC2TimeZone($this->start_datetime);
}
/**
* @param \DateTime $start_datetime
*/
public function setStartDatetime(\DateTime $start_datetime): void
{
$this->start_datetime = $start_datetime;
}
/**
* @return \DateTime
*/
public function getEndDatetime(): \DateTime
{
return $this->end_datetime;
}
/**
* @return \DateTime
*/
public function getLocalEndDatetime(): \DateTime
{
return $this->room->getSummit()->convertDateFromUTC2TimeZone($this->end_datetime);
}
/**
* @param \DateTime $end_datetime
*/
public function setEndDatetime(\DateTime $end_datetime): void
{
$this->end_datetime = $end_datetime;
}
/**
* @return string
*/
public function getStatus(): string
{
return $this->status;
}
/**
* @param string $status
*/
public function setStatus(string $status): void
{
$this->status = $status;
}
/**
* @return Member
*/
public function getOwner(): Member
{
return $this->owner;
}
/**
* @param Member $owner
*/
public function setOwner(Member $owner): void
{
$this->owner = $owner;
}
/**
* @return SummitBookableVenueRoom
*/
public function getRoom(): SummitBookableVenueRoom
{
return $this->room;
}
/**
* @param SummitBookableVenueRoom $room
*/
public function setRoom(SummitBookableVenueRoom $room): void
{
$this->room = $room;
}
/**
* @return string
*/
public function getPaymentGatewayCartId(): string
{
return $this->payment_gateway_cart_id;
}
/**
* @param string $payment_gateway_cart_id
*/
public function setPaymentGatewayCartId(string $payment_gateway_cart_id): void
{
$this->payment_gateway_cart_id = $payment_gateway_cart_id;
}
/**
* @return \DateTime|null
*/
public function getApprovedPaymentDate(): ?\DateTime
{
return $this->approved_payment_date;
}
/**
* @param \DateTime $approved_payment_date
*/
public function setApprovedPaymentDate(\DateTime $approved_payment_date): void
{
$this->approved_payment_date = $approved_payment_date;
}
/**
* @return string
*/
public function getCurrency(): string
{
return $this->currency;
}
/**
* @param string $currency
*/
public function setCurrency(string $currency): void
{
$this->currency = $currency;
}
/**
* @return float
*/
public function getAmount(): float
{
return $this->amount;
}
/**
* @param float $amount
*/
public function setAmount(float $amount): void
{
$this->amount = $amount;
}
public function __construct()
{
parent::__construct();
$this->amount = 0.0;
$this->status = self::ReservedStatus;
}
/**
* @return string|null
*/
public function getPaymentGatewayClientToken(): ?string
{
return $this->payment_gateway_client_token;
}
/**
* @param string $payment_gateway_client_token
*/
public function setPaymentGatewayClientToken(string $payment_gateway_client_token): void
{
$this->payment_gateway_client_token = $payment_gateway_client_token;
}
public function setPayed():void{
$this->status = self::PayedStatus;
$now = new \DateTime('now', new \DateTimeZone('UTC'));
$this->approved_payment_date = $now;
}
public function requestRefund():void{
$this->status = self::RequestedRefundStatus;
}
public function setPaymentError(string $error):void{
$this->status = self::ErrorStatus;
$this->last_error = $error;
}
/**
* @return int
*/
public function getOwnerId(){
try {
return is_null($this->owner) ? 0 : $this->owner->getId();
}
catch(\Exception $ex){
return 0;
}
}
/**
* @return int
*/
public function getRoomId(){
try {
return is_null($this->room) ? 0 : $this->room->getId();
}
catch(\Exception $ex){
return 0;
}
}
}

View File

@ -16,7 +16,6 @@ use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping AS ORM;
use Doctrine\Common\Collections\ArrayCollection;
use models\exceptions\ValidationException;
/**
* @ORM\Entity
* @ORM\Table(name="SummitVenue")

View File

@ -53,4 +53,10 @@ interface ISummitRepository extends IBaseRepository
* @return Summit[]
*/
public function getCurrentAndFutureSummits();
/**
* @param string $slug
* @return Summit
*/
public function getBySlug(string $slug):Summit;
}

View File

@ -0,0 +1,27 @@
<?php namespace App\Models\Foundation\Summit\Repositories;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use models\summit\SummitRoomReservation;
use models\utils\IBaseRepository;
/**
* Interface ISummitRoomReservationRepository
* @package App\Models\Foundation\Summit\Repositories
*/
interface ISummitRoomReservationRepository extends IBaseRepository
{
/**
* @param string $payment_gateway_cart_id
* @return SummitRoomReservation
*/
public function getByPaymentGatewayCartId(string $payment_gateway_cart_id):SummitRoomReservation;
}

View File

@ -165,11 +165,40 @@ class Summit extends SilverstripeBaseModel
*/
private $calendar_sync_desc;
/**
* @ORM\Column(name="MeetingRoomBookingStartTime", type="time", nullable=true)
* @var DateTime
*/
private $meeting_room_booking_start_time;
/**
* @ORM\Column(name="MeetingRoomBookingEndTime", type="time", nullable=true)
* @var DateTime
*/
private $meeting_room_booking_end_time;
/**
* @ORM\Column(name="MeetingRoomBookingSlotLength", type="integer", nullable=true)
* @var int
*/
private $meeting_room_booking_slot_length;
/**
* @ORM\Column(name="MeetingRoomBookingMaxAllowed", type="integer", nullable=true)
* @var int
*/
private $meeting_room_booking_max_allowed;
/**
* @ORM\OneToMany(targetEntity="SummitAbstractLocation", mappedBy="summit", cascade={"persist"}, orphanRemoval=true, fetch="EXTRA_LAZY")
*/
private $locations;
/**
* @ORM\OneToMany(targetEntity="models\summit\SummitBookableVenueRoomAttributeType", mappedBy="summit", cascade={"persist"}, orphanRemoval=true, fetch="EXTRA_LAZY")
*/
private $meeting_booking_room_allowed_attributes;
/**
* @ORM\OneToMany(targetEntity="SummitEvent", mappedBy="summit", cascade={"persist"}, orphanRemoval=true, fetch="EXTRA_LAZY")
*/
@ -549,6 +578,7 @@ class Summit extends SilverstripeBaseModel
$this->track_tag_groups = new ArrayCollection;
$this->notifications = new ArrayCollection;
$this->selection_plans = new ArrayCollection;
$this->meeting_booking_room_allowed_attributes = new ArrayCollection();
}
/**
@ -641,6 +671,17 @@ class Summit extends SilverstripeBaseModel
});
}
/**
* @return SummitBookableVenueRoom[]
*/
public function getBookableRooms()
{
return $this->locations->filter(function ($e) {
return $e instanceof SummitBookableVenueRoom;
});
}
/**
* @return SummitAirport[]
*/
@ -2420,4 +2461,85 @@ SQL;
public function setRawSlug(string $slug):void{
$this->slug = $slug;
}
/**
* @return DateTime
*/
public function getMeetingRoomBookingStartTime():?DateTime
{
return $this->meeting_room_booking_start_time;
}
/**
* @param DateTime $meeting_room_booking_start_time
*/
public function setMeetingRoomBookingStartTime(DateTime $meeting_room_booking_start_time): void
{
$this->meeting_room_booking_start_time = $meeting_room_booking_start_time;
}
/**
* @return DateTime
*/
public function getMeetingRoomBookingEndTime():?DateTime
{
return $this->meeting_room_booking_end_time;
}
/**
* @param DateTime $meeting_room_booking_end_time
*/
public function setMeetingRoomBookingEndTime(DateTime $meeting_room_booking_end_time): void
{
$this->meeting_room_booking_end_time = $meeting_room_booking_end_time;
}
/**
* @return int
*/
public function getMeetingRoomBookingSlotLength(): int
{
return $this->meeting_room_booking_slot_length;
}
/**
* @param int $meeting_room_booking_slot_length
*/
public function setMeetingRoomBookingSlotLength(int $meeting_room_booking_slot_length): void
{
$this->meeting_room_booking_slot_length = $meeting_room_booking_slot_length;
}
/**
* @return int
*/
public function getMeetingRoomBookingMaxAllowed(): int
{
return $this->meeting_room_booking_max_allowed;
}
/**
* @param int $meeting_room_booking_max_allowed
*/
public function setMeetingRoomBookingMaxAllowed(int $meeting_room_booking_max_allowed): void
{
$this->meeting_room_booking_max_allowed = $meeting_room_booking_max_allowed;
}
/**
* @return mixed
*/
public function getMeetingBookingRoomAllowedAttributes()
{
return $this->meeting_booking_room_allowed_attributes;
}
public function getMaxReservationsPerDay():int {
$interval = $this->meeting_room_booking_end_time->diff( $this->meeting_room_booking_start_time);
$minutes = $interval->days * 24 * 60;
$minutes += $interval->h * 60;
$minutes += $interval->i;
return intval ($minutes / $this->meeting_room_booking_slot_length);
}
}

View File

@ -597,8 +597,27 @@ class AppServiceProvider extends ServiceProvider
$value = trim($value);
return isset($countries[$value]);
});
Validator::extend('currency_iso', function($attribute, $value, $parameters, $validator)
{
$currencies =
[
'USD' => 'USD',
'EUR' => 'EUR',
'GBP' => 'GBP'
];
$validator->addReplacer('currency_iso', function($message, $attribute, $rule, $parameters) use ($validator) {
return sprintf("%s should be a valid currency iso 4217 code", $attribute);
});
if(!is_string($value)) return false;
$value = trim($value);
return isset($currencies[$value]);
});
}
/**
* Register any application services.
* @return void

View File

@ -1,10 +1,22 @@
<?php
namespace App\Providers;
<?php namespace App\Providers;
/**
* Copyright 2017 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
/**
* Class AuthServiceProvider
* @package App\Providers
*/
class AuthServiceProvider extends ServiceProvider
{
/**

View File

@ -1,10 +1,22 @@
<?php
namespace App\Providers;
<?php namespace App\Providers;
/**
* Copyright 2017 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Illuminate\Routing\Router;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
/**
* Class RouteServiceProvider
* @package App\Providers
*/
class RouteServiceProvider extends ServiceProvider
{
/**

View File

@ -30,6 +30,7 @@ use App\Models\Foundation\Summit\Repositories\ISpeakerOrganizationalRoleReposito
use App\Models\Foundation\Summit\Repositories\ISummitEventTypeRepository;
use App\Models\Foundation\Summit\Repositories\ISummitLocationBannerRepository;
use App\Models\Foundation\Summit\Repositories\ISummitLocationRepository;
use App\Models\Foundation\Summit\Repositories\ISummitRoomReservationRepository;
use App\Models\Foundation\Summit\Repositories\ISummitTrackRepository;
use App\Models\Foundation\Summit\Repositories\ITrackQuestionTemplateRepository;
use App\Models\Foundation\Summit\Repositories\ITrackTagGroupAllowedTagsRepository;
@ -58,6 +59,7 @@ use models\summit\SpeakerSummitRegistrationPromoCode;
use models\summit\SummitAbstractLocation;
use models\summit\SummitEventType;
use models\summit\SummitRegistrationPromoCode;
use models\summit\SummitRoomReservation;
use models\summit\SummitTicketType;
/**
* Class RepositoriesProvider
@ -401,5 +403,12 @@ final class RepositoriesProvider extends ServiceProvider
}
);
App::singleton(
ISummitRoomReservationRepository::class,
function(){
return EntityManager::getRepository(SummitRoomReservation::class);
}
);
}
}

View File

@ -96,6 +96,6 @@ final class DoctrineApiEndpointRepository
*/
protected function applyExtraFilters(QueryBuilder $query)
{
return [];
return $query;
}
}

View File

@ -17,10 +17,13 @@ use Doctrine\ORM\Tools\Pagination\Paginator;
use models\summit\Summit;
use models\summit\SummitAbstractLocation;
use models\summit\SummitAirport;
use models\summit\SummitBookableVenueRoom;
use models\summit\SummitBookableVenueRoomAttributeValue;
use models\summit\SummitExternalLocation;
use models\summit\SummitGeoLocatedLocation;
use models\summit\SummitHotel;
use models\summit\SummitVenue;
use utils\DoctrineFilterMapping;
use utils\DoctrineInstanceOfFilterMapping;
use utils\Filter;
use utils\Order;
@ -51,23 +54,31 @@ final class DoctrineSummitLocationRepository
protected function getFilterMappings()
{
return [
'name' => 'al.name:json_string',
'description' => 'al.description:json_string',
'address_1' => 'gll.address1:json_string',
'address_2' => 'gll.address2:json_string',
'zip_code' => 'gll.zip_code:json_string',
'city' => 'gll.city:json_string',
'state' => 'gll.state:json_string',
'country' => 'gll.country:json_string',
'sold_out' => 'h.sold_out:json_boolean',
'is_main' => 'v.is_main:json_boolean',
'class_name' => new DoctrineInstanceOfFilterMapping(
'name' => 'al.name:json_string',
'description' => 'al.description:json_string',
'address_1' => 'gll.address1:json_string',
'address_2' => 'gll.address2:json_string',
'zip_code' => 'gll.zip_code:json_string',
'city' => 'gll.city:json_string',
'state' => 'gll.state:json_string',
'country' => 'gll.country:json_string',
'sold_out' => 'h.sold_out:json_boolean',
'is_main' => 'v.is_main:json_boolean',
'time_slot_cost' => 'br.time_slot_cost',
'currency' => 'br.currency',
'capacity' => 'r.capacity',
'attribute' => new DoctrineFilterMapping
(
"(bra.value :operator ':value' or bra.id = ':value')"
),
'class_name' => new DoctrineInstanceOfFilterMapping(
"al",
[
SummitVenue::ClassName => SummitVenue::class,
SummitHotel::ClassName => SummitHotel::class,
SummitExternalLocation::ClassName => SummitExternalLocation::class,
SummitAirport::ClassName => SummitAirport::class,
SummitVenue::ClassName => SummitVenue::class,
SummitHotel::ClassName => SummitHotel::class,
SummitExternalLocation::ClassName => SummitExternalLocation::class,
SummitAirport::ClassName => SummitAirport::class,
SummitBookableVenueRoom::ClassName => SummitBookableVenueRoom::class,
]
)
];
@ -109,6 +120,9 @@ final class DoctrineSummitLocationRepository
->leftJoin(SummitExternalLocation::class, 'el', 'WITH', 'el.id = gll.id')
->leftJoin(SummitHotel::class, 'h', 'WITH', 'h.id = el.id')
->leftJoin(SummitAirport::class, 'ap', 'WITH', 'ap.id = el.id')
->leftJoin(SummitVenueRoom::class, 'r', 'WITH', 'r.id = al.id')
->leftJoin(SummitBookableVenueRoom::class, 'br', 'WITH', 'br.id = al.id')
->leftJoin('br.attributes', 'bra')
->leftJoin('al.summit', 's')
->where("s.id = :summit_id");
@ -133,6 +147,28 @@ final class DoctrineSummitLocationRepository
$query = $query->addOrderBy("al.id",'ASC');
}
if($filter->hasFilter("availability_day")){
// special case, we need to figure if each room has available slots
$res = $query->getQuery()->execute();
$rooms = [];
$availability_day = $filter->getUniqueFilter("availability_day")->getValue();
$day = new \DateTime("@$availability_day");
foreach ($res as $room){
if(!$room instanceof SummitBookableVenueRoom) continue;
if(count($room->getFreeSlots($day)) > 0)
$rooms[] = $room;
}
return new PagingResponse
(
count($rooms),
$paging_info->getPerPage(),
$paging_info->getCurrentPage(),
$paging_info->getLastPage(count($rooms)),
array_slice( $rooms, $paging_info->getOffset(), $paging_info->getPerPage() )
);
}
$query = $query
->setFirstResult($paging_info->getOffset())
->setMaxResults($paging_info->getPerPage());

View File

@ -105,6 +105,21 @@ final class DoctrineSummitRepository
->getOneOrNullResult();
}
/**
* @param string $slug
* @return Summit
*/
public function getBySlug(string $slug):Summit
{
return $this->getEntityManager()->createQueryBuilder()
->select("s")
->from(\models\summit\Summit::class, "s")
->where('s.slug = :slug')
->setParameter('slug', $slug)
->getQuery()
->getOneOrNullResult();
}
/**
* @return Summit
*/

View File

@ -0,0 +1,42 @@
<?php namespace App\Repositories\Summit;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use App\Models\Foundation\Summit\Repositories\ISummitRoomReservationRepository;
use App\Repositories\SilverStripeDoctrineRepository;
use models\summit\SummitRoomReservation;
/**
* Class DoctrineSummitRoomReservationRepository
* @package App\Repositories\Summit
*/
class DoctrineSummitRoomReservationRepository
extends SilverStripeDoctrineRepository
implements ISummitRoomReservationRepository
{
/**
* @return string
*/
protected function getBaseEntity()
{
return SummitRoomReservation::class;
}
/**
* @param string $payment_gateway_cart_id
* @return SummitRoomReservation
*/
public function getByPaymentGatewayCartId(string $payment_gateway_cart_id): SummitRoomReservation
{
return $this->findOneBy(["payment_gateway_cart_id" => trim($payment_gateway_cart_id)]);
}
}

View File

@ -18,47 +18,44 @@
*/
final class SummitScopes
{
const ReadSummitData = '%s/summits/read';
const ReadAllSummitData = '%s/summits/read/all';
const ReadNotifications = '%s/summits/read-notifications';
const WriteNotifications = '%s/summits/write-notifications';
const ReadSummitData = '%s/summits/read';
const ReadAllSummitData = '%s/summits/read/all';
const WriteSummitData = '%s/summits/write';
const WriteSpeakersData = '%s/speakers/write';
const ReadSpeakersData = '%s/speakers/read';
const WriteTrackTagGroupsData = '%s/track-tag-groups/write';
const WriteTrackQuestionTemplateData = '%s/track-question-templates/write';
const WriteMySpeakersData = '%s/speakers/write/me';
const ReadMySpeakersData = '%s/speakers/read/me';
// bookable rooms
const ReadBookableRoomsData = '%s/bookable-rooms/read';
const WriteMyBookableRoomsReservationData = '%s/bookable-rooms/my-reservations/write';
const ReadMyBookableRoomsReservationData = '%s/bookable-rooms/my-reservations/read';
const BookableRoomsReservation = '%s/bookable-rooms/reserve';
const WriteBookableRoomsData = '%s/bookable-rooms/write';
const PublishEventData = '%s/summits/publish-event';
const WriteEventData = '%s/summits/write-event';
const WriteVideoData = '%s/summits/write-videos';
const WritePresentationVideosData = '%s/summits/write-presentation-videos';
const WritePresentationLinksData = '%s/summits/write-presentation-links';
const WritePresentationSlidesData = '%s/summits/write-presentation-slides';
const WritePresentationMaterialsData = '%s/summits/write-presentation-materials';
const ReadNotifications = '%s/summits/read-notifications';
const WriteNotifications = '%s/summits/write-notifications';
const WriteSummitData = '%s/summits/write';
const WriteSpeakersData = '%s/speakers/write';
const ReadSpeakersData = '%s/speakers/read';
const WriteTrackTagGroupsData = '%s/track-tag-groups/write';
const WriteTrackQuestionTemplateData = '%s/track-question-templates/write';
const WriteMySpeakersData = '%s/speakers/write/me';
const ReadMySpeakersData = '%s/speakers/read/me';
const WriteAttendeesData = '%s/attendees/write';
const PublishEventData = '%s/summits/publish-event';
const WriteEventData = '%s/summits/write-event';
const WriteVideoData = '%s/summits/write-videos';
const WritePresentationVideosData = '%s/summits/write-presentation-videos';
const WritePresentationLinksData = '%s/summits/write-presentation-links';
const WritePresentationSlidesData = '%s/summits/write-presentation-slides';
const WritePresentationMaterialsData = '%s/summits/write-presentation-materials';
const WritePromoCodeData = '%s/promo-codes/write';
const WriteEventTypeData = '%s/event-types/write';
const WriteTracksData = '%s/tracks/write';
const WriteTrackGroupsData = '%s/track-groups/write';
const WriteLocationsData = '%s/locations/write';
const WriteRSVPTemplateData = '%s/rsvp-templates/write';
const WriteLocationBannersData = '%s/locations/banners/write';
const WriteSummitSpeakerAssistanceData = '%s/summit-speaker-assistance/write';
const WriteTicketTypeData = '%s/ticket-types/write';
const WritePresentationData = '%s/summits/write-presentation';
const WriteAttendeesData = '%s/attendees/write';
const WritePromoCodeData = '%s/promo-codes/write';
const WriteEventTypeData = '%s/event-types/write';
const WriteTracksData = '%s/tracks/write';
const WriteTrackGroupsData = '%s/track-groups/write';
const WriteLocationsData = '%s/locations/write';
const WriteRSVPTemplateData = '%s/rsvp-templates/write';
const WriteLocationBannersData = '%s/locations/banners/write';
const WriteSummitSpeakerAssistanceData = '%s/summit-speaker-assistance/write';
const WriteTicketTypeData = '%s/ticket-types/write';
const WritePresentationData = '%s/summits/write-presentation';
}

View File

@ -0,0 +1,52 @@
<?php namespace App\Services\Apis;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use models\summit\SummitRoomReservation;
use Illuminate\Http\Request as LaravelRequest;
/**
* Interface IPaymentGatewayAPI
* @package App\Services\Apis
*/
interface IPaymentGatewayAPI
{
/**
* @param SummitRoomReservation $reservation
* @return array
*/
public function generatePayment(SummitRoomReservation $reservation):array;
/**
* @param LaravelRequest $request
* @return array
*/
public function processCallback(LaravelRequest $request):array;
/**
* @param array $payload
* @return bool
*/
public function isSuccessFullPayment(array $payload):bool;
/**
* @param array $payload
* @return string
*/
public function getPaymentError(array $payload):string;
/**
* @param string $cart_id
* @param int $amount
* @throws \InvalidArgumentException
*/
public function refundPayment(string $cart_id, int $amount = 0): void;
}

View File

@ -0,0 +1,174 @@
<?php namespace App\Services\Apis\PaymentGateways;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use App\Services\Apis\IPaymentGatewayAPI;
use Illuminate\Http\Request as LaravelRequest;
use models\summit\SummitRoomReservation;
use Stripe\Charge;
use Stripe\Error\SignatureVerification;
use Stripe\Event;
use Stripe\Stripe;
use Stripe\PaymentIntent;
use Stripe\WebhookSignature;
/**
* Class StripesApi
* @package App\Services\Apis\PaymentGateways
*/
final class StripeApi implements IPaymentGatewayAPI
{
/**
* @var string
*/
private $api_key;
/**
* @var string
*/
private $webhook_secret;
/**
* StripeApi constructor.
* @param string $api_key
* @param string $webhook_secret
*/
public function __construct(string $api_key, string $webhook_secret)
{
$this->api_key = $api_key;
$this->webhook_secret = $webhook_secret;
}
/**
* @param SummitRoomReservation $reservation
* @return array
*/
public function generatePayment(SummitRoomReservation $reservation):array
{
if(empty($this->api_key))
throw new \InvalidArgumentException();
Stripe::setApiKey($this->api_key);
$intent = PaymentIntent::create([
'amount' => $reservation->getAmount(),
'currency' => $reservation->getCurrency(),
'receipt_email' => $reservation->getOwner()->getEmail()
]);
return [
'client_token' => $intent->client_secret,
'cart_id' => $intent->id,
];
}
/**
* @param LaravelRequest $request
* @return array
* @throws SignatureVerification
* @throws \Exception
* @throws \InvalidArgumentException
*/
public function processCallback(LaravelRequest $request): array
{
try {
WebhookSignature::verifyHeader(
$request->getContent(),
$request->header('Stripe-Signature'),
$this->webhook_secret
);
$event = Event::constructFrom(json_decode($request->getContent(), true));
if(!in_array($event->type, ["payment_intent.succeeded", "payment_intent.payment_failed"]))
throw new \InvalidArgumentException();
$intent = $event->data->object;
if ($event->type == "payment_intent.succeeded") {
return [
"event_type" => $event->type,
"cart_id" => $intent->id,
];
}
if ($event->type == "payment_intent.payment_failed") {
$intent = $event->data->object;
return [
"event_type" => $event->type,
"cart_id" => $intent->id,
"error" => [
"last_payment_error" => $intent->last_payment_error,
"message" => $intent->last_payment_error->message
]
];
}
}
catch(\UnexpectedValueException $e) {
// Invalid payload
throw $e;
}
catch(SignatureVerification $e) {
// Invalid signature
throw $e;
}
catch (\Exception $e){
throw $e;
}
}
/**
* @param array $payload
* @return bool
*/
public function isSuccessFullPayment(array $payload): bool
{
if(isset($payload['type']) && $payload['type'] == "payment_intent.succeeded") return true;
return false;
}
/**
* @param array $payload
* @return string
*/
public function getPaymentError(array $payload): string
{
if(isset($payload['type']) && $payload['type'] == "payment_intent.payment_failed"){
$error_message = $payload['error']["message"];
return $error_message;
}
return null;
}
/**
* @param string $cart_id
* @param int $amount
* @throws \InvalidArgumentException
*/
public function refundPayment(string $cart_id, int $amount = 0): void
{
if(empty($this->api_key))
throw new \InvalidArgumentException();
Stripe::setApiKey($this->api_key);
$intent = PaymentIntent::retrieve($cart_id);
if(is_null($intent))
throw new \InvalidArgumentException();
$charge = $intent->charges->data[0];
if(!$charge instanceof Charge)
throw new \InvalidArgumentException();
$params = [];
if($amount > 0 ){
$params['amount'] = $amount;
}
$charge->refund($params);
}
}

View File

@ -12,7 +12,9 @@
* limitations under the License.
**/
use App\Models\Foundation\Summit\Locations\Banners\SummitLocationBanner;
use models\main\Member;
use models\summit\SummitLocationImage;
use models\summit\SummitRoomReservation;
use models\summit\SummitVenueRoom;
use models\exceptions\EntityNotFoundException;
use models\exceptions\ValidationException;
@ -213,4 +215,30 @@ interface ILocationService
*/
public function deleteLocationImage(Summit $summit, $location_id, $image_id);
/**
* @param Summit $summit
* @param int $room_id
* @param array $payload
* @return SummitRoomReservation
* @throws EntityNotFoundException
* @throws ValidationException
*/
public function addBookableRoomReservation(Summit $summit, int $room_id, array $payload):SummitRoomReservation;
/**
* @param array $data
* @throws EntityNotFoundException
* @throws ValidationException
*/
public function processBookableRoomPayment(array $payload):void;
/**
* @param Summit $sumit
* @param Member $owner
* @param int $reservation_id
* @throws EntityNotFoundException
* @throws ValidationException
*/
public function cancelReservation(Summit $sumit, Member $owner, int $reservation_id):void;
}

View File

@ -28,11 +28,14 @@ use App\Http\Utils\IFileUploader;
use App\Models\Foundation\Summit\Factories\SummitLocationBannerFactory;
use App\Models\Foundation\Summit\Factories\SummitLocationFactory;
use App\Models\Foundation\Summit\Factories\SummitLocationImageFactory;
use App\Models\Foundation\Summit\Factories\SummitRoomReservationFactory;
use App\Models\Foundation\Summit\Factories\SummitVenueFloorFactory;
use App\Models\Foundation\Summit\Locations\Banners\SummitLocationBanner;
use App\Models\Foundation\Summit\Repositories\ISummitLocationRepository;
use App\Models\Foundation\Summit\Repositories\ISummitRoomReservationRepository;
use App\Services\Apis\GeoCodingApiException;
use App\Services\Apis\IGeoCodingAPI;
use App\Services\Apis\IPaymentGatewayAPI;
use App\Services\Model\Strategies\GeoLocation\GeoLocationStrategyFactory;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Event;
@ -40,10 +43,14 @@ use Illuminate\Support\Facades\Log;
use libs\utils\ITransactionService;
use models\exceptions\EntityNotFoundException;
use models\exceptions\ValidationException;
use models\main\IMemberRepository;
use models\main\Member;
use models\summit\Summit;
use models\summit\SummitAbstractLocation;
use models\summit\SummitBookableVenueRoom;
use models\summit\SummitGeoLocatedLocation;
use models\summit\SummitLocationImage;
use models\summit\SummitRoomReservation;
use models\summit\SummitVenue;
use models\summit\SummitVenueFloor;
use models\summit\SummitVenueRoom;
@ -70,28 +77,53 @@ final class SummitLocationService
*/
private $folder_service;
/**
* @var IPaymentGatewayAPI
*/
private $payment_gateway;
/**
* @var IMemberRepository
*/
private $member_repository;
/**
* @var ISummitRoomReservationRepository
*/
private $reservation_repository;
/**
* SummitLocationService constructor.
* @param ISummitLocationRepository $location_repository
* @param IMemberRepository $member_repository
* @param ISummitRoomReservationRepository $reservation_repository
* @param IGeoCodingAPI $geo_coding_api
* @param IFolderService $folder_service
* @param IFileUploader $file_uploader
* @param IPaymentGatewayAPI $payment_gateway
* @param ITransactionService $tx_service
*/
public function __construct
(
ISummitLocationRepository $location_repository,
IMemberRepository $member_repository,
ISummitRoomReservationRepository $reservation_repository,
IGeoCodingAPI $geo_coding_api,
IFolderService $folder_service,
IFileUploader $file_uploader,
IPaymentGatewayAPI $payment_gateway,
ITransactionService $tx_service
)
{
parent::__construct($tx_service);
$this->location_repository = $location_repository;
$this->geo_coding_api = $geo_coding_api;
$this->file_uploader = $file_uploader;
$this->folder_service = $folder_service;
$this->location_repository = $location_repository;
$this->member_repository = $member_repository;
$this->reservation_repository = $reservation_repository;
$this->geo_coding_api = $geo_coding_api;
$this->file_uploader = $file_uploader;
$this->folder_service = $folder_service;
$this->payment_gateway = $payment_gateway;
}
/**
@ -1646,4 +1678,107 @@ final class SummitLocationService
});
}
/**
* @param Summit $summit
* @param int $room_id
* @param array $payload
* @return SummitRoomReservation
* @throws EntityNotFoundException
* @throws ValidationException
*/
public function addBookableRoomReservation(Summit $summit, int $room_id, array $payload): SummitRoomReservation
{
return $this->tx_service->transaction(function () use ($summit, $room_id, $payload) {
$room = $summit->getLocation($room_id);
if (is_null($room)) {
throw new EntityNotFoundException();
}
if(!$room instanceof SummitBookableVenueRoom){
throw new EntityNotFoundException();
}
$owner_id = $payload["owner_id"];
$owner = $this->member_repository->getById($owner_id);
if (is_null($owner)) {
throw new EntityNotFoundException();
}
$payload['owner'] = $owner;
$reservation = SummitRoomReservationFactory::build($summit, $payload);
$room->addReservation($reservation);
$result = $this->payment_gateway->generatePayment
(
$reservation
);
$reservation->setPaymentGatewayCartId($result['cart_id']);
$reservation->setPaymentGatewayClientToken($result['client_token']);
return $reservation;
});
}
/**
* @param array $payload
* @throws \Exception
*/
public function processBookableRoomPayment(array $payload): void
{
$this->tx_service->transaction(function () use ($payload) {
$reservation = $this->reservation_repository->getByPaymentGatewayCartId($payload['cart_id']);
if(is_null($reservation)){
throw new EntityNotFoundException();
}
if($this->payment_gateway->isSuccessFullPayment($payload)) {
$reservation->setPayed();
return;
}
$reservation->setPaymentError($this->payment_gateway->getPaymentError($payload));
});
}
/**
* @param Summit $summit
* @param Member $owner
* @param int $reservation_id
* @throws EntityNotFoundException
* @throws ValidationException
*/
public function cancelReservation(Summit $summit, Member $owner, int $reservation_id): void
{
$this->tx_service->transaction(function () use ($summit, $owner, $reservation_id) {
$reservation = $owner->getReservationById($reservation_id);
if(is_null($reservation)){
throw new EntityNotFoundException();
}
if($reservation->getRoom()->getSummitId() != $summit->getId()){
throw new EntityNotFoundException();
}
if($reservation->getStatus() == SummitRoomReservation::ReservedStatus)
throw new ValidationException("can not request a refund on a reserved booking!");
if($reservation->getStatus() == SummitRoomReservation::RequestedRefundStatus ||
$reservation->getStatus() == SummitRoomReservation::RefundedStatus
)
throw new ValidationException("can not request a refund on an already refunded booking!");
$reservation->requestRefund();
});
}
}

View File

@ -13,10 +13,11 @@
**/
use App\Permissions\IPermissionsManager;
use App\Permissions\PermissionsManager;
use App\Repositories\DoctrineRepository;
use App\Services\Apis\CalendarSync\ICalendarSyncRemoteFacadeFactory;
use App\Services\Apis\GoogleGeoCodingAPI;
use App\Services\Apis\IGeoCodingAPI;
use App\Services\Apis\IPaymentGatewayAPI;
use App\Services\Apis\PaymentGateways\StripeApi;
use App\Services\Model\AttendeeService;
use App\Services\Model\FolderService;
use App\Services\Model\IAttendeeService;
@ -125,6 +126,13 @@ final class ServicesProvider extends ServiceProvider
return $api;
});
App::singleton(IPaymentGatewayAPI::class, function(){
return new StripeApi(
Config::get("stripe.private_key", null),
Config::get("stripe.endpoint_secret", null)
);
});
App::singleton
(
IAttendeeService::class,

View File

@ -34,6 +34,7 @@
"s-ichikawa/laravel-sendgrid-driver": "^2.0",
"smarcet/caldavclient": "1.1.6",
"smarcet/outlook-rest-client": "dev-master",
"stripe/stripe-php": "^6.37",
"symfony/yaml": "4.2.2"
},
"require-dev": {

58
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "d03caf628ec9076f0ab50d8cdab0685d",
"content-hash": "e8960c590d5b3ce6a88c1a2544a3751f",
"packages": [
{
"name": "cocur/slugify",
@ -3792,6 +3792,62 @@
],
"time": "2017-08-05T01:36:59+00:00"
},
{
"name": "stripe/stripe-php",
"version": "v6.37.0",
"source": {
"type": "git",
"url": "https://github.com/stripe/stripe-php.git",
"reference": "6915bed0b988ca837f3e15a1f31517a6172a663a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/6915bed0b988ca837f3e15a1f31517a6172a663a",
"reference": "6915bed0b988ca837f3e15a1f31517a6172a663a",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"php": ">=5.4.0"
},
"require-dev": {
"php-coveralls/php-coveralls": "1.*",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~2.0",
"symfony/process": "~2.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"psr-4": {
"Stripe\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Stripe and contributors",
"homepage": "https://github.com/stripe/stripe-php/contributors"
}
],
"description": "Stripe PHP Library",
"homepage": "https://stripe.com/",
"keywords": [
"api",
"payment processing",
"stripe"
],
"time": "2019-05-23T23:59:23+00:00"
},
{
"name": "swiftmailer/swiftmailer",
"version": "v6.2.0",

20
config/stripe.php Normal file
View File

@ -0,0 +1,20 @@
<?php
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
return [
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
"private_key" => env('STRIPE_PRIVATE_KEY', ''),
// You can find your endpoint's secret in your webhook settings
"endpoint_secret" => env('STRIPE_ENDPOINT_SECRET', ''),
];

View File

@ -0,0 +1,54 @@
<?php namespace Database\Migrations\Model;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Doctrine\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema as Schema;
use LaravelDoctrine\Migrations\Schema\Table;
use LaravelDoctrine\Migrations\Schema\Builder;
/**
* Class Version20190529015655
* @package Database\Migrations\Model
*/
class Version20190529015655 extends AbstractMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
$builder = new Builder($schema);
if(!$builder->hasColumn("Summit","MeetingRoomBookingStartTime")) {
$builder->table('Summit', function (Table $table) {
$table->time("MeetingRoomBookingStartTime")->setNotnull(false);
$table->time("MeetingRoomBookingEndTime")->setNotnull(false);
$table->integer("MeetingRoomBookingSlotLength")->setNotnull(true)->setDefault(0);
$table->integer("MeetingRoomBookingMaxAllowed")->setNotnull(true)->setDefault(0);
});
}
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
$builder = new Builder($schema);
$builder->table('Summit', function (Table $table) {
$table->dropColumn("MeetingRoomBookingStartTime");
$table->dropColumn("MeetingRoomBookingEndTime");
$table->dropColumn("MeetingRoomBookingSlotLength");
$table->dropColumn("MeetingRoomBookingMaxAllowed");
});
}
}

View File

@ -0,0 +1,57 @@
<?php namespace Database\Migrations\Model;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Doctrine\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema as Schema;
use LaravelDoctrine\Migrations\Schema\Table;
use LaravelDoctrine\Migrations\Schema\Builder;
/**
* Class Version20190529142913
* @package Database\Migrations\Model
*/
class Version20190529142913 extends AbstractMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
if(!$schema->hasTable("SummitBookableVenueRoom")) {
$sql = <<<SQL
ALTER TABLE SummitAbstractLocation MODIFY ClassName enum('SummitAbstractLocation', 'SummitGeoLocatedLocation', 'SummitExternalLocation', 'SummitAirport', 'SummitHotel', 'SummitVenue', 'SummitVenueRoom', 'SummitBookableVenueRoom') DEFAULT 'SummitAbstractLocation';
SQL;
$builder = new Builder($schema);
$this->addSql($sql);
$builder->create('SummitBookableVenueRoom', function (Table $table) {
$table->integer("ID", true, false);
$table->primary("ID");
$table->string("Currency",3);
$table->decimal("TimeSlotCost", 9, 2)->setDefault('0.00');
});
}
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
$sql = <<<SQL
ALTER TABLE SummitAbstractLocation MODIFY ClassName enum('SummitAbstractLocation', 'SummitGeoLocatedLocation', 'SummitExternalLocation', 'SummitAirport', 'SummitHotel', 'SummitVenue', 'SummitVenueRoom') DEFAULT 'SummitAbstractLocation';
SQL;
$builder = new Builder($schema);
$this->addSql($sql);
$builder->drop('SummitBookableVenueRoom');
}
}

View File

@ -0,0 +1,64 @@
<?php namespace Database\Migrations\Model;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use Doctrine\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema as Schema;
use LaravelDoctrine\Migrations\Schema\Table;
use LaravelDoctrine\Migrations\Schema\Builder;
/**
* Class Version20190529142927
* @package Database\Migrations\Model
*/
class Version20190529142927 extends AbstractMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
$builder = new Builder($schema);
if(!$schema->hasTable("SummitRoomReservation")) {
$builder->create('SummitRoomReservation', function (Table $table) {
$table->integer("ID", true, false);
$table->primary("ID");
$table->string('ClassName')->setDefault("SummitRoomReservation");
$table->index("ClassName", "ClassName");
$table->timestamp('Created');
$table->timestamp('LastEdited');
$table->timestamp('ApprovedPaymentDate')->setNotnull(false);
$table->timestamp('StartDateTime')->setNotnull(false);
$table->timestamp('EndDateTime')->setNotnull(false);
$table->string("Status");
$table->string("LastError");
$table->string("PaymentGatewayCartId", 512);
$table->decimal("Amount", 9, 2)->setDefault('0.00');
$table->string("Currency", 3);
$table->index("PaymentGatewayCartId", "PaymentGatewayCartId");
$table->integer("OwnerID", false, false)->setNotnull(false);
$table->index("OwnerID", "OwnerID");
//$table->foreign("Member", "OwnerID", "ID");
$table->integer("RoomID", false, false)->setNotnull(false);
$table->index("RoomID", "RoomID");
//$table->foreign("SummitBookableVenueRoom", "RoomID", "ID");
});
}
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
(new Builder($schema))->drop('SummitRoomReservation');
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace Database\Migrations\Model;
use Doctrine\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema as Schema;
use LaravelDoctrine\Migrations\Schema\Table;
use LaravelDoctrine\Migrations\Schema\Builder;
class Version20190530205326 extends AbstractMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
$builder = new Builder($schema);
if(!$schema->hasTable("SummitBookableVenueRoomAttributeType")) {
$builder->create('SummitBookableVenueRoomAttributeType', function (Table $table) {
$table->integer("ID", true, false);
$table->primary("ID");
$table->string('ClassName')->setDefault("SummitBookableVenueRoomAttributeType");
$table->index("ClassName", "ClassName");
$table->timestamp('Created');
$table->timestamp('LastEdited');
$table->string("Type", 255);
$table->index("Type", "Type");
$table->integer("SummitID", false, false)->setNotnull(false);
$table->index("SummitID", "SummitID");
$table->unique(["SummitID", "Type"], "SummitID_Type");
});
}
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
(new Builder($schema))->drop('BookableSummitVenueRoomAttributeType');
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Database\Migrations\Model;
use Doctrine\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema as Schema;
use LaravelDoctrine\Migrations\Schema\Table;
use LaravelDoctrine\Migrations\Schema\Builder;
class Version20190530205344 extends AbstractMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
$builder = new Builder($schema);
if(!$schema->hasTable("SummitBookableVenueRoomAttributeValue")) {
$builder->create('SummitBookableVenueRoomAttributeValue', function (Table $table) {
$table->integer("ID", true, false);
$table->primary("ID");
$table->string('ClassName')->setDefault("SummitBookableVenueRoomAttributeValue");
$table->index("ClassName", "ClassName");
$table->timestamp('Created');
$table->timestamp('LastEdited');
$table->string("Value", 255);
$table->index("Value", "Value");
$table->integer("TypeID", false, false)->setNotnull(false);
$table->index("TypeID", "TypeID");
$table->unique(["TypeID", "Value"], "TypeID_Value");
});
}
if(!$schema->hasTable("SummitBookableVenueRoom_Attributes")) {
$builder->create('SummitBookableVenueRoom_Attributes', function (Table $table) {
$table->integer("ID", true, false);
$table->primary("ID");
$table->integer("SummitBookableVenueRoomID", false, false)->setNotnull(false)->setDefault(0);
$table->index("SummitBookableVenueRoomID", "SummitBookableVenueRoomID");
$table->integer("SummitBookableVenueRoomAttributeValueID", false, false)->setNotnull(false)->setDefault(0);
$table->index("SummitBookableVenueRoomAttributeValueID", "SummitBookableVenueRoomAttributeValueID");
$table->unique(["SummitBookableVenueRoomID", "SummitBookableVenueRoomAttributeValueID"], "RoomID_ValueID");
});
}
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
(new Builder($schema))->drop('SummitBookableVenueRoomAttributeValue');
(new Builder($schema))->drop('SummitBookableVenueRoom_Attributes');
}
}

View File

@ -93,6 +93,15 @@ class ApiEndpointsSeeder extends Seeder
sprintf(SummitScopes::ReadAllSummitData, $current_realm)
],
],
[
'name' => 'get-summits-all-by-id-slug',
'route' => '/api/v1/summits/all/{id}',
'http_method' => 'GET',
'scopes' => [
sprintf(SummitScopes::ReadAllSummitData, $current_realm),
sprintf(SummitScopes::ReadBookableRoomsData, $current_realm),
],
],
[
'name' => 'get-summit-cached',
'route' => '/api/v1/summits/{id}',
@ -1050,6 +1059,85 @@ class ApiEndpointsSeeder extends Seeder
sprintf(SummitScopes::WriteLocationsData, $current_realm)
],
],
// bookable rooms
[
'name' => 'get-bookable-venue-rooms',
'route' => '/api/v1/summits/{id}/locations/bookable-rooms',
'http_method' => 'GET',
'scopes' => [
sprintf(SummitScopes::ReadBookableRoomsData, $current_realm),
sprintf(SummitScopes::ReadAllSummitData, $current_realm)
],
],
[
'name' => 'get-bookable-venue-room',
'route' => '/api/v1/summits/{id}/locations/bookable-rooms/{room_id}',
'http_method' => 'GET',
'scopes' => [
sprintf(SummitScopes::ReadBookableRoomsData, $current_realm),
sprintf(SummitScopes::ReadAllSummitData, $current_realm)
],
],
[
'name' => 'get-bookable-venue-room-availability',
'route' => '/api/v1/summits/{id}/locations/bookable-rooms/{room_id}/availability/{day}',
'http_method' => 'GET',
'scopes' => [
sprintf(SummitScopes::ReadBookableRoomsData, $current_realm),
sprintf(SummitScopes::ReadAllSummitData, $current_realm)
],
],
[
'name' => 'get-my-bookable-venue-room-reservations',
'route' => '/api/v1/summits/{id}/locations/bookable-rooms/all/reservations/me',
'http_method' => 'GET',
'scopes' => [
sprintf(SummitScopes::ReadMyBookableRoomsReservationData, $current_realm),
],
],
[
'name' => 'cancel-my-bookable-venue-room-reservation',
'route' => '/api/v1/summits/{id}/locations/bookable-rooms/all/reservations/{reservation_id}',
'http_method' => 'DELETE',
'scopes' => [
sprintf(SummitScopes::WriteMyBookableRoomsReservationData, $current_realm),
],
],
[
'name' => 'create-bookable-venue-room-reservation',
'route' => '/api/v1/summits/{id}/locations/bookable-rooms/{room_id}/reservations',
'http_method' => 'POST',
'scopes' => [
sprintf(SummitScopes::WriteMyBookableRoomsReservationData, $current_realm),
],
],
[
'name' => 'add-bookable-venue-room',
'route' => '/api/v1/summits/{id}/locations/venues/{venue_id}/bookable-rooms',
'http_method' => 'POST',
'scopes' => [
sprintf(SummitScopes::WriteSummitData, $current_realm),
sprintf(SummitScopes::WriteBookableRoomsData, $current_realm),
],
],
[
'name' => 'update-bookable-venue-room',
'route' => '/api/v1/summits/{id}/locations/venues/{venue_id}/bookable-rooms/{room_id}',
'http_method' => 'PUT',
'scopes' => [
sprintf(SummitScopes::WriteSummitData, $current_realm),
sprintf(SummitScopes::WriteBookableRoomsData, $current_realm),
],
],
[
'name' => 'delete-bookable-venue-room',
'route' => '/api/v1/summits/{id}/locations/venues/{venue_id}/bookable-rooms/{room_id}',
'http_method' => 'DELETE',
'scopes' => [
sprintf(SummitScopes::WriteSummitData, $current_realm),
sprintf(SummitScopes::WriteBookableRoomsData, $current_realm),
],
],
// floor rooms
[
'name' => 'get-venue-floor-room',

View File

@ -186,6 +186,16 @@ final class ApiScopesSeeder extends Seeder
'short_description' => 'Write Summit Presentation Materials Data',
'description' => 'Grants write access for Summit Materials Links Data',
],
[
'name' => sprintf(SummitScopes::ReadMyBookableRoomsReservationData, $current_realm),
'short_description' => 'Read my bookable rooms reservations',
'description' => 'Read my bookable rooms reservations',
],
[
'name' => sprintf(SummitScopes::WriteMyBookableRoomsReservationData, $current_realm),
'short_description' => 'Write my bookable rooms reservations',
'description' => 'Write my bookable rooms reservations',
],
];
foreach ($scopes as $scope_info) {

53
tests/MeetingRoomTest.php Normal file
View File

@ -0,0 +1,53 @@
<?php namespace Tests;
/**
* Copyright 2019 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
use LaravelDoctrine\ORM\Facades\EntityManager;
use Illuminate\Support\Facades\Redis;
use models\summit\Summit;
/**
* Class MeetingRoomTest
* @package Tests
*/
class MeetingRoomTest extends TestCase
{
use CreatesApplication;
private $redis;
/**
* The base URL to use while testing the application.
*
* @var string
*/
protected $baseUrl = 'http://localhost';
public function setUp()
{
parent::setUp(); // Don't forget this!
$this->redis = Redis::connection();
$this->redis->flushall();
$this->createApplication();
}
public function testMeetingRoomsAvalableSlots(){
$repository = EntityManager::getRepository(Summit::class);
$summit = $repository->getBySlug('shanghai-2019');
$rooms = $summit->getBookableRooms();
$this->assertTrue(count($rooms) > 0);
$room = $rooms[0];
$room->getAvailableSlots(new \DateTime("2019-11-05"));
}
}

View File

@ -1350,4 +1350,181 @@ final class OAuth2SummitLocationsApiTest extends ProtectedApiTest
$content = $response->getContent();
$this->assertResponseStatus(204);
}
// bookable rooms tests
public function testSummitGetBookableRoomsORFilter($summit_id = 27)
{
$params = [
'id' => $summit_id,
'page' => 1,
'per_page' => 10,
'order' => '-id',
'expand' => 'venue,attribute_type',
'filter' => [
"attribute==Ocean,attribute==Microwave",
"availability_day==1572912000",
],
];
$headers =
[
"HTTP_Authorization" => " Bearer " . $this->access_token,
"CONTENT_TYPE" => "application/json"
];
$response = $this->action
(
"GET",
"OAuth2SummitLocationsApiController@getBookableVenueRooms",
$params,
[],
[],
[],
$headers
);
$content = $response->getContent();
$this->assertResponseStatus(200);
$rooms = json_decode($content);
$this->assertTrue(!is_null($rooms));
}
public function testSummitGetBookableRoomAvailability($summit_id = 27, $room_id = 483, $day = 1572912000)
{
$params = [
'id' => $summit_id,
'room_id' => $room_id,
'day' => $day,
];
$headers =
[
"HTTP_Authorization" => " Bearer " . $this->access_token,
"CONTENT_TYPE" => "application/json"
];
$response = $this->action
(
"GET",
"OAuth2SummitLocationsApiController@getBookableVenueRoomAvailability",
$params,
[],
[],
[],
$headers
);
$content = $response->getContent();
$this->assertResponseStatus(200);
$slots = json_decode($content);
$this->assertTrue(!is_null($slots));
}
/**
* @param int $summit_id
* @param int $room_id
* @param int $start_date
* @return mixed
*/
public function testBookableRoomReservation($summit_id =27, $room_id = 483, $start_date = 1572883200, $end_date = 1572886800){
$params = [
'id' => $summit_id,
'room_id' => $room_id,
];
$data = [
'currency' => 'USD',
'amount' => 325,
'start_datetime' => $start_date,
'end_datetime' => $end_date,
];
$headers = [
"HTTP_Authorization" => " Bearer " . $this->access_token,
"CONTENT_TYPE" => "application/json"
];
$response = $this->action(
"POST",
"OAuth2SummitLocationsApiController@createBookableVenueRoomReservation",
$params,
[],
[],
[],
$headers,
json_encode($data)
);
$content = $response->getContent();
$this->assertResponseStatus(201);
$reservation = json_decode($content);
$this->assertTrue(!is_null($reservation));
return $reservation;
}
public function testGetMyReservations($summit_id = 27)
{
$params = [
'id' => $summit_id,
'expand' => 'room'
];
$headers =
[
"HTTP_Authorization" => " Bearer " . $this->access_token,
"CONTENT_TYPE" => "application/json"
];
$response = $this->action
(
"GET",
"OAuth2SummitLocationsApiController@getMyBookableVenueRoomReservations",
$params,
[],
[],
[],
$headers
);
$content = $response->getContent();
$this->assertResponseStatus(200);
$reservations = json_decode($content);
$this->assertTrue(!is_null($reservations));
}
public function testCancelMyReservations($summit_id = 27, $reservation_id = 4)
{
$params = [
'id' => $summit_id,
'reservation_id' => $reservation_id
];
$headers =
[
"HTTP_Authorization" => " Bearer " . $this->access_token,
"CONTENT_TYPE" => "application/json"
];
$response = $this->action
(
"DELETE",
"OAuth2SummitLocationsApiController@cancelMyBookableVenueRoomReservation",
$params,
[],
[],
[],
$headers
);
$content = $response->getContent();
$this->assertResponseStatus(200);
$reservations = json_decode($content);
$this->assertTrue(!is_null($reservations));
}
}

View File

@ -67,6 +67,8 @@ class AccessTokenServiceStub implements IAccessTokenService
sprintf(OrganizationScopes::WriteOrganizationData, $url),
sprintf(OrganizationScopes::ReadOrganizationData, $url),
sprintf(SummitScopes::WritePresentationMaterialsData, $url),
sprintf(SummitScopes::ReadMyBookableRoomsReservationData, $url),
sprintf(SummitScopes::WriteMyBookableRoomsReservationData, $url),
);
return AccessToken::createFromParams('123456789', implode(' ', $scopes), '1', $realm, '1','11624', 3600, 'WEB_APPLICATION', '', '');
@ -119,6 +121,8 @@ class AccessTokenServiceStub2 implements IAccessTokenService
sprintf(OrganizationScopes::WriteOrganizationData, $url),
sprintf(OrganizationScopes::ReadOrganizationData, $url),
sprintf(SummitScopes::WritePresentationMaterialsData, $url),
sprintf(SummitScopes::ReadMyBookableRoomsReservationData, $url),
sprintf(SummitScopes::WriteMyBookableRoomsReservationData, $url),
);
return AccessToken::createFromParams('123456789', implode(' ', $scopes), '1', $realm, null,null, 3600, 'SERVICE', '', '');