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 customerapi\models\available\EventInterval;
use customerapi\models\registrations\EventRegistrationAvailable;
use customerapi\models\details\EventRegistrationView;
use Exception;
use Throwable;
use Yii;
use yii\base\BaseObject;
use yii\db\ActiveRecord;
@ -37,6 +37,7 @@ class EventRegistrationManager extends BaseObject
const UNKNOWN_ERROR = 7;
const MAX_SEAT_COUNT_EXCEEDED = 8;
const EVENT_UNAVAILABLE = 9;
const ALREADY_REGISTERED = 10;
public static $STATES = [
self::CARD_NOT_FOUND => 'CARD_NOT_FOUND',
@ -48,8 +49,7 @@ class EventRegistrationManager extends BaseObject
self::UNKNOWN_ERROR => 'UNKNOWN_ERROR',
self::MAX_SEAT_COUNT_EXCEEDED => 'MAX_SEAT_COUNT_EXCEEDED',
self::EVENT_UNAVAILABLE => 'EVENT_UNAVAILABLE',
self::ALREADY_REGISTERED => 'ALREADY_REGISTERED',
];
/**
@ -77,7 +77,7 @@ class EventRegistrationManager extends BaseObject
$activeTickets = $card->getActiveTickets();
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 */
@ -101,24 +101,23 @@ class EventRegistrationManager extends BaseObject
}
//detect if customer is already registered to event
/** @var EventRegistration[] $registrations */
$registrations = $event->getActiveEventRegistrations()->all();
foreach ($registrations as $registration ){
if ($registration->customer_id == $card->customer->id_customer){
throw new BadRequestHttpException("Already registered");
if ($registration->id_customer == $card->customer->id_customer){
throw new BadRequestHttpException("Already registered", self::ALREADY_REGISTERED);
}
}
$selectedTicket = $eventType->findTicketAllowingEventType($activeTickets);
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();
$registration = new EventRegistration();

View File

@ -83,4 +83,56 @@ class EventRegistration extends \yii\db\ActiveRecord
public function getCustomerClass(){
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

@ -48,7 +48,7 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
const STATUS_DELETED = 0;
const STATUS_ACTIVE = 10;
const STATUS_INACTIVE = 20;
public static $SQL_UPDATE = "UPDATE card as c1
left JOIN ( select ticket.id_card as id_card , max(ticket.id_ticket) as id_ticket
from ticket
@ -68,7 +68,7 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
, c1.flag = case when c1.type = 50 then ( c1.flag & ~(1 << 0) ) when t.id_card is null then ( c1.flag | 1 << 0 ) else ( c1.flag & ~(1 << 0) ) end
, c1.id_ticket_current = case when t.id_ticket is null then null else t.id_ticket end
where c1.status = 10";
public static $SQL_UPDATE_CARD = "UPDATE card as c1
left JOIN ( select ticket.id_card as id_card , max(ticket.id_ticket) as id_ticket
from ticket
@ -141,11 +141,11 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
[['start', 'end', 'created_at', 'updated_at'], 'safe'],
[['comment'], 'required'],
[['comment'], 'string', 'max' => 255],
[[ 'start', ], 'date' , 'timestampAttribute' => 'timestampStart' ,'timestampAttributeFormat' => 'yyyy-MM-dd' ],
[[ 'end' , ], 'date' , 'timestampAttribute' => 'timestampEnd' ,'timestampAttributeFormat' => 'yyyy-MM-dd' ],
];
}
@ -176,12 +176,12 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
'original_end' => Yii::t('common/transfer', 'Eredeti érvényesség vége'),
];
}
public function getCard(){
return $this->hasOne( Card::className(), ["id_card" =>"id_card" ] );
}
public function getContract(){
return $this->hasOne( Contract::className(), ["id_contract" =>"id_contract" ] );
}
@ -189,7 +189,7 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
public function getTransfer(){
return $this->hasOne( Transfer::className(), ["id_object" =>"id_ticket"] )->andWhere(['transfer.type' => Transfer::TYPE_TICKET]);
}
public function getCardNumber(){
$result = "";
/** @noinspection PhpUndefinedFieldInspection */
@ -199,11 +199,11 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
}
return $result;
}
public function getCustomer(){
return $this->hasOne( Customer::className(), ["id_customer_card" =>"id_card" ] )->via('card');
}
public function getCustomerName(){
$result = "";
/** @noinspection PhpUndefinedFieldInspection */
@ -217,14 +217,14 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
public function getUser(){
return $this->hasOne( User::className(), ["id" =>"id_user" ] );
}
public function getTicketType(){
return $this->hasOne( TicketType::className(), ["id_ticket_type" =>"id_ticket_type" ] );
}
public function getDiscount(){
return $this->hasOne( Discount::className(), ["id_discount" =>"id_discount" ] );
}
public function getAccount() {
return $this->hasOne ( Account::className (), [
'id_account' => 'id_account'
@ -277,10 +277,10 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
* @return array|\yii\db\ActiveRecord[]
*/
public static function readActive($card, $validOnDay = null){
if ( $card == null )
return [];
$query = Ticket::find();
if (!isset( $validOnDay ) ){
$today = date('Y-m-d');
@ -292,11 +292,11 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
$query->andWhere( 'ticket.start <= :today' ,[ 'today' => $today] );
$query->andWhere( 'ticket.end >= :today' ,[ 'today' => $today] );
$query->andWhere( 'ticket.status = :status' ,[ 'status' => Ticket::STATUS_ACTIVE] );
$query->orderBy([ "ticket.created_at" =>SORT_DESC] );
$query->orderBy([ "ticket.created_at" =>SORT_DESC] );
$result = $query->all();
return $result;
}
/**
@ -305,10 +305,10 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
* @return array|\yii\db\ActiveRecord[]
*/
public static function readUnpaid($card){
if ( $card == null )
return [];
$query = Ticket::find();
$query->innerJoin("transfer", "transfer.id_object = ticket.id_ticket ");
$today = date('Y-m-d');
@ -320,39 +320,39 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
$query->andWhere( ["transfer.type" => Transfer::TYPE_TICKET] );
$query->andWhere( [ "transfer.status" => Transfer::STATUS_NOT_PAID ]);
$query->orderBy([ "ticket.created_at" =>SORT_DESC] );
$result = $query->all();
return $result;
}
public static function mkStatisticQuery($start,$end,$id_card = null){
$query = new Query();
$query->addSelect( [
new Expression( 'ticket_type.id_ticket_type as id'),
new Expression( 'ticket_type.name as name'),
new Expression( 'coalesce( count(ticket.id_ticket),0) as total'),
new Expression( "coalesce( sum( case when ticket.status <> " .Ticket::STATUS_DELETED ." AND ". Helper::sqlValidRule('ticket.start', 'ticket.end', ':start', ':end') . " then 1 else 0 end) , 0) as valid" ), //valid
new Expression( "coalesce( sum( case when ticket.status <> " .Ticket::STATUS_DELETED ." AND ". Helper::sqlInIntervalRule('ticket.created_at', ':start', ':end') . " then 1 else 0 end) , 0) as created" ),//created
new Expression( "coalesce( sum( case when ticket.status <> " .Ticket::STATUS_DELETED ." AND ". Helper::sqlInIntervalRule('ticket.created_at', ':start', ':end') . " then transfer.money else 0 end) , 0) as created_money" ),//created_money
new Expression( "coalesce( sum( case when ticket.status <> " .Ticket::STATUS_DELETED ." AND " . Helper::sqlExpireRule('ticket.start', 'ticket.end', ':start', ":end") . " then 1 else 0 end) , 0) as expired" ),
]);
$query->from('ticket_type');
$query->innerJoin('ticket', 'ticket.id_ticket_type = ticket_type.id_ticket_type');
$query->innerJoin('transfer', 'ticket.id_ticket = transfer.id_object and transfer.type = '. Transfer::TYPE_TICKET );
Helper::queryAccountConstraint($query, 'ticket.id_account');
$query->andFilterWhere(['id_card' =>$id_card]);
$query->andWhere(
[
'or',
@ -361,23 +361,23 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
Helper::queryExpireRule('ticket.start', 'ticket.end', $start, $end),
]
);
$query->groupBy("ticket_type.id_ticket_type, ticket_type.name");
$query->params([':start' => $start, ':end' => $end]);
return $query;
}
public static function statuses( ) {
return [
Ticket::STATUS_ACTIVE => 'Aktív',
Ticket::STATUS_DELETED => 'Törölve',
Ticket::STATUS_INACTIVE => 'Inaktív',
Ticket::STATUS_ACTIVE => 'Aktív',
Ticket::STATUS_DELETED => 'Törölve',
Ticket::STATUS_INACTIVE => 'Inaktív',
];
}
public static function toStatusName($id_status){
$result = "Ismeretlen";
$statuses = Ticket::statuses();
@ -386,23 +386,23 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
}
return $result;
}
public function getStatusName(){
return static::toStatusName($this->status);
}
public function isDeleted(){
return $this->status == Ticket::STATUS_DELETED;
}
/**csoportos beszedéses a bérlet*/
public function isInstallmentTicket(){
return ( isset($this->part_count) && $this->part_count > 0 );
}
/**
* Apply request
*
*
* @var \common\models\TicketInstallmentRequest $request megbízás
* */
public function applyTicketInstallmentRequest( $request ) {
@ -414,12 +414,12 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
}
$this->recalclulate();
}else if ( $request->isStatusRejected() ){
$this->status = static::STATUS_INACTIVE;
$this->status = static::STATUS_INACTIVE;
}
}
}
/*
* *
* *
* @param common\models\TicketInstallmentRequest $request megbízás
*/
public function setPartRequired($request){
@ -431,7 +431,7 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
}
}
}
/**
* Csoportos beszedéses bérlet érvényességének újraszámolása
* */
@ -457,7 +457,7 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
public function consumeReservationCount($count){
$newReservationCount = $this->reservation_count + $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;
}
@ -473,7 +473,7 @@ class Ticket extends \common\models\BaseFitnessActiveRecord
public function afterSave($insert, $changedAttributes) {
Card::updateCardFlagTicket($this->id_card);;
}
public function afterDelete(){
Card::updateCardFlagTicket($this->id_card);;
}

