Quellcode durchsuchen

a lot of new filed and features

master
Daniel vor 10 Monaten
Ursprung
Commit
4ede9bac3f
60 geänderte Dateien mit 829 neuen und 212 gelöschten Zeilen
  1. +1
    -1
      angular/openapi.json
  2. +88
    -8
      angular/openapi.yaml
  3. +1
    -1
      angular/src/app/_components/_abstract/abstract-data-form-component.ts
  4. +131
    -9
      angular/src/app/_components/search-select/search-select.component.ts
  5. +24
    -2
      angular/src/app/_forms/apiForms.ts
  6. +7
    -0
      angular/src/app/_views/location/location-form/location-form.component.html
  7. +2
    -59
      angular/src/app/_views/location/location-list/location-list.component.ts
  8. +25
    -0
      angular/src/app/_views/trip/trip-detail/trip-detail.component.html
  9. +117
    -63
      angular/src/app/_views/trip/trip-detail/trip-detail.component.ts
  10. +0
    -4
      angular/src/app/_views/trip/trip-form/trip-form.component.html
  11. +6
    -1
      angular/src/app/_views/trip/trip-form/trip-form.component.ts
  12. +0
    -8
      angular/src/app/_views/trip/trip-list/trip-list.component.ts
  13. +0
    -6
      angular/src/app/_views/user-trip/user-trip-detail/user-trip-detail.component.ts
  14. +7
    -0
      angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.html
  15. +2
    -2
      angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.ts
  16. +8
    -0
      angular/src/app/_views/user-trip/user-trip-list/user-trip-list.component.ts
  17. +7
    -0
      angular/src/app/_views/user/user-form/user-form.component.html
  18. +17
    -0
      angular/src/app/_views/user/user-list/user-list.component.ts
  19. +44
    -0
      angular/src/app/_views/vessel/vessel-form/vessel-form.component.html
  20. +1
    -3
      angular/src/app/_views/vessel/vessel-list/vessel-list.component.ts
  21. +9
    -4
      angular/src/app/core/api/v1/api/user.service.ts
  22. +1
    -0
      angular/src/app/core/api/v1/model/location.ts
  23. +1
    -0
      angular/src/app/core/api/v1/model/locationJsonld.ts
  24. +1
    -1
      angular/src/app/core/api/v1/model/trip.ts
  25. +1
    -1
      angular/src/app/core/api/v1/model/tripJsonld.ts
  26. +2
    -0
      angular/src/app/core/api/v1/model/tripLocation.ts
  27. +2
    -0
      angular/src/app/core/api/v1/model/tripLocationJsonld.ts
  28. +1
    -0
      angular/src/app/core/api/v1/model/user.ts
  29. +1
    -0
      angular/src/app/core/api/v1/model/userJsonld.ts
  30. +1
    -0
      angular/src/app/core/api/v1/model/userTrip.ts
  31. +1
    -0
      angular/src/app/core/api/v1/model/userTripJsonld.ts
  32. +6
    -0
      angular/src/app/core/api/v1/model/vessel.ts
  33. +6
    -0
      angular/src/app/core/api/v1/model/vesselJsonld.ts
  34. +29
    -14
      angular/src/assets/i18n/en.json
  35. +65
    -0
      httpdocs/migrations/Version20250428094124.php
  36. +2
    -0
      httpdocs/src/ApiResource/LocationApi.php
  37. +2
    -2
      httpdocs/src/ApiResource/TripApi.php
  38. +5
    -1
      httpdocs/src/ApiResource/TripLocationApi.php
  39. +5
    -0
      httpdocs/src/ApiResource/UserApi.php
  40. +3
    -0
      httpdocs/src/ApiResource/UserTripApi.php
  41. +12
    -0
      httpdocs/src/ApiResource/VesselApi.php
  42. +13
    -0
      httpdocs/src/Entity/Location.php
  43. +13
    -14
      httpdocs/src/Entity/Trip.php
  44. +26
    -0
      httpdocs/src/Entity/TripLocation.php
  45. +13
    -0
      httpdocs/src/Entity/User.php
  46. +13
    -0
      httpdocs/src/Entity/UserTrip.php
  47. +78
    -0
      httpdocs/src/Entity/Vessel.php
  48. +3
    -2
      httpdocs/src/Filter/CustomJsonFilter.php
  49. +1
    -0
      httpdocs/src/Mapper/LocationApiToEntityMapper.php
  50. +1
    -0
      httpdocs/src/Mapper/LocationEntityToApiMapper.php
  51. +0
    -1
      httpdocs/src/Mapper/TripApiToEntityMapper.php
  52. +1
    -1
      httpdocs/src/Mapper/TripEntityToApiMapper.php
  53. +2
    -0
      httpdocs/src/Mapper/TripLocationApiToEntityMapper.php
  54. +4
    -2
      httpdocs/src/Mapper/TripLocationEntityToApiMapper.php
  55. +1
    -0
      httpdocs/src/Mapper/UserApiToEntityMapper.php
  56. +1
    -0
      httpdocs/src/Mapper/UserEntityToApiMapper.php
  57. +1
    -0
      httpdocs/src/Mapper/UserTripApiToEntityMapper.php
  58. +1
    -0
      httpdocs/src/Mapper/UserTripEntityToApiMapper.php
  59. +6
    -1
      httpdocs/src/Mapper/VesselApiToEntityMapper.php
  60. +7
    -1
      httpdocs/src/Mapper/VesselEntityToApiMapper.php

+ 1
- 1
angular/openapi.json
Datei-Diff unterdrückt, da er zu groß ist
Datei anzeigen


+ 88
- 8
angular/openapi.yaml Datei anzeigen

@@ -1937,6 +1937,18 @@ paths:
style: form style: form
explode: false explode: false
allowReserved: false 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 name: custom_json_order
in: query in: query
@@ -3947,6 +3959,8 @@ components:
type: boolean type: boolean
isPort: isPort:
type: boolean type: boolean
isStartEnd:
type: boolean
createdAt: createdAt:
readOnly: true readOnly: true
type: type:
@@ -4012,6 +4026,8 @@ components:
type: boolean type: boolean
isPort: isPort:
type: boolean type: boolean
isStartEnd:
type: boolean
createdAt: createdAt:
readOnly: true readOnly: true
type: type:
@@ -4171,10 +4187,6 @@ components:
type: type:
- string - string
- 'null' - 'null'
captainName:
type:
- string
- 'null'
startLocation: startLocation:
readOnly: true readOnly: true
type: string type: string
@@ -4207,6 +4219,8 @@ components:
type: type:
- string - string
- 'null' - 'null'
completed:
type: boolean
createdAt: createdAt:
readOnly: true readOnly: true
type: type:
@@ -4270,10 +4284,6 @@ components:
type: type:
- string - string
- 'null' - 'null'
captainName:
type:
- string
- 'null'
startLocation: startLocation:
readOnly: true readOnly: true
$ref: '#/components/schemas/Location.jsonld' $ref: '#/components/schemas/Location.jsonld'
@@ -4302,6 +4312,8 @@ components:
type: type:
- string - string
- 'null' - 'null'
completed:
type: boolean
createdAt: createdAt:
readOnly: true readOnly: true
type: type:
@@ -4342,6 +4354,10 @@ components:
example: 'https://example.com/' example: 'https://example.com/'
isArrival: isArrival:
type: boolean type: boolean
isTransit:
type: boolean
isDeparture:
type: boolean
date: date:
type: string type: string
format: date-time format: date-time
@@ -4406,6 +4422,10 @@ components:
example: 'https://example.com/' example: 'https://example.com/'
isArrival: isArrival:
type: boolean type: boolean
isTransit:
type: boolean
isDeparture:
type: boolean
date: date:
type: string type: string
format: date-time format: date-time
@@ -4471,6 +4491,8 @@ components:
type: boolean type: boolean
active: active:
type: boolean type: boolean
isPilot:
type: boolean
roles: roles:
readOnly: true readOnly: true
type: array type: array
@@ -4489,6 +4511,7 @@ components:
- lastName - lastName
- isAdmin - isAdmin
- active - active
- isPilot
User.jsonld: User.jsonld:
type: object type: object
description: '' description: ''
@@ -4562,6 +4585,8 @@ components:
type: boolean type: boolean
active: active:
type: boolean type: boolean
isPilot:
type: boolean
roles: roles:
readOnly: true readOnly: true
type: array type: array
@@ -4580,6 +4605,7 @@ components:
- lastName - lastName
- isAdmin - isAdmin
- active - active
- isPilot
UserTrip: UserTrip:
type: object type: object
description: '' description: ''
@@ -4618,6 +4644,8 @@ components:
- 'null' - 'null'
completed: completed:
type: boolean type: boolean
approved:
type: boolean
signature: signature:
readOnly: true readOnly: true
type: string type: string
@@ -4649,6 +4677,7 @@ components:
- tripIri - tripIri
- userIri - userIri
- completed - completed
- approved
UserTrip.jsonld: UserTrip.jsonld:
type: object type: object
description: '' description: ''
@@ -4706,6 +4735,8 @@ components:
- 'null' - 'null'
completed: completed:
type: boolean type: boolean
approved:
type: boolean
signature: signature:
readOnly: true readOnly: true
$ref: '#/components/schemas/MediaObject.jsonld' $ref: '#/components/schemas/MediaObject.jsonld'
@@ -4735,6 +4766,7 @@ components:
- tripIri - tripIri
- userIri - userIri
- completed - completed
- approved
UserTripEvent: UserTripEvent:
type: object type: object
description: '' description: ''
@@ -4905,6 +4937,30 @@ components:
- 'null' - 'null'
format: iri-reference format: iri-reference
example: 'https://example.com/' example: 'https://example.com/'
length:
type:
- number
- 'null'
breadth:
type:
- number
- 'null'
draft:
type:
- number
- 'null'
grossTonnage:
type:
- number
- 'null'
imoNo:
type:
- string
- 'null'
callSign:
type:
- string
- 'null'
createdAt: createdAt:
readOnly: true readOnly: true
type: type:
@@ -4961,6 +5017,30 @@ components:
- 'null' - 'null'
format: iri-reference format: iri-reference
example: 'https://example.com/' example: 'https://example.com/'
length:
type:
- number
- 'null'
breadth:
type:
- number
- 'null'
draft:
type:
- number
- 'null'
grossTonnage:
type:
- number
- 'null'
imoNo:
type:
- string
- 'null'
callSign:
type:
- string
- 'null'
createdAt: createdAt:
readOnly: true readOnly: true
type: type:


+ 1
- 1
angular/src/app/_components/_abstract/abstract-data-form-component.ts Datei anzeigen

@@ -88,7 +88,7 @@ export abstract class AbstractDataFormComponent<T extends { [key: string]: any }
return; return;
} }


