improve customer timetable

This commit is contained in:
Roland Schneider 2021-09-19 21:01:07 +02:00
parent fe2f0766e3
commit 678b005c1c
24 changed files with 314 additions and 246 deletions

View File

@ -10,8 +10,8 @@ use common\models\EventRegistration;
use common\models\Ticket; use common\models\Ticket;
use customerapi\models\available\EventInterval; use customerapi\models\available\EventInterval;
use customerapi\models\registrations\EventRegistrationAvailable; use customerapi\models\registrations\EventRegistrationAvailable;
use customerapi\models\details\EventRegistrationView;
use Exception; use Exception;
use Throwable;
use Yii; use Yii;
use yii\base\BaseObject; use yii\base\BaseObject;
use yii\db\ActiveRecord; use yii\db\ActiveRecord;
@ -37,6 +37,7 @@ class EventRegistrationManager extends BaseObject
const UNKNOWN_ERROR = 7; const UNKNOWN_ERROR = 7;
const MAX_SEAT_COUNT_EXCEEDED = 8; const MAX_SEAT_COUNT_EXCEEDED = 8;
const EVENT_UNAVAILABLE = 9; const EVENT_UNAVAILABLE = 9;
const ALREADY_REGISTERED = 10;
public static $STATES = [ public static $STATES = [
self::CARD_NOT_FOUND => 'CARD_NOT_FOUND', self::CARD_NOT_FOUND => 'CARD_NOT_FOUND',
@ -48,8 +49,7 @@ class EventRegistrationManager extends BaseObject
self::UNKNOWN_ERROR => 'UNKNOWN_ERROR', self::UNKNOWN_ERROR => 'UNKNOWN_ERROR',
self::MAX_SEAT_COUNT_EXCEEDED => 'MAX_SEAT_COUNT_EXCEEDED', self::MAX_SEAT_COUNT_EXCEEDED => 'MAX_SEAT_COUNT_EXCEEDED',
self::EVENT_UNAVAILABLE => 'EVENT_UNAVAILABLE', self::EVENT_UNAVAILABLE => 'EVENT_UNAVAILABLE',
self::ALREADY_REGISTERED => 'ALREADY_REGISTERED',
]; ];
/** /**
@ -77,7 +77,7 @@ class EventRegistrationManager extends BaseObject
$activeTickets = $card->getActiveTickets(); $activeTickets = $card->getActiveTickets();
if (count($activeTickets) === 0) { if (count($activeTickets) === 0) {
throw new NotFoundHttpException('Ticket not found', self::TICKET_NOT_FOUND); throw new NotFoundHttpException('Ticket not found1', self::TICKET_NOT_FOUND);
} }
/** @var Event $event */ /** @var Event $event */
@ -101,24 +101,23 @@ class EventRegistrationManager extends BaseObject
} }
//detect if customer is already registered to event //detect if customer is already registered to event
/** @var EventRegistration[] $registrations */
$registrations = $event->getActiveEventRegistrations()->all(); $registrations = $event->getActiveEventRegistrations()->all();
foreach ($registrations as $registration ){ foreach ($registrations as $registration ){
if ($registration->customer_id == $card->customer->id_customer){ if ($registration->id_customer == $card->customer->id_customer){
throw new BadRequestHttpException("Already registered"); throw new BadRequestHttpException("Already registered", self::ALREADY_REGISTERED);
} }
} }
$selectedTicket = $eventType->findTicketAllowingEventType($activeTickets); $selectedTicket = $eventType->findTicketAllowingEventType($activeTickets);
if (!isset($selectedTicket)) { if (!isset($selectedTicket)) {
throw new NotFoundHttpException('Ticket not found', self::TICKET_INSUFFICIENT); throw new NotFoundHttpException('Ticket not found2', self::TICKET_INSUFFICIENT);
} }
if ($selectedTicket->hasOpenReservationCount()) { $selectedTicket->consumeReservationCount(1);
$selectedTicket->consumeReservationCount(1);
}
$selectedTicket->save(); $selectedTicket->save();
$registration = new EventRegistration(); $registration = new EventRegistration();