View File

@ -1,6 +1,6 @@
import { Injectable } from "@angular/core";
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 { AuthenticationService} from "../services/authentication.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.navigationService.navigateToLogin();
// location.reload(true);
return EMPTY;
}
const error = err.error.message || err.statusText;
return throwError(error);
// const error = err.error.message || err.statusText;
// return throwError(error);
return throwError(err);
})
)

View File

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

View File

@ -9,3 +9,17 @@ export interface PasswordChangeRequest{
passwordOld: 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()"
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}}
<ng-container *ngIf="hasTrainer()">
<br>
{{formatTrainer()}}
</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
})
}
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>
</header>
<div class="row text-white gx-2 d-none d-md-flex">
<div *ngFor="let dayOfWeek of daysOfWeek" class="col mb-2">
<div class="border bg-primary text-center py-1 px-2">
<div *ngFor="let dayOfWeek of daysOfWeek; let i = index" class="col mb-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-md-inline d-lg-none text-capitalize" >{{dayOfWeek.shortName}}</span>
</div>

View File

@ -1,4 +1,4 @@
<div class="container" *ngIf="event">
<div class="container" *ngIf="event && eventForm">
<h1>Jelentkezés</h1>
<form [formGroup]="eventForm">
<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-9 col-sm-12"><span>{{event?.room?.name }}</span></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="col-lg-9 col-sm-12 pt-2">
<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>
<span *ngIf="!mayCancel(event) && noFreeSeat(event)">Már nincs szabad hely</span>
<a *ngIf="mayRegister(event)" class="btn btn-primary me-3" (click)="register(event)">Foglalás</a>
<a *ngIf="mayCancel(event)" class="btn btn-primary me-3" (click)="cancel(event)">Lemondás</a>
<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 class="col-lg-3 text-lg-right col-sm-12 pt-2">
<a class="btn btn-primary " (click)="goBack(event)">Vissza</a>
</div>
</div>
</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 {ActivatedRoute} from "@angular/router";
import {NavigationService} from "../../services/navigation.service";
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {ToastrService} from "ngx-toastr";
import {RegistrationErrors} from "../../app.types";
@Component({
selector: 'app-event-details',
@ -20,39 +22,32 @@ export class EventDetailsComponent implements OnInit {
private fb: FormBuilder,
private eventService: EventService,
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() {
console.info("event details")
this.formControls = {
idEvent: ["", [Validators.required ]],
}
let idEvent = +this.route.snapshot.paramMap.get('idEvent');
this.eventService.findEvent(idEvent).subscribe(
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})
}
);
}
mayRegister(event: Event) {
// return event.reservedAt == null && event.reservationCount < event.seat_count;
return true
return event.hasFreeSeats && event.registrations.length == 0;
}
mayCancel(event: Event) {
return event.reservedAt;
}
noFreeSeat(event: Event) {
return event.reservedAt == null && event.reservationCount >= event.seat_count;
return event.registrations.length > 0;
}
goBack(event: Event) {
@ -60,33 +55,53 @@ export class EventDetailsComponent implements OnInit {
}
register(event: Event) {
console.info("register", event);
console.info(this.eventForm.value);
let request :RegisterEventRequest= {
let request: RegisterEventRequest = {
idEvent: this.eventForm.value.idEvent,
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)
.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) {
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="col-lg-9 col-sm-12 pt-2">
<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) && noFreeSeat(event)" class="btn btn-info btn-di" disabled >Már nincsen szabad hely</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>-->
</div>
<div class="col-lg-3 text-lg-right col-sm-12 pt-2">
<a class="btn btn-primary" (click)="closeEvent(event)" >Vissza</a>

View File

@ -69,15 +69,15 @@ export class EventsComponent implements OnInit {
}
mayRegister(event: Event) {
return event.reservedAt == null && event.reservationCount < event.seat_count;
return event.hasFreeSeats && event.registrations.length == 0;
}
mayCancel(event: Event) {
return event.reservedAt;
}
// mayCancel(event: Event) {
// return event.reservedAt;
// }
noFreeSeat(event: Event) {
return event.reservedAt == null && event.reservationCount >= event.seat_count;
return !event.hasFreeSeats;
}
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 {HttpClient} from "@angular/common/http";
import {Endpoints} from "./endpoints";
import {forkJoin, Observable} from "rxjs";
import {map} from "rxjs/operators";
import {CalendarEvent} from "../app.types";
import { Observable} from "rxjs";
@Injectable({
providedIn: 'root'
@ -64,9 +62,9 @@ export interface Event {
seat_count: number;
reservationCount: number;
eventType: EventType;
reservedAt: number;
room: Room;
equipmentTypeAssignments?: EquipmentTypeAssignment[];
registrations: Registration[];
hasFreeSeats?: boolean;
}
export interface EquipmentTypeAssignment {
@ -106,7 +104,7 @@ export interface EventsAvailableResponse {
export interface Registration {
id: number;
created_at: number;
event: Event
event?: Event
}
export interface RegisterEventRequest{

View File

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

View File

@ -9,13 +9,19 @@
namespace customerapi\controllers;
use common\models\Event;
use common\models\EventRegistration;
use customerapi\manager\EventRegistrationManager;
use customerapi\models\available\EventInterval;
use customerapi\models\available\EventAvailable;
use customerapi\models\DayToDisplay;
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 Exception;
use yii\db\ActiveRecord;
use yii\db\Query;
use yii\web\Response;
@ -63,7 +69,7 @@ class EventController extends CustomerApiController
->innerJoinWith('trainer')
->innerJoinWith('eventType')
->innerJoinWith('room')
->joinWith('activeEventRegistrationsForCustomer')
// ->leftJoin('activeEventRegistrationsForCustomer')
->andWhere(['>=', 'event.start', $interval->firstActiveDate->getTimestamp()])
->andWhere(['<', 'event.start', (clone $interval->lastActiveDate)->modify('+1 day')->getTimestamp()])
->andWhere(['event.active' => '1'])
@ -77,6 +83,7 @@ class EventController extends CustomerApiController
$eventDay->setTime(0, 0);
$event->reservationCount = $event->getEventRegistrationCount();
$event->hasFreeSeats = $event->reservationCount < $event->seat_count;
/** @var DayToDisplay $date */
foreach ($dates as $date) {
@ -105,7 +112,7 @@ class EventController extends CustomerApiController
->innerJoinWith('trainer')
->innerJoinWith('eventType')
->innerJoinWith('room')
->joinWith('activeEventRegistrations')
->joinWith('activeEventRegistrations','event.id = activeEventRegistrations.id_event')
->andWhere(['>=', 'event.start', $interval->firstActiveDate->getTimestamp()])
->andWhere(['<', 'event.start', (clone $interval->lastActiveDate)->modify('+1 day')->getTimestamp()])
->andWhere(['event.active' => '1']);
@ -121,11 +128,32 @@ class EventController extends CustomerApiController
{
$interval = EventInterval::createInterval();
try {
$query = EventDetailsView::find();
$query = Event::find();
$query = $this->buildEventQuery($query, $interval);
$query = $query->andWhere(['event.id' => $id_event]);
/** @var Event $event */
$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) {
throw new \yii\base\Exception($e->getMessage());
}

View File

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

View File

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

View File

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

View File

@ -5,37 +5,37 @@ namespace customerapi\models\details;
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 $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",
"start" => "start",
"end" => "end",
"seat_count" => "seat_count",
"active" => "active",
// "reservationCount" => "reservationCount"
"seat_count" => "seatCount",
"reservationCount" => "reservationCount",
"registrations" => "registrations",
"hasFreeSeats" => "hasFreeSeats"
];
$fields['trainer'] = 'trainer';
$fields['eventType'] = 'eventType';
$fields['room'] = 'room';
$fields['equipmentTypeAssignments'] = 'equipmentTypeAssignments';
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
HOST=localhost
docker container stop fitness-web || true
docker run \
-v ${PROJECT_DIR}:/var/www/html/fitness_web \
--rm \