Daniel 11 месяцев назад
Родитель
Сommit
091e87b495
16 измененных файлов: 760 добавлений и 126 удалений
  1. +1
    -1
      angular/openapi.json
  2. +75
    -0
      angular/openapi.yaml
  3. +13
    -0
      angular/src/app/_components/search-select/search-select.component.ts
  4. +3
    -3
      angular/src/app/_views/trip/trip-detail/trip-detail.component.html
  5. +107
    -68
      angular/src/app/_views/trip/trip-detail/trip-detail.component.ts
  6. +85
    -1
      angular/src/app/_views/user-trip/user-trip-detail/user-trip-detail.component.html
  7. +260
    -18
      angular/src/app/_views/user-trip/user-trip-detail/user-trip-detail.component.ts
  8. +97
    -4
      angular/src/app/core/api/v1/api/userTripEvent.service.ts
  9. +2
    -1
      angular/src/assets/i18n/en.json
  10. +1
    -1
      httpdocs/src/ApiResource/UserApi.php
  11. +12
    -0
      httpdocs/src/ApiResource/UserTripEventApi.php
  12. +2
    -0
      httpdocs/src/Entity/UserTripEvent.php
  13. +49
    -0
      httpdocs/src/Mapper/EventApiToEntityMapper.php
  14. +12
    -8
      httpdocs/src/Mapper/UserTripApiToEntityMapper.php
  15. +40
    -20
      httpdocs/src/Mapper/UserTripEventApiToEntityMapper.php
  16. +1
    -1
      httpdocs/src/Mapper/UserTripEventEntityToApiMapper.php

+ 1
- 1
angular/openapi.json
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 75
- 0
angular/openapi.yaml Просмотреть файл

@@ -1659,6 +1659,56 @@ paths:
style: form
explode: false
allowReserved: false
-
name: userTrip
in: query
description: ''
required: false
deprecated: false
allowEmptyValue: false
schema:
type: string
style: form
explode: false
allowReserved: false
-
name: 'userTrip[]'
in: query
description: ''
required: false
deprecated: false
allowEmptyValue: false
schema:
type: array
items:
type: string
style: form
explode: true
allowReserved: false
-
name: custom_json_filter
in: query
description: ''
required: false
deprecated: false
allowEmptyValue: false
schema:
type: string
style: form
explode: false
allowReserved: false
-
name: custom_json_order
in: query
description: ''
required: false
deprecated: false
allowEmptyValue: false
schema:
type: string
style: form
explode: false
allowReserved: false
deprecated: false
post:
operationId: api_user_trip_events_post
@@ -1717,6 +1767,31 @@ paths:
explode: false
allowReserved: false
deprecated: false
delete:
operationId: api_user_trip_events_id_delete
tags:
- UserTripEvent
responses:
'204':
description: 'UserTripEvent resource deleted'
'404':
description: 'Resource not found'
summary: 'Removes the UserTripEvent resource.'
description: 'Removes the UserTripEvent resource.'
parameters:
-
name: id
in: path
description: 'UserTripEvent identifier'
required: true
deprecated: false
allowEmptyValue: false
schema:
type: string
style: simple
explode: false
allowReserved: false
deprecated: false
patch:
operationId: api_user_trip_events_id_patch
tags:


+ 13
- 0
angular/src/app/_components/search-select/search-select.component.ts Просмотреть файл

@@ -241,4 +241,17 @@ export class SearchSelectComponent implements OnInit, AfterViewInit {
} as ListColDefinition,
];
}

public static getDefaultColDefEvents(subResource?: string): ListColDefinition[] {
return [
{
name: 'name',
text: 'common.name',
type: ListComponent.COLUMN_TYPE_TEXT,
field: 'name',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_TEXT,
} as ListColDefinition,
];
}
}

+ 3
- 3
angular/src/app/_views/trip/trip-detail/trip-detail.component.html Просмотреть файл

@@ -104,7 +104,7 @@
[getDataFunction]="getUsers"
[displayedDataField]="'fullName'"
[listColDefinitions]="userColDefinitions"
[dataSet]="userTrip.userIri"
[dataSet]="userTrip.user"
(change)="onUserSelectChange(i)"
>
</app-search-select>
@@ -121,8 +121,8 @@
</div>

<div class="mt-4">
<button type="button" class="btn btn-success" (click)="saveAllUserTrips()">
{{ 'trip.save_user_assignments' | translate }}
<button type="button" class="btn btn-primary" (click)="saveAllUserTrips()">
{{ 'basic.save' | translate }}
</button>
</div>
</div>


+ 107
- 68
angular/src/app/_views/trip/trip-detail/trip-detail.component.ts Просмотреть файл