View File

@ -83,4 +83,56 @@ class EventRegistration extends \yii\db\ActiveRecord
public function getCustomerClass(){ public function getCustomerClass(){
return Customer::class; return Customer::class;
} }
/**
* @param EventRegistration $eventRegistration
*/
public static function isActive($eventRegistration){
if ( !isset($eventRegistration ) ){
return false;
}
if ( isset($eventRegistration->canceled_at ) ){
return false;
}
if ( isset($eventRegistration->deleted_at ) ){
return false;
}
return true;
}
/**
* @param EventRegistration $eventRegistration
*/
public static function isForCustomer($eventRegistration,$idCustomer){
if ( !isset($eventRegistration ) ){
return false;
}
if ( !isset($eventRegistration->id_customer ) ){
return false;
}
return $eventRegistration->id_customer == $idCustomer;
}
/**
* @param EventRegistration[] $eventRegistrations
*/
public static function filterActive($eventRegistrations){
return array_filter($eventRegistrations, EventRegistration::class.'::isActive' );
}
/**
* @param EventRegistration[] $eventRegistrations
*/
public static function filterForCustomer($eventRegistrations,$idCustomer){
$result = [];
foreach ($eventRegistrations as $eventRegistration){
if ( EventRegistration::isForCustomer($eventRegistration,$idCustomer)){
$result[] = $eventRegistration;
}
}
return $result;
}
} }

View File

@ -457,7 +457,7 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
public function consumeReservationCount($count){ public function consumeReservationCount($count){
$newReservationCount = $this->reservation_count + $count; $newReservationCount = $this->reservation_count + $count;
if ( $newReservationCount > $this->max_reservation_count ){ if ( $newReservationCount > $this->max_reservation_count ){
throw new HttpException(406, 'Max ticket seat count exceeded',EventRegistrationManager::MAX_SEAT_COUNT_EXCEEDED); throw new HttpException(400, 'Max ticket seat count exceeded',EventRegistrationManager::MAX_SEAT_COUNT_EXCEEDED);
} }
$this->reservation_count = $newReservationCount; $this->reservation_count = $newReservationCount;
} }

View File

@ -1,6 +1,6 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from "@angular/common/http"; import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from "@angular/common/http";
import { Observable, throwError } from "rxjs"; import {EMPTY, Observable, throwError} from "rxjs";
import { catchError } from "rxjs/operators"; import { catchError } from "rxjs/operators";
import { AuthenticationService} from "../services/authentication.service"; import { AuthenticationService} from "../services/authentication.service";
import {NavigationService} from "../services/navigation.service"; import {NavigationService} from "../services/navigation.service";
@ -26,10 +26,11 @@ All other errors are RE-THROWN to be caught by the calling service so an alert c
this.authenticationService.logout(); this.authenticationService.logout();
this.navigationService.navigateToLogin(); this.navigationService.navigateToLogin();
// location.reload(true); // location.reload(true);
return EMPTY;
} }
// const error = err.error.message || err.statusText;
const error = err.error.message || err.statusText; // return throwError(error);
return throwError(error); return throwError(err);
}) })
) )

View File

@ -83,7 +83,8 @@ export class FakeBackendInterceptor implements HttpInterceptor {
end: this.createEventDate(dayIndex, hourIndex + 1), end: this.createEventDate(dayIndex, hourIndex + 1),
trainer: [trainer1, trainer2][id % 2], trainer: [trainer1, trainer2][id % 2],
eventType: eventTypes[id % eventTypes.length], eventType: eventTypes[id % eventTypes.length],
reservedAt: reservedAt, // reservedAt: reservedAt,
registrations: [],
reservationCount: id % 11, reservationCount: id % 11,
seat_count: 10 seat_count: 10
} }