this.translateService.get('basic.delete_confirm').subscribe((confirmMessage: string) => {
this.translateService.get('basic.delete_confirm_related').subscribe((confirmMessage: string) => {
if (confirm(confirmMessage)) { if (confirm(confirmMessage)) {
this.deleteFn!(this.id!).subscribe({ this.deleteFn!(this.id!).subscribe({
next: () => { next: () => {


+ 131
- 9
angular/src/app/_components/search-select/search-select.component.ts Datei anzeigen

@@ -124,6 +124,46 @@ export class SearchSelectComponent implements OnInit, AfterViewInit {
sortable: true, sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_TEXT, filterType: FilterBarComponent.FILTER_TYPE_TEXT,
} as ListColDefinition, } as ListColDefinition,
{
name: 'is_zone',
text: 'location.is_zone',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'isZone',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{
name: 'is_place',
text: 'location.is_place',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'isPlace',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{
name: 'is_port',
text: 'location.is_port',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'isPort',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{
name: 'is_start_end',
text: 'location.is_start_end',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'isStartEnd',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{
name: 'createdAt',
text: 'common.created_at',
type: ListComponent.COLUMN_TYPE_DATE,
field: 'createdAt',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_DATE,
} as ListColDefinition,
]; ];
} }


@@ -204,6 +244,54 @@ export class SearchSelectComponent implements OnInit, AfterViewInit {
sortable: true, sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_TEXT, filterType: FilterBarComponent.FILTER_TYPE_TEXT,
} as ListColDefinition, } as ListColDefinition,
{
name: 'length',
text: 'vessel.length',
type: ListComponent.COLUMN_TYPE_NUMBER,
field: 'length',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_NUMBER,
} as ListColDefinition,
{
name: 'breadth',
text: 'vessel.breadth',
type: ListComponent.COLUMN_TYPE_NUMBER,
field: 'breadth',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_NUMBER,
} as ListColDefinition,
{
name: 'draft',
text: 'vessel.draft',
type: ListComponent.COLUMN_TYPE_NUMBER,
field: 'draft',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_NUMBER,
} as ListColDefinition,
{
name: 'grossTonnage',
text: 'vessel.grossTonnage',
type: ListComponent.COLUMN_TYPE_NUMBER,
field: 'grossTonnage',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_NUMBER,
} as ListColDefinition,
{
name: 'imoNo',
text: 'vessel.imoNo',
type: ListComponent.COLUMN_TYPE_TEXT,
field: 'imoNo',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_TEXT,
} as ListColDefinition,
{
name: 'callSign',
text: 'vessel.callSign',
type: ListComponent.COLUMN_TYPE_TEXT,
field: 'callSign',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_TEXT,
} as ListColDefinition,
{ {
name: 'createdAt', name: 'createdAt',
text: 'common.created_at', text: 'common.created_at',
@@ -218,27 +306,61 @@ export class SearchSelectComponent implements OnInit, AfterViewInit {
public static getDefaultColDefUsers(subResource?: string): ListColDefinition[] { public static getDefaultColDefUsers(subResource?: string): ListColDefinition[] {
return [ return [
{ {
name: 'fullName',
text: 'users.full_name',
name: 'image',
text: 'basic.image',
type: ListComponent.COLUMN_TYPE_IMAGE,
field: 'imageUrl',
} as ListColDefinition,
{
name: 'firstName',
text: 'users.firstname',
type: ListComponent.COLUMN_TYPE_TEXT, type: ListComponent.COLUMN_TYPE_TEXT,
field: 'fullName',
field: 'firstName',
sortable: true, sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_TEXT,
},
} as ListColDefinition,
{
name: 'lastName',
text: 'users.lastname',
type: ListComponent.COLUMN_TYPE_TEXT_BOLD,
field: 'lastName',
sortable: true,
} as ListColDefinition,
{ {
name: 'email', name: 'email',
text: 'users.email', text: 'users.email',
type: ListComponent.COLUMN_TYPE_TEXT,
type: ListComponent.COLUMN_TYPE_EMAIL,
field: 'email', field: 'email',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_TEXT,
},
} as ListColDefinition,
{ {
name: 'referenceId', name: 'referenceId',
text: 'users.pilotIdNo', text: 'users.pilotIdNo',
type: ListComponent.COLUMN_TYPE_TEXT_BOLD, type: ListComponent.COLUMN_TYPE_TEXT_BOLD,
field: 'referenceId', field: 'referenceId',
} as ListColDefinition, } as ListColDefinition,
{
name: 'isAdmin',
text: 'users.is_admin',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'isAdmin',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{
name: 'isPilot',
text: 'users.is_pilot',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'isPilot',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{
name: 'active',
text: 'users.active',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'active',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
]; ];
} }




+ 24
- 2
angular/src/app/_forms/apiForms.ts Datei anzeigen

@@ -55,6 +55,7 @@ export const locationForm = new FormGroup({
isZone: new FormControl(null, []), isZone: new FormControl(null, []),
isPlace: new FormControl(null, []), isPlace: new FormControl(null, []),
isPort: new FormControl(null, []), isPort: new FormControl(null, []),
isStartEnd: new FormControl(null, []),
createdAt: new FormControl(null, []) createdAt: new FormControl(null, [])
}); });


@@ -68,6 +69,7 @@ export const locationJsonldForm = new FormGroup({
isZone: new FormControl(null, []), isZone: new FormControl(null, []),
isPlace: new FormControl(null, []), isPlace: new FormControl(null, []),
isPort: new FormControl(null, []), isPort: new FormControl(null, []),
isStartEnd: new FormControl(null, []),
createdAt: new FormControl(null, []) createdAt: new FormControl(null, [])
}); });


@@ -98,7 +100,6 @@ export const tripForm = new FormGroup({
vesselIri: new FormControl(null, [Validators.required]), vesselIri: new FormControl(null, [Validators.required]),
pilotageReference: new FormControl(null, []), pilotageReference: new FormControl(null, []),
customerReference: new FormControl(null, []), customerReference: new FormControl(null, []),
captainName: new FormControl(null, []),
startLocation: new FormControl(null, []), startLocation: new FormControl(null, []),
startLocationIri: new FormControl(null, [Validators.required]), startLocationIri: new FormControl(null, [Validators.required]),
endLocation: new FormControl(null, []), endLocation: new FormControl(null, []),
@@ -106,6 +107,7 @@ export const tripForm = new FormGroup({
startDate: new FormControl(null, [Validators.required]), startDate: new FormControl(null, [Validators.required]),
endDate: new FormControl(null, [Validators.required]), endDate: new FormControl(null, [Validators.required]),
note: new FormControl(null, []), note: new FormControl(null, []),
completed: new FormControl(null, []),
createdAt: new FormControl(null, []) createdAt: new FormControl(null, [])
}); });


@@ -115,7 +117,6 @@ export const tripJsonldForm = new FormGroup({
vesselIri: new FormControl(null, [Validators.required]), vesselIri: new FormControl(null, [Validators.required]),
pilotageReference: new FormControl(null, []), pilotageReference: new FormControl(null, []),
customerReference: new FormControl(null, []), customerReference: new FormControl(null, []),
captainName: new FormControl(null, []),
startLocation: new FormControl(null, []), startLocation: new FormControl(null, []),
startLocationIri: new FormControl(null, [Validators.required]), startLocationIri: new FormControl(null, [Validators.required]),
endLocation: new FormControl(null, []), endLocation: new FormControl(null, []),
@@ -123,6 +124,7 @@ export const tripJsonldForm = new FormGroup({
startDate: new FormControl(null, [Validators.required]), startDate: new FormControl(null, [Validators.required]),
endDate: new FormControl(null, [Validators.required]), endDate: new FormControl(null, [Validators.required]),
note: new FormControl(null, []), note: new FormControl(null, []),
completed: new FormControl(null, []),
createdAt: new FormControl(null, []) createdAt: new FormControl(null, [])
}); });


@@ -133,6 +135,8 @@ export const tripLocationForm = new FormGroup({
location: new FormControl(null, []), location: new FormControl(null, []),
locationIri: new FormControl(null, [Validators.required]), locationIri: new FormControl(null, [Validators.required]),
isArrival: new FormControl(null, []), isArrival: new FormControl(null, []),
isTransit: new FormControl(null, []),
isDeparture: new FormControl(null, []),
date: new FormControl(null, [Validators.required]), date: new FormControl(null, [Validators.required]),
createdAt: new FormControl(null, []) createdAt: new FormControl(null, [])
}); });
@@ -144,6 +148,8 @@ export const tripLocationJsonldForm = new FormGroup({
location: new FormControl(null, []), location: new FormControl(null, []),
locationIri: new FormControl(null, [Validators.required]), locationIri: new FormControl(null, [Validators.required]),
isArrival: new FormControl(null, []), isArrival: new FormControl(null, []),
isTransit: new FormControl(null, []),
isDeparture: new FormControl(null, []),
date: new FormControl(null, [Validators.required]), date: new FormControl(null, [Validators.required]),
createdAt: new FormControl(null, []) createdAt: new FormControl(null, [])
}); });
@@ -161,6 +167,7 @@ export const userForm = new FormGroup({
password: new FormControl(null, []), password: new FormControl(null, []),
isAdmin: new FormControl(null, [Validators.required]), isAdmin: new FormControl(null, [Validators.required]),
active: new FormControl(null, [Validators.required]), active: new FormControl(null, [Validators.required]),
isPilot: new FormControl(null, [Validators.required]),
roles: new FormControl(null, []), roles: new FormControl(null, []),
createdAt: new FormControl(null, []) createdAt: new FormControl(null, [])
}); });
@@ -178,6 +185,7 @@ export const userJsonldForm = new FormGroup({
password: new FormControl(null, []), password: new FormControl(null, []),
isAdmin: new FormControl(null, [Validators.required]), isAdmin: new FormControl(null, [Validators.required]),
active: new FormControl(null, [Validators.required]), active: new FormControl(null, [Validators.required]),
isPilot: new FormControl(null, [Validators.required]),
roles: new FormControl(null, []), roles: new FormControl(null, []),
createdAt: new FormControl(null, []) createdAt: new FormControl(null, [])
}); });
@@ -190,6 +198,7 @@ export const userTripForm = new FormGroup({
userIri: new FormControl(null, [Validators.required]), userIri: new FormControl(null, [Validators.required]),
captainName: new FormControl(null, []), captainName: new FormControl(null, []),
completed: new FormControl(null, [Validators.required]), completed: new FormControl(null, [Validators.required]),
approved: new FormControl(null, [Validators.required]),
signature: new FormControl(null, []), signature: new FormControl(null, []),
signatureIri: new FormControl(null, []), signatureIri: new FormControl(null, []),
signatureUrl: new FormControl(null, []), signatureUrl: new FormControl(null, []),
@@ -205,6 +214,7 @@ export const userTripJsonldForm = new FormGroup({
userIri: new FormControl(null, [Validators.required]), userIri: new FormControl(null, [Validators.required]),
captainName: new FormControl(null, []), captainName: new FormControl(null, []),
completed: new FormControl(null, [Validators.required]), completed: new FormControl(null, [Validators.required]),
approved: new FormControl(null, [Validators.required]),
signature: new FormControl(null, []), signature: new FormControl(null, []),
signatureIri: new FormControl(null, []), signatureIri: new FormControl(null, []),
signatureUrl: new FormControl(null, []), signatureUrl: new FormControl(null, []),
@@ -246,6 +256,12 @@ export const vesselForm = new FormGroup({
code: new FormControl(null, [Validators.required]), code: new FormControl(null, [Validators.required]),
company: new FormControl(null, []), company: new FormControl(null, []),
companyIri: new FormControl(null, [Validators.required]), companyIri: new FormControl(null, [Validators.required]),
length: new FormControl(null, []),
breadth: new FormControl(null, []),
draft: new FormControl(null, []),
grossTonnage: new FormControl(null, []),
imoNo: new FormControl(null, []),
callSign: new FormControl(null, []),
createdAt: new FormControl(null, []) createdAt: new FormControl(null, [])
}); });


@@ -255,6 +271,12 @@ export const vesselJsonldForm = new FormGroup({
code: new FormControl(null, [Validators.required]), code: new FormControl(null, [Validators.required]),
company: new FormControl(null, []), company: new FormControl(null, []),
companyIri: new FormControl(null, [Validators.required]), companyIri: new FormControl(null, [Validators.required]),
length: new FormControl(null, []),
breadth: new FormControl(null, []),
draft: new FormControl(null, []),
grossTonnage: new FormControl(null, []),
imoNo: new FormControl(null, []),
callSign: new FormControl(null, []),
createdAt: new FormControl(null, []) createdAt: new FormControl(null, [])
}); });




+ 7
- 0
angular/src/app/_views/location/location-form/location-form.component.html Datei anzeigen

@@ -48,6 +48,13 @@
<span class="slider round"></span> <span class="slider round"></span>
</label> </label>
</div> </div>
<div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-3 switch-widget">
<p class="form-label">{{ 'location.is_start_end' | translate }}:</p>
<label class="switch">
<input type="checkbox" formControlName="isStartEnd">
<span class="slider round"></span>
</label>
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 mb-3"> <div class="col-12 mb-3">


+ 2
- 59
angular/src/app/_views/location/location-list/location-list.component.ts Datei anzeigen

@@ -8,6 +8,7 @@ import {ListGetDataFunctionType} from "@app/_components/list/list-get-data-funct
import {ROUTE_LOCATIONS} from "@app/app-routing.module"; import {ROUTE_LOCATIONS} from "@app/app-routing.module";
import {Sort} from "@angular/material/sort"; import {Sort} from "@angular/material/sort";
import {LocationFormComponent} from "@app/_views/location/location-form/location-form.component"; import {LocationFormComponent} from "@app/_views/location/location-form/location-form.component";
import {SearchSelectComponent} from "@app/_components/search-select/search-select.component";


@Component({ @Component({
selector: 'app-location-list', selector: 'app-location-list',
@@ -26,65 +27,7 @@ export class LocationListComponent implements OnInit, AfterViewInit {
protected appHelperService: AppHelperService, protected appHelperService: AppHelperService,
) { ) {


this.listColDefinitions = [
{
name: 'name',
text: 'common.name',
type: ListComponent.COLUMN_TYPE_TEXT,
field: 'name',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_TEXT,
} as ListColDefinition,
{
name: 'code',
text: 'common.code',
type: ListComponent.COLUMN_TYPE_TEXT,
field: 'code',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_TEXT,
} as ListColDefinition,
{
name: 'zone',
text: 'model.zone',
type: ListComponent.COLUMN_TYPE_TEXT,
subResource: 'zone',
field: 'name',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_TEXT,
} as ListColDefinition,
{
name: 'is_zone',
text: 'location.is_zone',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'isZone',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{
name: 'is_place',
text: 'location.is_place',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'isPlace',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{
name: 'is_port',
text: 'location.is_port',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'isPort',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{
name: 'createdAt',
text: 'common.created_at',
type: ListComponent.COLUMN_TYPE_DATE,
field: 'createdAt',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_DATE,
} as ListColDefinition,
];
this.listColDefinitions = SearchSelectComponent.getDefaultColDefLocations();
} }


ngOnInit() { ngOnInit() {


+ 25
- 0
angular/src/app/_views/trip/trip-detail/trip-detail.component.html Datei anzeigen

@@ -70,6 +70,31 @@
{{ 'trip.is_arrival' | translate }} {{ 'trip.is_arrival' | translate }}
</label> </label>
</div> </div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
[id]="'isTransit_' + i"
[checked]="tripLocation.isTransit"
(change)="onIsTransitChange($event, i)"
>
<label class="form-check-label" [for]="'isTransit_' + i">
{{ 'trip.is_transit' | translate }}
</label>
</div>

<div class="form-check">
<input
class="form-check-input"
type="checkbox"
[id]="'isDeparture_' + i"
[checked]="tripLocation.isDeparture"
(change)="onIsDepartureChange($event, i)"
>
<label class="form-check-label" [for]="'isDeparture_' + i">
{{ 'trip.is_departure' | translate }}
</label>
</div>
</div> </div>


<div class="col-12 col-md-2 mb-1 d-flex align-items-end"> <div class="col-12 col-md-2 mb-1 d-flex align-items-end">


+ 117
- 63
angular/src/app/_views/trip/trip-detail/trip-detail.component.ts Datei anzeigen

@@ -7,7 +7,7 @@ import {
TripService, TripService,
LocationService, LocationService,
LocationJsonld, LocationJsonld,
UserTripService, UserService, UserTripJsonld, UserJsonld, UserTripEventJsonld, UserTripEventService
UserTripService, UserService, UserTripJsonld, UserJsonld, UserTripEventJsonld, UserTripEventService, TripLocation
} from "@app/core/api/v1"; } from "@app/core/api/v1";
import { AppHelperService } from "@app/_helpers/app-helper.service"; import { AppHelperService } from "@app/_helpers/app-helper.service";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
@@ -17,6 +17,7 @@ import {firstValueFrom, Observable} from 'rxjs';
import { ListColDefinition } from '@app/_components/list/list-col-definition'; import { ListColDefinition } from '@app/_components/list/list-col-definition';
import { SearchSelectComponent } from '@app/_components/search-select/search-select.component'; import { SearchSelectComponent } from '@app/_components/search-select/search-select.component';
import {map} from "rxjs/operators"; import {map} from "rxjs/operators";
import {TranslateService} from "@ngx-translate/core";


@Component({ @Component({
selector: 'app-trip-detail', selector: 'app-trip-detail',
@@ -35,6 +36,8 @@ export class TripDetailComponent implements OnInit, AfterViewInit {
protected locationColDefinitions: ListColDefinition[] = SearchSelectComponent.getDefaultColDefLocations(); protected locationColDefinitions: ListColDefinition[] = SearchSelectComponent.getDefaultColDefLocations();
protected userForms: FormGroup[] = []; protected userForms: FormGroup[] = [];
protected userColDefinitions: ListColDefinition[] = SearchSelectComponent.getDefaultColDefUsers(); protected userColDefinitions: ListColDefinition[] = SearchSelectComponent.getDefaultColDefUsers();
protected originalTripLocations: TripLocation[] = [];



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


@@ -46,6 +49,7 @@ export class TripDetailComponent implements OnInit, AfterViewInit {
private userTripEventService: UserTripEventService, private userTripEventService: UserTripEventService,
private userService: UserService, private userService: UserService,
protected appHelperService: AppHelperService, protected appHelperService: AppHelperService,
protected translateService: TranslateService,
private route: ActivatedRoute, private route: ActivatedRoute,
private fb: FormBuilder private fb: FormBuilder
) {} ) {}
@@ -85,16 +89,17 @@ export class TripDetailComponent implements OnInit, AfterViewInit {
this.tripLocations = data.member; this.tripLocations = data.member;
// Create a form for each trip location // Create a form for each trip location
this.locationForms = []; this.locationForms = [];
this.tripLocations.forEach(() => {
this.locationForms.push(this.createLocationForm());
this.originalTripLocations = JSON.parse(JSON.stringify(this.tripLocations));
this.tripLocations.forEach((tripLocation) => {
this.locationForms.push(this.createLocationForm(tripLocation.location?.id ?? null));
}); });
} }
); );
} }


createLocationForm(): FormGroup {
createLocationForm(locationIri: string | null): FormGroup {
return this.fb.group({ return this.fb.group({
location: [null]
location: [locationIri]
}); });
} }


@@ -126,7 +131,14 @@ export class TripDetailComponent implements OnInit, AfterViewInit {
}); });


// Filtere Benutzer, die bereits ausgewählt sind (gespeichert oder nicht) // Filtere Benutzer, die bereits ausgewählt sind (gespeichert oder nicht)
return this.userService.usersGetCollection(page, pageSize, undefined, term).pipe(
return this.userService.usersGetCollection(
page,
pageSize,
undefined,
undefined,
term,
'{"isPilot":true}'
).pipe(
map(response => { map(response => {
response.member = response.member.filter(user => response.member = response.member.filter(user =>
!assignedUserIds.includes(this.appHelperService.extractId(user.id!)) !assignedUserIds.includes(this.appHelperService.extractId(user.id!))
@@ -191,6 +203,16 @@ export class TripDetailComponent implements OnInit, AfterViewInit {
this.tripLocations[index].isArrival = checkbox.checked; this.tripLocations[index].isArrival = checkbox.checked;
} }


onIsTransitChange(event: Event, index: number) {
const checkbox = event.target as HTMLInputElement;
this.tripLocations[index].isTransit = checkbox.checked;
}

onIsDepartureChange(event: Event, index: number) {
const checkbox = event.target as HTMLInputElement;
this.tripLocations[index].isDeparture = checkbox.checked;
}

addNewTripLocation() { addNewTripLocation() {
// Create a new empty trip location // Create a new empty trip location
const newTripLocation: TripLocationJsonld = { const newTripLocation: TripLocationJsonld = {
@@ -201,7 +223,7 @@ export class TripDetailComponent implements OnInit, AfterViewInit {
}; };


this.tripLocations.push(newTripLocation); this.tripLocations.push(newTripLocation);
this.locationForms.push(this.createLocationForm());
this.locationForms.push(this.createLocationForm(null));


// Force update in the next event loop to properly initialize the search-select // Force update in the next event loop to properly initialize the search-select
setTimeout(() => { setTimeout(() => {
@@ -219,7 +241,8 @@ export class TripDetailComponent implements OnInit, AfterViewInit {
const newUserTrip: UserTripJsonld = { const newUserTrip: UserTripJsonld = {
tripIri: this.trip.id!, tripIri: this.trip.id!,
userIri: null, userIri: null,
completed: false
completed: false,
approved: false,
}; };


// Füge es als UserTripJsonld hinzu // Füge es als UserTripJsonld hinzu
@@ -280,79 +303,110 @@ export class TripDetailComponent implements OnInit, AfterViewInit {
}) })
} }


removeTripLocation(index: number) {
const tripLocationId = this.tripLocations[index].id;

if (tripLocationId) {
// If it exists on the server, delete it
this.tripLocationService.tripLocationsIdDelete(this.appHelperService.extractId(tripLocationId)).subscribe(
() => {
this.tripLocations.splice(index, 1);
this.locationForms.splice(index, 1);
}
);
} else {
// If it's only local, just remove it from the array
removeTripLocation(index: number): void {
this.translateService.get('basic.delete_confirm').subscribe((confirmMessage: string) => {
if (!confirm(confirmMessage)) {
return;
}
// Just remove from arrays; deletion happens later in saveAllTripLocations()
this.tripLocations.splice(index, 1); this.tripLocations.splice(index, 1);
this.locationForms.splice(index, 1); this.locationForms.splice(index, 1);
}
});
} }


removeUserTrip(index: number) { removeUserTrip(index: number) {
// Nur aus dem Array entfernen, tatsächliches Löschen erfolgt beim Speichern
this.userTrips.splice(index, 1);
this.userForms.splice(index, 1);
this.translateService.get('basic.delete_confirm').subscribe((confirmMessage: string) => {
if (!confirm(confirmMessage)) {
return;
}
// Nur aus dem Array entfernen, tatsächliches Löschen erfolgt beim Speichern
this.userTrips.splice(index, 1);
this.userForms.splice(index, 1);
});
} }


saveAllTripLocations() {
saveAllTripLocations(): void {
// A) DELETE: find which original dbIds are missing → delete those
const originalDbIds = this.originalTripLocations
.map(o => o.dbId)
// narrow type to number (filter out null/undefined)
.filter((id): id is number => id != null);

const currentDbIds = this.tripLocations
.map(t => t.dbId)
.filter((id): id is number => id != null);

const dbIdsToDelete = originalDbIds.filter(id => !currentDbIds.includes(id));
dbIdsToDelete.forEach(dbId => {
this.tripLocationService
.tripLocationsIdDelete(dbId.toString())
.subscribe({
next: () => console.log(`TripLocation ${dbId} deleted.`),
error: err => console.error(`Error deleting TripLocation ${dbId}:`, err)
});
});


// First update the location objects in our tripLocations array
// B) UPSERT: create or update remaining entries
this.tripLocations.forEach((tripLocation, index) => { this.tripLocations.forEach((tripLocation, index) => {
const locationFormValue = this.locationForms[index].get('location')?.value;
const form = this.locationForms[index];
const locationVal = form.get('location')?.value;
const dbId = tripLocation.dbId;
const isNew = dbId == null; // no dbId → brand new

// only for existing entries compare against snapshot
let locationChanged = false;
let modelChanged = false;
if (!isNew) {
const original = this.originalTripLocations[index];
locationChanged = locationVal !== original.locationIri;
modelChanged = (
tripLocation.date !== original.date ||
tripLocation.isArrival !== original.isArrival ||
tripLocation.isTransit !== original.isTransit ||
tripLocation.isDeparture !== original.isDeparture
);
}


// Ensure we have a location
if (locationFormValue) {
// If just an ID was set, create a proper location object
if (typeof locationFormValue === 'string') {
tripLocation.locationIri = locationFormValue;
}
} else {
// If no location is set, show an error
// skip if neither new nor changed
if (!(isNew || locationChanged || modelChanged)) {
return; return;
} }
});


// Filter out trip locations that have no location set
const validTripLocations = this.tripLocations.filter(tl => tl.locationIri);

if (validTripLocations.length === 0) {
return;
}

// Save all trip locations
const savePromises = validTripLocations.map(tripLocation => {
if (tripLocation.id) {
// Update existing
return firstValueFrom(this.tripLocationService.tripLocationsIdPatch(
this.appHelperService.extractId(tripLocation.id),
this.appHelperService.convertJsonldToJson(tripLocation)
));
} else {
// Create new
return firstValueFrom(this.tripLocationService.tripLocationsPost(tripLocation));
// validate a location was chosen
if (!locationVal) {
console.error(`Location missing for entry #${index}`);
return;
} }
});
if (typeof locationVal === 'string') {
tripLocation.locationIri = locationVal;
}
// always ensure the parent trip IRI is set
tripLocation.tripIri = this.trip?.id ?? null;

// choose correct API call
const save$ = isNew
? this.tripLocationService.tripLocationsPost(tripLocation)
: this.tripLocationService.tripLocationsIdPatch(
dbId!.toString(),
this.appHelperService.convertJsonldToJson(tripLocation)
);


Promise.all(savePromises)
.then(() => {
// Reload trip locations to get updated data
this.loadTripLocations();
})
.catch(error => {
console.error('Error saving trip locations:', error);
// execute save and log result
save$.subscribe({
next: saved => {
console.log(`TripLocation #${index} ${isNew ? 'created' : 'updated'}.`);
},
error: err => {
console.error(`Error saving TripLocation #${index}:`, err);
}
}); });
});

// C) Finally: reset original snapshot to current state
this.originalTripLocations = JSON.parse(JSON.stringify(this.tripLocations));
} }



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


+ 0
- 4
angular/src/app/_views/trip/trip-form/trip-form.component.html Datei anzeigen

@@ -53,10 +53,6 @@
<label for="customerReference" class="form-label">{{ 'trip.customer_reference' | translate }}:</label> <label for="customerReference" class="form-label">{{ 'trip.customer_reference' | translate }}:</label>
<input type="text" class="form-control" id="customerReference" formControlName="customerReference"/> <input type="text" class="form-control" id="customerReference" formControlName="customerReference"/>
</div> </div>
<div class="col-12 col-lg-6 mb-3">
<label for="captainName" class="form-label">{{ 'trip.captain_name' | translate }}:</label>
<input type="text" class="form-control" id="captainName" formControlName="captainName"/>
</div>
<div class="col-12 col-lg-6 mb-3"> <div class="col-12 col-lg-6 mb-3">
<app-datetime-picker <app-datetime-picker
[label]="'trip.start_date' | translate" [label]="'trip.start_date' | translate"


+ 6
- 1
angular/src/app/_views/trip/trip-form/trip-form.component.ts Datei anzeigen

@@ -60,6 +60,11 @@ export class TripFormComponent extends AbstractDataFormComponent<TripJsonld> {
} }


getLocations: ListGetDataFunctionType = (index: number, pageSize: number, term?: string) => { getLocations: ListGetDataFunctionType = (index: number, pageSize: number, term?: string) => {
return this.locationService.locationsGetCollection(index, pageSize, term);
return this.locationService.locationsGetCollection(
index,
pageSize,
term,
'{"isStartEnd":true}'
);
} }
} }

+ 0
- 8
angular/src/app/_views/trip/trip-list/trip-list.component.ts Datei anzeigen

@@ -49,14 +49,6 @@ export class TripListComponent {
sortable: true, sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_TEXT, filterType: FilterBarComponent.FILTER_TYPE_TEXT,
} as ListColDefinition, } as ListColDefinition,
{
name: 'captainName',
text: 'trip.captain_name',
type: ListComponent.COLUMN_TYPE_TEXT,
field: 'captainName',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_TEXT,
} as ListColDefinition,
{ {
name: 'startLocation', name: 'startLocation',
text: 'trip.start_location', text: 'trip.start_location',


+ 0
- 6
angular/src/app/_views/user-trip/user-trip-detail/user-trip-detail.component.ts Datei anzeigen

@@ -2,15 +2,11 @@ import { Component, OnInit, AfterViewInit, QueryList, ViewChildren } from '@angu
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { import {
LocationService, LocationService,
TripJsonld,
LocationJsonld,
UserService,
UserTripJsonld, UserTripJsonld,
UserTripService, UserTripService,
UserTripEventJsonld, UserTripEventJsonld,
UserTripEventService, UserTripEventService,
EventService, EventService,
EventJsonld
} from "@app/core/api/v1"; } from "@app/core/api/v1";
import { AppHelperService } from "@app/_helpers/app-helper.service"; import { AppHelperService } from "@app/_helpers/app-helper.service";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
@@ -19,8 +15,6 @@ import { ModalStatus } from "@app/_helpers/modal.states";
import { firstValueFrom, Observable } from 'rxjs'; import { firstValueFrom, Observable } from 'rxjs';
import { ListColDefinition } from '@app/_components/list/list-col-definition'; import { ListColDefinition } from '@app/_components/list/list-col-definition';
import { SearchSelectComponent } from '@app/_components/search-select/search-select.component'; 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({ @Component({
selector: 'app-user-trip-detail', selector: 'app-user-trip-detail',


+ 7
- 0
angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.html Datei anzeigen

@@ -17,6 +17,13 @@
<span class="slider round"></span> <span class="slider round"></span>
</label> </label>
</div> </div>
<div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-3 switch-widget">
<p class="form-label">{{ 'user_trip.approved' | translate }}:</p>
<label class="switch">
<input type="checkbox" formControlName="approved" [disabled]="data.approved">
<span class="slider round"></span>
</label>
</div>
</div> </div>






+ 2
- 2
angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.ts Datei anzeigen

@@ -29,9 +29,9 @@ export class UserTripFormComponent extends AbstractImageFormComponent<UserTripJs
userTripForm, userTripForm,
undefined, undefined,
(id: string | number, data: UserTripJsonld) => (id: string | number, data: UserTripJsonld) =>
userTripService.userTripsIdPatch(
this.userTripService.userTripsIdPatch(
id.toString(), id.toString(),
appHelperService.convertJsonldToJson(data)
this.appHelperService.convertJsonldToJson(data)
), ),
(id: string | number) => userTripService.userTripsIdDelete(id.toString()), (id: string | number) => userTripService.userTripsIdDelete(id.toString()),
'signatureIri', 'signatureIri',


+ 8
- 0
angular/src/app/_views/user-trip/user-trip-list/user-trip-list.component.ts Datei anzeigen

@@ -82,6 +82,14 @@ export class UserTripListComponent {
sortable: true, sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN, filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition, } as ListColDefinition,
{
name: 'approved',
text: 'user_trip.approved',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'approved',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{ {
name: 'completedDate', name: 'completedDate',
text: 'user_trip.completed_at', text: 'user_trip.completed_at',


+ 7
- 0
angular/src/app/_views/user/user-form/user-form.component.html Datei anzeigen

@@ -42,6 +42,13 @@
<span class="slider round"></span> <span class="slider round"></span>
</label> </label>
</div> </div>
<div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-3 switch-widget">
<p class="form-label">{{ 'users.is_pilot' | translate }}:</p>
<label class="switch">
<input type="checkbox" formControlName="isPilot">
<span class="slider round"></span>
</label>
</div>
</div> </div>


<app-image-upload <app-image-upload


+ 17
- 0
angular/src/app/_views/user/user-list/user-list.component.ts Datei anzeigen

@@ -65,6 +65,22 @@ export class UserListComponent implements OnInit, AfterViewInit {
type: ListComponent.COLUMN_TYPE_TEXT_BOLD, type: ListComponent.COLUMN_TYPE_TEXT_BOLD,
field: 'referenceId', field: 'referenceId',
} as ListColDefinition, } as ListColDefinition,
{
name: 'isAdmin',
text: 'users.is_admin',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'isAdmin',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{
name: 'isPilot',
text: 'users.is_pilot',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'isPilot',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{ {
name: 'active', name: 'active',
text: 'users.active', text: 'users.active',
@@ -91,6 +107,7 @@ export class UserListComponent implements OnInit, AfterViewInit {
undefined, undefined,
undefined, undefined,
term, term,
this.listComponent.getFilterJsonString(),
this.listComponent.getSortingJsonString() this.listComponent.getSortingJsonString()
); );
} }


+ 44
- 0
angular/src/app/_views/vessel/vessel-form/vessel-form.component.html Datei anzeigen

@@ -35,6 +35,50 @@
<input id="companyIri" type="hidden" formControlName="companyIri"/> <input id="companyIri" type="hidden" formControlName="companyIri"/>
</div> </div>
</div> </div>
<div class="row">
<div class="col-12 col-lg-6 mb-3">
<label for="length" class="form-label">{{ 'vessel.length' | translate }}:</label>
<input type="number"
class="form-control"
id="length"
formControlName="length"/>
</div>
<div class="col-12 col-lg-6 mb-3">
<label for="breadth" class="form-label">{{ 'vessel.breadth' | translate }}:</label>
<input type="number"
class="form-control"
id="breadth"
formControlName="breadth"/>
</div>
<div class="col-12 col-lg-6 mb-3">
<label for="draft" class="form-label">{{ 'vessel.draft' | translate }}:</label>
<input type="number"
class="form-control"
id="draft"
formControlName="draft"/>
</div>
<div class="col-12 col-lg-6 mb-3">
<label for="grossTonnage" class="form-label">{{ 'vessel.grossTonnage' | translate }}:</label>
<input type="number"
class="form-control"
id="grossTonnage"
formControlName="grossTonnage"/>
</div>
<div class="col-12 col-lg-6 mb-3">
<label for="imoNo" class="form-label">{{ 'vessel.imoNo' | translate }}:</label>
<input type="text"
class="form-control"
id="imoNo"
formControlName="imoNo"/>
</div>
<div class="col-12 col-lg-6 mb-3">
<label for="callSign" class="form-label">{{ 'vessel.callSign' | translate }}:</label>
<input type="text"
class="form-control"
id="callSign"
formControlName="callSign"/>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-12 mb-3"> <div class="col-12 mb-3">
<button type="submit" class="btn btn-primary" [disabled]="form.invalid"> <button type="submit" class="btn btn-primary" [disabled]="form.invalid">


+ 1
- 3
angular/src/app/_views/vessel/vessel-list/vessel-list.component.ts Datei anzeigen

@@ -1,12 +1,10 @@
import {Component, ViewChild} from '@angular/core'; import {Component, ViewChild} from '@angular/core';
import {ListComponent} from "@app/_components/list/list.component"; import {ListComponent} from "@app/_components/list/list.component";
import {ListColDefinition} from "@app/_components/list/list-col-definition"; import {ListColDefinition} from "@app/_components/list/list-col-definition";
import {VesselJsonld, VesselService, ZoneJsonld} from "@app/core/api/v1";
import {VesselService, ZoneJsonld} from "@app/core/api/v1";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
import {AppHelperService} from "@app/_helpers/app-helper.service"; import {AppHelperService} from "@app/_helpers/app-helper.service";
import {FilterBarComponent} from "@app/_components/filter-bar/filter-bar.component";
import {ListGetDataFunctionType} from "@app/_components/list/list-get-data-function-type"; import {ListGetDataFunctionType} from "@app/_components/list/list-get-data-function-type";
import {Sort} from "@angular/material/sort";
import {ROUTE_VESSELS, ROUTE_ZONES} from "@app/app-routing.module"; import {ROUTE_VESSELS, ROUTE_ZONES} from "@app/app-routing.module";
import {VesselFormComponent} from "@app/_views/vessel/vessel-form/vessel-form.component"; import {VesselFormComponent} from "@app/_views/vessel/vessel-form/vessel-form.component";
import {SearchSelectComponent} from "@app/_components/search-select/search-select.component"; import {SearchSelectComponent} from "@app/_components/search-select/search-select.component";


+ 9
- 4
angular/src/app/core/api/v1/api/user.service.ts Datei anzeigen

@@ -109,14 +109,15 @@ export class UserService {
* @param firstName * @param firstName
* @param lastName * @param lastName
* @param userNameSearch * @param userNameSearch
* @param customJsonFilter
* @param customJsonOrder * @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 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. * @param reportProgress flag to report request and response progress.
*/ */
public usersGetCollection(page?: number, itemsPerPage?: number, firstName?: string, lastName?: string, userNameSearch?: string, customJsonOrder?: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json' | 'application/problem+json' | 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<ApiUsersGetCollection200Response>;
public usersGetCollection(page?: number, itemsPerPage?: number, firstName?: string, lastName?: string, userNameSearch?: string, customJsonOrder?: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json' | 'application/problem+json' | 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<ApiUsersGetCollection200Response>>;
public usersGetCollection(page?: number, itemsPerPage?: number, firstName?: string, lastName?: string, userNameSearch?: string, customJsonOrder?: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json' | 'application/problem+json' | 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<ApiUsersGetCollection200Response>>;
public usersGetCollection(page?: number, itemsPerPage?: number, firstName?: string, lastName?: string, userNameSearch?: string, customJsonOrder?: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/ld+json' | 'application/problem+json' | 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<any> {
public usersGetCollection(page?: number, itemsPerPage?: number, firstName?: string, lastName?: string, userNameSearch?: string, customJsonFilter?: string, customJsonOrder?: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json' | 'application/problem+json' | 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<ApiUsersGetCollection200Response>;
public usersGetCollection(page?: number, itemsPerPage?: number, firstName?: string, lastName?: string, userNameSearch?: string, customJsonFilter?: string, customJsonOrder?: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json' | 'application/problem+json' | 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<ApiUsersGetCollection200Response>>;
public usersGetCollection(page?: number, itemsPerPage?: number, firstName?: string, lastName?: string, userNameSearch?: string, customJsonFilter?: string, customJsonOrder?: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json' | 'application/problem+json' | 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<ApiUsersGetCollection200Response>>;
public usersGetCollection(page?: number, itemsPerPage?: number, firstName?: string, lastName?: string, userNameSearch?: string, customJsonFilter?: string, customJsonOrder?: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/ld+json' | 'application/problem+json' | 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<any> {


let localVarQueryParameters = new HttpParams({encoder: this.encoder}); let localVarQueryParameters = new HttpParams({encoder: this.encoder});
if (page !== undefined && page !== null) { if (page !== undefined && page !== null) {
@@ -139,6 +140,10 @@ export class UserService {
localVarQueryParameters = this.addToHttpParams(localVarQueryParameters, localVarQueryParameters = this.addToHttpParams(localVarQueryParameters,
<any>userNameSearch, 'userNameSearch'); <any>userNameSearch, 'userNameSearch');
} }
if (customJsonFilter !== undefined && customJsonFilter !== null) {
localVarQueryParameters = this.addToHttpParams(localVarQueryParameters,
<any>customJsonFilter, 'custom_json_filter');
}
if (customJsonOrder !== undefined && customJsonOrder !== null) { if (customJsonOrder !== undefined && customJsonOrder !== null) {
localVarQueryParameters = this.addToHttpParams(localVarQueryParameters, localVarQueryParameters = this.addToHttpParams(localVarQueryParameters,
<any>customJsonOrder, 'custom_json_order'); <any>customJsonOrder, 'custom_json_order');


+ 1
- 0
angular/src/app/core/api/v1/model/location.ts Datei anzeigen

@@ -24,6 +24,7 @@ export interface Location {
isZone?: boolean; isZone?: boolean;
isPlace?: boolean; isPlace?: boolean;
isPort?: boolean; isPort?: boolean;
isStartEnd?: boolean;
readonly createdAt?: string | null; readonly createdAt?: string | null;
} }



+ 1
- 0
angular/src/app/core/api/v1/model/locationJsonld.ts Datei anzeigen

@@ -28,6 +28,7 @@ export interface LocationJsonld {
isZone?: boolean; isZone?: boolean;
isPlace?: boolean; isPlace?: boolean;
isPort?: boolean; isPort?: boolean;
isStartEnd?: boolean;
readonly createdAt?: string | null; readonly createdAt?: string | null;
} }



+ 1
- 1
angular/src/app/core/api/v1/model/trip.ts Datei anzeigen

@@ -20,7 +20,6 @@ export interface Trip {
vesselIri: string | null; vesselIri: string | null;
readonly pilotageReference?: string | null; readonly pilotageReference?: string | null;
customerReference?: string | null; customerReference?: string | null;
captainName?: string | null;
readonly startLocation?: string; readonly startLocation?: string;
startLocationIri: string | null; startLocationIri: string | null;
readonly endLocation?: string; readonly endLocation?: string;
@@ -28,6 +27,7 @@ export interface Trip {
startDate: string; startDate: string;
endDate: string; endDate: string;
note?: string | null; note?: string | null;
completed?: boolean;
readonly createdAt?: string | null; readonly createdAt?: string | null;
} }



+ 1
- 1
angular/src/app/core/api/v1/model/tripJsonld.ts Datei anzeigen

@@ -25,7 +25,6 @@ export interface TripJsonld {
vesselIri: string | null; vesselIri: string | null;
readonly pilotageReference?: string | null; readonly pilotageReference?: string | null;
customerReference?: string | null; customerReference?: string | null;
captainName?: string | null;
readonly startLocation?: LocationJsonld; readonly startLocation?: LocationJsonld;
startLocationIri: string | null; startLocationIri: string | null;
readonly endLocation?: LocationJsonld; readonly endLocation?: LocationJsonld;
@@ -33,6 +32,7 @@ export interface TripJsonld {
startDate: string; startDate: string;
endDate: string; endDate: string;
note?: string | null; note?: string | null;
completed?: boolean;
readonly createdAt?: string | null; readonly createdAt?: string | null;
} }



+ 2
- 0
angular/src/app/core/api/v1/model/tripLocation.ts Datei anzeigen

@@ -23,6 +23,8 @@ export interface TripLocation {
location?: Location; location?: Location;
locationIri: string | null; locationIri: string | null;
isArrival?: boolean; isArrival?: boolean;
isTransit?: boolean;
isDeparture?: boolean;
date: string; date: string;
readonly createdAt?: string | null; readonly createdAt?: string | null;
} }


+ 2
- 0
angular/src/app/core/api/v1/model/tripLocationJsonld.ts Datei anzeigen

@@ -26,6 +26,8 @@ export interface TripLocationJsonld {
location?: LocationJsonld; location?: LocationJsonld;
locationIri: string | null; locationIri: string | null;
isArrival?: boolean; isArrival?: boolean;
isTransit?: boolean;
isDeparture?: boolean;
date: string; date: string;
readonly createdAt?: string | null; readonly createdAt?: string | null;
} }


+ 1
- 0
angular/src/app/core/api/v1/model/user.ts Datei anzeigen

@@ -30,6 +30,7 @@ export interface User {
password?: string | null; password?: string | null;
isAdmin: boolean; isAdmin: boolean;
active: boolean; active: boolean;
isPilot: boolean;
roles?: Array<string>; roles?: Array<string>;
readonly createdAt?: string | null; readonly createdAt?: string | null;
} }


+ 1
- 0
angular/src/app/core/api/v1/model/userJsonld.ts Datei anzeigen

@@ -34,6 +34,7 @@ export interface UserJsonld {
password?: string | null; password?: string | null;
isAdmin: boolean; isAdmin: boolean;
active: boolean; active: boolean;
isPilot: boolean;
roles?: Array<string>; roles?: Array<string>;
readonly createdAt?: string | null; readonly createdAt?: string | null;
} }


+ 1
- 0
angular/src/app/core/api/v1/model/userTrip.ts Datei anzeigen

@@ -22,6 +22,7 @@ export interface UserTrip {
userIri: string | null; userIri: string | null;
captainName?: string | null; captainName?: string | null;
completed: boolean; completed: boolean;
approved: boolean;
readonly signature?: string; readonly signature?: string;
signatureIri?: string | null; signatureIri?: string | null;
readonly signatureUrl?: string | null; readonly signatureUrl?: string | null;


+ 1
- 0
angular/src/app/core/api/v1/model/userTripJsonld.ts Datei anzeigen

@@ -28,6 +28,7 @@ export interface UserTripJsonld {
userIri: string | null; userIri: string | null;
captainName?: string | null; captainName?: string | null;
completed: boolean; completed: boolean;
approved: boolean;
readonly signature?: MediaObjectJsonld; readonly signature?: MediaObjectJsonld;
signatureIri?: string | null; signatureIri?: string | null;
readonly signatureUrl?: string | null; readonly signatureUrl?: string | null;


+ 6
- 0
angular/src/app/core/api/v1/model/vessel.ts Datei anzeigen

@@ -20,6 +20,12 @@ export interface Vessel {
code: string; code: string;
readonly company?: string; readonly company?: string;
companyIri: string | null; companyIri: string | null;
length?: number | null;
breadth?: number | null;
draft?: number | null;
grossTonnage?: number | null;
imoNo?: string | null;
callSign?: string | null;
readonly createdAt?: string | null; readonly createdAt?: string | null;
} }



+ 6
- 0
angular/src/app/core/api/v1/model/vesselJsonld.ts Datei anzeigen

@@ -24,6 +24,12 @@ export interface VesselJsonld {
code: string; code: string;
readonly company?: ShippingCompanyJsonld; readonly company?: ShippingCompanyJsonld;
companyIri: string | null; companyIri: string | null;
length?: number | null;
breadth?: number | null;
draft?: number | null;
grossTonnage?: number | null;
imoNo?: string | null;
callSign?: string | null;
readonly createdAt?: string | null; readonly createdAt?: string | null;
} }



+ 29
- 14
angular/src/assets/i18n/en.json Datei anzeigen

@@ -30,24 +30,27 @@
"end_location": "End location", "end_location": "End location",
"start_date": "Start date", "start_date": "Start date",
"end_date": "End date", "end_date": "End date",
"is_arrival": "is arrival",
"is_arrival": "Arrival",
"is_transit": "Transit",
"is_departure": "Departure",
"itinerary": "Itinerary", "itinerary": "Itinerary",
"itinerary_locations": "Itinerary locations", "itinerary_locations": "Itinerary locations",
"add_itinerary_location": "Add itinerary location", "add_itinerary_location": "Add itinerary location",
"remove_itinerary_location": "Remove itinerary location", "remove_itinerary_location": "Remove itinerary location",
"save_itinerary": "Save itinerary", "save_itinerary": "Save itinerary",
"assigned_users": "Assigned Users (User Trips)",
"assigned_users": "Assigned Pilots",
"save_user_assignments": "Save user assignments", "save_user_assignments": "Save user assignments",
"events": "Events" "events": "Events"
}, },
"user_trip": "user_trip":
{ {
"view": "User Trips",
"view": "Pilotage",
"events": "Events", "events": "Events",
"completed": "Completed", "completed": "Completed",
"completed_at": "Completed at", "completed_at": "Completed at",
"signature": "Signature Image", "signature": "Signature Image",
"event_date": "Event date"
"event_date": "Event date",
"approved": "Approved"
}, },
"base_data": "base_data":
{ {
@@ -64,18 +67,28 @@
"shipping_company": "Shipping Company", "shipping_company": "Shipping Company",
"trip": "Trip", "trip": "Trip",
"user": "User", "user": "User",
"user_trip": "User Trip",
"user_trip": "Pilotage",
"event": "Event" "event": "Event"
}, },
"location": "location":
{ {
"is_zone": "Is zone",
"is_place": "Is place",
"is_port": "Is port"
"is_zone": "Zone",
"is_place": "Place",
"is_port": "Port",
"is_start_end": "Start/End"
}, },
"zone": "zone":
{ {


},
"vessel":
{
"length": "Length",
"breadth": "Breadth",
"draft": "Draft",
"grossTonnage": "Gross Tonnage",
"imoNo": "Imo",
"callSign": "Call sign"
}, },
"profile": "profile":
{ {
@@ -85,7 +98,7 @@
{ {
"logout": "Logout", "logout": "Logout",
"company_name": "Imaq Pilotage", "company_name": "Imaq Pilotage",
"users": "Users",
"users": "Users / Pilots",
"new": "New", "new": "New",
"edit_before": "", "edit_before": "",
"edit_after": "Edit", "edit_after": "Edit",
@@ -113,26 +126,28 @@
"edit": "Edit", "edit": "Edit",
"save": "Save", "save": "Save",
"delete": "Delete", "delete": "Delete",
"delete_confirm": "Do you really want to delete this dataset? There might be related data, which makes deletion impossible unless the related data will be removed first!"
"delete_confirm": "Do you really want to delete this dataset?",
"delete_confirm_related": "Do you really want to delete this dataset? There might be related data, which makes deletion impossible unless the related data will be removed first!"
}, },
"users": "users":
{ {
"view": "Users",
"view": "Users / Pilots",
"user": "User", "user": "User",
"email": "Email", "email": "Email",
"full_name": "Name", "full_name": "Name",
"firstname": "Firstname", "firstname": "Firstname",
"lastname": "Lastname", "lastname": "Lastname",
"userTrips": "User trips",
"userTrips": "Pilotage",
"pilotIdNo": "#Pilot id", "pilotIdNo": "#Pilot id",
"password": "Password", "password": "Password",
"active": "Active", "active": "Active",
"image": "Image", "image": "Image",
"is_admin": "Is admin"
"is_admin": "Admin",
"is_pilot": "Pilot"
}, },
"form": "form":
{ {
"mandatory": " is mandatory",
"mandatory": " Mandatory",
"email_validation": "Please provide a valid email address", "email_validation": "Please provide a valid email address",
"search_placeholder": "Search", "search_placeholder": "Search",
"no_data": "No datasets found." "no_data": "No datasets found."


+ 65
- 0
httpdocs/migrations/Version20250428094124.php Datei anzeigen

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

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250428094124 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE location ADD is_start_end TINYINT(1) NOT NULL
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE trip ADD completed TINYINT(1) NOT NULL, DROP captain_name
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE trip_location ADD is_transit TINYINT(1) NOT NULL, ADD is_departure TINYINT(1) NOT NULL
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE user ADD is_pilot TINYINT(1) NOT NULL
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE user_trip ADD approved TINYINT(1) NOT NULL
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE vessel ADD length DOUBLE PRECISION DEFAULT NULL, ADD breadth DOUBLE PRECISION DEFAULT NULL, ADD draft DOUBLE PRECISION DEFAULT NULL, ADD gross_tonnage DOUBLE PRECISION DEFAULT NULL, ADD imo_no VARCHAR(255) DEFAULT NULL, ADD call_sign VARCHAR(255) DEFAULT NULL
SQL);
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE location DROP is_start_end
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE `user` DROP is_pilot
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE vessel DROP length, DROP breadth, DROP draft, DROP gross_tonnage, DROP imo_no, DROP call_sign
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE trip_location DROP is_transit, DROP is_departure
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE trip ADD captain_name VARCHAR(255) DEFAULT NULL, DROP completed
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE user_trip DROP approved
SQL);
}
}

+ 2
- 0
httpdocs/src/ApiResource/LocationApi.php Datei anzeigen

@@ -93,6 +93,8 @@ class LocationApi


public bool $isPort; public bool $isPort;


public bool $isStartEnd;

#[ApiProperty(writable: false)] #[ApiProperty(writable: false)]
public ?\DateTimeImmutable $createdAt = null; public ?\DateTimeImmutable $createdAt = null;
} }

+ 2
- 2
httpdocs/src/ApiResource/TripApi.php Datei anzeigen

@@ -83,8 +83,6 @@ class TripApi


public ?string $customerReference = null; public ?string $customerReference = null;


public ?string $captainName = null;

/** /**
* @var LocationApi * @var LocationApi
*/ */
@@ -133,6 +131,8 @@ class TripApi


public ?string $note; public ?string $note;


public bool $completed = false;

#[ApiProperty(writable: false)] #[ApiProperty(writable: false)]
public ?\DateTimeImmutable $createdAt = null; public ?\DateTimeImmutable $createdAt = null;
} }

+ 5
- 1
httpdocs/src/ApiResource/TripLocationApi.php Datei anzeigen

@@ -98,7 +98,11 @@ class TripLocationApi
#[ApiProperty(writable: true)] #[ApiProperty(writable: true)]
public ?LocationApi $locationIri = null; public ?LocationApi $locationIri = null;


public bool $isArrival;
public bool $isArrival = false;

public bool $isTransit = false;

public bool $isDeparture = false;


#[Assert\NotBlank] #[Assert\NotBlank]
public \DateTimeImmutable $date; public \DateTimeImmutable $date;


+ 5
- 0
httpdocs/src/ApiResource/UserApi.php Datei anzeigen

@@ -18,6 +18,7 @@ use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Post;
use App\Entity\User; use App\Entity\User;
use App\Filter\CustomJsonFilter;
use App\Filter\CustomJsonOrderFilter; use App\Filter\CustomJsonOrderFilter;
use App\Filter\UserNameSearchFilter; use App\Filter\UserNameSearchFilter;
use App\State\EntityClassDtoStateProcessor; use App\State\EntityClassDtoStateProcessor;
@@ -53,6 +54,7 @@ use Symfony\Component\Validator\Constraints as Assert;
)] )]
#[ApiFilter(SearchFilter::class, properties: ['firstName' => 'ipartial', 'lastName' => 'ipartial'])] #[ApiFilter(SearchFilter::class, properties: ['firstName' => 'ipartial', 'lastName' => 'ipartial'])]
#[ApiFilter(UserNameSearchFilter::class)] #[ApiFilter(UserNameSearchFilter::class)]
#[ApiFilter(CustomJsonFilter::class)]
#[ApiFilter(CustomJsonOrderFilter::class)] #[ApiFilter(CustomJsonOrderFilter::class)]
class UserApi class UserApi
{ {
@@ -113,6 +115,9 @@ class UserApi
#[Assert\NotNull] #[Assert\NotNull]
public bool $active = false; public bool $active = false;


#[Assert\NotNull]
public bool $isPilot = false;

#[ApiProperty(writable: false)] #[ApiProperty(writable: false)]
public array $roles = []; public array $roles = [];




+ 3
- 0
httpdocs/src/ApiResource/UserTripApi.php Datei anzeigen

@@ -105,6 +105,9 @@ class UserTripApi
#[Assert\NotNull] #[Assert\NotNull]
public bool $completed = false; public bool $completed = false;


#[Assert\NotNull]
public bool $approved = false;

/** /**
* @var MediaObjectApi * @var MediaObjectApi
*/ */


+ 12
- 0
httpdocs/src/ApiResource/VesselApi.php Datei anzeigen

@@ -84,6 +84,18 @@ class VesselApi
#[ApiProperty(writable: true)] #[ApiProperty(writable: true)]
public ?ShippingCompanyApi $companyIri = null; public ?ShippingCompanyApi $companyIri = null;


public ?float $length = null;

public ?float $breadth = null;

public ?float $draft = null;

public ?float $grossTonnage = null;

public ?string $imoNo = null;

public ?string $callSign = null;

#[ApiProperty(writable: false)] #[ApiProperty(writable: false)]
public ?\DateTimeImmutable $createdAt = null; public ?\DateTimeImmutable $createdAt = null;
} }

+ 13
- 0
httpdocs/src/Entity/Location.php Datei anzeigen

@@ -37,6 +37,9 @@ class Location
#[ORM\Column(nullable: false)] #[ORM\Column(nullable: false)]
private bool $isPort = false; private bool $isPort = false;


#[ORM\Column(nullable: false)]
private bool $isStartEnd = false;

#[ORM\Column] #[ORM\Column]
private DateTimeImmutable $createdAt; private DateTimeImmutable $createdAt;


@@ -119,6 +122,16 @@ class Location
$this->isPort = $isPort; $this->isPort = $isPort;
} }


public function isStartEnd(): bool
{
return $this->isStartEnd;
}

public function setIsStartEnd(bool $isStartEnd): void
{
$this->isStartEnd = $isStartEnd;
}

public function getCreatedAt(): DateTimeImmutable public function getCreatedAt(): DateTimeImmutable
{ {
return $this->createdAt; return $this->createdAt;


+ 13
- 14
httpdocs/src/Entity/Trip.php Datei anzeigen

@@ -25,9 +25,6 @@ class Trip
#[ORM\Column(length: 255, nullable: true)] #[ORM\Column(length: 255, nullable: true)]
private ?string $customerReference = null; private ?string $customerReference = null;


#[ORM\Column(length: 255, nullable: true)]
private ?string $captainName = null;

#[ORM\ManyToOne(targetEntity: Location::class)] #[ORM\ManyToOne(targetEntity: Location::class)]
#[ORM\JoinColumn(name: 'start_location_id', nullable: false)] #[ORM\JoinColumn(name: 'start_location_id', nullable: false)]
private Location $startLocation; private Location $startLocation;
@@ -45,6 +42,9 @@ class Trip
#[ORM\Column(type: "text", nullable: true)] #[ORM\Column(type: "text", nullable: true)]
protected ?string $note; protected ?string $note;


#[ORM\Column(nullable: false)]
private bool $completed = false;

#[ORM\Column] #[ORM\Column]
private DateTimeImmutable $createdAt; private DateTimeImmutable $createdAt;


@@ -97,17 +97,6 @@ class Trip
$this->customerReference = $customerReference; $this->customerReference = $customerReference;
} }


public function getCaptainName(): ?string
{
return $this->captainName;
}

public function setCaptainName(?string $captainName): self
{
$this->captainName = $captainName;
return $this;
}

public function getStartLocation(): Location public function getStartLocation(): Location
{ {
return $this->startLocation; return $this->startLocation;
@@ -162,6 +151,16 @@ class Trip
$this->note = $note; $this->note = $note;
} }


public function isCompleted(): bool
{
return $this->completed;
}

public function setCompleted(bool $completed): void
{
$this->completed = $completed;
}

public function getCreatedAt(): DateTimeImmutable public function getCreatedAt(): DateTimeImmutable
{ {
return $this->createdAt; return $this->createdAt;


+ 26
- 0
httpdocs/src/Entity/TripLocation.php Datei anzeigen

@@ -27,6 +27,12 @@ class TripLocation
#[ORM\Column(nullable: false)] #[ORM\Column(nullable: false)]
private bool $isArrival = true; private bool $isArrival = true;


#[ORM\Column(nullable: false)]
private bool $isTransit = true;

#[ORM\Column(nullable: false)]
private bool $isDeparture = true;

#[ORM\Column] #[ORM\Column]
private DateTimeImmutable $date; private DateTimeImmutable $date;


@@ -89,6 +95,26 @@ class TripLocation
$this->isArrival = $isArrival; $this->isArrival = $isArrival;
} }


public function isTransit(): bool
{
return $this->isTransit;
}

public function setIsTransit(bool $isTransit): void
{
$this->isTransit = $isTransit;
}

public function isDeparture(): bool
{
return $this->isDeparture;
}

public function setIsDeparture(bool $isDeparture): void
{
$this->isDeparture = $isDeparture;
}

public function getCreatedAt(): DateTimeImmutable public function getCreatedAt(): DateTimeImmutable
{ {
return $this->createdAt; return $this->createdAt;


+ 13
- 0
httpdocs/src/Entity/User.php Datei anzeigen

@@ -35,6 +35,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\JoinColumn(nullable: true, onDelete: "SET NULL")] #[ORM\JoinColumn(nullable: true, onDelete: "SET NULL")]
private ?MediaObject $image = null; private ?MediaObject $image = null;


#[ORM\Column(nullable: false)]
private bool $isPilot = false;

#[ORM\Column] #[ORM\Column]
private array $roles = []; private array $roles = [];


@@ -180,6 +183,16 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
$this->active = $active; $this->active = $active;
} }


public function isPilot(): bool
{
return $this->isPilot;
}

public function setIsPilot(bool $isPilot): void
{
$this->isPilot = $isPilot;
}

public function getCreatedAt(): ?\DateTimeImmutable public function getCreatedAt(): ?\DateTimeImmutable
{ {
return $this->createdAt; return $this->createdAt;


+ 13
- 0
httpdocs/src/Entity/UserTrip.php Datei anzeigen

@@ -37,6 +37,9 @@ class UserTrip
#[ORM\Column(nullable: false)] #[ORM\Column(nullable: false)]
private bool $completed = false; private bool $completed = false;


#[ORM\Column(nullable: false)]
private bool $approved = false;

#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]
private ?DateTimeImmutable $completedDate; private ?DateTimeImmutable $completedDate;


@@ -124,6 +127,16 @@ class UserTrip
$this->completed = $completed; $this->completed = $completed;
} }


public function isApproved(): bool
{
return $this->approved;
}

public function setApproved(bool $approved): void
{
$this->approved = $approved;
}

public function getUserTripEvents(): Collection public function getUserTripEvents(): Collection
{ {
return $this->userTripEvents; return $this->userTripEvents;


+ 78
- 0
httpdocs/src/Entity/Vessel.php Datei anzeigen

@@ -28,6 +28,24 @@ class Vessel
#[ORM\Column(length: 255, unique: true)] #[ORM\Column(length: 255, unique: true)]
private string $code; private string $code;


#[ORM\Column(nullable: true)]
private ?float $length = null;

#[ORM\Column(nullable: true)]
private ?float $breadth = null;

#[ORM\Column(nullable: true)]
private ?float $draft = null;

#[ORM\Column(nullable: true)]
private ?float $grossTonnage = null;

#[ORM\Column(length: 255, nullable: true)]
private ?string $imoNo = null;

#[ORM\Column(length: 255, nullable: true)]
private ?string $callSign = null;

#[ORM\Column] #[ORM\Column]
private DateTimeImmutable $createdAt; private DateTimeImmutable $createdAt;


@@ -80,6 +98,66 @@ class Vessel
$this->code = $code; $this->code = $code;
} }


public function getLength(): ?float
{
return $this->length;
}

public function setLength(?float $length): void
{
$this->length = $length;
}

public function getBreadth(): ?float
{
return $this->breadth;
}

public function setBreadth(?float $breadth): void
{
$this->breadth = $breadth;
}

public function getDraft(): ?float
{
return $this->draft;
}

public function setDraft(?float $draft): void
{
$this->draft = $draft;
}

public function getGrossTonnage(): ?float
{
return $this->grossTonnage;
}

public function setGrossTonnage(?float $grossTonnage): void
{
$this->grossTonnage = $grossTonnage;
}

public function getImoNo(): ?string
{
return $this->imoNo;
}

public function setImoNo(?string $imoNo): void
{
$this->imoNo = $imoNo;
}

public function getCallSign(): ?string
{
return $this->callSign;
}

public function setCallSign(?string $callSign): void
{
$this->callSign = $callSign;
}

public function getCreatedAt(): DateTimeImmutable public function getCreatedAt(): DateTimeImmutable
{ {
return $this->createdAt; return $this->createdAt;


+ 3
- 2
httpdocs/src/Filter/CustomJsonFilter.php Datei anzeigen

@@ -6,6 +6,7 @@ use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\ClassMetadataInfo;
@@ -66,12 +67,12 @@ class CustomJsonFilter extends AbstractFilter
->setParameter($parameterName, '%' . $value . '%'); ->setParameter($parameterName, '%' . $value . '%');
} }


private function isValidProperty(ClassMetadataInfo $metadata, string $property): bool
private function isValidProperty(ClassMetadata $metadata, string $property): bool
{ {
return $metadata->hasField($property) || $metadata->hasAssociation($property); return $metadata->hasField($property) || $metadata->hasAssociation($property);
} }


private function isAssociation(ClassMetadataInfo $metadata, string $property): bool
private function isAssociation(ClassMetadata $metadata, string $property): bool
{ {
return $metadata->hasAssociation($property); return $metadata->hasAssociation($property);
} }


+ 1
- 0
httpdocs/src/Mapper/LocationApiToEntityMapper.php Datei anzeigen

@@ -56,6 +56,7 @@ class LocationApiToEntityMapper implements MapperInterface
$entity->setIsZone($dto->isZone); $entity->setIsZone($dto->isZone);
$entity->setIsPlace($dto->isPlace); $entity->setIsPlace($dto->isPlace);
$entity->setIsPort($dto->isPort); $entity->setIsPort($dto->isPort);
$entity->setIsStartEnd($dto->isStartEnd);


if ($dto->zoneIri) { if ($dto->zoneIri) {
$zone = $this->zoneRepository->find($dto->zoneIri->id); $zone = $this->zoneRepository->find($dto->zoneIri->id);


+ 1
- 0
httpdocs/src/Mapper/LocationEntityToApiMapper.php Datei anzeigen

@@ -42,6 +42,7 @@ class LocationEntityToApiMapper implements MapperInterface
$dto->isZone = $entity->isZone(); $dto->isZone = $entity->isZone();
$dto->isPlace = $entity->isPlace(); $dto->isPlace = $entity->isPlace();
$dto->isPort = $entity->isPort(); $dto->isPort = $entity->isPort();
$dto->isStartEnd = $entity->isStartEnd();
$dto->createdAt = $entity->getCreatedAt(); $dto->createdAt = $entity->getCreatedAt();


$dto->zoneIri = $dto->zone = $this->microMapper->map($entity->getZone(), ZoneApi::class, [ $dto->zoneIri = $dto->zone = $this->microMapper->map($entity->getZone(), ZoneApi::class, [


+ 0
- 1
httpdocs/src/Mapper/TripApiToEntityMapper.php Datei anzeigen

@@ -74,7 +74,6 @@ class TripApiToEntityMapper implements MapperInterface
assert($entity instanceof Trip); assert($entity instanceof Trip);


$entity->setCustomerReference($dto->customerReference); $entity->setCustomerReference($dto->customerReference);
$entity->setCaptainName($dto->captainName);
$entity->setStartDate($dto->startDate); $entity->setStartDate($dto->startDate);
$entity->setEndDate($dto->endDate); $entity->setEndDate($dto->endDate);
$entity->setNote($dto->note); $entity->setNote($dto->note);


+ 1
- 1
httpdocs/src/Mapper/TripEntityToApiMapper.php Datei anzeigen

@@ -39,10 +39,10 @@ class TripEntityToApiMapper implements MapperInterface
$dto->dbId = $entity->getId(); $dto->dbId = $entity->getId();
$dto->pilotageReference = "P-" . $entity->getId() . "-" . $entity->getStartDate()->format('Y'); $dto->pilotageReference = "P-" . $entity->getId() . "-" . $entity->getStartDate()->format('Y');
$dto->customerReference = $entity->getCustomerReference(); $dto->customerReference = $entity->getCustomerReference();
$dto->captainName = $entity->getCaptainName();
$dto->startDate = $entity->getStartDate(); $dto->startDate = $entity->getStartDate();
$dto->endDate = $entity->getEndDate(); $dto->endDate = $entity->getEndDate();
$dto->note = $entity->getNote(); $dto->note = $entity->getNote();
$dto->completed = $entity->isCompleted();
$dto->createdAt = $entity->getCreatedAt(); $dto->createdAt = $entity->getCreatedAt();


// Map related entities // Map related entities


+ 2
- 0
httpdocs/src/Mapper/TripLocationApiToEntityMapper.php Datei anzeigen

@@ -68,6 +68,8 @@ class TripLocationApiToEntityMapper implements MapperInterface
} }


$entity->setIsArrival($dto->isArrival); $entity->setIsArrival($dto->isArrival);
$entity->setIsTransit($dto->isTransit);
$entity->setIsDeparture($dto->isDeparture);
$entity->setDate($dto->date); $entity->setDate($dto->date);


return $entity; return $entity;


+ 4
- 2
httpdocs/src/Mapper/TripLocationEntityToApiMapper.php Datei anzeigen

@@ -39,13 +39,15 @@ class TripLocationEntityToApiMapper implements MapperInterface
$dto->dbId = $entity->getId(); $dto->dbId = $entity->getId();
$dto->date = $entity->getDate(); $dto->date = $entity->getDate();
$dto->isArrival = $entity->isArrival(); $dto->isArrival = $entity->isArrival();
$dto->isTransit = $entity->isTransit();
$dto->isDeparture = $entity->isDeparture();
$dto->createdAt = $entity->getCreatedAt(); $dto->createdAt = $entity->getCreatedAt();


$dto->trip = $this->microMapper->map($entity->getTrip(), TripApi::class, [
$dto->tripIri = $dto->trip = $this->microMapper->map($entity->getTrip(), TripApi::class, [
MicroMapperInterface::MAX_DEPTH => 5, MicroMapperInterface::MAX_DEPTH => 5,
]); ]);


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




+ 1
- 0
httpdocs/src/Mapper/UserApiToEntityMapper.php Datei anzeigen

@@ -46,6 +46,7 @@ class UserApiToEntityMapper implements MapperInterface
$entity->setFirstName($dto->firstName); $entity->setFirstName($dto->firstName);
$entity->setLastName($dto->lastName); $entity->setLastName($dto->lastName);
$entity->setActive($dto->active); $entity->setActive($dto->active);
$entity->setIsPilot($dto->isPilot);


$roles = $entity->getRoles(); $roles = $entity->getRoles();
if ($dto->isAdmin && !in_array(User::ROLE_ADMIN, $roles, true)) { if ($dto->isAdmin && !in_array(User::ROLE_ADMIN, $roles, true)) {


+ 1
- 0
httpdocs/src/Mapper/UserEntityToApiMapper.php Datei anzeigen

@@ -46,6 +46,7 @@ class UserEntityToApiMapper implements MapperInterface
$dto->roles = $entity->getRoles(); $dto->roles = $entity->getRoles();
$dto->isAdmin = in_array(User::ROLE_ADMIN, $entity->getRoles(), true); $dto->isAdmin = in_array(User::ROLE_ADMIN, $entity->getRoles(), true);
$dto->active = $entity->isActive(); $dto->active = $entity->isActive();
$dto->isPilot = $entity->isPilot();


$dto->imageIri = $dto->image = null; $dto->imageIri = $dto->image = null;
if ($entity->getImage() !== null) { if ($entity->getImage() !== null) {


+ 1
- 0
httpdocs/src/Mapper/UserTripApiToEntityMapper.php Datei anzeigen

@@ -70,6 +70,7 @@ class UserTripApiToEntityMapper implements MapperInterface
$entity->setCompletedDate(new \DateTimeImmutable()); $entity->setCompletedDate(new \DateTimeImmutable());
} }
$entity->setCompleted($dto->completed); $entity->setCompleted($dto->completed);
$entity->setApproved($dto->approved);


if ($dto->signatureIri) { if ($dto->signatureIri) {
$signature = $this->mediaObjectRepository->find($dto->signatureIri->id); $signature = $this->mediaObjectRepository->find($dto->signatureIri->id);


+ 1
- 0
httpdocs/src/Mapper/UserTripEntityToApiMapper.php Datei anzeigen

@@ -43,6 +43,7 @@ class UserTripEntityToApiMapper implements MapperInterface
$dto->captainName = $entity->getCaptainName(); $dto->captainName = $entity->getCaptainName();
$dto->signatureUrl = $this->fileUrlService->getFileUrl($entity->getSignature()); $dto->signatureUrl = $this->fileUrlService->getFileUrl($entity->getSignature());
$dto->completed = $entity->isCompleted(); $dto->completed = $entity->isCompleted();
$dto->approved = $entity->isApproved();
$dto->completedDate = $entity->getCompletedDate(); $dto->completedDate = $entity->getCompletedDate();
$dto->createdAt = $entity->getCreatedAt(); $dto->createdAt = $entity->getCreatedAt();




+ 6
- 1
httpdocs/src/Mapper/VesselApiToEntityMapper.php Datei anzeigen

@@ -8,7 +8,6 @@ use App\Repository\VesselRepository;
use App\Repository\ShippingCompanyRepository; use App\Repository\ShippingCompanyRepository;
use Symfonycasts\MicroMapper\AsMapper; use Symfonycasts\MicroMapper\AsMapper;
use Symfonycasts\MicroMapper\MapperInterface; use Symfonycasts\MicroMapper\MapperInterface;
use Symfonycasts\MicroMapper\MicroMapperInterface;


#[AsMapper(from: VesselApi::class, to: Vessel::class)] #[AsMapper(from: VesselApi::class, to: Vessel::class)]
class VesselApiToEntityMapper implements MapperInterface class VesselApiToEntityMapper implements MapperInterface
@@ -54,6 +53,12 @@ class VesselApiToEntityMapper implements MapperInterface


$entity->setName($dto->name); $entity->setName($dto->name);
$entity->setCode($dto->code); $entity->setCode($dto->code);
$entity->setLength($dto->length);
$entity->setBreadth($dto->breadth);
$entity->setDraft($dto->draft);
$entity->setGrossTonnage($dto->grossTonnage);
$entity->setImoNo($dto->imoNo);
$entity->setCallSign($dto->callSign);
$dto->createdAt = $entity->getCreatedAt(); $dto->createdAt = $entity->getCreatedAt();


return $entity; return $entity;


+ 7
- 1
httpdocs/src/Mapper/VesselEntityToApiMapper.php Datei anzeigen

@@ -38,9 +38,15 @@ class VesselEntityToApiMapper implements MapperInterface
$dto->dbId = $entity->getId(); $dto->dbId = $entity->getId();
$dto->name = $entity->getName(); $dto->name = $entity->getName();
$dto->code = $entity->getCode(); $dto->code = $entity->getCode();
$dto->length = $entity->getLength();
$dto->breadth = $entity->getBreadth();
$dto->draft = $entity->getDraft();
$dto->grossTonnage = $entity->getGrossTonnage();
$dto->imoNo = $entity->getImoNo();
$dto->callSign = $entity->getCallSign();
$dto->createdAt = $entity->getCreatedAt(); $dto->createdAt = $entity->getCreatedAt();


$dto->company = $this->microMapper->map($entity->getCompany(), ShippingCompanyApi::class, [
$dto->companyIri = $dto->company = $this->microMapper->map($entity->getCompany(), ShippingCompanyApi::class, [
MicroMapperInterface::MAX_DEPTH => 5, MicroMapperInterface::MAX_DEPTH => 5,
]); ]);




Laden…
Abbrechen
Speichern