@@ -26,6 +26,7 @@ import {map} from "rxjs/operators";
export class TripDetailComponent implements OnInit, AfterViewInit {
protected trip!: TripJsonld;
protected readonly FormMode = FormMode;
protected originalUserTrips: UserTripJsonld[] = [];
protected tripLocations: TripLocationJsonld[] = [];
protected userTrips: UserTripJsonld[] = [];
protected users: UserJsonld[] = [];
@@ -212,11 +213,10 @@ export class TripDetailComponent implements OnInit, AfterViewInit {

addNewUserTrip() {
// Erstelle ein unvollständiges Objekt (ohne user-Property)
const newUserTrip: any = {
trip: {
'id': this.trip.id
} as TripJsonld,
user: undefined
const newUserTrip: UserTripJsonld = {
tripIri: this.trip.id!,
userIri: null,
completed: false
};

// Füge es als UserTripJsonld hinzu
@@ -239,16 +239,28 @@ export class TripDetailComponent implements OnInit, AfterViewInit {
1,
200,
this.trip !== undefined ? this.trip.id : undefined,
).subscribe(
data => {
).subscribe({
next: (data) => {
this.userTrips = data.member;
// Create a form for each user trip
this.originalUserTrips = [...data.member]; // Kopie der ursprünglichen UserTrips speichern

// Formulare für jeden UserTrip erstellen
this.userForms = [];
this.userTrips.forEach(() => {
this.userForms.push(this.createUserForm());
this.userTrips.forEach((userTrip, index) => {
const form = this.createUserForm();

// Formularwerte initialisieren
if (userTrip.userIri || (userTrip.user && userTrip.user.id)) {
form.get('user')?.setValue(userTrip.userIri || userTrip.user?.id);
}

this.userForms.push(form);
});
},
error: (error) => {
console.error('Fehler beim Laden der Benutzerzuweisungen:', error);
}
);
});
}

removeTripLocation(index: number) {
@@ -270,21 +282,9 @@ export class TripDetailComponent implements OnInit, AfterViewInit {
}

removeUserTrip(index: number) {
const userTripId = this.userTrips[index].id;

if (userTripId) {
// If it exists on the server, delete it
this.userTripService.userTripsIdDelete(this.appHelperService.extractId(userTripId)).subscribe(
() => {
this.userTrips.splice(index, 1);
this.userForms.splice(index, 1);
}
);
} else {
// If it's only local, just remove it from the array
this.userTrips.splice(index, 1);
this.userForms.splice(index, 1);
}
// Nur aus dem Array entfernen, tatsächliches Löschen erfolgt beim Speichern
this.userTrips.splice(index, 1);
this.userForms.splice(index, 1);
}

saveAllTripLocations() {
@@ -296,9 +296,7 @@ export class TripDetailComponent implements OnInit, AfterViewInit {
if (locationFormValue) {
// If just an ID was set, create a proper location object
if (typeof locationFormValue === 'string') {
tripLocation.location = {
'id': locationFormValue
} as LocationJsonld;
tripLocation.locationIri = locationFormValue;
}
} else {
// If no location is set, show an error
@@ -338,50 +336,91 @@ export class TripDetailComponent implements OnInit, AfterViewInit {
}

saveAllUserTrips() {
// First update the user objects in our userTrips array
this.userTrips.forEach((userTrip, index) => {
const userFormValue = this.userForms[index].get('user')?.value;

// Ensure we have a user
if (userFormValue) {
// If just an ID was set, create a proper user object
if (typeof userFormValue === 'string') {
userTrip.userIri = userFormValue;
// Aktuelle IDs speichern, um später gelöschte Einträge zu identifizieren
let originalUserTripIds: string[] = [];

// Zuerst alle existierenden UserTrips vom Server holen
this.userTripService.userTripsGetCollection(
1, 200, this.trip.dbId?.toString()
).subscribe({
next: (existingUserTrips) => {
// IDs der existierenden UserTrips speichern
originalUserTripIds = existingUserTrips.member
.filter(userTrip => userTrip.id)
.map(userTrip => this.appHelperService.extractId(userTrip.id!));

// Aktualisieren der user objects in unserem userTrips-Array
this.userTrips.forEach((userTrip, index) => {
const userFormValue = this.userForms[index].get('user')?.value;

// User setzen, wenn verfügbar
if (userFormValue) {
if (typeof userFormValue === 'string') {
userTrip.userIri = userFormValue;
}
}
});

// Filtern: Nur UserTrips mit gültigen User-Zuweisungen behalten
const validUserTrips = this.userTrips.filter(ut => ut.userIri || (ut.user && ut.user.id));

if (validUserTrips.length === 0 && this.userTrips.length > 0) {
window.alert('Bitte wähle für jeden Eintrag einen Benutzer aus oder entferne ungenutzte Einträge.');
return;
}
}
});

// Check if there are any invalid user trips (without a proper user selection)
const invalidEntries = this.userTrips.filter(ut => !ut.user || !ut.user.id);
// Array für alle Operationen erstellen
const allPromises: Promise<any>[] = [];

// 1. Neue UserTrips erstellen und existierende aktualisieren
validUserTrips.forEach(userTrip => {
if (userTrip.id) {
// Existierenden UserTrip aktualisieren
const id = this.appHelperService.extractId(userTrip.id);

// ID aus der Liste der zu löschenden IDs entfernen
const idIndex = originalUserTripIds.indexOf(id);
if (idIndex > -1) {
originalUserTripIds.splice(idIndex, 1);
}

// Update-Promise hinzufügen
allPromises.push(
firstValueFrom(this.userTripService.userTripsIdPatch(
id,
this.appHelperService.convertJsonldToJson(userTrip)
))
);
} else {
// Neuen UserTrip erstellen
allPromises.push(
firstValueFrom(this.userTripService.userTripsPost(userTrip))
);
}
});

if (invalidEntries.length > 0) {
// Show alert to the user
window.alert('Please select a user for all entries or remove unused entries before saving.');
return;
}
// 2. Gelöschte UserTrips von der Datenbank entfernen
originalUserTripIds.forEach(id => {
allPromises.push(
firstValueFrom(this.userTripService.userTripsIdDelete(id))
);
});

// At this point, all entries are valid
const savePromises = this.userTrips.map(userTrip => {
if (userTrip.id) {
// Update existing
return firstValueFrom(this.userTripService.userTripsIdPatch(
this.appHelperService.extractId(userTrip.id),
this.appHelperService.convertJsonldToJson(userTrip)
));
} else {
// Create new
return firstValueFrom(this.userTripService.userTripsPost(userTrip));
// Alle Operationen ausführen
Promise.all(allPromises)
.then(() => {
// Nach dem Speichern alle UserTrips neu laden
this.loadUserTrips();
})
.catch(error => {
console.error('Fehler beim Speichern der Benutzerzuweisungen:', error);
window.alert('Beim Speichern der Benutzerzuweisungen ist ein Fehler aufgetreten. Bitte versuche es erneut.');
});
},
error: (error) => {
console.error('Fehler beim Laden der existierenden Benutzerzuweisungen:', error);
window.alert('Die aktuellen Benutzerzuweisungen konnten nicht geladen werden. Bitte versuche es erneut.');
}
});

Promise.all(savePromises)
.then(() => {
// Reload user trips to get updated data
this.loadUserTrips();
})
.catch(error => {
console.error('Error saving user trips:', error);
window.alert('An error occurred while saving user assignments. Please try again.');
});
}
}

+ 85
- 1
angular/src/app/_views/user-trip/user-trip-detail/user-trip-detail.component.html Просмотреть файл

@@ -16,7 +16,91 @@
</app-user-trip-form>
</mat-tab>
<mat-tab label="{{ 'user_trip.events' | translate }}">
<div>
<h4 class="mb-4">{{ 'user_trip.events' | translate }}</h4>

<div *ngFor="let userTripEvent of userTripEvents; let i = index" class="mb-4">
<div class="row">
<div class="col-12 col-md-3 mb-3">
<label [for]="'event_' + i" class="form-label">{{ 'model.event' | translate }}*:</label>
<app-search-select
[formId]="'eventIri'"
[formLabelLangKey]="'model.event'"
[documentForm]="eventForms[i]"
[getDataFunction]="getEvents"
[displayedDataField]="'name'"
[listColDefinitions]="eventColDefinitions"
[dataSet]="userTripEvent.event"
>
</app-search-select>
</div>

<div class="col-12 col-md-3 mb-3">
<label [for]="'location_' + i" class="form-label">{{ 'model.location' | translate }}*:</label>
<app-search-select
[formId]="'locationIri'"
[formLabelLangKey]="'model.location'"
[documentForm]="eventForms[i]"
[getDataFunction]="getLocations"
[displayedDataField]="'name'"
[listColDefinitions]="locationColDefinitions"
[dataSet]="userTripEvent.location"
>
</app-search-select>
</div>

<div class="col-12 col-md-2 mb-3">
<label class="form-label">{{ 'user_trip.event_date' | translate }} ({{ 'common.date' | translate }}):</label>
<div>
<input
type="date"
class="form-control"
[value]="formatDateForInput(userTripEvent.date)"
(change)="onDateInputChange($event, i)"
/>
</div>
</div>

<div class="col-12 col-md-2 mb-3">
<label class="form-label">{{ 'user_trip.event_date' | translate }} ({{ 'common.time' | translate }}):</label>
<div>
<input
type="time"
class="form-control"
[value]="formatTimeForInput(userTripEvent.date)"
(change)="onTimeInputChange($event, i)"
/>
</div>
</div>

<div class="col-12 col-md-1 mb-3 d-flex align-items-end">
<button type="button" class="btn btn-danger" (click)="removeUserTripEvent(i)">X</button>
</div>
</div>

<div class="row">
<div class="col-12 mb-3">
<label [for]="'note_' + i" class="form-label">{{ 'common.note' | translate }}:</label>
<textarea
class="form-control"
[id]="'note_' + i"
rows="2"
[(ngModel)]="userTripEvent.note"
></textarea>
</div>
</div>
</div>

<div class="my-3">
<button type="button" class="btn btn-primary" (click)="addNewUserTripEvent()">+</button>
</div>

<div class="mt-4">
<button type="button" class="btn btn-success" (click)="saveAllUserTripEvents()">
{{ 'basic.save' | translate }}
</button>
</div>
</div>
</mat-tab>
</mat-tab-group>
}
}

+ 260
- 18
angular/src/app/_views/user-trip/user-trip-detail/user-trip-detail.component.ts Просмотреть файл

@@ -1,52 +1,294 @@
import { Component } from '@angular/core';
import { Component, OnInit, AfterViewInit, QueryList, ViewChildren } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import {
LocationService,
TripJsonld,
TripLocationService,
TripService, UserService,
LocationJsonld,
UserService,
UserTripJsonld,
UserTripService
UserTripService,
UserTripEventJsonld,
UserTripEventService,
EventService,
EventJsonld
} from "@app/core/api/v1";
import {AppHelperService} from "@app/_helpers/app-helper.service";
import {ActivatedRoute} from "@angular/router";
import {FormBuilder} from "@angular/forms";
import {FormMode, FormSubmitEvent} from "@app/_components/_abstract/abstract-data-form-component";
import {ModalStatus} from "@app/_helpers/modal.states";
import { AppHelperService } from "@app/_helpers/app-helper.service";
import { ActivatedRoute } from "@angular/router";
import { FormMode, FormSubmitEvent } from "@app/_components/_abstract/abstract-data-form-component";
import { ModalStatus } from "@app/_helpers/modal.states";
import { firstValueFrom, Observable } from 'rxjs';
import { ListColDefinition } from '@app/_components/list/list-col-definition';
import { SearchSelectComponent } from '@app/_components/search-select/search-select.component';
import { ListComponent } from '@app/_components/list/list.component';
import { FilterBarComponent } from "@app/_components/filter-bar/filter-bar.component";

@Component({
selector: 'app-user-trip-detail',
templateUrl: './user-trip-detail.component.html',
styleUrl: './user-trip-detail.component.scss'
selector: 'app-user-trip-detail',
templateUrl: './user-trip-detail.component.html',
styleUrl: './user-trip-detail.component.scss'
})
export class UserTripDetailComponent {
export class UserTripDetailComponent implements OnInit, AfterViewInit {
protected userTrip!: UserTripJsonld;
protected readonly FormMode = FormMode;

// Events related properties
protected userTripEvents: UserTripEventJsonld[] = [];
protected originalUserTripEvents: UserTripEventJsonld[] = []; // Original-Events zum Vergleich
protected eventForms: FormGroup[] = [];
protected locationColDefinitions: ListColDefinition[] = SearchSelectComponent.getDefaultColDefLocations();
protected eventColDefinitions: ListColDefinition[] = [];

@ViewChildren(SearchSelectComponent) searchSelects!: QueryList<SearchSelectComponent>;

constructor(
private tripService: TripService,
private tripLocationService: TripLocationService,
private locationService: LocationService,
private userTripService: UserTripService,
private userService: UserService,
private userTripEventService: UserTripEventService,
private eventService: EventService,
protected appHelperService: AppHelperService,
private route: ActivatedRoute,
private fb: FormBuilder
) {}
) {
// Initialize event column definitions
this.eventColDefinitions = SearchSelectComponent.getDefaultColDefEvents();
}

ngOnInit() {
this.route.params.subscribe(params => {
this.userTripService.userTripsIdGet(params['id']).subscribe(
data => {
this.userTrip = data;
this.loadUserTripEvents();
}
);
});
}

ngAfterViewInit() {
// Reinitialize search selects when they change
this.searchSelects.changes.subscribe(components => {
components.forEach((component: SearchSelectComponent) => {
// Force search selects to initialize
if (component.dataSet) {
component.ngAfterViewInit();
}
});
});
}

onFormUpdate(event: FormSubmitEvent<UserTripJsonld>) {
if (event.status === ModalStatus.Submitted && event.data) {
this.userTrip = event.data;
}
}

}
loadUserTripEvents() {
if (!this.userTrip || !this.userTrip.id) return;

this.userTripEventService.userTripEventsGetCollection(
1,
200,
this.userTrip.id
).subscribe({
next: (data) => {
this.userTripEvents = data.member;
this.originalUserTripEvents = [...data.member]; // Kopie der ursprünglichen Events speichern

// Formular für jedes Event erstellen
this.eventForms = [];
this.userTripEvents.forEach((event, index) => {
const form = this.createEventForm();

// Formularwerte initialisieren
if (event.eventIri) {
form.get('eventIri')?.setValue(event.eventIri);
}
if (event.locationIri) {
form.get('locationIri')?.setValue(event.locationIri);
}

this.eventForms.push(form);
});
},
error: (error) => {
console.error('Fehler beim Laden der Events:', error);
}
});
}

createEventForm(): FormGroup {
return this.fb.group({
eventIri: [null],
locationIri: [null]
});
}

getEvents = (page: number, pageSize: number, term?: string): Observable<any> => {
return this.eventService.eventsGetCollection(page, pageSize, undefined, term);
}

getLocations = (page: number, pageSize: number, term?: string): Observable<any> => {
return this.locationService.locationsGetCollection(page, pageSize, undefined, term);
}

formatDateForInput(dateString: string): string {
if (!dateString) return '';
const date = new Date(dateString);
return date.toISOString().split('T')[0];
}

formatTimeForInput(dateString: string): string {
if (!dateString) return '';
const date = new Date(dateString);
return date.toTimeString().slice(0, 5);
}

onDateInputChange(event: Event, index: number) {
const input = event.target as HTMLInputElement;
const currentDate = new Date(this.userTripEvents[index].date || new Date());
const [year, month, day] = input.value.split('-').map(Number);

currentDate.setFullYear(year, month - 1, day);
this.userTripEvents[index].date = currentDate.toISOString();
}

onTimeInputChange(event: Event, index: number) {
const input = event.target as HTMLInputElement;
const currentDate = new Date(this.userTripEvents[index].date || new Date());
const [hours, minutes] = input.value.split(':').map(Number);

currentDate.setHours(hours, minutes);
this.userTripEvents[index].date = currentDate.toISOString();
}

addNewUserTripEvent() {
// Neues leeres User-Trip-Event erstellen
const newUserTripEvent: UserTripEventJsonld = {
userTripIri: this.userTrip.id!,
date: new Date().toISOString(),
eventIri: null,
locationIri: null,
note: null
};

this.userTripEvents.push(newUserTripEvent);
this.eventForms.push(this.createEventForm());

// Im nächsten Event-Loop erzwingen, dass die search-select-Komponenten initialisiert werden
setTimeout(() => {
if (this.searchSelects) {
const lastSelects = this.searchSelects.toArray().slice(-2);
lastSelects.forEach(select => {
if (select) {
select.ngAfterViewInit();
}
});
}
});
}

removeUserTripEvent(index: number) {
// Nur aus dem Array entfernen, tatsächliches Löschen erfolgt beim Speichern
this.userTripEvents.splice(index, 1);
this.eventForms.splice(index, 1);
}

saveAllUserTripEvents() {
// Aktuelle IDs speichern, um später gelöschte Einträge zu identifizieren
let originalEventIds: string[] = [];

// Zuerst alle existierenden Events vom Server holen
this.userTripEventService.userTripEventsGetCollection(
1, 200, this.userTrip.id
).subscribe({
next: (existingEvents) => {
// IDs der existierenden Events speichern
originalEventIds = existingEvents.member
.filter(event => event.id)
.map(event => this.appHelperService.extractId(event.id!));

// Aktualisieren der Event- und Location-Objekte in unserem userTripEvents-Array
this.userTripEvents.forEach((userTripEvent, index) => {
const eventFormValue = this.eventForms[index].get('eventIri')?.value;
const locationFormValue = this.eventForms[index].get('locationIri')?.value;

// Event setzen, wenn verfügbar
if (eventFormValue) {
if (typeof eventFormValue === 'string') {
userTripEvent.eventIri = eventFormValue;
}
}

// Location setzen, wenn verfügbar
if (locationFormValue) {
if (typeof locationFormValue === 'string') {
userTripEvent.locationIri = locationFormValue;
}
}
});

// Events filtern, die sowohl Event als auch Location haben
const validUserTripEvents = this.userTripEvents.filter(
ute => ute.eventIri && ute.locationIri
);

if (validUserTripEvents.length === 0 && this.userTripEvents.length > 0) {
window.alert('Bitte stelle sicher, dass alle Events sowohl einen Event-Typ als auch einen Ort haben.');
return;
}

// Array für alle Operationen erstellen
const allPromises: Promise<any>[] = [];

// 1. Neue Events erstellen und existierende aktualisieren
validUserTripEvents.forEach(userTripEvent => {
if (userTripEvent.id) {
// Existierendes Event aktualisieren
const id = this.appHelperService.extractId(userTripEvent.id);

// ID aus der Liste der zu löschenden IDs entfernen
const idIndex = originalEventIds.indexOf(id);
if (idIndex > -1) {
originalEventIds.splice(idIndex, 1);
}

// Update-Promise hinzufügen
allPromises.push(
firstValueFrom(this.userTripEventService.userTripEventsIdPatch(
id,
this.appHelperService.convertJsonldToJson(userTripEvent)
))
);
} else {
// Neues Event erstellen
allPromises.push(
firstValueFrom(this.userTripEventService.userTripEventsPost(userTripEvent))
);
}
});

// 2. Gelöschte Events von der Datenbank entfernen
originalEventIds.forEach(id => {
allPromises.push(
firstValueFrom(this.userTripEventService.userTripEventsIdDelete(id))
);
});

// Alle Operationen ausführen
Promise.all(allPromises)
.then(() => {
// Nach dem Speichern alle Events neu laden
this.loadUserTripEvents();
})
.catch(error => {
console.error('Fehler beim Speichern der Events:', error);
window.alert('Beim Speichern der Events ist ein Fehler aufgetreten. Bitte versuche es erneut.');
});
},
error: (error) => {
console.error('Fehler beim Laden der existierenden Events:', error);
window.alert('Die aktuellen Events konnten nicht geladen werden. Bitte versuche es erneut.');
}
});
}
}

+ 97
- 4
angular/src/app/core/api/v1/api/userTripEvent.service.ts Просмотреть файл

@@ -100,13 +100,17 @@ export class UserTripEventService {
* Retrieves the collection of UserTripEvent resources.
* @param page The collection page number
* @param itemsPerPage The number of items per page
* @param userTrip
* @param userTrip2
* @param customJsonFilter
* @param customJsonOrder
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public userTripEventsGetCollection(page?: number, itemsPerPage?: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<ApiUserTripEventsGetCollection200Response>;
public userTripEventsGetCollection(page?: number, itemsPerPage?: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<ApiUserTripEventsGetCollection200Response>>;
public userTripEventsGetCollection(page?: number, itemsPerPage?: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<ApiUserTripEventsGetCollection200Response>>;
public userTripEventsGetCollection(page?: number, itemsPerPage?: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<any> {
public userTripEventsGetCollection(page?: number, itemsPerPage?: number, userTrip?: string, userTrip2?: Array<string>, customJsonFilter?: string, customJsonOrder?: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<ApiUserTripEventsGetCollection200Response>;
public userTripEventsGetCollection(page?: number, itemsPerPage?: number, userTrip?: string, userTrip2?: Array<string>, customJsonFilter?: string, customJsonOrder?: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<ApiUserTripEventsGetCollection200Response>>;
public userTripEventsGetCollection(page?: number, itemsPerPage?: number, userTrip?: string, userTrip2?: Array<string>, customJsonFilter?: string, customJsonOrder?: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<ApiUserTripEventsGetCollection200Response>>;
public userTripEventsGetCollection(page?: number, itemsPerPage?: number, userTrip?: string, userTrip2?: Array<string>, customJsonFilter?: string, customJsonOrder?: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<any> {

let localVarQueryParameters = new HttpParams({encoder: this.encoder});
if (page !== undefined && page !== null) {
@@ -117,6 +121,24 @@ export class UserTripEventService {
localVarQueryParameters = this.addToHttpParams(localVarQueryParameters,
<any>itemsPerPage, 'itemsPerPage');
}
if (userTrip !== undefined && userTrip !== null) {
localVarQueryParameters = this.addToHttpParams(localVarQueryParameters,
<any>userTrip, 'userTrip');
}
if (userTrip2) {
userTrip2.forEach((element) => {
localVarQueryParameters = this.addToHttpParams(localVarQueryParameters,
<any>element, 'userTrip[]');
})
}
if (customJsonFilter !== undefined && customJsonFilter !== null) {
localVarQueryParameters = this.addToHttpParams(localVarQueryParameters,
<any>customJsonFilter, 'custom_json_filter');
}
if (customJsonOrder !== undefined && customJsonOrder !== null) {
localVarQueryParameters = this.addToHttpParams(localVarQueryParameters,
<any>customJsonOrder, 'custom_json_order');
}

let localVarHeaders = this.defaultHeaders;

@@ -176,6 +198,77 @@ export class UserTripEventService {
);
}

/**
* Removes the UserTripEvent resource.
* Removes the UserTripEvent resource.
* @param id UserTripEvent identifier
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public userTripEventsIdDelete(id: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<any>;
public userTripEventsIdDelete(id: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<any>>;
public userTripEventsIdDelete(id: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<any>>;
public userTripEventsIdDelete(id: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<any> {
if (id === null || id === undefined) {
throw new Error('Required parameter id was null or undefined when calling userTripEventsIdDelete.');
}

let localVarHeaders = this.defaultHeaders;

let localVarCredential: string | undefined;
// authentication (JWT) required
localVarCredential = this.configuration.lookupCredential('JWT');
if (localVarCredential) {
localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential);
}

let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
if (localVarHttpHeaderAcceptSelected === undefined) {
// to determine the Accept header
const httpHeaderAccepts: string[] = [
];
localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
}
if (localVarHttpHeaderAcceptSelected !== undefined) {
localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
}

let localVarHttpContext: HttpContext | undefined = options && options.context;
if (localVarHttpContext === undefined) {
localVarHttpContext = new HttpContext();
}

let localVarTransferCache: boolean | undefined = options && options.transferCache;
if (localVarTransferCache === undefined) {
localVarTransferCache = true;
}


let responseType_: 'text' | 'json' | 'blob' = 'json';
if (localVarHttpHeaderAcceptSelected) {
if (localVarHttpHeaderAcceptSelected.startsWith('text')) {
responseType_ = 'text';
} else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) {
responseType_ = 'json';
} else {
responseType_ = 'blob';
}
}

let localVarPath = `/api/user_trip_events/${this.configuration.encodeParam({name: "id", value: id, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined})}`;
return this.httpClient.request<any>('delete', `${this.configuration.basePath}${localVarPath}`,
{
context: localVarHttpContext,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
transferCache: localVarTransferCache,
reportProgress: reportProgress
}
);
}

/**
* Retrieves a UserTripEvent resource.
* Retrieves a UserTripEvent resource.


+ 2
- 1
angular/src/assets/i18n/en.json Просмотреть файл

@@ -59,7 +59,8 @@
"shipping_company": "Shipping Company",
"trip": "Trip",
"user": "User",
"user_trip": "User Trip"
"user_trip": "User Trip",
"event": "Event"
},
"location":
{


+ 1
- 1
httpdocs/src/ApiResource/UserApi.php Просмотреть файл

@@ -88,7 +88,7 @@ class UserApi
*/
#[ApiProperty(readable: false)]
#[Assert\NotBlank(groups: ['postValidation'])]
public string $password;
public ?string $password = null;

// Object is null ONLY during deserialization: so this allows isPublished
// to be writable in ALL cases (which is ok because the operations are secured).


+ 12
- 0
httpdocs/src/ApiResource/UserTripEventApi.php Просмотреть файл

@@ -2,8 +2,11 @@

namespace App\ApiResource;

use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
@@ -11,6 +14,8 @@ use ApiPlatform\Metadata\Post;
use ApiPlatform\Doctrine\Orm\State\Options;
use App\Entity\UserTrip;
use App\Entity\UserTripEvent;
use App\Filter\CustomJsonFilter;
use App\Filter\CustomJsonOrderFilter;
use App\State\EntityClassDtoStateProcessor;
use App\State\EntityToDtoStateProvider;
use Symfony\Component\Validator\Constraints as Assert;
@@ -32,12 +37,19 @@ use Symfony\Component\Validator\Constraints\NotBlank;
new Patch(
security: 'is_granted("ROLE_ADMIN")'
),
new Delete(
security: 'is_granted("ROLE_ADMIN")'
)
],
security: 'is_granted("ROLE_USER")',
provider: EntityToDtoStateProvider::class,
processor: EntityClassDtoStateProcessor::class,
stateOptions: new Options(entityClass: UserTripEvent::class),
)]

#[ApiFilter(SearchFilter::class, properties: ['userTrip' => 'exact'])]
#[ApiFilter(CustomJsonFilter::class)]
#[ApiFilter(CustomJsonOrderFilter::class)]
class UserTripEventApi
{
#[ApiProperty(readable: false, writable: false, identifier: true)]


+ 2
- 0
httpdocs/src/Entity/UserTripEvent.php Просмотреть файл

@@ -40,10 +40,12 @@ class UserTripEvent
public function __construct(
UserTrip $userTrip,
Event $event,
Location $location,
DateTimeImmutable $date
) {
$this->userTrip = $userTrip;
$this->event = $event;
$this->location = $location;
$this->date = $date;
$this->createdAt = new DateTimeImmutable();
}


+ 49
- 0
httpdocs/src/Mapper/EventApiToEntityMapper.php Просмотреть файл

@@ -0,0 +1,49 @@
<?php

namespace App\Mapper;

use App\ApiResource\EventApi;
use App\Entity\Event;
use App\Repository\EventRepository;
use Symfonycasts\MicroMapper\AsMapper;
use Symfonycasts\MicroMapper\MapperInterface;

#[AsMapper(from: EventApi::class, to: Event::class)]
class EventApiToEntityMapper implements MapperInterface
{
public function __construct(
private EventRepository $repository,
) {
}

public function load(object $from, string $toClass, array $context): object
{
$dto = $from;
assert($dto instanceof EventApi);

if ($dto->id) {
$entity = $this->repository->find($dto->id);
if (!$entity) {
throw new \Exception('Event not found');
}
return $entity;
}

return new Event();
}

public function populate(object $from, object $to, array $context): object
{
$dto = $from;
$entity = $to;
assert($dto instanceof EventApi);
assert($entity instanceof Event);

$entity->setName($dto->name);
$entity->setIdentifier($dto->identifier);
$entity->setSequence($dto->sequence);
$entity->setMandatory($dto->mandatory);

return $entity;
}
}

+ 12
- 8
httpdocs/src/Mapper/UserTripApiToEntityMapper.php Просмотреть файл

@@ -3,6 +3,8 @@
namespace App\Mapper;

use App\ApiResource\UserTripApi;
use App\Entity\Trip;
use App\Entity\User;
use App\Entity\UserTrip;
use App\Repository\MediaObjectRepository;
use App\Repository\UserTripRepository;
@@ -19,7 +21,8 @@ class UserTripApiToEntityMapper implements MapperInterface
private UserTripRepository $repository,
private TripRepository $tripRepository,
private UserRepository $userRepository,
private MediaObjectRepository $mediaObjectRepository
private MediaObjectRepository $mediaObjectRepository,
private MicroMapperInterface $microMapper
) {
}

@@ -37,24 +40,25 @@ class UserTripApiToEntityMapper implements MapperInterface
}

// For new user trips, we need trip and user
if (!$dto->trip?->id || !$dto->user?->id) {
if (!$dto->tripIri || !$dto->userIri) {
throw new \Exception('Trip and User are required for new user trips');
}

$trip = $this->tripRepository->find($dto->trip->id);
$trip = $this->microMapper->map($dto->tripIri, Trip::class, [
MicroMapperInterface::MAX_DEPTH => 1,
]);
if (!$trip) {
throw new \Exception('Trip not found');
}

$user = $this->userRepository->find($dto->user->id);
$user = $this->microMapper->map($dto->userIri, User::class, [
MicroMapperInterface::MAX_DEPTH => 1,
]);
if (!$user) {
throw new \Exception('User not found');
}

return new UserTrip(
$trip,
$user,
);
return new UserTrip($trip, $user);
}

public function populate(object $from, object $to, array $context): object


+ 40
- 20
httpdocs/src/Mapper/UserTripEventApiToEntityMapper.php Просмотреть файл

@@ -2,11 +2,13 @@

namespace App\Mapper;

use App\ApiResource\UserTripApi;
use App\ApiResource\UserTripEventApi;
use App\Entity\Event;
use App\Entity\Location;
use App\Entity\UserTrip;
use App\Entity\UserTripEvent;
use App\Repository\UserTripEventRepository;
use App\Repository\UserTripRepository;
use App\Repository\EventRepository;
use Symfonycasts\MicroMapper\AsMapper;
use Symfonycasts\MicroMapper\MapperInterface;
use Symfonycasts\MicroMapper\MicroMapperInterface;
@@ -16,8 +18,7 @@ class UserTripEventApiToEntityMapper implements MapperInterface
{
public function __construct(
private UserTripEventRepository $repository,
private UserTripRepository $userTripRepository,
private EventRepository $eventRepository,
private MicroMapperInterface $microMapper
) {
}

@@ -35,21 +36,32 @@ class UserTripEventApiToEntityMapper implements MapperInterface
}

// For new user trip events, we need userTrip and event
if (!$dto->userTrip?->id || !$dto->event?->id) {
if (!$dto->userTripIri || !$dto->eventIri || !$dto->locationIri) {
throw new \Exception('UserTrip and Event are required for new user trip events');
}

$userTrip = $this->userTripRepository->find($dto->userTrip->id);
$userTrip = $this->microMapper->map($dto->userTripIri, UserTrip::class, [
MicroMapperInterface::MAX_DEPTH => 1,
]);
if (!$userTrip) {
throw new \Exception('UserTrip not found');
}

$event = $this->eventRepository->find($dto->event->id);
$event = $this->microMapper->map($dto->eventIri, Event::class, [
MicroMapperInterface::MAX_DEPTH => 1,
]);
if (!$event) {
throw new \Exception('Event not found');
}

return new UserTripEvent($userTrip, $event, $dto->date);
$location = $this->microMapper->map($dto->locationIri, Location::class, [
MicroMapperInterface::MAX_DEPTH => 1,
]);
if (!$location) {
throw new \Exception('Location not found');
}

return new UserTripEvent($userTrip, $event, $location, $dto->date);
}

public function populate(object $from, object $to, array $context): object
@@ -59,21 +71,29 @@ class UserTripEventApiToEntityMapper implements MapperInterface
assert($dto instanceof UserTripEventApi);
assert($entity instanceof UserTripEvent);

if ($dto->event) {
$event = $this->eventRepository->find($dto->event->id);
if (!$event) {
throw new \Exception('Event not found');
}
$entity->setEvent($event);
$userTrip = $this->microMapper->map($dto->userTripIri, UserTrip::class, [
MicroMapperInterface::MAX_DEPTH => 1,
]);
if (!$userTrip) {
throw new \Exception('UserTrip not found');
}
$entity->setUserTrip($userTrip);

if ($dto->userTrip) {
$userTrip = $this->userTripRepository->find($dto->userTrip->id);
if (!$userTrip) {
throw new \Exception('UserTrip not found');
}
$entity->setUserTrip($userTrip);
$event = $this->microMapper->map($dto->eventIri, Event::class, [
MicroMapperInterface::MAX_DEPTH => 1,
]);
if (!$event) {
throw new \Exception('Event not found');
}
$entity->setEvent($event);

$location = $this->microMapper->map($dto->locationIri, Location::class, [
MicroMapperInterface::MAX_DEPTH => 1,
]);
if (!$location) {
throw new \Exception('Location not found');
}
$entity->setLocation($location);

$entity->setNote($dto->note);
$entity->setDate($dto->date);


+ 1
- 1
httpdocs/src/Mapper/UserTripEventEntityToApiMapper.php Просмотреть файл

@@ -50,7 +50,7 @@ class UserTripEventEntityToApiMapper implements MapperInterface
MicroMapperInterface::MAX_DEPTH => 1,
]);

$dto->locationIri = $dto->location = $this->microMapper->map($entity->getEvent(), LocationApi::class, [
$dto->locationIri = $dto->location = $this->microMapper->map($entity->getLocation(), LocationApi::class, [
MicroMapperInterface::MAX_DEPTH => 1,
]);



Загрузка…
Отмена
Сохранить