View File

@ -9,3 +9,17 @@ export interface PasswordChangeRequest{
passwordOld: string; passwordOld: string;
password: string; password: string;
} }
export const RegistrationErrors ={
CARD_NOT_FOUND : 1,
CUSTOMER_NOT_FOUND : 2,
TICKET_NOT_FOUND : 3,
NO_FREE_SEATS : 4,
EVENT_TYPE_NOT_FOUND : 5,
TICKET_INSUFFICIENT : 6,
UNKNOWN_ERROR : 7,
MAX_SEAT_COUNT_EXCEEDED : 8,
EVENT_UNAVAILABLE : 9,
ALREADY_REGISTERED : 10,
}

View File

@ -1,9 +1,13 @@
<a <span
(click)="selectEvent()" (click)="selectEvent()"
class="event d-block p-1 pl-2 pr-2 mb-1 rounded text-truncate small bg-success text-white" title="Test Event 2"> class="event d-block p-1 pl-2 pr-2 mb-1 rounded text-truncate small text-white" title="Test Event 2"
[class.bg-dark]="maySelect()"
[class.bg-primary]="isRegistered()"
[class.bg-secondary]="isDisabled()"
>
{{formatTime()}}:{{event.eventType.name}} {{formatTime()}}:{{event.eventType.name}}
<ng-container *ngIf="hasTrainer()"> <ng-container *ngIf="hasTrainer()">
<br> <br>
{{formatTrainer()}} {{formatTrainer()}}
</ng-container> </ng-container>
</a> </span>

View File

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MonthCalendarEventComponent } from './month-calendar-event.component';
describe('MonthCalendarEventComponent', () => {
let component: MonthCalendarEventComponent;
let fixture: ComponentFixture<MonthCalendarEventComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ MonthCalendarEventComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MonthCalendarEventComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -40,4 +40,16 @@ export class MonthCalendarEventComponent implements OnInit {
event: this.event event: this.event
}) })
} }
maySelect() {
return this.event.registrations?.length == 0 && this.event.hasFreeSeats;
}
isRegistered() {
return this.event.registrations?.length > 0;
}
isDisabled() {
return false;
}
} }

View File

@ -2,8 +2,11 @@
<h1 class="text-center">Naptár</h1> <h1 class="text-center">Naptár</h1>
</header> </header>
<div class="row text-white gx-2 d-none d-md-flex"> <div class="row text-white gx-2 d-none d-md-flex">
<div *ngFor="let dayOfWeek of daysOfWeek" class="col mb-2"> <div *ngFor="let dayOfWeek of daysOfWeek; let i = index" class="col mb-2">
<div class="border bg-primary text-center py-1 px-2"> <div class="border text-center py-1 px-2"
[class.bg-primary]="i % 7 < 6"
[class.bg-dark]="i % 7 == 6"
>
<span class="d-none d-lg-inline text-capitalize">{{dayOfWeek.name}}</span> <span class="d-none d-lg-inline text-capitalize">{{dayOfWeek.name}}</span>
<span class="d-none d-md-inline d-lg-none text-capitalize" >{{dayOfWeek.shortName}}</span> <span class="d-none d-md-inline d-lg-none text-capitalize" >{{dayOfWeek.shortName}}</span>
</div> </div>

View File

