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

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

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 \