@ -1,4 +1,4 @@
<div class="container" *ngIf="event"> <div class="container" *ngIf="event && eventForm">
<h1>Jelentkezés</h1> <h1>Jelentkezés</h1>
<form [formGroup]="eventForm"> <form [formGroup]="eventForm">
<div class="row"> <div class="row">
@ -25,51 +25,15 @@
<div class="col-lg-3 col-sm-12"><span class="title">Terem</span></div> <div class="col-lg-3 col-sm-12"><span class="title">Terem</span></div>
<div class="col-lg-9 col-sm-12"><span>{{event?.room?.name }}</span></div> <div class="col-lg-9 col-sm-12"><span>{{event?.room?.name }}</span></div>
</div> </div>
<h2 *ngIf="event.equipmentTypeAssignments.length > 0">
Szükséges eszközök
</h2>
<ng-container *ngFor="let equipment of event.equipmentTypeAssignments; let i = index; ">
<h3>{{equipment?.equipmentType?.name }}</h3>
<div class="row">
<div class="col-lg-12 col-sm-12">
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="{{'equipment'+equipment.id}}"
formControlName="{{'equipment'+equipment.id}}"
autocomplete="off"
id="equipment_{{equipment.id}}_1"
value="1"
>
<label class="form-check-label" for="equipment_{{equipment.id}}_1">
Szeretném a terem saját eszközét használni
</label>
</div>
<div class="form-check">
<input class="form-check-input"
type="radio"
name="{{'equipment'+equipment.id}}"
formControlName="{{'equipment'+equipment.id}}"
autocomplete="off"
id="equipment_{{equipment.id}}_0"
value="0"
>
<label class="form-check-label" for="equipment_{{equipment.id}}_0">
A saját eszközömet használom
</label>
</div>
</div>
</div>
</ng-container>
<div class="row"> <div class="row">
<div class="col-lg-9 col-sm-12 pt-2"> <div class="col-lg-9 col-sm-12 pt-2">
<a *ngIf="mayRegister(event)" class="btn btn-primary " (click)="register(event)">Foglalás</a> <a *ngIf="mayRegister(event)" class="btn btn-primary me-3" (click)="register(event)">Foglalás</a>
<a *ngIf="mayCancel(event)" class="btn btn-primary" (click)="cancel(event)">Lemondás</a> <a *ngIf="mayCancel(event)" class="btn btn-primary me-3" (click)="cancel(event)">Lemondás</a>
<span *ngIf="!mayCancel(event) && noFreeSeat(event)">Már nincs szabad hely</span> <span class="warning me-3" *ngIf="!event.hasFreeSeats && event.registrations.length == 0">Már nincs szabad hely</span>
<a class="btn btn-primary me-3" (click)="goBack(event)">Vissza</a>
</div> </div>
<div class="col-lg-3 text-lg-right col-sm-12 pt-2"> <div class="col-lg-3 text-lg-right col-sm-12 pt-2">
<a class="btn btn-primary " (click)="goBack(event)">Vissza</a>
</div> </div>
</div> </div>
</form> </form>

View File

@ -1,8 +1,10 @@
import { Component, OnInit } from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {Event, EventService, RegisterEventRequest} from "../../services/event.service"; import {Event, EventService, RegisterEventRequest} from "../../services/event.service";
import {ActivatedRoute} from "@angular/router"; import {ActivatedRoute} from "@angular/router";
import {NavigationService} from "../../services/navigation.service"; import {NavigationService} from "../../services/navigation.service";
import {FormBuilder, FormGroup, Validators} from "@angular/forms"; import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {ToastrService} from "ngx-toastr";
import {RegistrationErrors} from "../../app.types";
@Component({ @Component({
selector: 'app-event-details', selector: 'app-event-details',
@ -20,39 +22,32 @@ export class EventDetailsComponent implements OnInit {
private fb: FormBuilder, private fb: FormBuilder,
private eventService: EventService, private eventService: EventService,
private route: ActivatedRoute, private route: ActivatedRoute,
private navigationService: NavigationService private navigationService: NavigationService,
) { } private toastr: ToastrService
) {
this.formControls = {
idEvent: ["", [Validators.required]],
}
this.eventForm = this.fb.group(this.formControls);
}
ngOnInit() { ngOnInit() {
console.info("event details")
this.formControls = {
idEvent: ["", [Validators.required ]],
}
let idEvent = +this.route.snapshot.paramMap.get('idEvent'); let idEvent = +this.route.snapshot.paramMap.get('idEvent');
this.eventService.findEvent(idEvent).subscribe( this.eventService.findEvent(idEvent).subscribe(
value => { value => {
this.event = value; this.event = value;
this.event.equipmentTypeAssignments.forEach((assignment,index) =>{
this.formControls['equipment'+assignment.id] = ["", [Validators.required ]]
})
this.eventForm = this.fb.group(this.formControls);
this.eventForm.patchValue({"idEvent": this.event.id}) this.eventForm.patchValue({"idEvent": this.event.id})
} }
); );
} }
mayRegister(event: Event) { mayRegister(event: Event) {
// return event.reservedAt == null && event.reservationCount < event.seat_count; return event.hasFreeSeats && event.registrations.length == 0;
return true
} }
mayCancel(event: Event) { mayCancel(event: Event) {
return event.reservedAt; return event.registrations.length > 0;
}
noFreeSeat(event: Event) {
return event.reservedAt == null && event.reservationCount >= event.seat_count;
} }
goBack(event: Event) { goBack(event: Event) {
@ -60,33 +55,53 @@ export class EventDetailsComponent implements OnInit {
} }
register(event: Event) { register(event: Event) {
console.info("register", event);
console.info(this.eventForm.value);
let request :RegisterEventRequest= { let request: RegisterEventRequest = {
idEvent: this.eventForm.value.idEvent, idEvent: this.eventForm.value.idEvent,
equipment: [] equipment: []
}; };
this.event.equipmentTypeAssignments.forEach((value, index) => {
if ( this.eventForm.value.hasOwnProperty('equipment'+value.id) ){
if ( this.eventForm.value['equipment'+value.id] === "1"){
request.equipment.push(value.id);
}
}
});
console.info(request);
this.eventService.register(request) this.eventService.register(request)
.subscribe( .subscribe(
value => {}, value => {
value => {}, this.toastr.success('Sikeresen Foglalás', 'Foglalás');
this.navigationService.navigateToHome()
},
error => {
let status = error.status;
let code = error?.error?.code;
let message = "Hiba történt";
if (status == 400) {
switch (code) {
case RegistrationErrors.ALREADY_REGISTERED:
message = "Már regisztárlt erre az edzésre!";
break;
case RegistrationErrors.MAX_SEAT_COUNT_EXCEEDED:
case RegistrationErrors.TICKET_INSUFFICIENT:
message = "Nem rendelkezik megfelelő bérlettel!"
break;
case RegistrationErrors.NO_FREE_SEATS:
message = "Nincs több szabad hely!"
break;
}
}
this.toastr.error(message, "Foglalás")
},
); );
} }
cancel(event: Event) { cancel(event: Event) {
this.eventService.cancelRegistration(event.registrations[0].id)
.subscribe(
() => {
this.navigationService.navigateToHome();
},
() => {
this.toastr.error("Hiba történt", "Lemondás")
});
} }
} }

View File

@ -27,8 +27,8 @@
<div class="row"> <div class="row">
<div class="col-lg-9 col-sm-12 pt-2"> <div class="col-lg-9 col-sm-12 pt-2">
<a *ngIf="mayRegister(event)" class="btn btn-primary " (click)="register(event)" >Foglalás</a> <a *ngIf="mayRegister(event)" class="btn btn-primary " (click)="register(event)" >Foglalás</a>
<a *ngIf="mayCancel(event)" class="btn btn-primary" (click)="cancel(event)" >Lemondás</a> <!-- <a *ngIf="mayCancel(event)" class="btn btn-primary" (click)="cancel(event)" >Lemondás</a>-->
<a *ngIf="!mayCancel(event) && noFreeSeat(event)" class="btn btn-info btn-di" disabled >Már nincsen szabad hely</a> <!-- <a *ngIf="!mayCancel(event) && noFreeSeat(event)" class="btn btn-info btn-di" disabled >Már nincsen szabad hely</a>-->
</div> </div>
<div class="col-lg-3 text-lg-right col-sm-12 pt-2"> <div class="col-lg-3 text-lg-right col-sm-12 pt-2">
<a class="btn btn-primary" (click)="closeEvent(event)" >Vissza</a> <a class="btn btn-primary" (click)="closeEvent(event)" >Vissza</a>

View File

@ -69,15 +69,15 @@ export class EventsComponent implements OnInit {
} }
mayRegister(event: Event) { mayRegister(event: Event) {
return event.reservedAt == null && event.reservationCount < event.seat_count; return event.hasFreeSeats && event.registrations.length == 0;
} }
mayCancel(event: Event) { // mayCancel(event: Event) {
return event.reservedAt; // return event.reservedAt;
} // }
noFreeSeat(event: Event) { noFreeSeat(event: Event) {
return event.reservedAt == null && event.reservationCount >= event.seat_count; return !event.hasFreeSeats;
} }
prepareEventDates(events: Event[]) { prepareEventDates(events: Event[]) {

View File

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RegistrationsComponent } from './registrations.component';
describe('RegistrationsComponent', () => {
let component: RegistrationsComponent;
let fixture: ComponentFixture<RegistrationsComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ RegistrationsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RegistrationsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,9 +1,7 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http"; import {HttpClient} from "@angular/common/http";
import {Endpoints} from "./endpoints"; import {Endpoints} from "./endpoints";
import {forkJoin, Observable} from "rxjs"; import { Observable} from "rxjs";
import {map} from "rxjs/operators";
import {CalendarEvent} from "../app.types";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -64,9 +62,9 @@ export interface Event {
seat_count: number; seat_count: number;
reservationCount: number; reservationCount: number;
eventType: EventType; eventType: EventType;
reservedAt: number;
room: Room; room: Room;
equipmentTypeAssignments?: EquipmentTypeAssignment[]; registrations: Registration[];
hasFreeSeats?: boolean;
} }
export interface EquipmentTypeAssignment { export interface EquipmentTypeAssignment {
@ -106,7 +104,7 @@ export interface EventsAvailableResponse {
export interface Registration { export interface Registration {
id: number; id: number;
created_at: number; created_at: number;
event: Event event?: Event
} }
export interface RegisterEventRequest{ export interface RegisterEventRequest{

View File

@ -29,5 +29,9 @@ export class NavigationService {
this.navigate(['/registration/'+idRegistration ]) this.navigate(['/registration/'+idRegistration ])
} }
public navigateRegistrations(){
this.navigate(['/registrations' ]);
}
} }

View File

@ -9,13 +9,19 @@
namespace customerapi\controllers; namespace customerapi\controllers;
use common\models\Event;
use common\models\EventRegistration;
use customerapi\manager\EventRegistrationManager;
use customerapi\models\available\EventInterval; use customerapi\models\available\EventInterval;
use customerapi\models\available\EventAvailable; use customerapi\models\available\EventAvailable;
use customerapi\models\DayToDisplay; use customerapi\models\DayToDisplay;
use customerapi\models\details\EventDetailsView; use customerapi\models\details\EventDetailsView;
use customerapi\models\details\EventRegistrationView;
use customerapi\models\details\EventTypeDetailsView;
use customerapi\models\details\RoomDetailsView;
use customerapi\models\details\TrainerDetailsView;
use DateTime; use DateTime;
use Exception; use Exception;
use yii\db\ActiveRecord;
use yii\db\Query; use yii\db\Query;
use yii\web\Response; use yii\web\Response;
@ -63,7 +69,7 @@ class EventController extends CustomerApiController
->innerJoinWith('trainer') ->innerJoinWith('trainer')
->innerJoinWith('eventType') ->innerJoinWith('eventType')
->innerJoinWith('room') ->innerJoinWith('room')
->joinWith('activeEventRegistrationsForCustomer') // ->leftJoin('activeEventRegistrationsForCustomer')
->andWhere(['>=', 'event.start', $interval->firstActiveDate->getTimestamp()]) ->andWhere(['>=', 'event.start', $interval->firstActiveDate->getTimestamp()])
->andWhere(['<', 'event.start', (clone $interval->lastActiveDate)->modify('+1 day')->getTimestamp()]) ->andWhere(['<', 'event.start', (clone $interval->lastActiveDate)->modify('+1 day')->getTimestamp()])
->andWhere(['event.active' => '1']) ->andWhere(['event.active' => '1'])
@ -77,6 +83,7 @@ class EventController extends CustomerApiController
$eventDay->setTime(0, 0); $eventDay->setTime(0, 0);
$event->reservationCount = $event->getEventRegistrationCount(); $event->reservationCount = $event->getEventRegistrationCount();
$event->hasFreeSeats = $event->reservationCount < $event->seat_count;
/** @var DayToDisplay $date */ /** @var DayToDisplay $date */
foreach ($dates as $date) { foreach ($dates as $date) {
@ -105,7 +112,7 @@ class EventController extends CustomerApiController
->innerJoinWith('trainer') ->innerJoinWith('trainer')
->innerJoinWith('eventType') ->innerJoinWith('eventType')
->innerJoinWith('room') ->innerJoinWith('room')
->joinWith('activeEventRegistrations') ->joinWith('activeEventRegistrations','event.id = activeEventRegistrations.id_event')
->andWhere(['>=', 'event.start', $interval->firstActiveDate->getTimestamp()]) ->andWhere(['>=', 'event.start', $interval->firstActiveDate->getTimestamp()])
->andWhere(['<', 'event.start', (clone $interval->lastActiveDate)->modify('+1 day')->getTimestamp()]) ->andWhere(['<', 'event.start', (clone $interval->lastActiveDate)->modify('+1 day')->getTimestamp()])
->andWhere(['event.active' => '1']); ->andWhere(['event.active' => '1']);
@ -121,11 +128,32 @@ class EventController extends CustomerApiController
{ {
$interval = EventInterval::createInterval(); $interval = EventInterval::createInterval();
try { try {
$query = EventDetailsView::find(); $query = Event::find();
$query = $this->buildEventQuery($query, $interval); $query = $this->buildEventQuery($query, $interval);
$query = $query->andWhere(['event.id' => $id_event]); $query = $query->andWhere(['event.id' => $id_event]);
/** @var Event $event */
$event = $query->one(); $event = $query->one();
return $this->asJson($event);
$result = new EventDetailsView();
$result->id = $event->id;
$result->start = $event->start;
$result->end = $event->end;
$result->seatCount = $event->seat_count;
$result->trainer = TrainerDetailsView::findOne($event->id_trainer);
$result->room = RoomDetailsView::findOne($event->id_room);
$result->eventType = EventTypeDetailsView::findOne($event->id_event_type);
$registrations = EventRegistrationView::find()->all();
$allActiveRegistrations = EventRegistration::filterActive($registrations);
$customerActiveRegistrations = EventRegistration::filterForCustomer($allActiveRegistrations,\Yii::$app->user->id);
$result->reservationCount = count($allActiveRegistrations);
$result->registrations = $customerActiveRegistrations;
$result->hasFreeSeats = $event->seat_count > count($allActiveRegistrations);
return $this->asJson($result);
} catch (Exception $e) { } catch (Exception $e) {
throw new \yii\base\Exception($e->getMessage()); throw new \yii\base\Exception($e->getMessage());
} }

View File

@ -59,7 +59,7 @@ class EventRegistrationController extends CustomerApiController
$form->event_id = $id_event; $form->event_id = $id_event;
$form->card_number = $card->number; $form->card_number = $card->number;
$manager = new EventRegistrationManager(); $manager = new \common\manager\EventRegistrationManager();
$manager->registerCard($form); $manager->registerCard($form);
return $form->registration; return $form->registration;

View File

@ -17,7 +17,7 @@ class EventRegistrationManager
return $this->prepareQueryFindRegistrationsForCustomer()->all(); return $this->prepareQueryFindRegistrationsForCustomer()->all();
} }
public function findCustomerRegistrations() public function findCustomerRegistrations($clazz= EventRegistration::class )
{ {
$interval = EventInterval::createInterval(); $interval = EventInterval::createInterval();
return EventRegistration::find() return EventRegistration::find()

View File

@ -10,6 +10,7 @@ class EventAvailable extends Event
{ {
public $reservationCount; public $reservationCount;
public $hasFreeSeats;
protected function getTrainerClass() protected function getTrainerClass()
{ {
@ -39,6 +40,7 @@ class EventAvailable extends Event
"seat_count" => "seat_count", "seat_count" => "seat_count",
"active" => "active", "active" => "active",
"reservationCount" => "reservationCount", "reservationCount" => "reservationCount",
"hasFreeSeats" => "hasFreeSeats",
]; ];
$fields['trainer'] = 'trainer'; $fields['trainer'] = 'trainer';
$fields['eventType'] = 'eventType'; $fields['eventType'] = 'eventType';

View File

@ -5,37 +5,37 @@ namespace customerapi\models\details;
use common\models\Event; use common\models\Event;
use yii\base\Model;
class EventDetailsView extends Event /**
$property string $eventName;
$property string $start;
$property string $end;
$property string $seatCount;
$property string $reservationCount;
$property customerapi\models\details\EventDetailsView $room;
$property customerapi\models\details\TrainerDetailsView $trainer;
$property customerapi\models\details\EventTypeDetailsView $eventType;
$property string $registrations;
*/
class EventDetailsView extends Model // extends Event
{ {
public $id;
public $start;
public $end;
public $seatCount;
// total count of registrations
public $reservationCount; public $reservationCount;
public $room;
public $trainer;
public $eventType;
// registrations for current user
public $registrations;
public $hasFreeSeats;
/**
* @return string
*/
protected function getTrainerClass()
{
// override trainer class to have more control
// about json fields
return TrainerDetailsView::class;
}
protected function getEventTypeClass()
{
return EventTypeDetailsView::class;
}
protected function getRoomClass()
{
return RoomDetailsView::class;
}
protected function getEquipmentTypeAssignmentsClass()
{
return EventEquipmentTypeAssignmentView::class;
}
/** /**
@ -49,15 +49,17 @@ class EventDetailsView extends Event
"id" => "id", "id" => "id",
"start" => "start", "start" => "start",
"end" => "end", "end" => "end",
"seat_count" => "seat_count", "seat_count" => "seatCount",
"active" => "active", "reservationCount" => "reservationCount",
// "reservationCount" => "reservationCount" "registrations" => "registrations",
"hasFreeSeats" => "hasFreeSeats"
]; ];
$fields['trainer'] = 'trainer'; $fields['trainer'] = 'trainer';
$fields['eventType'] = 'eventType'; $fields['eventType'] = 'eventType';
$fields['room'] = 'room'; $fields['room'] = 'room';
$fields['equipmentTypeAssignments'] = 'equipmentTypeAssignments';
return $fields; return $fields;
} }
} }

View File

@ -0,0 +1,17 @@
<?php
namespace customerapi\models\details;
use common\models\EventRegistration;
class EventRegistrationView extends EventRegistration
{
public function fields()
{
return [
'id' => 'id',
'created_at' => 'created_at',
];
}
}

View File

@ -4,6 +4,8 @@ PROJECT_DIR=$(pwd)/..
PORT=86 PORT=86
HOST=localhost HOST=localhost
docker container stop fitness-web || true
docker run \ docker run \
-v ${PROJECT_DIR}:/var/www/html/fitness_web \ -v ${PROJECT_DIR}:/var/www/html/fitness_web \
--rm \ --rm \