Compare commits

..

No commits in common. "develop" and "feature/api_input_validation" have entirely different histories.

150 changed files with 2210 additions and 10238 deletions

3
.vscode/launch.json vendored
View File

@ -12,8 +12,7 @@
"env": { "env": {
"FLASK_APP": "src/server/BreCal", "FLASK_APP": "src/server/BreCal",
"FLASK_DEBUG": "1", "FLASK_DEBUG": "1",
"SECRET_KEY" : "zdiTz8P3jXOc7jztIQAoelK4zztyuCpJ", // https://randomkeygen.com/ "SECRET_KEY" : "zdiTz8P3jXOc7jztIQAoelK4zztyuCpJ" // https://randomkeygen.com/
"FLASK_RUN_PORT": "5000"
}, },
"args": [ "args": [
"run", "run",

View File

@ -61,12 +61,11 @@ PUT / DELETE calls referencing entities that are not found in the database will
### Return value ### Return value
If a validation rule fails the call should return 400 (Bad request) including an error message in the following format: If a validation rule fails the call should return 400 (Bad request) including an error message in the format already in use:
```json ```json
{ {
"error_field" : "A reference to the respective field(s) which have caused the error", "message" : "reason why this call failed"
"error_description" : "Reason why this call failed"
} }
``` ```
@ -82,10 +81,6 @@ Date and date+time values are specified as text formatted in [RFC 3339](https://
Usually the "Z" is missing at the end indicating local time. Usually the "Z" is missing at the end indicating local time.
Generally, times may not be updated to a value in the past. There are exception (see SHIPCALL PUT below).
Times should also not be set to a value more than 1 year in the future. The reasoning is to prevent shipcalls to stick to the top of the list by implausible (or for the workings of Bremen calling irrelevant) values too far in the future.
## /shipcall POST ## /shipcall POST
1. The call may only be performed by a user belonging to participant group type BSMD. 1. The call may only be performed by a user belonging to participant group type BSMD.
@ -110,14 +105,14 @@ Times should also not be set to a value more than 1 year in the future. The reas
| tidal_window_from | value must be in the future | | tidal_window_from | value must be in the future |
| tidal_window_to | value must be in the future, value must be > tidal_window_from | | tidal_window_to | value must be in the future, value must be > tidal_window_from |
| recommended_tugs | 0 < value < 10 | | recommended_tugs | 0 < value < 10 |
| canceled | optional on POST | | canceled | may not be set on POST |
| evaluation | may not be set | | evaluation | may not be set |
| evaluation_message | may not be set | | evaluation_message | may not be set |
| created / modified | may not be set | | created / modified | may not be set |
#### Required fields #### Required fields
* eta / etd (depending on value of type: 1: eta, 2: etd, 3: etd) * eta / etd (depending on value of type: 1: eta, 2: etd, 3: both)
* type * type
* ship_id * ship_id
* arrival_berth_id / departure_berth_id (depending on type, see above) * arrival_berth_id / departure_berth_id (depending on type, see above)
@ -128,11 +123,7 @@ Times should also not be set to a value more than 1 year in the future. The reas
1. The call may only be performed by a user belonging to participant group type BSMD. 1. The call may only be performed by a user belonging to participant group type BSMD.
2. If a agency is selected via the shipcall_participant_map entry, users of this agency may also edit the shipcall. Care has to be taken: The agency must have been set _before_ a member of the group may edit the record. 2. If a agency is selected via the shipcall_participant_map entry, users of this agency may also edit the shipcall. Care has to be taken: The agency must have been set _before_ a member of the group may edit the record.
In other words: no setting the agency and editing the record by a member of the agency within the same call. In other words: no setting the agency and editing the record by a member of the agency within the same call.
3. See value rules in /shipcall POST. 3. See value rules in /shipcall POST. Exception: Canceled may be set but only if not already set.
Exceptions:
a) Canceled may be set but only if not already set.
b) ETA/ETD may be in the past. This can happen if an agency has entered an ETA/ETD (times) in the future but
wants to edit fields of the shipcall record (e.g. the draft) but the shipcall was originally created with an ETA/ETD in the past
4. A cancelled shipcall may not be changed (is logical delete) 4. A cancelled shipcall may not be changed (is logical delete)
#### Required fields #### Required fields
@ -156,7 +147,6 @@ The id field is required, missing fields will not be updated.
| Field | Validation | | Field | Validation |
|-------|------------| |-------|------------|
| eta_berth, etd_berth, lock_time, zone_entry, operations_start, operations_end | if set these values must be in the future| | eta_berth, etd_berth, lock_time, zone_entry, operations_start, operations_end | if set these values must be in the future|
| eta_interval_end, etd_interval_end | if set these values must be in the future. They must be larger than their ETA/ETD counterparts. |
| remarks, berth_info | must be <= 512 chars | | remarks, berth_info | must be <= 512 chars |
| participant_type | must not be BSMD | | participant_type | must not be BSMD |
@ -198,7 +188,6 @@ shipcall_id, participant_id, participant_type
1. A dataset may only be changed by a user belonging to the same participant as the times dataset is referring to. 1. A dataset may only be changed by a user belonging to the same participant as the times dataset is referring to.
2. See reference and value checking as specified in /times POST. 2. See reference and value checking as specified in /times POST.
3. The shipcall type may not be changed.
#### Required fields #### Required fields

View File

@ -32,7 +32,7 @@ ___
| 0002 | Zeiten für einen Eintrag weichen voneinander ab | Bedingungen:<br/> - Header der Zeile ist zugeordnet (Agentur, Festmacher usw. - außer BSMD-Spalte)<br/> - Zeiten ungleich (leere Einträge nicht berücksichtigen => 0001) | | | 0002 | Zeiten für einen Eintrag weichen voneinander ab | Bedingungen:<br/> - Header der Zeile ist zugeordnet (Agentur, Festmacher usw. - außer BSMD-Spalte)<br/> - Zeiten ungleich (leere Einträge nicht berücksichtigen => 0001) | |
| 0002 - A | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / einkommend | Schnittmenge aus:<br/>times_agency:<br/> - ETA Berth <br/>____und____<br/>times_mooring:<br/> - ETA Berth <br/>____und____<br/>times_portauthority:<br/>- ETA Berth <br/>____und____<br/>times_pilot:<br/> - ETA Berth <br/>____und____<br/>times_tug:<br/> - ETA Berth | rot | | 0002 - A | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / einkommend | Schnittmenge aus:<br/>times_agency:<br/> - ETA Berth <br/>____und____<br/>times_mooring:<br/> - ETA Berth <br/>____und____<br/>times_portauthority:<br/>- ETA Berth <br/>____und____<br/>times_pilot:<br/> - ETA Berth <br/>____und____<br/>times_tug:<br/> - ETA Berth | rot |
| 0002 - B | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / ausgehend | Schnittmenge aus:<br/>times_agency:<br/> - ETD Berth <br/>____und____<br/>times_mooring:<br/> - ETD Berth <br/>____und____<br/>times_portauthority:<br/>- ETD Berth <br/>____und____<br/>times_pilot:<br/> - ETD Berth <br/>____und____<br/>times_tug:<br/> - ETD Berth | rot | | 0002 - B | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / ausgehend | Schnittmenge aus:<br/>times_agency:<br/> - ETD Berth <br/>____und____<br/>times_mooring:<br/> - ETD Berth <br/>____und____<br/>times_portauthority:<br/>- ETD Berth <br/>____und____<br/>times_pilot:<br/> - ETD Berth <br/>____und____<br/>times_tug:<br/> - ETD Berth | rot |
| 0002 - C | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / Verholung | Schnittmenge aus:<br/>times_agency:<br/> - ETD Berth <br/>____und____<br/>times_mooring:<br/> - ETD Berth <br/>____und____<br/>times_portauthority:<br/>- ETD Berth <br/>____und____<br/>times_pilot:<br/> - ETD Berth <br/>____und____<br/>times_tug:<br/> - ETD Berth | rot | | 0002 - C | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / Verholung | Schnittmenge aus:<br />times_agency:<br/> - ETA Berth <br/> - ETD Berth <br/>____und____<br/>times_mooring:<br/> - ETA Berth <br/> - ETD Berth <br/>____und____<br/>times_portauthority:<br/> - ETA Berth <br/>- ETD Berth <br/>____und____<br/>times_pilot:<br/> - ETA Berth <br/> - ETD Berth <br/>____und____<br/>times_tug:<br/> - ETA Berth <br/> - ETD Berth | rot |
| 0003 | Arbeitszeit überschneidet sich mit Fahrtzeit | Bedingungen:<br/> - Header der Zeile ist zugeordnet (Terminal)<br/> - Zeiten passt nicht zu Ankunft / Abfahrt (leere Einträge nicht berücksichtigen => 0001) | | | 0003 | Arbeitszeit überschneidet sich mit Fahrtzeit | Bedingungen:<br/> - Header der Zeile ist zugeordnet (Terminal)<br/> - Zeiten passt nicht zu Ankunft / Abfahrt (leere Einträge nicht berücksichtigen => 0001) | |
| 0003 - A | Terminal / einkommend | times_terminal:<br/> - Operation Start<br/>___vor (kleiner als)____<br/> times_agency:<br/> - ETA Berth | rot, aktuell __deaktiviert__! | | 0003 - A | Terminal / einkommend | times_terminal:<br/> - Operation Start<br/>___vor (kleiner als)____<br/> times_agency:<br/> - ETA Berth | rot, aktuell __deaktiviert__! |
| 0003 - B | Terminal / ausgehend + Verholung | times_terminal:<br/> - Operation Ende<br/>___nach (größer als)____<br/> times_agency:<br/> - ETD Berth | rot, aktuell __deaktiviert__! | | 0003 - B | Terminal / ausgehend + Verholung | times_terminal:<br/> - Operation Ende<br/>___nach (größer als)____<br/> times_agency:<br/> - ETD Berth | rot, aktuell __deaktiviert__! |

File diff suppressed because it is too large Load Diff

View File

@ -1,537 +0,0 @@
# Bremen calling API
Version: _1.7.0_
Last change: _Nov 14, 2025_
## Introduction
This API allows users to interact with "Bremen calling" without an UI. Apart vom querying data via _GET_ endpoints users may create and update shipcalls, assign participants and update participant times for shipcalls.
Creating and updating times and shipcalls depend on the participant roles a user is assigned to. For example, if a participant has the role "AGENCY" they may change assignments _and_ create and update agency times. A participant with the role "PILOT" on the other hand may not change the assigments and only create/update times for the pilot.
### Authentication
- **ApiKey**: API key in `header` header named `Authorization`. This is a JWT Token that the caller receives upon login.
### Notes on this version
This version refers to _1.7_ whereas the public client currently has version _1.6_. This means that there is some functionality available in the API that cannot be accessed through the UI yet, specifically notifications.
There is no documentation for the structures returned by _GET_ requests but these can easily be determined via a single query.
## Ship Endpoints
### `DELETE /ships`
**Summary:** Delete a ship (logically).
A ship can only be logically deleted, since it is possible to have been used in previous shipcalls. On logical delete, the ship can no longer be selected in a new ship call.
#### Parameters
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| id | query | integer | Yes | **Id of ship**. *Example: 42*. Id of ship to be deleted. |
#### Responses
- **200**
- **400**
- **401**
- **500**
- **503**
---
### `GET /ships`
**Summary:** gets a list of ships
Gets a list of ships including logically deleted ships to be used with shipcalls
#### Responses
- **200**: list of ships
- **400**
- **401**
- **500**
- **503**
---
### `POST /ships`
**Summary:** create a new ship entry
adds a new non-existing ship to the database. The ships IMO number is the unique identifier.
#### Request Body
Ship details. **Do not** provide id parameter.
**JSON Schema**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | integer | No | |
| name | string | No | |
| imo | integer | No | |
| callsign | string | No | |
| participant_id | integer | No | Optional reference to participant (tug role) |
| length | number | No | |
| width | number | No | |
| is_tug | boolean | No | |
| bollard_pull | integer | No | |
| eni | integer | No | BSMD internal use |
| created | string | No | Readonly field set by the database when ship was created |
| modified | string | No | Readonly field set by the database when ship was last modified |
| deleted | boolean | No | marks the ship as logically deleted |
#### Responses
- **201**
- **400**
- **401**
- **500**
- **503**
---
### `PUT /ships`
**Summary:** Update a ship entry
Updating a ship entry. Please do not modify the IMO number. In that case please add a new entry.
#### Request Body
Updated ship entry. The id parameter is **required**.
**JSON Schema**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | integer | No | |
| name | string | No | |
| imo | integer | No | |
| callsign | string | No | |
| participant_id | integer | No | Optional reference to participant (tug role) |
| length | number | No | |
| width | number | No | |
| is_tug | boolean | No | |
| bollard_pull | integer | No | |
| eni | integer | No | BSMD internal use |
| created | string | No | Readonly field set by the database when ship was created |
| modified | string | No | Readonly field set by the database when ship was last modified |
| deleted | boolean | No | marks the ship as logically deleted |
#### Responses
- **200**
- **400**
- **401**
- **500**
- **503**
---
## Shipcall Endpoints
### `GET /shipcalls`
**Summary:** Gets a list of ship calls
Get current ship calls
#### Parameters
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| past_days | query | integer | No | number of days in the past to include in the result. *Example: 7*. |
#### Responses
- **200**: ship call list
- **400**
- **401**
- **500**
- **503**
---
### `POST /shipcalls`
**Summary:** Create a new ship call
A new shipcall is created without times at this point. This is ususally done by the BSMD or a participant with that particular role.
#### Request Body
Creates a new ship call. **Do not** provide id parameter.
**JSON Schema**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | integer | No | |
| ship_id | integer | Yes | |
| port_id | integer | No | |
| type | string | Yes | Type of ship call |
| eta | string | No | |
| voyage | string | No | |
| etd | string | No | |
| arrival_berth_id | integer | No | |
| departure_berth_id | integer | No | |
| tug_required | boolean | No | |
| pilot_required | boolean | No | |
| flags | integer | No | |
| pier_side | boolean | No | |
| bunkering | boolean | No | |
| replenishing_terminal | boolean | No | |
| replenishing_lock | boolean | No | |
| draft | number | No | |
| tidal_window_from | string | No | |
| tidal_window_to | string | No | |
| rain_sensitive_cargo | boolean | No | |
| recommended_tugs | integer | No | |
| anchored | boolean | No | |
| moored_lock | boolean | No | |
| canceled | boolean | No | |
| evaluation | string | No | Evaluation of the ship call |
| evaluation_message | string | No | |
| time_ref_point | integer | No | Physical reference point for all times given in shipcall and depending times entries |
| participants | array<object> | No | |
| created | string | No | Readonly field set by the database when shipcall was created |
| modified | string | No | Readonly field set by the database when shipcall was last modified |
#### Responses
- **201**
- **400**
- **401**
- **500**
- **503**
---
### `PUT /shipcalls`
**Summary:** Updates a ship call
Updates a shipcall. Usually done if the participant assignments change.
#### Request Body
Creates a new ship call. The id parameter is **required**.
**JSON Schema**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | integer | No | |
| ship_id | integer | Yes | |
| port_id | integer | No | |
| type | string | Yes | Type of ship call |
| eta | string | No | |
| voyage | string | No | |
| etd | string | No | |
| arrival_berth_id | integer | No | |
| departure_berth_id | integer | No | |
| tug_required | boolean | No | |
| pilot_required | boolean | No | |
| flags | integer | No | |
| pier_side | boolean | No | |
| bunkering | boolean | No | |
| replenishing_terminal | boolean | No | |
| replenishing_lock | boolean | No | |
| draft | number | No | |
| tidal_window_from | string | No | |
| tidal_window_to | string | No | |
| rain_sensitive_cargo | boolean | No | |
| recommended_tugs | integer | No | |
| anchored | boolean | No | |
| moored_lock | boolean | No | |
| canceled | boolean | No | |
| evaluation | string | No | Evaluation of the ship call |
| evaluation_message | string | No | |
| time_ref_point | integer | No | Physical reference point for all times given in shipcall and depending times entries |
| participants | array<object> | No | |
| created | string | No | Readonly field set by the database when shipcall was created |
| modified | string | No | Readonly field set by the database when shipcall was last modified |
#### Responses
- **200**
- **400**
- **401**
- **500**
- **503**
---
## Static Endpoints
### `GET /berths`
**Summary:** Gets a list of all berths registered
Returns a list of berths, including berths that are (logically) deleted
#### Responses
- **200**: list of berths
- **400**
- **401**
- **500**
- **503**
---
### `GET /history`
**Summary:** History data
This endpoint returns a list of changes made to the specific shipcall
#### Parameters
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| shipcall_id | query | integer | Yes | **Id of ship call**. *Example: 3*. Id given in ship call list |
#### Responses
- **200**: list of history entries
- **400**
- **401**
- **500**
- **503**
---
### `GET /notifications`
**Summary:** Gets a list of notifications pursuant to a specified participant and ship call
List of notifications (tbd)
#### Parameters
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| shipcall_id | query | integer | No | **Id of ship call**. *Example: 52*. Id given in ship call list |
#### Responses
- **200**: notification list
- **400**
- **401**
- **500**
- **503**
---
### `GET /participants`
**Summary:** gets one or all participants
If no parameter is given, all participants are returned. The list can be used to display participant information in the context of ship calls.
#### Parameters
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| user_id | query | integer | No | **Id of user**. *Example: 2*. User id returned by verify call. |
#### Responses
- **200**: one or all participants as list
- **400**
- **401**
- **404**
- **500**
- **503**
---
### `GET /ports`
**Summary:** Your GET endpoint
Returns a list of ports
#### Responses
- **200**: list of ports
- **401**
- **403**
- **500**
- **503**
---
## Times Endpoints
### `DELETE /times`
**Summary:** Delete a times entry for a ship call.
A times entry is typically deleted if the agent for example changes or removes the participant assignment for a particular role.
#### Parameters
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| id | query | integer | Yes | **Id of times**. *Example: 42*. Id of times entry to be deleted. |
#### Responses
- **200**
- **400**
- **401**
- **500**
- **503**
---
### `GET /times`
**Summary:** Gets list of times
Get all times assigned to a shipcall. These might not be complete.
#### Parameters
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| shipcall_id | query | integer | No | **Id**. *Example: 42*. Id of referenced ship call. |
#### Responses
- **200**: list of recorded times
- **400**
- **401**
- **500**
- **503**
---
### `POST /times`
**Summary:** Create a new times entry for a ship call
The times entry for a shipcall is created with reference to a participant. For each participant type there should be only one times data record.
#### Request Body
Times entry that will be added to the ship call. **Do not** provide id parameter.
**JSON Schema**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | integer | No | |
| eta_berth | string | No | Arrival time at berth |
| eta_berth_fixed | boolean | No | If true, the eta is fixed and cannot be changed |
| etd_berth | string | No | departure time from berth |
| etd_berth_fixed | boolean | No | If true, the etd is fixed and cannot be changed |
| lock_time | string | No | arrival time at lock |
| lock_time_fixed | boolean | No | If true, the lock time is fixed and cannot be changed |
| zone_entry | string | No | Expected time of entry into the zone |
| zone_entry_fixed | boolean | No | If true, the zone entry time is fixed and cannot be changed |
| operations_start | string | No | Start time for terminal operations |
| operations_end | string | No | End time for terminal operations |
| remarks | string | No | Additional remarks |
| shipcall_id | integer | Yes | Reference to a shipcall id |
| participant_id | integer | Yes | Reference to a participant id |
| berth_id | integer | No | Reference to a berth id |
| berth_info | string | No | Additional info text for berth |
| pier_side | boolean | No | true if ship is rotated, false otherwise |
| participant_type | integer | No | |
| ata | string | No | ata can be set by mooring if actual times are different from planned |
| atd | string | No | atd can be set by mooring if actual times are different from planned |
| eta_interval_end | string | No | Optional end of the interval for the times eta entry |
| etd_interval_end | string | No | Optional end of the interval for the times etd entry |
| created | string | No | Readonly field set by the database when times record was created |
| modified | string | No | Readonly field set by the database when times record was last modified |
#### Responses
- **201**
- **400**
- **401**
- **500**
- **503**
---
### `PUT /times`
**Summary:** Update a times entry for a ship call
Updating a times entry for a ship for a particular participant. The times entries are required for a shipcall to pass the validation rules.
#### Request Body
Times entry that will be added to the ship call. The id parameter is **required**.
**JSON Schema**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | integer | No | |
| eta_berth | string | No | Arrival time at berth |
| eta_berth_fixed | boolean | No | If true, the eta is fixed and cannot be changed |
| etd_berth | string | No | departure time from berth |
| etd_berth_fixed | boolean | No | If true, the etd is fixed and cannot be changed |
| lock_time | string | No | arrival time at lock |
| lock_time_fixed | boolean | No | If true, the lock time is fixed and cannot be changed |
| zone_entry | string | No | Expected time of entry into the zone |
| zone_entry_fixed | boolean | No | If true, the zone entry time is fixed and cannot be changed |
| operations_start | string | No | Start time for terminal operations |
| operations_end | string | No | End time for terminal operations |
| remarks | string | No | Additional remarks |
| shipcall_id | integer | Yes | Reference to a shipcall id |
| participant_id | integer | Yes | Reference to a participant id |
| berth_id | integer | No | Reference to a berth id |
| berth_info | string | No | Additional info text for berth |
| pier_side | boolean | No | true if ship is rotated, false otherwise |
| participant_type | integer | No | |
| ata | string | No | ata can be set by mooring if actual times are different from planned |
| atd | string | No | atd can be set by mooring if actual times are different from planned |
| eta_interval_end | string | No | Optional end of the interval for the times eta entry |
| etd_interval_end | string | No | Optional end of the interval for the times etd entry |
| created | string | No | Readonly field set by the database when times record was created |
| modified | string | No | Readonly field set by the database when times record was last modified |
#### Responses
- **200**
- **400**
- **401**
- **500**
- **503**
---
## User Endpoints
### `POST /login`
**Summary:** Returns a JWT session token and user data if successful
Perform login
#### Request Body
Login credentials
**JSON Schema**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| username | string | Yes | |
| password | string | Yes | |
#### Responses
- **200**: Successful response
- **400**
- **403**
- **500**
- **503**
**Response 200 JSON Schema**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | integer | No | |
| participant_id | integer | No | |
| first_name | string | No | |
| last_name | string | No | |
| user_name | string | No | |
| user_phone | string | No | |
| user_email | string | No | |
| notify_email | boolean | No | |
| notify_whatsapp | boolean | No | |
| notify_signal | boolean | No | |
| notify_popup | boolean | No | |
| exp | number | No | |
| token | string | No | |
| notify_on | array<string> | No | |
---
### `PUT /user`
**Summary:** Update user details (first/last name, phone, password)
Update user information
#### Request Body
User details
**JSON Schema**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| id | integer | No | |
| old_password | string | No | |
| new_password | string | No | |
| first_name | string | No | |
| last_name | string | No | |
| user_phone | string | No | |
| user_email | string | No | |
| notify_email | boolean | No | |
| notify_popup | boolean | No | |
| notify_whatsapp | boolean | No | |
| notify_signal | boolean | No | |
| notify_on | array<string> | No | |
#### Responses
- **200**
- **400**
- **401**
- **500**
- **503**
---

File diff suppressed because it is too large Load Diff

View File

@ -48,156 +48,3 @@ DROP TABLE IF EXISTS `shipcall`;
SET FOREIGN_KEY_CHECKS = 1; SET FOREIGN_KEY_CHECKS = 1;
``` ```
## Schema
```mermaid
erDiagram
participant {
INT id PK
VARCHAR name
VARCHAR street
VARCHAR postal_code
VARCHAR city
INT type
INT flags
}
port {
INT id PK
VARCHAR name
CHAR locode
}
berth {
INT id PK
VARCHAR name
BIT lock
INT owner_id FK
INT authority_id FK
INT port_id FK
BIT deleted
}
ship {
INT id PK
VARCHAR name
INT imo
VARCHAR callsign
INT participant_id FK
FLOAT length
FLOAT width
BIT is_tug
INT bollard_pull
INT eni
BIT deleted
}
shipcall {
INT id PK
INT ship_id FK
TINYINT type
DATETIME eta
DATETIME etd
INT arrival_berth_id FK
INT departure_berth_id FK
INT port_id FK
INT flags
BIT tug_required
BIT pilot_required
}
times {
INT id PK
INT shipcall_id FK
INT participant_id FK
INT berth_id FK
INT participant_type
DATETIME eta_berth
DATETIME etd_berth
DATETIME lock_time
DATETIME zone_entry
}
notification {
INT id PK
INT shipcall_id FK
INT participant_id FK
TINYINT level
TINYINT type
}
history {
INT id PK
INT participant_id FK
INT user_id FK
INT shipcall_id FK
DATETIME timestamp
DATETIME eta
INT type
INT operation
}
shipcall_participant_map {
INT id PK
INT shipcall_id FK
INT participant_id FK
INT type
}
shipcall_tug_map {
INT id PK
INT shipcall_id FK
INT ship_id FK
}
participant_port_map {
INT id PK
INT participant_id FK
INT port_id FK
}
user {
INT id PK
INT participant_id FK
VARCHAR first_name
VARCHAR last_name
VARCHAR user_name
VARCHAR user_email
}
role {
INT id PK
VARCHAR name
VARCHAR description
}
securable {
INT id PK
VARCHAR name
}
role_securable_map {
INT id PK
INT role_id FK
INT securable_id FK
}
user_role_map {
INT id PK
INT user_id FK
INT role_id FK
}
participant ||--o{ berth : owner_id
participant ||--o{ berth : authority_id
port ||--o{ berth : port_id
participant ||--o{ ship : participant_id
ship ||--o{ shipcall : ship_id
berth ||--o{ shipcall : arrival_berth_id
berth ||--o{ shipcall : departure_berth_id
port ||--o{ shipcall : port_id
shipcall ||--|| times : shipcall_id
participant ||--|| times : participant_id
berth ||--o{ times : berth_id
shipcall ||--o{ notification : shipcall_id
participant ||--o{ notification : participant_id
participant ||--o{ history : participant_id
user ||--o{ history : user_id
shipcall ||--o{ history : shipcall_id
shipcall ||--o{ shipcall_participant_map : shipcall_id
participant ||--o{ shipcall_participant_map : participant_id
shipcall ||--o{ shipcall_tug_map : shipcall_id
ship ||--o{ shipcall_tug_map : ship_id
participant ||--o{ participant_port_map : participant_id
port ||--o{ participant_port_map : port_id
participant ||--o{ user : participant_id
user ||--o{ user_role_map : user_id
role ||--o{ user_role_map : role_id
role ||--o{ role_securable_map : role_id
securable ||--o{ role_securable_map : securable_id
```

View File

@ -1,21 +0,0 @@
# Tests
___
## schemathesis
Unter dem Verzeichnis ./src/server/tests befinden sich die Test-Cases, die Max bei der ersten Implementierung der Validierung angelegt hat.
Diese sind derzeit nicht "aktiv", bzw. noch nicht bereinigt.
Ich möchte gern ein automatisches Framework verwenden, das aber nur "manuell" betrieben wird. Änderungen an der API sind selten.
Aktueller Stand (7.1.26):
Im Verzeichnis ./src/server/tests kann folgendes ausgeführt werden:
```pytest -q --maxfail=1 contract/test_openapi_fuzz.py```
Das Ganze funktioniert nur, wenn auch schemathesis und hypothesis in den passenden(!) Versionen im lokalen _venv_ installiert sind.
Aktuell habe ich schemathesis ("latest") und hypothesis 6.120.0:
```pip install "hypothesis==6.120.0"```
Das muss wegen dependencies so blöd gepinnt werden.

View File

@ -1,27 +0,0 @@
# Bremen Calling Open API spec
## Infos zur Generierung der CS Wrapper / Mapping Datei aus YAML
Verwendung von "OpenAPIGenerator" aus dem [Rest API Client Code Generator 2022](https://marketplace.visualstudio.com/items?itemName=ChristianResmaHelle.ApiClientCodeGenerator2022).
Die automatisch generierte Datei muss leider noch nachgearbeitet werden:
1) #pragma warning disable CS8073 (direkt nach der NS declaration).
2) #pragma warning restore CS8073 // The result of the expression is always the same since a value of this type is never equal to 'null' (am Schluss vor der schließenden Klammer des NS)
3) Für readOnly Properties wird bei einem Enum kein korrekter Code emittiert. Daher muss aktuell folgende Funktion ergänzt werden, damit beim Speichern das "evaluation" Flag nicht mitgesendet wird:
```C++
public bool ShouldSerializeEvaluation()
{
return false;
}
```
Witziger(!)weise funktioniert es für das Property EvaluationMessage korrekt.
### Vacuum Yaml Linter
Example Usage:
```bash
vacuum lint -d .\misc\BreCalApi.yaml --fail-severity warn
```

View File

@ -1,16 +0,0 @@
# Versionshistorie
## 1.7
### YAML / API
1. Notifications GET: Der Parameter "shipcall_id" ist jetzt optional für den Abruf von Benachrichtigungen.
2. Notification: Enthält jetzt ein neues Feld "participant_id". Ist dieses gesetzt, richtet sich die Benachrichtigung an diesen Teilnehmer. Ist das Feld nicht vorhanden, richtet sich die Benachrichtigung an alle Beteiligten des shipcall
3. Die Benutzerdaten (login_result) enthalten jetzt die Felder (Flags) der Zuordnung für die verschiedenen Benachrichtigungs-Wege, aktuell implementiert ist notify_email und notify_popup. Diese können auch über user_details analog zu Telefonnummer, Name etc. gesetzt werden.
4. Die Enumeration NotificationType enthält jetzt nicht mehr den Benachrichtigungsweg, sondern den Typ des Ereignisses, das die Benachrichtigung ausgelöst hat. Aktuell werden 7 Ereignisse unterschieden.
5. Die Benutzerdaten enthalten eine Liste NotifyOn vom Typ NotificationType. In dieser Aufzählung sind die Ereignisse enthalten, über die der Benutzer benachrichtigt werden will. Wenn diese Liste leer oder nicht vorhanden ist erhält der Benutzer keine Nachrichten, auch wenn er einen Benachrichtigungsweg ausgewählt hat.

View File

@ -1,46 +0,0 @@
-- Inspect duplicates first
WITH duplicate_participants AS (
SELECT
shipcall_id,
participant_type,
COUNT(*) AS cnt
FROM times
GROUP BY shipcall_id, participant_type
HAVING COUNT(*) > 1
)
SELECT
t.*
FROM times AS t
JOIN duplicate_participants AS d
ON d.shipcall_id = t.shipcall_id
AND (d.participant_type <=> t.participant_type)
ORDER BY t.shipcall_id, t.participant_type, t.id;
-- Delete all but the highest-id entry per (shipcall_id, participant_type)
WITH ordered_times AS (
SELECT
id,
ROW_NUMBER() OVER (
PARTITION BY shipcall_id, participant_type
ORDER BY id DESC
) AS rn
FROM times
)
DELETE FROM times
WHERE id IN (
SELECT id
FROM ordered_times
WHERE rn > 1
);
-- Optional: re-check that no duplicates remain
WITH duplicate_participants AS (
SELECT
shipcall_id,
participant_type,
COUNT(*) AS cnt
FROM times
GROUP BY shipcall_id, participant_type
HAVING COUNT(*) > 1
)
SELECT COUNT(*) AS remaining_duplicates FROM duplicate_participants;

View File

@ -1,37 +0,0 @@
-- This script clears all data from the database tables related to the port management system.
DELETE FROM notification WHERE id > 0;
DELETE FROM history WHERE id > 0;
DELETE FROM notification WHERE id > 0;
DELETE FROM shipcall_participant_map WHERE id > 0;
DELETE FROM participant_port_map WHERE id > 0;
DELETE FROM shipcall_tug_map WHERE id > 0;
DELETE FROM times WHERE id > 0;
DELETE FROM shipcall WHERE id > 0;
DELETE FROM user_role_map WHERE id > 0;
DELETE FROM role_securable_map WHERE id > 0;
DELETE FROM user_role_map WHERE id > 0;
DELETE FROM securable WHERE id > 0;
DELETE FROM role WHERE id > 0;
DELETE FROM user WHERE id > 0;
delete FROM ship WHERE id > 0;
DELETE FROM berth WHERE id > 0;
DELETE FROM participant WHERE id > 0;
DELETE FROM port WHERE id > 0;

View File

@ -1,23 +0,0 @@
DELETE FROM times WHERE
times.shipcall_id IN
(
SELECT s.id FROM shipcall s
JOIN shipcall_participant_map spm ON s.id = spm.shipcall_id
JOIN participant p ON spm.participant_id = p.id
WHERE p.id = 10
);
DELETE `history` FROM `history`
JOIN shipcall s on `history`.shipcall_id = s.id
JOIN shipcall_participant_map spm ON s.id = spm.shipcall_id
WHERE spm.participant_id = 10;
-- damit das hier funktioniert muss der FK in shipcall_participant_map von "RESTRICT" auf "SET NULL"
-- geändert werden
DELETE shipcall FROM shipcall
INNER JOIN shipcall_participant_map spm ON shipcall.id = spm.shipcall_id
JOIN participant p ON spm.participant_id = p.id
WHERE p.id = 10;
DELETE FROM shipcall_participant_map WHERE participant_id = 10;

View File

@ -1,8 +1,8 @@
-- MySQL dump 10.13 Distrib 8.0.43, for Win64 (x86_64) -- MySQL dump 10.13 Distrib 8.0.33, for Win64 (x86_64)
-- --
-- Host: localhost Database: bremen_calling_test -- Host: localhost Database: bremen_calling_test
-- ------------------------------------------------------ -- ------------------------------------------------------
-- Server version 8.0.42-0ubuntu0.24.10.1 -- Server version 8.0.34-0ubuntu0.22.04.1
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
@ -28,65 +28,17 @@ CREATE TABLE `berth` (
`lock` bit(1) DEFAULT NULL COMMENT 'The lock must be used', `lock` bit(1) DEFAULT NULL COMMENT 'The lock must be used',
`owner_id` int unsigned DEFAULT NULL, `owner_id` int unsigned DEFAULT NULL,
`authority_id` int unsigned DEFAULT NULL, `authority_id` int unsigned DEFAULT NULL,
`port_id` int unsigned DEFAULT NULL,
`created` datetime DEFAULT CURRENT_TIMESTAMP, `created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, `modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`deleted` bit(1) DEFAULT b'0', `deleted` bit(1) DEFAULT b'0',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `FK_OWNER_PART_idx` (`owner_id`), KEY `FK_OWNER_PART_idx` (`owner_id`),
KEY `FK_AUTHORITY_PART_idx` (`authority_id`) /*!80000 INVISIBLE */, KEY `FK_AUTHORITY_PART_idx` (`authority_id`),
KEY `FK_PORT_PART_idx` (`port_id`),
CONSTRAINT `FK_AUTHORITY_PART` FOREIGN KEY (`authority_id`) REFERENCES `participant` (`id`), CONSTRAINT `FK_AUTHORITY_PART` FOREIGN KEY (`authority_id`) REFERENCES `participant` (`id`),
CONSTRAINT `FK_OWNER_PART` FOREIGN KEY (`owner_id`) REFERENCES `participant` (`id`), CONSTRAINT `FK_OWNER_PART` FOREIGN KEY (`owner_id`) REFERENCES `participant` (`id`)
CONSTRAINT `FK_PORT` FOREIGN KEY (`port_id`) REFERENCES `port` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE=InnoDB AUTO_INCREMENT=195 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Berth of ship for a ship call';
) ENGINE=InnoDB AUTO_INCREMENT=205 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Berth of ship for a ship call';
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `history`
--
DROP TABLE IF EXISTS `history`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `history` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`participant_id` int unsigned NOT NULL,
`user_id` int unsigned DEFAULT NULL,
`shipcall_id` int unsigned NOT NULL,
`timestamp` datetime NOT NULL COMMENT 'Time of saving',
`eta` datetime DEFAULT NULL COMMENT 'Current ETA / ETD value (depends if shipcall or times were saved)',
`type` int NOT NULL COMMENT 'shipcall or times',
`operation` int NOT NULL COMMENT 'insert, update or delete',
PRIMARY KEY (`id`),
KEY `FK_HISTORY_PARTICIPANT_idx` (`participant_id`),
KEY `FK_HISTORY_SHIPCALL_idx` (`shipcall_id`),
KEY `FK_HISTORY_USER` (`user_id`),
CONSTRAINT `FK_HISTORY_PARTICIPANT` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`),
CONSTRAINT `FK_HISTORY_SHIPCALL` FOREIGN KEY (`shipcall_id`) REFERENCES `shipcall` (`id`),
CONSTRAINT `FK_HISTORY_USER` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=23537 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='This table stores a history of changes made to shipcalls so that everyone can see who changed what and when';
/*!40101 SET character_set_client = @saved_cs_client */;
CREATE TABLE `history` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`participant_id` int unsigned NOT NULL,
`user_id` int unsigned DEFAULT NULL,
`shipcall_id` int unsigned NOT NULL,
`timestamp` datetime NOT NULL COMMENT 'Time of saving',
`eta` datetime DEFAULT NULL COMMENT 'Current ETA / ETD value (depends if shipcall or times were saved)',
`type` int NOT NULL COMMENT 'shipcall or times',
`operation` int NOT NULL COMMENT 'insert, update or delete',
PRIMARY KEY (`id`),
KEY `FK_HISTORY_PARTICIPANT_idx` (`participant_id`),
KEY `FK_HISTORY_SHIPCALL_idx` (`shipcall_id`),
KEY `FK_HISTORY_USER` (`user_id`),
CONSTRAINT `FK_HISTORY_PARTICIPANT` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`),
CONSTRAINT `FK_HISTORY_SHIPCALL` FOREIGN KEY (`shipcall_id`) REFERENCES `shipcall` (`id`),
CONSTRAINT `FK_HISTORY_USER` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=29292 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='This table stores a history of changes made to shipcalls so that everyone can see who changed what and when';
-- --
-- Table structure for table `notification` -- Table structure for table `notification`
-- --
@ -96,19 +48,20 @@ DROP TABLE IF EXISTS `notification`;
/*!50503 SET character_set_client = utf8mb4 */; /*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `notification` ( CREATE TABLE `notification` (
`id` int unsigned NOT NULL AUTO_INCREMENT, `id` int unsigned NOT NULL AUTO_INCREMENT,
`shipcall_id` int unsigned DEFAULT NULL, `times_id` int unsigned NOT NULL COMMENT 'times record that caused the notification',
`participant_id` int unsigned DEFAULT NULL, `participant_id` int unsigned NOT NULL COMMENT 'participant ref',
`acknowledged` bit(1) DEFAULT b'0' COMMENT 'true if UI acknowledged',
`level` tinyint DEFAULT NULL COMMENT 'severity of the notification', `level` tinyint DEFAULT NULL COMMENT 'severity of the notification',
`type` tinyint DEFAULT NULL COMMENT 'Email/UI/Other', `type` tinyint DEFAULT NULL COMMENT 'Email/UI/Other',
`message` varchar(512) DEFAULT NULL COMMENT 'individual message', `message` varchar(256) DEFAULT NULL COMMENT 'individual message',
`created` datetime DEFAULT CURRENT_TIMESTAMP, `created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, `modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `FK_NOTIFICATION_SHIPCALL_idx` (`shipcall_id`), KEY `FK_NOT_TIMES` (`times_id`),
KEY `FK_NOTIFICATION_PARTICIPANT_idx` (`participant_id`), KEY `FK_NOT_PART` (`participant_id`),
CONSTRAINT `FK_NOTIFICATION_PARTICIPANT` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT `FK_NOT_PART` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`),
CONSTRAINT `FK_NOTIFICATION_SHIPCALL` FOREIGN KEY (`shipcall_id`) REFERENCES `shipcall` (`id`) CONSTRAINT `FK_NOT_TIMES` FOREIGN KEY (`times_id`) REFERENCES `times` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10398 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='An entry corresponds to an alarm given by a violated rule during times update'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='An entry corresponds to an alarm given by a violated rule during times update';
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@ -130,46 +83,7 @@ CREATE TABLE `participant` (
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, `modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`deleted` bit(1) DEFAULT b'0', `deleted` bit(1) DEFAULT b'0',
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=160 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='An organization taking part'; ) ENGINE=InnoDB AUTO_INCREMENT=137 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='An organization taking part';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `participant_port_map`
--
DROP TABLE IF EXISTS `participant_port_map`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `participant_port_map` (
`id` int NOT NULL AUTO_INCREMENT,
`participant_id` int unsigned NOT NULL COMMENT 'Ref to participant',
`port_id` int unsigned NOT NULL COMMENT 'Ref to port',
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `FK_PP_PARTICIPANT` (`participant_id`),
KEY `FK_PP_PORT` (`port_id`),
CONSTRAINT `FK_PP_PARTICIPANT` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`),
CONSTRAINT `FK_PP_PORT` FOREIGN KEY (`port_id`) REFERENCES `port` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=86 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Mapping table that assigns participants to a port';
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `port`
--
DROP TABLE IF EXISTS `port`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `port` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL COMMENT 'Name of port',
`locode` char(5) DEFAULT NULL COMMENT 'UNECE locode',
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`deleted` bit(1) DEFAULT b'0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Port as reference for shipcalls and berths';
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@ -252,7 +166,7 @@ CREATE TABLE `ship` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `FK_SHIP_PARTICIPANT` (`participant_id`), KEY `FK_SHIP_PARTICIPANT` (`participant_id`),
CONSTRAINT `FK_SHIP_PARTICIPANT` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`) CONSTRAINT `FK_SHIP_PARTICIPANT` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=485 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; ) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@ -288,25 +202,16 @@ CREATE TABLE `shipcall` (
`canceled` bit(1) DEFAULT NULL, `canceled` bit(1) DEFAULT NULL,
`evaluation` int unsigned DEFAULT NULL, `evaluation` int unsigned DEFAULT NULL,
`evaluation_message` varchar(512) DEFAULT NULL, `evaluation_message` varchar(512) DEFAULT NULL,
`evaluation_time` datetime DEFAULT NULL,
`evaluation_notifications_sent` bit(1) DEFAULT NULL,
`port_id` int unsigned NOT NULL DEFAULT '1' COMMENT 'Selected port for this shipcall',
`time_ref_point` int DEFAULT '0' COMMENT 'Index of a location which is the reference point for all time value entries, e.g. berth or Geeste',
`created` datetime DEFAULT CURRENT_TIMESTAMP, `created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, `modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `FK_SHIPCALL_SHIP` (`ship_id`), KEY `FK_SHIPCALL_SHIP` (`ship_id`),
KEY `FK_SHIPCALL_BERTH_ARRIVAL` (`arrival_berth_id`), KEY `FK_SHIPCALL_BERTH_ARRIVAL` (`arrival_berth_id`),
KEY `FK_SHIPCALL_BERTH_DEPARTURE` (`departure_berth_id`), KEY `FK_SHIPCALL_BERTH_DEPARTURE` (`departure_berth_id`),
KEY `idx_shipcall_type` (`type`),
KEY `idx_shipcall_eta` (`eta`),
KEY `idx_shipcall_etd` (`etd`),
KEY `FK_SHIPCALL_PORT_idx` (`port_id`),
CONSTRAINT `FK_SHIPCALL_BERTH_ARRIVAL` FOREIGN KEY (`arrival_berth_id`) REFERENCES `berth` (`id`), CONSTRAINT `FK_SHIPCALL_BERTH_ARRIVAL` FOREIGN KEY (`arrival_berth_id`) REFERENCES `berth` (`id`),
CONSTRAINT `FK_SHIPCALL_BERTH_DEPARTURE` FOREIGN KEY (`departure_berth_id`) REFERENCES `berth` (`id`), CONSTRAINT `FK_SHIPCALL_BERTH_DEPARTURE` FOREIGN KEY (`departure_berth_id`) REFERENCES `berth` (`id`),
CONSTRAINT `FK_SHIPCALL_PORT` FOREIGN KEY (`port_id`) REFERENCES `port` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FK_SHIPCALL_SHIP` FOREIGN KEY (`ship_id`) REFERENCES `ship` (`id`) CONSTRAINT `FK_SHIPCALL_SHIP` FOREIGN KEY (`ship_id`) REFERENCES `ship` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2789 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Incoming, outgoing or moving to another berth'; ) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Incoming, outgoing or moving to another berth';
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@ -320,15 +225,15 @@ CREATE TABLE `shipcall_participant_map` (
`id` int NOT NULL AUTO_INCREMENT, `id` int NOT NULL AUTO_INCREMENT,
`shipcall_id` int unsigned DEFAULT NULL, `shipcall_id` int unsigned DEFAULT NULL,
`participant_id` int unsigned DEFAULT NULL, `participant_id` int unsigned DEFAULT NULL,
`type` int unsigned DEFAULT NULL, `type` int unsigned DEFAULT NULL COMMENT 'Type of participant role',
`created` datetime DEFAULT CURRENT_TIMESTAMP, `created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, `modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `FK_MAP_PARTICIPANT_SHIPCALL` (`shipcall_id`), KEY `FK_MAP_PARTICIPANT_SHIPCALL` (`shipcall_id`),
KEY `FK_MAP_SHIPCALL_PARTICIPANT` (`participant_id`), KEY `FK_MAP_SHIPCALL_PARTICIPANT` (`participant_id`),
CONSTRAINT `FK_MAP_PARTICIPANT_SHIPCALL` FOREIGN KEY (`shipcall_id`) REFERENCES `shipcall` (`id`) ON DELETE SET NULL, CONSTRAINT `FK_MAP_PARTICIPANT_SHIPCALL` FOREIGN KEY (`shipcall_id`) REFERENCES `shipcall` (`id`),
CONSTRAINT `FK_MAP_SHIPCALL_PARTICIPANT` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`) CONSTRAINT `FK_MAP_SHIPCALL_PARTICIPANT` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8933 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Associates a participant with a shipcall'; ) ENGINE=InnoDB AUTO_INCREMENT=128 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Associates a participant with a shipcall';
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@ -380,20 +285,13 @@ CREATE TABLE `times` (
`berth_info` varchar(512) DEFAULT NULL, `berth_info` varchar(512) DEFAULT NULL,
`pier_side` bit(1) DEFAULT NULL, `pier_side` bit(1) DEFAULT NULL,
`participant_type` int unsigned DEFAULT NULL, `participant_type` int unsigned DEFAULT NULL,
`ata` datetime DEFAULT NULL COMMENT 'Relevant only for mooring, this field can be used to record actual ATA',
`atd` datetime DEFAULT NULL COMMENT 'Relevant only for mooring, this field can be used to record actual ATD',
`eta_interval_end` datetime DEFAULT NULL COMMENT 'If this value is set the times are given as interval instead of a single point in time. The start time value depends on the participant type.',
`etd_interval_end` datetime DEFAULT NULL COMMENT 'If this value is set the times are given as interval instead of a single point in time. The start time value depends on the participant type.',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `uniq_shipcall_participant` (`shipcall_id`,`participant_type`),
KEY `FK_TIME_SHIPCALL` (`shipcall_id`), KEY `FK_TIME_SHIPCALL` (`shipcall_id`),
KEY `FK_TIME_PART` (`participant_id`) /*!80000 INVISIBLE */, KEY `FK_TIME_PART` (`participant_id`) /*!80000 INVISIBLE */,
KEY `FK_TIME_BERTH` (`berth_id`) /*!80000 INVISIBLE */, KEY `FK_TIME_BERTH` (`berth_id`) /*!80000 INVISIBLE */,
KEY `idx_times_eta_berth` (`eta_berth`),
KEY `idx_times_etd_berth` (`etd_berth`),
CONSTRAINT `FK_TIME_BERTH` FOREIGN KEY (`berth_id`) REFERENCES `berth` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT `FK_TIME_BERTH` FOREIGN KEY (`berth_id`) REFERENCES `berth` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FK_TIME_PART` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`) CONSTRAINT `FK_TIME_PART` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7863 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='the planned time for the participants work'; ) ENGINE=InnoDB AUTO_INCREMENT=44 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='the planned time for the participants work';
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@ -405,7 +303,7 @@ DROP TABLE IF EXISTS `user`;
/*!50503 SET character_set_client = utf8mb4 */; /*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `user` ( CREATE TABLE `user` (
`id` int unsigned NOT NULL AUTO_INCREMENT, `id` int unsigned NOT NULL AUTO_INCREMENT,
`participant_id` int unsigned NOT NULL, `participant_id` int unsigned DEFAULT NULL,
`first_name` varchar(45) DEFAULT NULL, `first_name` varchar(45) DEFAULT NULL,
`last_name` varchar(45) DEFAULT NULL, `last_name` varchar(45) DEFAULT NULL,
`user_name` varchar(45) DEFAULT NULL, `user_name` varchar(45) DEFAULT NULL,
@ -413,17 +311,12 @@ CREATE TABLE `user` (
`user_phone` varchar(128) DEFAULT NULL, `user_phone` varchar(128) DEFAULT NULL,
`password_hash` varchar(128) DEFAULT NULL, `password_hash` varchar(128) DEFAULT NULL,
`api_key` varchar(256) DEFAULT NULL, `api_key` varchar(256) DEFAULT NULL,
`notify_email` bit(1) DEFAULT NULL,
`notify_whatsapp` bit(1) DEFAULT NULL,
`notify_signal` bit(1) DEFAULT NULL,
`notify_popup` bit(1) DEFAULT NULL,
`notify_event` int DEFAULT NULL COMMENT 'Bitflag of selected notification event types that the user wants to be notified of',
`created` datetime DEFAULT CURRENT_TIMESTAMP, `created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, `modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `FK_USER_PART` (`participant_id`), KEY `FK_USER_PART` (`participant_id`),
CONSTRAINT `FK_USER_PART` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`) CONSTRAINT `FK_USER_PART` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=55 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='member of a participant'; ) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='member of a participant';
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
-- --
@ -446,57 +339,6 @@ CREATE TABLE `user_role_map` (
CONSTRAINT `FK_USER_ROLE` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) CONSTRAINT `FK_USER_ROLE` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Assigns a user to a role'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Assigns a user to a role';
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping routines for database 'bremen_calling_test'
--
/*!50003 DROP PROCEDURE IF EXISTS `delete_data` */;
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
/*!50003 SET character_set_client = utf8mb4 */ ;
/*!50003 SET character_set_results = utf8mb4 */ ;
/*!50003 SET collation_connection = utf8mb4_0900_ai_ci */ ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;;
CREATE DEFINER=`ds`@`localhost` PROCEDURE `delete_data`()
BEGIN
DECLARE shipcall_id_var int;
DECLARE done INT DEFAULT FALSE;
DECLARE shipcall_iter CURSOR FOR
SELECT shipcall.id FROM shipcall
LEFT JOIN times ON
times.shipcall_id = shipcall.id AND times.participant_type = 8
WHERE
-- ARRIVAL
(type = 1 AND GREATEST(shipcall.eta, COALESCE(times.eta_berth, 0)) <= CURRENT_DATE() - INTERVAL 1 MONTH) OR
-- DEPARTURE / SHIFTING
(type != 1 AND GREATEST(shipcall.etd, COALESCE(times.etd_berth, 0)) <= CURRENT_DATE() - INTERVAL 1 MONTH);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN shipcall_iter;
delete_loop: LOOP
FETCH shipcall_iter INTO shipcall_id_var;
IF done THEN
LEAVE delete_loop;
END IF;
DELETE FROM shipcall_participant_map WHERE shipcall_id = shipcall_id_var;
DELETE FROM shipcall_tug_map WHERE shipcall_id = shipcall_id_var;
DELETE FROM times WHERE shipcall_id = shipcall_id_var;
DELETE FROM history WHERE shipcall_id = shipcall_id_var;
DELETE FROM shipcall WHERE id = shipcall_id_var;
END LOOP;
CLOSE shipcall_iter;
END ;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
/*!50003 SET collation_connection = @saved_col_connection */ ;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
@ -507,4 +349,4 @@ DELIMITER ;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2025-11-17 8:26:36 -- Dump completed on 2023-10-06 14:52:04

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -1,11 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Bremen Calling</title>
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,113 +0,0 @@
# Benachrichtigungen
___
## Benachrichtigungs-Typen (Auslöser)
### 1. Teilnehmer wird zugeordnet
Ein Teilnehmer wird über die Anwendung einem Anlauf zugeordnet. Dies ist entweder die Agentur (durch BSMD zugeordnet) oder ein weiterer Teilnehmer außer dem Hafenamt, der durch die Agentur zugeordnet wird. Die Zuordnung des Hafenamts erfolgt automatisch bei Anlage des Anlaufs.
### 2. Morgenrunde ist relevant
Ein Teilnehmer ist einem Anlauf zugeordnet. Dieser Anlauf findet in den nächsten 24 Stunden statt und ist daher für die "Morgenrunde" relevant. Der Teilnehmer erhält dazu eine Benachrichtigung.
### 3. Zeitlicher Konflikt ("Ampel")
Durch unterschiedliche Zeitangaben der Teilnehmer wird die Ampel ausgelöst und stellt eine entsprechende Fehlermeldung dar. Die Benachrichtigung wird ausgelöst bei folgenden Ampel-Wechseln:
* grün -> gelb
* grün -> rot
* gelb -> rot
### 4. Auflösung zeitl. Konflikt
* rot -> gelb
* rot -> grün
* gelb -> grün
### 5. Abwählen eines Teilnehmer
Der Teilnehmer ist nicht mehr länger dem Anlauf zugeordnet.
### 6. Fehlende Daten
Dienstleister, die 16 Stunden vor ETA/ETD und Agenturen, die 20 Stunden vor ETA/ETD keine Angaben gemacht haben.
### 7. Storno
Wird ein Anlauf storniert erhalten alle bis dahin zugeordneten Teilnehmer eine Benachrichtigung.
## API
```yaml
NotificationType:
type: string
description: Type of notification
enum:
- assignment
- next24h
- time_conflict
- time_conflict_resolved
- unassigned
- missing_data
- cancelled
```
## Entfernen von Benachrichtigungen
Unter den folgenden Voraussetzungen werden Benachrichtigungen wieder aus dem System entfernt:
* Die Benachrichtigung ist älter als 3 Tage (n.B.: Zeitraum definieren)
* Ein Teilnehmer wird wieder abgewählt
* Ein zeitlicher Konflikt wird aufgelöst
## Ablauf der Benachrichtigungen
Eine Benachrichtung enthält folgende Informationen:
* Verweis auf den Anlauf (shipcall)
* ein Erstell- und Änderungsdatum
* einen Benachrichtigungs-Typ
* einen Zustand ("level")
Der Zustand steuert den Ablauf, wenn die Prüfungsfunktion die Anläufe durchsucht oder ein Anlauf gespeichert wird.
Wird einer der Zustände 1-3 erkannt wird geprüft, ob bereits eine Benachrichtigung vorhanden ist. Ist dies nicht der Fall, wird eine Benachrichtigung neu erstellt im Zustand "0".
Die Prüfungsfunktion durchläuft alle Benachrichtigungen. Abhängig vom Zustand (0-2) werden folgende Aktionen ausgeführt:
* Ist die Benachrichtigung um Zustand "0" und sind mind. 10 Minuten vergangen, wird die Benachrichtigung in den Zustand "1" versetzt.
* Ist die Benachrichtigung im Zustand "1" wird versucht, allen dafür eingetragenenen Benutzern eine E-Mail zu senden. Ist dies erfolgreich, wechselt die Benachrichtigung in den Zustand "2".
* Ist die Benachrichtigung im Zustand "2" und sind mind. 3 Tage vergangen wird die Benachrichtigung gelöscht.
```mermaid
---
title: Ablauf
---
stateDiagram-v2
state "Level 0" as lvl0
state "Level 1" as lvl1
state "Level 2" as lvl2
[*] --> lvl0: Zustand 1-3 erkannt
lvl0 --> lvl1: +10 min.
lvl1 --> lvl2: E-Mail Versand erfolgt
lvl2 --> [*]: +3 Tage ODER Zustand 1-3 nicht mehr relevant
lvl0 --> [*]: Zustand 1-3 nicht mehr relevant
```
## Bemerkungen
Für die Zukunft sind ggf. auch Benachrichtigungen via Whatsapp/Signal geplant. Diese verhalten sich analog zu den E-Mail Benachrichtigungen und werden nur einmal versendet. Die Anzahl der Zustände wird dabei erhöht bzw. der Wechsel in den Endzustand ensprechend angepasst.
## Benachrichtigungstext
... TBD

View File

@ -1,37 +0,0 @@
bcrypt==4.2.0
blinker==1.8.2
cached-property==1.5.2
click==8.1.7
coro-context-manager==0.2.0
coverage==7.6.1
dsnparse==0.1.15
Flask==3.0.3
Flask-JWT-Extended==4.6.0
iniconfig==2.0.0
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.5
marshmallow==3.22.0
marshmallow-enum==1.5.1
marshmallow_dataclass==8.7.1
mypy-extensions==1.0.0
mysql-connector-python==9.0.0
numpy==2.1.1
packaging==24.1
pandas==2.2.3
pluggy==1.5.0
pydapper==0.10.0
PyJWT==2.9.0
pytest==8.3.3
pytest-cov==5.0.0
python-dateutil==2.9.0.post0
pytz==2024.2
schedule==1.2.2
six==1.16.0
tqdm==4.66.5
typeguard==4.3.0
typing-inspect==0.9.0
typing_extensions==4.12.2
tzdata==2024.1
webargs==8.6.0
Werkzeug==3.0.4

View File

@ -1,57 +0,0 @@
CREATE TABLE `port` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL COMMENT 'Name of port',
`locode` char(5) DEFAULT NULL COMMENT 'UNECE locode',
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`deleted` bit(1) DEFAULT b'0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Port as reference for shipcalls and berths';
-- Add default port to table
INSERT INTO port (id, name, locode) VALUES (1, 'Bremen', 'DEBRE');
-- Adding new ref column to berth
ALTER TABLE `berth`
ADD COLUMN `port_id` INT UNSIGNED DEFAULT NULL AFTER `authority_id`;
ALTER TABLE `berth` ALTER INDEX `FK_AUTHORITY_PART_idx` INVISIBLE;
-- adding a foreign key berth.port_id -> port.id
ALTER TABLE `berth`
ADD INDEX `FK_PORT_PART_idx` (`port_id` ASC) VISIBLE;
ALTER TABLE `berth`
ADD CONSTRAINT `FK_PORT`
FOREIGN KEY (`port_id`)
REFERENCES `port` (`id`)
ON DELETE RESTRICT
ON UPDATE RESTRICT;
-- adding new ref column to shipcall incl. foreign key
ALTER TABLE `shipcall`
ADD COLUMN `port_id` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'Selected port for this shipcall' AFTER `evaluation_notifications_sent`,
CHANGE COLUMN `time_ref_point` `time_ref_point` INT NULL DEFAULT '0' COMMENT 'Index of a location which is the reference point for all time value entries, e.g. berth or Geeste' AFTER `port_id`,
ADD INDEX `FK_SHIPCALL_PORT_idx` (`port_id` ASC) VISIBLE;
;
ALTER TABLE `shipcall`
ADD CONSTRAINT `FK_SHIPCALL_PORT`
FOREIGN KEY (`port_id`)
REFERENCES `port` (`id`)
ON DELETE RESTRICT
ON UPDATE RESTRICT;
CREATE TABLE `participant_port_map` (
`id` int NOT NULL AUTO_INCREMENT,
`participant_id` int unsigned NOT NULL COMMENT 'Ref to participant',
`port_id` int unsigned NOT NULL COMMENT 'Ref to port',
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `FK_PP_PARTICIPANT` (`participant_id`),
KEY `FK_PP_PORT` (`port_id`),
CONSTRAINT `FK_PP_PARTICIPANT` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`),
CONSTRAINT `FK_PP_PORT` FOREIGN KEY (`port_id`) REFERENCES `port` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Mapping table that assigns participants to a port';
-- all existing berths shall default to "bremen"
UPDATE berth SET port_id = 1 where port_id is null;

View File

@ -1,13 +0,0 @@
ALTER TABLE `notification`
ADD COLUMN `participant_id` INT UNSIGNED NULL DEFAULT NULL AFTER `shipcall_id`,
ADD INDEX `FK_NOTIFICATION_PARTICIPANT_idx` (`participant_id` ASC) VISIBLE;
;
ALTER TABLE `notification`
ADD CONSTRAINT `FK_NOTIFICATION_PARTICIPANT`
FOREIGN KEY (`participant_id`)
REFERENCES `participant` (`id`)
ON DELETE RESTRICT
ON UPDATE RESTRICT;
ALTER TABLE `user`
ADD COLUMN `notify_event` INT NULL COMMENT 'Bitflag of selected notification event types that the user wants to be notified of' AFTER `notify_popup`;

View File

@ -1 +1 @@
1.8.0.0 1.4.0.0

View File

@ -1,10 +0,0 @@
# Schnittstelle Weserport Anforderungen
##
Automatische Zuordnung:
* Hafenamt
* Festmacher
* Lotsen (>120m l 13m br)

View File

@ -7,12 +7,11 @@
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:p = "clr-namespace:BreCalClient.Resources" xmlns:p = "clr-namespace:BreCalClient.Resources"
mc:Ignorable="d" Left="{local:SettingBinding W1Left}" Top="{local:SettingBinding W1Top}" mc:Ignorable="d" Left="{local:SettingBinding W1Left}" Top="{local:SettingBinding W1Top}"
Title="Help" Height="512" Width="800" Loaded="Window_Loaded"> Title="Help" Height="374" Width="500" Loaded="Window_Loaded">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="180" /> <ColumnDefinition Width="180" />
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="28" /> <RowDefinition Height="28" />
@ -26,13 +25,6 @@
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="10" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="10" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
@ -50,33 +42,21 @@
Informatikbüro Daniel Schick Informatikbüro Daniel Schick
</Hyperlink> </Hyperlink>
</TextBlock> </TextBlock>
<Border BorderThickness="0 0 0 2" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" BorderBrush="Gray" />
<Label FontWeight="DemiBold" Grid.Row="3" Grid.Column="0" Content="{x:Static p:Resources.textChangeContactInfo}" HorizontalContentAlignment="Right"/> <Label FontWeight="DemiBold" Grid.Row="3" Grid.Column="0" Content="{x:Static p:Resources.textChangeContactInfo}" HorizontalContentAlignment="Right"/>
<Label Grid.Row="4" Grid.Column="0" Content="{x:Static p:Resources.textEmail}" HorizontalContentAlignment="Right" /> <Label Grid.Row="4" Grid.Column="0" Content="{x:Static p:Resources.textEmail}" HorizontalContentAlignment="Right" />
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static p:Resources.textPhone}" HorizontalContentAlignment="Right" /> <Label Grid.Row="5" Grid.Column="0" Content="{x:Static p:Resources.textPhone}" HorizontalContentAlignment="Right" />
<TextBox Name="textBoxUserEmail" Grid.Column="1" Grid.Row="4" Margin="2" VerticalContentAlignment="Center" /> <TextBox Name="textBoxUserEmail" Grid.Column="1" Grid.Row="4" Margin="2" VerticalContentAlignment="Center" />
<TextBox Name="textBoxUserPhone" Grid.Column="1" Grid.Row="5" Margin="2" VerticalContentAlignment="Center" /> <TextBox Name="textBoxUserPhone" Grid.Column="1" Grid.Row="5" Margin="2" VerticalContentAlignment="Center" />
<Label FontWeight="DemiBold" Grid.Row="7" Grid.Column="0" Content="{x:Static p:Resources.textNotifications}" HorizontalContentAlignment="Right"/> <Label FontWeight="DemiBold" Grid.Row="7" Grid.Column="0" Content="{x:Static p:Resources.textChangePassword}" HorizontalContentAlignment="Right"/>
<CheckBox HorizontalAlignment="Right" Margin="2" Grid.Column="0" Grid.Row="8" VerticalAlignment="Center" x:Name="checkboxEMailNotify" />
<Label Grid.Row="8" Grid.Column="1" Content="{x:Static p:Resources.textNotifyEmail}" />
<CheckBox HorizontalAlignment="Right" Margin="2" Grid.Column="0" Grid.Row="9" VerticalAlignment="Center" x:Name="checkboxPushNotify" />
<Label Grid.Row="9" Grid.Column="1" Content="{x:Static p:Resources.textNotifyPush}" />
<Label Grid.Row="7" Grid.Column="2" Content="{x:Static p:Resources.textNotifyOn}" />
<xctk:CheckListBox Grid.Column="2" Grid.Row="8" Grid.RowSpan="3" x:Name="checkListBoxEventSelection" Margin="2" />
<Button x:Name="buttonChangeUserFields" Click="buttonChangeUserFields_Click" Grid.Column="2" Grid.Row="11" Margin="2" Content="{x:Static p:Resources.textChange}" Width="80" HorizontalAlignment="Right" IsEnabled="True" />
<Border BorderThickness="0 0 0 2" Grid.Row="12" Grid.Column="0" Grid.ColumnSpan="3" BorderBrush="Gray" /> <xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textOldPassword}" Grid.Column="1" Grid.Row="7" Margin="2" x:Name="wpBoxOldPassword" TextChanged="wpBoxOldPassword_TextChanged"/>
<Label FontWeight="DemiBold" Grid.Row="13" Grid.Column="0" Content="{x:Static p:Resources.textChangePassword}" HorizontalContentAlignment="Right"/> <xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textNewPassword}" Grid.Column="1" Grid.Row="8" Margin="2" x:Name="wpBoxNewPassword" TextChanged="wpBoxOldPassword_TextChanged"/>
<xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textRepeatNewPassword}" Grid.Column="1" Grid.Row="9" Margin="2" x:Name="wpBoxNewPasswordRepeat" TextChanged="wpBoxOldPassword_TextChanged"/>
<Button x:Name="buttonChangePassword" Click="buttonChangePassword_Click" Grid.Column="1" Grid.Row="10" Margin="2" Content="{x:Static p:Resources.textChange}" Width="80" HorizontalAlignment="Left" IsEnabled="True" />
<xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textOldPassword}" Grid.Column="1" Grid.Row="13" Margin="2" x:Name="wpBoxOldPassword" TextChanged="wpBoxOldPassword_TextChanged"/> <Button x:Name="buttonClose" Click="buttonClose_Click" Content="{x:Static p:Resources.textClose}" Width="80" Margin="2" Grid.Column="1" Grid.Row="12" HorizontalAlignment="Right" />
<xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textNewPassword}" Grid.Column="1" Grid.Row="14" Margin="2" x:Name="wpBoxNewPassword" TextChanged="wpBoxOldPassword_TextChanged"/>
<xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textRepeatNewPassword}" Grid.Column="1" Grid.Row="15" Margin="2" x:Name="wpBoxNewPasswordRepeat" TextChanged="wpBoxOldPassword_TextChanged"/>
<Button x:Name="buttonChangePassword" Click="buttonChangePassword_Click" Grid.Column="1" Grid.Row="16" Margin="2" Content="{x:Static p:Resources.textChangePassword}" Width="120" HorizontalAlignment="Right" IsEnabled="False" />
<Border BorderThickness="0 0 0 2" Grid.Row="17" Grid.Column="0" Grid.ColumnSpan="3" BorderBrush="Gray" />
<Button x:Name="buttonClose" Click="buttonClose_Click" Content="{x:Static p:Resources.textClose}" Width="80" Margin="2" Grid.Column="2" Grid.Row="19" HorizontalAlignment="Right" />
</Grid> </Grid>
</Window> </Window>

View File

@ -34,7 +34,6 @@ namespace BreCalClient
#region events #region events
public event Action<string, string>? ChangePasswordRequested; public event Action<string, string>? ChangePasswordRequested;
public event Action? ChangeUserSettingsRequested;
#endregion #endregion
@ -46,25 +45,14 @@ namespace BreCalClient
} }
private void buttonChangePassword_Click(object sender, RoutedEventArgs e) private void buttonChangePassword_Click(object sender, RoutedEventArgs e)
{
this.ChangePasswordRequested?.Invoke(this.wpBoxOldPassword.Password, this.wpBoxNewPassword.Password);
}
private void buttonChangeUserFields_Click(object sender, RoutedEventArgs e)
{ {
if (this.LoginResult != null) if (this.LoginResult != null)
{ {
this.LoginResult.UserPhone = this.textBoxUserPhone.Text.Trim(); this.LoginResult.UserPhone = this.textBoxUserPhone.Text.Trim();
this.LoginResult.UserEmail = this.textBoxUserEmail.Text.Trim(); this.LoginResult.UserEmail = this.textBoxUserEmail.Text.Trim();
this.LoginResult.NotifyEmail = this.checkboxEMailNotify.IsChecked ?? false;
this.LoginResult.NotifyPopup = this.checkboxPushNotify.IsChecked ?? false;
if ((this.checkListBoxEventSelection.SelectedItems.Count > 0) && (this.LoginResult.NotifyOn == null))
this.LoginResult.NotifyOn = new();
this.LoginResult.NotifyOn.Clear();
foreach (NotificationType nt in this.checkListBoxEventSelection.SelectedItems)
this.LoginResult.NotifyOn.Add(nt);
this.ChangeUserSettingsRequested?.Invoke();
} }
this.ChangePasswordRequested?.Invoke(this.wpBoxOldPassword.Password, this.wpBoxNewPassword.Password);
} }
private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
@ -85,23 +73,13 @@ namespace BreCalClient
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
this.checkListBoxEventSelection.ItemsSource = Enum.GetValues(typeof(BreCalClient.misc.Model.NotificationType));
if(LoginResult != null) if(LoginResult != null)
{ {
this.textBoxUserEmail.Text = LoginResult.UserEmail; this.textBoxUserEmail.Text = LoginResult.UserEmail;
this.textBoxUserPhone.Text = LoginResult.UserPhone; this.textBoxUserPhone.Text = LoginResult.UserPhone;
this.checkboxEMailNotify.IsChecked = LoginResult.NotifyEmail;
this.checkboxPushNotify.IsChecked = LoginResult.NotifyPopup;
if (LoginResult.NotifyOn != null)
{
foreach (NotificationType nt in LoginResult.NotifyOn)
this.checkListBoxEventSelection.SelectedItems.Add(nt);
}
} }
} }
#endregion #endregion
} }
} }

View File

@ -29,16 +29,16 @@
<applicationSettings> <applicationSettings>
<BreCalClient.Properties.Settings> <BreCalClient.Properties.Settings>
<setting name="BG_COLOR" serializeAs="String"> <setting name="BG_COLOR" serializeAs="String">
<value>#751D1F</value> <value>#1D751F</value>
</setting> </setting>
<setting name="APP_TITLE" serializeAs="String"> <setting name="APP_TITLE" serializeAs="String">
<value>!!Bremen calling Entwicklungsversion!!</value> <value>!!Bremen calling Testversion!!</value>
</setting> </setting>
<setting name="LOGO_IMAGE_URL" serializeAs="String"> <setting name="LOGO_IMAGE_URL" serializeAs="String">
<value>https://www.textbausteine.net/</value> <value>https://www.textbausteine.net/</value>
</setting> </setting>
<setting name="API_URL" serializeAs="String"> <setting name="API_URL" serializeAs="String">
<value>https://brecaltest.bsmd-emswe.eu</value> <value>https://brecaldevel.bsmd-emswe.eu</value>
</setting> </setting>
</BreCalClient.Properties.Settings> </BreCalClient.Properties.Settings>
</applicationSettings> </applicationSettings>
@ -86,12 +86,6 @@
<setting name="FilterCriteriaMap" serializeAs="String"> <setting name="FilterCriteriaMap" serializeAs="String">
<value /> <value />
</setting> </setting>
<setting name="W5Top" serializeAs="String">
<value>0</value>
</setting>
<setting name="W5Left" serializeAs="String">
<value>0</value>
</setting>
</BreCalClient.Properties.Settings> </BreCalClient.Properties.Settings>
</userSettings> </userSettings>
</configuration> </configuration>

View File

@ -3,7 +3,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BreCalClient" xmlns:local="clr-namespace:BreCalClient"
xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
StartupUri="MainWindow.xaml" Exit="Application_Exit" Startup="Application_Startup" > StartupUri="MainWindow.xaml" Exit="Application_Exit" Startup="Application_Startup" >
<Application.Resources> <Application.Resources>
@ -15,95 +14,6 @@
<sys:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">10</sys:Double> <sys:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">10</sys:Double>
<sys:Double x:Key="{x:Static SystemParameters.HorizontalScrollBarHeightKey}">10</sys:Double> <sys:Double x:Key="{x:Static SystemParameters.HorizontalScrollBarHeightKey}">10</sys:Double>
<Color x:Key="InformationColor">#147ec9</Color>
<SolidColorBrush x:Key="InformationColorBrush" Color="{StaticResource InformationColor}" options:Freeze="True" />
<Color x:Key="SuccessColor">#11ad45</Color>
<SolidColorBrush x:Key="SuccessColorBrush" Color="{StaticResource SuccessColor}" options:Freeze="True" />
<Color x:Key="ErrorColor">#e60914</Color>
<SolidColorBrush x:Key="ErrorColorBrush" Color="{StaticResource ErrorColor}" options:Freeze="True" />
<Color x:Key="WarningColor">#f5a300</Color>
<SolidColorBrush x:Key="WarningColorBrush" Color="{StaticResource WarningColor}" options:Freeze="True" />
<Canvas x:Key="InformationIcon" Width="24" Height="24">
<Path Data="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z" Fill="White" />
</Canvas>
<Canvas x:Key="SuccessIcon" Width="24" Height="24">
<Path Data="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z" Fill="White" />
</Canvas>
<Canvas x:Key="ErrorIcon" Width="24" Height="24">
<Path Data="M11,15H13V17H11V15M11,7H13V13H11V7M12,2C6.47,2 2,6.5 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20Z" Fill="White" />
</Canvas>
<Canvas x:Key="WarningIcon" Width="24" Height="24">
<Path Data="M12,2L1,21H23M12,6L19.53,19H4.47M11,10V14H13V10M11,16V18H13V16" Fill="White" />
</Canvas>
<Canvas x:Key="CloseIcon" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
<Path Width="31.6666" Height="31.6667" Canvas.Left="22.1666" Canvas.Top="22.1667" Stretch="Fill" Fill="#FF000000" Data="F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z "/>
</Canvas>
<Style TargetType="Border" x:Key="NotificationBorder">
<Setter Property="Padding" Value="5" />
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect Opacity="0.5" ShadowDepth="1" BlurRadius="2" />
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Rectangle" x:Key="NotificationIcon">
<Setter Property="Width" Value="24"/>
<Setter Property="Height" Value="24"/>
<Setter Property="Margin" Value="0,5,5,5" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Fill" Value="White"/>
</Style>
<Style TargetType="TextBlock" x:Key="NotificationText">
<Setter Property="Foreground" Value="White" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Margin" Value="5,0,0,0" />
</Style>
<Style TargetType="{x:Type Button}" x:Key="NotificationCloseButton">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="#FFF" />
<Setter Property="FontSize" Value="15" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#33000000" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#77000000" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="Rectangle" x:Key="CloseButtonIcon">
<Setter Property="Width" Value="10"/>
<Setter Property="Height" Value="10"/>
<Setter Property="Fill" Value="{Binding Path=Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}" />
</Style>
</ResourceDictionary> </ResourceDictionary>
</Application.Resources> </Application.Resources>

View File

@ -1,207 +0,0 @@
// Copyright (c) 2024- schick Informatik
// Description: Helper (static) class to handle polled API notifications
//
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ToastNotifications.Core;
using BreCalClient.misc.Model;
namespace BreCalClient
{
internal class AppNotification(int id)
{
private static readonly Dictionary<int, AppNotification> _notifications = [];
private static readonly ObservableCollection<AppNotification> _notificationsCollection = [];
#region Properties
public int Id { get { return id; } }
public string? NotificationType
{
get; private set;
}
public string? NotificationDisplay
{
get; private set;
}
public string? NotificationDate
{
get; private set;
}
public string? Ship
{
get; private set;
}
public string? ShipcallType
{
get; private set;
}
public string? Berth
{
get; private set;
}
public string? ETA
{
get; private set;
}
public string? Message
{
get; private set;
}
public static ObservableCollection<AppNotification> AppNotifications { get { return _notificationsCollection; } }
#endregion
#region internal statics
internal static void LoadFromSettings()
{
_notifications.Clear();
if (Properties.Settings.Default.Notifications != null)
{
// load notification ids that have been processed
foreach (string? notification_id in Properties.Settings.Default.Notifications)
{
if (Int32.TryParse(notification_id, out int result))
_notifications.Add(result, new AppNotification(result));
}
}
}
internal static void Clear()
{
_notifications.Clear();
SaveNotifications();
}
internal static bool UpdateNotifications(List<Notification> notifications, System.Collections.Concurrent.ConcurrentDictionary<int, ShipcallControlModel> currentShipcalls, ToastViewModel vm, LoginResult loginResult)
{
bool result = false;
foreach (Notification notification in notifications)
{
if (notification.ParticipantId.HasValue && notification.ParticipantId.Value != App.Participant.Id) // not meant for us
continue;
if (!currentShipcalls.ContainsKey(notification.ShipcallId)) // not one of our shipcalls (maybe for another port or filtered)
continue;
// filter out notifications for shipcalls where we are not nomiated/assigned
if (!notification.ParticipantId.HasValue)
{
bool iAmAssigned = false;
foreach (ParticipantAssignment p in currentShipcalls[notification.ShipcallId].AssignedParticipants.Values)
{
if (p.ParticipantId.Equals(App.Participant.Id))
{
iAmAssigned = true; break;
}
}
if (!iAmAssigned) continue;
}
// filter out notifications the user is not interested in
if((notification.Type != null) && !loginResult.NotifyOn.Contains(notification.Type.Value))
continue;
if (!_notificationsCollection.Where(x => x.Id == notification.Id).Any())
{
List<AppNotification> newList = new(_notificationsCollection);
AppNotification ap = new(notification.Id)
{
NotificationType = notification.Type.ToString(),
NotificationDate = notification.Created.ToString(),
Ship = currentShipcalls[notification.ShipcallId]?.Ship?.Name,
ShipcallType = currentShipcalls[notification.ShipcallId]?.Shipcall?.Type.ToString(),
ETA = currentShipcalls[notification.ShipcallId]?.GetETAETD(true)
};
Times? agencyTimes = currentShipcalls[notification.ShipcallId]?.GetTimesForParticipantType(Extensions.ParticipantType.AGENCY);
ap.Berth = currentShipcalls[notification.ShipcallId]?.GetBerthText(agencyTimes);
ap.Message = notification.Message;
System.Diagnostics.Trace.WriteLine($"Notification {notification.Id} Type {notification.Type}");
MessageOptions options = new()
{
FontSize = 14,
ShowCloseButton = true,
Tag = ap
};
newList.Add(ap);
newList.Sort((a, b) => (a.NotificationDate ?? "").CompareTo(b.NotificationDate));
_notificationsCollection.Clear();
foreach(AppNotification newAp in newList)
_notificationsCollection.Add(newAp);
ap.NotificationDisplay = string.Empty;
switch(notification.Type)
{
case misc.Model.NotificationType.Assignment:
ap.NotificationDisplay = Resources.Resources.textAssignment; break;
case misc.Model.NotificationType.Next24h:
ap.NotificationDisplay = Resources.Resources.textNext24h; break;
case misc.Model.NotificationType.TimeConflict:
ap.NotificationDisplay = Resources.Resources.textTimeConflict; break;
case misc.Model.NotificationType.TimeConflictResolved:
ap.NotificationDisplay = Resources.Resources.textTimeConflictResolved; break;
case misc.Model.NotificationType.MissingData:
ap.NotificationDisplay = Resources.Resources.textMissingData; break;
case misc.Model.NotificationType.Cancelled:
ap.NotificationDisplay = Resources.Resources.textCancelled; break;
case misc.Model.NotificationType.Unassigned:
ap.NotificationDisplay = Resources.Resources.textUnassigned; break;
}
string toastText = ap.NotificationDisplay + "\n";
toastText += $"{ap.Ship} ({ap.ShipcallType}) - {ap.ETA} - {ap.Berth}";
if (!string.IsNullOrEmpty(ap.Message))
toastText += $" \n{ap.Message}";
if (_notifications.TryAdd(notification.Id, ap))
{
App.Current.Dispatcher.Invoke(() =>
{
vm.ShowAppNotification(toastText, options);
});
result = true;
}
}
}
if (result)
SaveNotifications(); // store notification ids in config array on change
return result;
}
internal static void SaveNotifications()
{
if (Properties.Settings.Default.Notifications == null)
Properties.Settings.Default.Notifications = [];
else
Properties.Settings.Default.Notifications.Clear();
foreach (int notification_id in _notifications.Keys)
{
Properties.Settings.Default.Notifications.Add(notification_id.ToString());
}
}
#endregion
}
}

View File

@ -1,23 +0,0 @@
// Copyright (c) 2024- schick Informatik
// Description:
//
using ToastNotifications;
using ToastNotifications.Core;
namespace BreCalClient
{
public static class AppNotificationExtension
{
public static void ShowAppNotification(this Notifier notifier, string message)
{
notifier.Notify(() => new AppNotificationMessage(message));
}
public static void ShowAppNotification(this Notifier notifier, string message, MessageOptions displayOptions)
{
notifier.Notify(() => new AppNotificationMessage(message, displayOptions));
}
}
}

View File

@ -1,30 +0,0 @@
using System.Windows;
using ToastNotifications.Core;
using ToastNotifications.Messages.Core;
namespace BreCalClient
{
public class AppNotificationMessage : MessageBase<AppNotificationPart>
{
public AppNotificationMessage(string message) : this(message, new MessageOptions())
{
}
public AppNotificationMessage(string message, MessageOptions options) : base(message, options)
{
}
protected override AppNotificationPart CreateDisplayPart()
{
return new AppNotificationPart(this);
}
protected override void UpdateDisplayOptions(AppNotificationPart displayPart, MessageOptions options)
{
// if (options.FontSize != null)
// displayPart.Text.FontSize = options.FontSize.Value;
// displayPart.CloseButton.Visibility = options.ShowCloseButton ? Visibility.Visible : Visibility.Collapsed;
}
}
}

View File

@ -1,31 +0,0 @@
<core:NotificationDisplayPart x:Class="BreCalClient.AppNotificationPart"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:core="clr-namespace:ToastNotifications.Core;assembly=ToastNotifications"
mc:Ignorable="d" d:DesignWidth="250" >
<Border x:Name="ContentWrapper" Style="{DynamicResource NotificationBorder}" Background="{DynamicResource ErrorColorBrush}">
<Grid x:Name="ContentContainer">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Rectangle x:Name="Icon" Width="24" Height="24">
<Rectangle.Fill>
<VisualBrush Visual="{StaticResource ErrorIcon}" />
</Rectangle.Fill>
</Rectangle>
<TextBlock x:Name="Text" Text="{Binding Message, Mode=OneTime}" Style="{StaticResource NotificationText}" Grid.Column="1" />
<Button x:Name="CloseButton" Style="{StaticResource NotificationCloseButton}" Padding="1" Grid.Column="2" Click="OnClose" Visibility="Hidden">
<Rectangle Style="{StaticResource CloseButtonIcon}" Margin="2">
<Rectangle.OpacityMask>
<VisualBrush Stretch="Fill" Visual="{StaticResource CloseIcon}" />
</Rectangle.OpacityMask>
</Rectangle>
</Button>
</Grid>
</Border>
</core:NotificationDisplayPart>

View File

@ -1,57 +0,0 @@
using BreCalClient.misc.Model;
using System.Windows;
using System.Windows.Media;
using ToastNotifications.Core;
namespace BreCalClient
{
/// <summary>
/// Interaction logic for NotificationPart.xaml
/// </summary>
public partial class AppNotificationPart : NotificationDisplayPart
{
public AppNotificationPart(AppNotificationMessage appNotification)
{
InitializeComponent();
Bind(appNotification);
if (appNotification.Options.Tag is AppNotification ap)
{
switch (ap.NotificationType)
{
case "TimeConflict":
this.ContentWrapper.Background = Brushes.Red;
break;
case "TimeConflictResolved":
this.ContentWrapper.Background = Brushes.Green;
break;
case "Assignment":
this.ContentWrapper.Background = Brushes.Blue;
break;
case "Next24h":
this.ContentWrapper.Background = Brushes.DarkOrange;
break;
case "Unassigned":
this.ContentWrapper.Background = Brushes.Gray;
break;
case "MissingData":
this.ContentWrapper.Background = Brushes.DarkKhaki;
break;
case "Cancelled":
this.ContentWrapper.Background = Brushes.DarkGray;
break;
default:
break;
}
}
}
private void OnClose(object sender, RoutedEventArgs e)
{
Notification.Close();
}
}
}

View File

@ -2,18 +2,18 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows7.0</TargetFramework> <TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<SignAssembly>True</SignAssembly> <SignAssembly>True</SignAssembly>
<StartupObject>BreCalClient.App</StartupObject> <StartupObject>BreCalClient.App</StartupObject>
<AssemblyOriginatorKeyFile>..\..\misc\brecal.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>..\..\misc\brecal.snk</AssemblyOriginatorKeyFile>
<AssemblyVersion>1.8.0.0</AssemblyVersion> <AssemblyVersion>1.4.0.0</AssemblyVersion>
<FileVersion>1.8.0.0</FileVersion> <FileVersion>1.4.0.0</FileVersion>
<Title>Bremen calling client</Title> <Title>Bremen calling client</Title>
<Description>A Windows WPF client for the Bremen calling API.</Description> <Description>A Windows WPF client for the Bremen calling API.</Description>
<ApplicationIcon>containership.ico</ApplicationIcon> <ApplicationIcon>containership.ico</ApplicationIcon>
<AssemblyName>BreCalTestClient</AssemblyName> <AssemblyName>BreCalDevelClient</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -26,7 +26,6 @@
<None Remove="Resources\arrow_up_blue.png" /> <None Remove="Resources\arrow_up_blue.png" />
<None Remove="Resources\arrow_up_green.png" /> <None Remove="Resources\arrow_up_green.png" />
<None Remove="Resources\arrow_up_red.png" /> <None Remove="Resources\arrow_up_red.png" />
<None Remove="Resources\bell3.png" />
<None Remove="Resources\check.png" /> <None Remove="Resources\check.png" />
<None Remove="Resources\clipboard.png" /> <None Remove="Resources\clipboard.png" />
<None Remove="Resources\clock.png" /> <None Remove="Resources\clock.png" />
@ -85,7 +84,6 @@
<Resource Include="Resources\arrow_up_blue.png" /> <Resource Include="Resources\arrow_up_blue.png" />
<Resource Include="Resources\arrow_up_green.png" /> <Resource Include="Resources\arrow_up_green.png" />
<Resource Include="Resources\arrow_up_red.png" /> <Resource Include="Resources\arrow_up_red.png" />
<Resource Include="Resources\bell3.png" />
<Resource Include="Resources\check.png" /> <Resource Include="Resources\check.png" />
<Resource Include="Resources\clipboard.png" /> <Resource Include="Resources\clipboard.png" />
<Resource Include="Resources\clock.png" /> <Resource Include="Resources\clock.png" />
@ -118,14 +116,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.6.1" /> <PackageReference Include="Extended.Wpf.Toolkit" Version="4.6.0" />
<PackageReference Include="JsonSubTypes" Version="2.0.1" /> <PackageReference Include="JsonSubTypes" Version="2.0.1" />
<PackageReference Include="log4net" Version="3.0.3" /> <PackageReference Include="log4net" Version="2.0.17" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Polly" Version="8.5.1" /> <PackageReference Include="Polly" Version="8.3.1" />
<PackageReference Include="RestSharp" Version="112.0.0" /> <PackageReference Include="RestSharp" Version="110.2.0" />
<PackageReference Include="ToastNotifications" Version="2.5.1" />
<PackageReference Include="ToastNotifications.Messages" Version="2.5.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -25,19 +25,16 @@ namespace BreCalClient
private static List<Participant> _participants = new(); private static List<Participant> _participants = new();
private static readonly List<ShipModel> _ships = new(); private static readonly List<ShipModel> _ships = new();
private static readonly List<ShipModel> _allShips = new(); private static readonly List<ShipModel> _allShips = new();
private static readonly List<Port> _ports = new();
private static readonly List<Port> _allPorts = new();
private readonly static ConcurrentDictionary<int, ShipModel> _shipLookupDict = new(); private readonly static ConcurrentDictionary<int, ShipModel> _shipLookupDict = new();
private readonly static ConcurrentDictionary<int, Berth> _berthLookupDict = new(); private readonly static ConcurrentDictionary<int, Berth> _berthLookupDict = new();
private readonly static Dictionary<int, Participant> _participantLookupDict = new(); private readonly static Dictionary<int, Participant> _participantLookupDict = new();
private readonly static ConcurrentDictionary<int, Port> _portLookupDict = new();
/// <summary> /// <summary>
/// List of TimeRef points /// List of TimeRef points
/// </summary> /// </summary>
// TODO: To make this portable the list of texts should come from a configuration file // TODO: To make this portable the list of texts should come from a configuration file
private readonly static List<string> _timeRefs = new() private readonly static List<string> _timeRefs = new List<string>
{ {
"ETB", "ETB",
"Geeste", "Geeste",
@ -48,26 +45,12 @@ namespace BreCalClient
#region Properties #region Properties
/// <summary>
/// fast ship lookup
/// </summary>
public static ConcurrentDictionary<int, ShipModel> ShipLookupDict { get { return _shipLookupDict; } } public static ConcurrentDictionary<int, ShipModel> ShipLookupDict { get { return _shipLookupDict; } }
/// <summary>
/// fast port lookup
/// </summary>
public static ConcurrentDictionary<int, Berth> BerthLookupDict { get { return _berthLookupDict; } } public static ConcurrentDictionary<int, Berth> BerthLookupDict { get { return _berthLookupDict; } }
/// <summary>
/// fast participant lookup
/// </summary>
public static Dictionary<int, Participant> ParticipantLookupDict { get { return _participantLookupDict; } } public static Dictionary<int, Participant> ParticipantLookupDict { get { return _participantLookupDict; } }
/// <summary>
/// fast port lookup
/// </summary>
public static ConcurrentDictionary<int, Port> PortLookupDict { get { return _portLookupDict; } }
/// <summary> /// <summary>
/// Participants that are agents /// Participants that are agents
/// </summary> /// </summary>
@ -108,16 +91,6 @@ namespace BreCalClient
/// </summary> /// </summary>
public static List<Berth> AllBerths { get { return _allBerths; } } public static List<Berth> AllBerths { get { return _allBerths; } }
/// <summary>
/// All active ports
/// </summary>
public static List<Port> Ports { get { return _ports; } }
/// <summary>
/// All ports including deleted ports
/// </summary>
public static List<Port> AllPorts { get { return _allPorts; } }
/// <summary> /// <summary>
/// All active ships /// All active ships
/// </summary> /// </summary>
@ -135,33 +108,7 @@ namespace BreCalClient
#endregion #endregion
#region public static methods #region methods
public static List<Berth> GetBerthsByPort(int port)
{
List<Berth> berths = new();
foreach(Berth berth in _berths)
{
if(berth.PortId == port)
berths.Add(berth);
}
return berths;
}
public static List<Participant> GetParticipants(int port, Extensions.ParticipantType type)
{
List<Participant> participants = new();
foreach(Participant participant in _participants)
{
if(participant.IsTypeFlagSet(type) && participant.Ports.Contains(port))
participants.Add(participant);
}
return participants;
}
#endregion
#region Internal initializer methods
internal static void InitializeParticipants(List<Participant> participants) internal static void InitializeParticipants(List<Participant> participants)
{ {
@ -210,17 +157,6 @@ namespace BreCalClient
} }
} }
internal static void InitializePorts(List<Port> ports)
{
foreach(var port in ports)
{
_portLookupDict[port.Id] = port;
if(!port.Deleted)
_ports.Add(port);
_allPorts.Add(port);
}
}
#endregion #endregion
} }

View File

@ -41,8 +41,8 @@ namespace BreCalClient
} }
this.Ship.Imo = this.integerUpDownIMO.Value; this.Ship.Imo = this.integerUpDownIMO.Value;
this.Ship.Callsign = this.textBoxCallsign.Text.ToUpper().Trim(); this.Ship.Callsign = this.textBoxCallsign.Text.ToUpper().Trim();
this.Ship.Length = this.doubleUpDownLength.Value; this.Ship.Length = (float?) this.doubleUpDownLength.Value;
this.Ship.Width = this.doubleUpDownWidth.Value; this.Ship.Width = (float?) this.doubleUpDownWidth.Value;
this.DialogResult = true; this.DialogResult = true;
this.Close(); this.Close();

View File

@ -44,9 +44,7 @@
<xctk:DoubleUpDown x:Name="doubleUpDownWidth" Margin="2" Grid.Column="1" Grid.Row="4" FormatString="N2" IsReadOnly="True" IsEnabled="False" ShowButtonSpinner="False"/> <xctk:DoubleUpDown x:Name="doubleUpDownWidth" Margin="2" Grid.Column="1" Grid.Row="4" FormatString="N2" IsReadOnly="True" IsEnabled="False" ShowButtonSpinner="False"/>
<Label Content="{x:Static p:Resources.textCancelled}" Grid.Column="0" Grid.Row="5" HorizontalContentAlignment="Right" /> <Label Content="{x:Static p:Resources.textCancelled}" Grid.Column="0" Grid.Row="5" HorizontalContentAlignment="Right" />
<CheckBox x:Name="checkBoxCancelled" Grid.Column="1" Grid.Row="5" Margin="2" VerticalContentAlignment="Center" /> <CheckBox x:Name="checkBoxCancelled" Grid.Column="1" Grid.Row="5" Margin="2" VerticalContentAlignment="Center" />
<Label Content="{x:Static p:Resources.textHarbour}" Grid.Column="0" Grid.Row="6" HorizontalContentAlignment="Right" /> <Button x:Name="buttonEditShips" Grid.Column="1" Grid.Row="6" Margin="2" Content="{x:Static p:Resources.textEditShips}" Click="buttonEditShips_Click" Visibility="Hidden" />
<ComboBox x:Name="comboBoxHarbour" Grid.Column="1" Margin="2" Grid.Row="6" DisplayMemberPath="Name" SelectedValuePath="Id" />
<Button x:Name="buttonEditShips" Grid.Column="1" Grid.Row="7" Margin="2" Content="{x:Static p:Resources.textEditShips}" Click="buttonEditShips_Click" Visibility="Hidden" />
<Label Content="{x:Static p:Resources.textType}" Grid.Column="2" Grid.Row="0" HorizontalContentAlignment="Right" /> <Label Content="{x:Static p:Resources.textType}" Grid.Column="2" Grid.Row="0" HorizontalContentAlignment="Right" />
@ -87,7 +85,7 @@
<Label x:Name="labelBSMDGranted" Grid.Row="7" Grid.Column="3" Grid.ColumnSpan="1" Content="{x:Static p:Resources.textBSMDGranted}" Visibility="Hidden" FontWeight="DemiBold" /> <Label x:Name="labelBSMDGranted" Grid.Row="7" Grid.Column="3" Grid.ColumnSpan="1" Content="{x:Static p:Resources.textBSMDGranted}" Visibility="Hidden" FontWeight="DemiBold" />
<Label x:Name="labelShiftingCount" Grid.Row="6" Grid.Column="2" HorizontalAlignment="Right" Content="{x:Static p:Resources.textShiftingSequence}" /> <Label x:Name="labelShiftingCount" Grid.Row="6" Grid.Column="2" HorizontalAlignment="Right" Content="{x:Static p:Resources.textShiftingSequence}" />
<xctk:IntegerUpDown x:Name="integerUpDownShiftingCount" Grid.Row="6" Grid.Column="3" Margin="2" Minimum="0" Maximum="127" /> <xctk:IntegerUpDown x:Name="integerUpDownShiftingCount" Grid.Row="6" Grid.Column="3" Margin="2" Minimum="0" Maximum="255" />
<StackPanel Grid.Row="8" Grid.Column="3" Orientation="Horizontal" HorizontalAlignment="Right"> <StackPanel Grid.Row="8" Grid.Column="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" IsEnabled="False" /> <Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" IsEnabled="False" />

View File

@ -6,7 +6,6 @@ using BreCalClient.misc.Api;
using BreCalClient.misc.Model; using BreCalClient.misc.Model;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using static BreCalClient.Extensions; using static BreCalClient.Extensions;
@ -42,14 +41,13 @@ namespace BreCalClient
public ShipApi? ShipApi { get; set; } public ShipApi? ShipApi { get; set; }
public bool IsCreate { get; set; } = false;
#endregion #endregion
#region Event handler #region Event handler
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
this.comboBoxAgency.ItemsSource = BreCalLists.Participants_Agent;
this.comboBoxShip.ItemsSource = BreCalLists.Ships; this.comboBoxShip.ItemsSource = BreCalLists.Ships;
Array types = Enum.GetValues(typeof(ShipcallType)); Array types = Enum.GetValues(typeof(ShipcallType));
@ -61,8 +59,10 @@ namespace BreCalClient
else first = false; else first = false;
} }
this.comboBoxArrivalBerth.ItemsSource = BreCalLists.Berths;
this.comboBoxDepartureBerth.ItemsSource = BreCalLists.Berths;
this.comboBoxTimeRef.ItemsSource = BreCalLists.TimeRefs; this.comboBoxTimeRef.ItemsSource = BreCalLists.TimeRefs;
this.comboBoxHarbour.ItemsSource = BreCalLists.Ports.Where(x => App.Participant.Ports.Contains(x.Id));
this.integerUpDownShiftingCount.Value = this.ShipcallModel.ShiftSequence; this.integerUpDownShiftingCount.Value = this.ShipcallModel.ShiftSequence;
@ -188,13 +188,10 @@ namespace BreCalClient
void CheckForCompletion() void CheckForCompletion()
{ {
if (this.ShipcallModel.Shipcall?.Canceled ?? false) return; // Cancelled shipcall never clicks ok
bool isEnabled = true; bool isEnabled = true;
isEnabled &= this.comboBoxShip.SelectedItem != null; isEnabled &= this.comboBoxShip.SelectedItem != null;
isEnabled &= this.comboBoxCategories.SelectedItem != null; isEnabled &= this.comboBoxCategories.SelectedItem != null;
isEnabled &= this.comboBoxHarbour.SelectedItem != null;
if (comboBoxCategories.SelectedItem == null) if (comboBoxCategories.SelectedItem == null)
{ {
@ -208,25 +205,15 @@ namespace BreCalClient
case ShipcallType.Departure: case ShipcallType.Departure:
isEnabled &= this.comboBoxDepartureBerth.SelectedItem != null; isEnabled &= this.comboBoxDepartureBerth.SelectedItem != null;
isEnabled &= this.datePickerETD.Value.HasValue; isEnabled &= this.datePickerETD.Value.HasValue;
isEnabled &= !(this.datePickerETD.Value.IsTooOld() && this.datePickerETD.Value != this.ShipcallModel.Shipcall?.Etd);
isEnabled &= !this.datePickerETD.Value.IsTooFar();
break; break;
case ShipcallType.Arrival: case ShipcallType.Arrival:
isEnabled &= this.comboBoxArrivalBerth.SelectedItem != null; isEnabled &= this.comboBoxArrivalBerth.SelectedItem != null;
isEnabled &= this.datePickerETA.Value.HasValue; isEnabled &= this.datePickerETA.Value.HasValue;
isEnabled &= !(this.datePickerETA.Value.IsTooOld() && this.datePickerETA.Value != this.ShipcallModel.Shipcall?.Eta);
isEnabled &= !this.datePickerETA.Value.IsTooFar();
break; break;
case ShipcallType.Shifting: case ShipcallType.Shifting:
isEnabled &= ((this.comboBoxDepartureBerth.SelectedItem != null) && (this.comboBoxArrivalBerth.SelectedItem != null)); isEnabled &= ((this.comboBoxDepartureBerth.SelectedItem != null) && (this.comboBoxArrivalBerth.SelectedItem != null));
isEnabled &= this.datePickerETD.Value.HasValue; isEnabled &= this.datePickerETD.Value.HasValue;
isEnabled &= this.datePickerETA.Value.HasValue; isEnabled &= this.datePickerETA.Value.HasValue;
isEnabled &= !(this.datePickerETD.Value.IsTooOld() && this.datePickerETD.Value != this.ShipcallModel.Shipcall?.Etd);
isEnabled &= !(this.datePickerETA.Value.IsTooOld() && this.datePickerETA.Value != this.ShipcallModel.Shipcall?.Eta);
if (this.datePickerETA.Value.HasValue && this.datePickerETD.Value.HasValue)
isEnabled &= (this.datePickerETA.Value.Value > this.datePickerETD.Value.Value);
isEnabled &= !this.datePickerETD.Value.IsTooFar();
isEnabled &= !this.datePickerETA.Value.IsTooFar();
break; break;
} }
} }
@ -312,7 +299,7 @@ namespace BreCalClient
// set the time reference value (which point do all times refer to?) // set the time reference value (which point do all times refer to?)
this.ShipcallModel.Shipcall.TimeRefPoint = this.comboBoxTimeRef.SelectedIndex; this.ShipcallModel.Shipcall.TimeRefPoint = this.comboBoxTimeRef.SelectedIndex;
this.ShipcallModel.Shipcall.PortId = ((Port) this.comboBoxHarbour.SelectedItem).Id;
} }
} }
@ -325,7 +312,7 @@ namespace BreCalClient
this.comboBoxCategories.SelectedItem = new EnumToStringConverter().Convert(this.ShipcallModel.Shipcall.Type, typeof(ShipcallType), new object(), System.Globalization.CultureInfo.CurrentCulture); this.comboBoxCategories.SelectedItem = new EnumToStringConverter().Convert(this.ShipcallModel.Shipcall.Type, typeof(ShipcallType), new object(), System.Globalization.CultureInfo.CurrentCulture);
if (this.ShipcallModel.Shipcall.Eta != DateTime.MinValue) if (this.ShipcallModel.Shipcall.Eta != DateTime.MinValue)
this.datePickerETA.Value = this.ShipcallModel.Shipcall.Eta; this.datePickerETA.Value = this.ShipcallModel.Shipcall.Eta;
// this.textBoxVoyage.Text = this.ShipcallModel.Shipcall.Voyage;
this.datePickerETD.Value = this.ShipcallModel.Shipcall.Etd; this.datePickerETD.Value = this.ShipcallModel.Shipcall.Etd;
if (BreCalLists.Ships.Find(x => x.Ship.Id == this.ShipcallModel.Shipcall.ShipId) != null) if (BreCalLists.Ships.Find(x => x.Ship.Id == this.ShipcallModel.Shipcall.ShipId) != null)
{ {
@ -336,16 +323,6 @@ namespace BreCalClient
} }
this.checkBoxCancelled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false; this.checkBoxCancelled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false;
if (BreCalLists.PortLookupDict.ContainsKey(this.ShipcallModel.Shipcall.PortId))
this.comboBoxHarbour.SelectedValue = this.ShipcallModel.Shipcall.PortId;
List<Berth> availableBerths = BreCalLists.GetBerthsByPort(this.ShipcallModel.Shipcall.PortId);
this.comboBoxArrivalBerth.ItemsSource = availableBerths;
this.comboBoxDepartureBerth.ItemsSource = availableBerths;
// Filter agency combobox by port
List<Participant> availableAgencies = BreCalLists.GetParticipants(this.ShipcallModel.Shipcall.PortId, ParticipantType.AGENCY);
this.comboBoxAgency.ItemsSource = availableAgencies;
if (this.ShipcallModel.Shipcall.Type != ShipcallType.Shifting) // incoming, outgoing if (this.ShipcallModel.Shipcall.Type != ShipcallType.Shifting) // incoming, outgoing
{ {
this.comboBoxArrivalBerth.SelectedValue = this.ShipcallModel.Shipcall.ArrivalBerthId; this.comboBoxArrivalBerth.SelectedValue = this.ShipcallModel.Shipcall.ArrivalBerthId;
@ -366,10 +343,6 @@ namespace BreCalClient
this.comboBoxAgency.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId; this.comboBoxAgency.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId;
} }
} }
this.comboBoxHarbour.SelectionChanged += this.comboBoxHarbour_SelectionChanged;
this.comboBoxHarbour.IsEnabled = this.ShipcallModel.AllowPortChange;
} }
} }
@ -380,8 +353,6 @@ namespace BreCalClient
bool editRightGrantedForBSMD = false; bool editRightGrantedForBSMD = false;
if (this.ShipcallModel.Shipcall?.Canceled ?? false) return; // do not allow edit on canceled shipcall
// Special case: Selected Agency allows BSMD to edit their fields // Special case: Selected Agency allows BSMD to edit their fields
if (this.comboBoxAgency.SelectedIndex >= 0) if (this.comboBoxAgency.SelectedIndex >= 0)
{ {
@ -405,7 +376,6 @@ namespace BreCalClient
this.datePickerETD.IsEnabled = isAgency || isBsmd; this.datePickerETD.IsEnabled = isAgency || isBsmd;
this.labelBSMDGranted.Visibility = editRightGrantedForBSMD ? Visibility.Visible : Visibility.Hidden; this.labelBSMDGranted.Visibility = editRightGrantedForBSMD ? Visibility.Visible : Visibility.Hidden;
this.comboBoxHarbour.IsEnabled = this.IsCreate && this.ShipcallModel.AllowPortChange;
this.comboBoxCategories_SelectionChanged(null, null); this.comboBoxCategories_SelectionChanged(null, null);
} }
@ -448,24 +418,6 @@ namespace BreCalClient
this.comboBoxShip.ItemsSource = BreCalLists.Ships; this.comboBoxShip.ItemsSource = BreCalLists.Ships;
} }
private void comboBoxHarbour_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Port port = (Port)this.comboBoxHarbour.SelectedItem;
if (port == null) return;
// Filter berth selection combobox by port
List<Berth> availableBerths = BreCalLists.GetBerthsByPort(port.Id);
this.comboBoxArrivalBerth.ItemsSource = null;
this.comboBoxArrivalBerth.ItemsSource = availableBerths;
this.comboBoxDepartureBerth.ItemsSource = null;
this.comboBoxDepartureBerth.ItemsSource = availableBerths;
// Filter agency combobox by port
List<Participant> availableAgencies = BreCalLists.GetParticipants(port.Id, ParticipantType.AGENCY);
this.comboBoxAgency.ItemsSource = null;
this.comboBoxAgency.ItemsSource = availableAgencies;
}
#endregion #endregion
} }

View File

@ -41,27 +41,19 @@ namespace BreCalClient
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
this.comboBoxMooring.ItemsSource = BreCalLists.Participants_Mooring;
this.comboBoxPilot.ItemsSource = BreCalLists.Participants_Pilot;
this.comboBoxTug.ItemsSource = BreCalLists.Participants_Tug;
this.comboBoxTerminal.ItemsSource = BreCalLists.Participants_Terminal;
if ((this.ShipcallModel != null) && (this.ShipcallModel.Shipcall != null)) this.comboBoxArrivalBerth.ItemsSource = BreCalLists.Berths;
{
int portId = this.ShipcallModel.Shipcall.PortId;
this.comboBoxArrivalBerth.ItemsSource = BreCalLists.GetBerthsByPort(portId);
this.comboBoxMooring.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.MOORING);
this.comboBoxPilot.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.PILOT);
this.comboBoxTug.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.TUG);
this.comboBoxTerminal.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.TERMINAL);
}
this.CopyToControls(); this.CopyToControls();
this.Title = this.ShipcallModel?.Title; this.Title = this.ShipcallModel.Title;
Participant? p = null; Participant? p = null;
if (this.ShipcallModel != null) if(this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
{
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId); p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId);
}
bool allowBSMD = false; bool allowBSMD = false;
if (p != null) if (p != null)
@ -77,18 +69,11 @@ namespace BreCalClient
} }
private void buttonOK_Click(object sender, RoutedEventArgs e) private void buttonOK_Click(object sender, RoutedEventArgs e)
{
if (!CheckValues(out string message))
{
MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{ {
this.CopyToModel(); this.CopyToModel();
this.DialogResult = true; this.DialogResult = true;
this.Close(); this.Close();
} }
}
private void buttonCancel_Click(object sender, RoutedEventArgs e) private void buttonCancel_Click(object sender, RoutedEventArgs e)
{ {
@ -100,62 +85,6 @@ namespace BreCalClient
#region private methods #region private methods
private bool CheckValues(out string message)
{
message = "";
if ((this.datePickerETA.Value != this.Times.EtaBerth) || (this.datePickerETA_End.Value != this.Times.EtaIntervalEnd)) // something has changed
{
if (datePickerETA.Value.IsTooOld() || datePickerETA_End.Value.IsTooOld())
{
message = BreCalClient.Resources.Resources.textETAInThePast;
return false;
}
if (this.datePickerETA.Value.HasValue && this.datePickerETA_End.Value.HasValue && this.datePickerETA.Value > this.datePickerETA_End.Value)
{
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
return false;
}
}
if((this.datePickerTidalWindowFrom.Value != this.ShipcallModel.Shipcall?.TidalWindowFrom) || (this.datePickerTidalWindowTo.Value != this.ShipcallModel.Shipcall?.TidalWindowTo)) // something has changed
{
if(datePickerTidalWindowTo.Value.IsTooOld() || this.datePickerTidalWindowFrom.Value.IsTooOld())
{
message = BreCalClient.Resources.Resources.textTideTimesInThePast;
return false;
}
if (this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue && this.datePickerTidalWindowFrom.Value > this.datePickerTidalWindowTo.Value)
{
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
return false;
}
if ((this.datePickerTidalWindowFrom.Value.HasValue && !this.datePickerTidalWindowTo.Value.HasValue) || (!this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textTidalBothValues;
return false;
}
}
if(this.datePickerETA.Value.IsTooFar() || this.datePickerETA_End.Value.IsTooFar() || this.datePickerTidalWindowFrom.Value.IsTooFar() || this.datePickerTidalWindowTo.Value.IsTooFar())
{
message = BreCalClient.Resources.Resources.textTooFarInTheFuture;
return false;
}
if((this.datePickerETA_End.Value.HasValue && !this.datePickerETA.Value.HasValue) ||
(this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textStartTimeMissing;
return false;
}
return true;
}
private void CopyToModel() private void CopyToModel()
{ {
if (this.ShipcallModel.Shipcall != null) if (this.ShipcallModel.Shipcall != null)
@ -366,7 +295,7 @@ namespace BreCalClient
private void CheckOKButton() private void CheckOKButton()
{ {
this.buttonOK.IsEnabled = _editing && RequiredFieldsSet() && !(this.ShipcallModel.Shipcall?.Canceled ?? false); this.buttonOK.IsEnabled = _editing && RequiredFieldsSet();
} }
#endregion #endregion

View File

@ -4,8 +4,15 @@
using BreCalClient.misc.Model; using BreCalClient.misc.Model;
using System; using System;
using System.Text.RegularExpressions;
using System.Windows; using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Controls;
using Xceed.Wpf.Toolkit;
using static BreCalClient.Extensions; using static BreCalClient.Extensions;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Media;
namespace BreCalClient namespace BreCalClient
{ {
@ -42,27 +49,20 @@ namespace BreCalClient
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
this.comboBoxMooring.ItemsSource = BreCalLists.Participants_Mooring;
this.comboBoxPilot.ItemsSource = BreCalLists.Participants_Pilot;
this.comboBoxTug.ItemsSource = BreCalLists.Participants_Tug;
this.comboBoxTerminal.ItemsSource = BreCalLists.Participants_Terminal;
if ((this.ShipcallModel != null) && (this.ShipcallModel.Shipcall != null)) this.comboBoxDepartureBerth.ItemsSource = BreCalLists.Berths;
{
int portId = this.ShipcallModel.Shipcall.PortId;
this.comboBoxDepartureBerth.ItemsSource = BreCalLists.GetBerthsByPort(portId);
this.comboBoxMooring.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.MOORING);
this.comboBoxPilot.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.PILOT);
this.comboBoxTug.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.TUG);
this.comboBoxTerminal.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.TERMINAL);
}
this.CopyToControls(); this.CopyToControls();
this.Title = this.ShipcallModel?.Title; this.Title = this.ShipcallModel.Title;
Participant? p = null; Participant? p = null;
if (this.ShipcallModel != null)
{
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY)) if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId); p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId);
}
bool allowBSMD = false; bool allowBSMD = false;
if (p != null) if (p != null)
{ {
@ -79,18 +79,11 @@ namespace BreCalClient
private void buttonOK_Click(object sender, RoutedEventArgs e) private void buttonOK_Click(object sender, RoutedEventArgs e)
{
if (!CheckValues(out string message))
{
System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{ {
this.CopyToModel(); this.CopyToModel();
this.DialogResult = true; this.DialogResult = true;
this.Close(); this.Close();
} }
}
private void buttonCancel_Click(object sender, RoutedEventArgs e) private void buttonCancel_Click(object sender, RoutedEventArgs e)
{ {
@ -102,63 +95,6 @@ namespace BreCalClient
#region private methods #region private methods
private bool CheckValues(out string message)
{
message = "";
if((this.datePickerETD.Value != this.Times.EtdBerth) || (this.datePickerETD_End.Value != this.Times.EtdIntervalEnd))
{
if (datePickerETD.Value.IsTooOld() || datePickerETD_End.Value.IsTooOld())
{
message = BreCalClient.Resources.Resources.textETDInThePast;
return false;
}
}
if (this.datePickerETD.Value.HasValue && this.datePickerETD_End.Value.HasValue && this.datePickerETD.Value > this.datePickerETD_End.Value)
{
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
return false;
}
if((this.datePickerTidalWindowFrom.Value != this.ShipcallModel.Shipcall?.TidalWindowFrom) || (this.datePickerTidalWindowTo.Value != this.ShipcallModel.Shipcall?.TidalWindowTo))
{
if (this.datePickerTidalWindowTo.Value.IsTooOld() || this.datePickerTidalWindowFrom.Value.IsTooOld())
{
message = BreCalClient.Resources.Resources.textTideTimesInThePast;
return false;
}
}
if (this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue && this.datePickerTidalWindowFrom.Value > this.datePickerTidalWindowTo.Value)
{
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
return false;
}
if ((this.datePickerTidalWindowFrom.Value.HasValue && !this.datePickerTidalWindowTo.Value.HasValue) || (!this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textTidalBothValues;
return false;
}
if (this.datePickerETD.Value.IsTooFar() || this.datePickerETD_End.Value.IsTooFar() || this.datePickerTidalWindowFrom.Value.IsTooFar() || this.datePickerTidalWindowTo.Value.IsTooFar())
{
message = BreCalClient.Resources.Resources.textTooFarInTheFuture;
return false;
}
if((this.datePickerETD_End.Value.HasValue && !this.datePickerETD.Value.HasValue) ||
(this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textStartTimeMissing;
return false;
}
return true;
}
private void CopyToModel() private void CopyToModel()
{ {
if (this.ShipcallModel.Shipcall != null) if (this.ShipcallModel.Shipcall != null)
@ -356,7 +292,7 @@ namespace BreCalClient
private void CheckOKButton() private void CheckOKButton()
{ {
this.buttonOK.IsEnabled = _editing && RequiredFieldsSet() && !(this.ShipcallModel.Shipcall?.Canceled ?? false); this.buttonOK.IsEnabled = _editing && RequiredFieldsSet();
} }
#endregion #endregion

View File

@ -4,9 +4,7 @@
using BreCalClient.misc.Model; using BreCalClient.misc.Model;
using System; using System;
using System.Collections.Generic;
using System.Windows; using System.Windows;
using System.Windows.Documents;
using static BreCalClient.Extensions; using static BreCalClient.Extensions;
namespace BreCalClient namespace BreCalClient
@ -43,29 +41,20 @@ namespace BreCalClient
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
this.comboBoxMooring.ItemsSource = BreCalLists.Participants_Mooring;
this.comboBoxPilot.ItemsSource = BreCalLists.Participants_Pilot;
this.comboBoxTug.ItemsSource = BreCalLists.Participants_Tug;
this.comboBoxTerminal.ItemsSource = BreCalLists.Participants_Terminal;
if ((this.ShipcallModel != null) && (this.ShipcallModel.Shipcall != null)) this.comboBoxDepartureBerth.ItemsSource = BreCalLists.Berths;
{ this.comboBoxArrivalBerth.ItemsSource = BreCalLists.Berths;
int portId = this.ShipcallModel.Shipcall.PortId;
List<Berth> availableBerths = BreCalLists.GetBerthsByPort(portId);
this.comboBoxArrivalBerth.ItemsSource = availableBerths;
this.comboBoxDepartureBerth.ItemsSource = availableBerths;
this.comboBoxMooring.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.MOORING);
this.comboBoxPilot.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.PILOT);
this.comboBoxTug.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.TUG);
this.comboBoxTerminal.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.TERMINAL);
}
this.CopyToControls(); this.CopyToControls();
Participant? p = null;
if (this.ShipcallModel != null)
{
this.Title = this.ShipcallModel.Title; this.Title = this.ShipcallModel.Title;
Participant? p = null;
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY)) if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId); p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId);
}
bool allowBSMD = false; bool allowBSMD = false;
if (p != null) if (p != null)
@ -81,18 +70,11 @@ namespace BreCalClient
} }
private void buttonOK_Click(object sender, RoutedEventArgs e) private void buttonOK_Click(object sender, RoutedEventArgs e)
{
if (!CheckValues(out string message))
{
System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{ {
this.CopyToModel(); this.CopyToModel();
this.DialogResult = true; this.DialogResult = true;
this.Close(); this.Close();
} }
}
private void buttonCancel_Click(object sender, RoutedEventArgs e) private void buttonCancel_Click(object sender, RoutedEventArgs e)
{ {
@ -104,80 +86,6 @@ namespace BreCalClient
#region private methods #region private methods
private bool CheckValues(out string message)
{
message = "";
if((this.datePickerETA.Value != this.Times.EtaBerth) || (this.datePickerETA_End.Value != this.Times.EtaIntervalEnd))
{
if (this.datePickerETA.Value.IsTooOld() && this.datePickerETA_End.Value.IsTooOld())
{
message = BreCalClient.Resources.Resources.textETAInThePast;
return false;
}
}
if (this.datePickerETA.Value.HasValue && this.datePickerETA_End.Value.HasValue && this.datePickerETA.Value > this.datePickerETA_End.Value)
{
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
return false;
}
if((this.datePickerETD.Value != this.Times.EtdBerth) || (this.datePickerETD_End.Value != this.Times.EtdIntervalEnd))
{
if (this.datePickerETD.Value.IsTooOld() || this.datePickerETD_End.Value.IsTooOld())
{
message = BreCalClient.Resources.Resources.textETDInThePast;
return false;
}
}
if (this.datePickerETD.Value.HasValue && this.datePickerETD_End.Value.HasValue && this.datePickerETD.Value > this.datePickerETD_End.Value)
{
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
return false;
}
if((this.datePickerTidalWindowFrom.Value != this.ShipcallModel.Shipcall?.TidalWindowFrom) || (this.datePickerTidalWindowTo.Value != this.ShipcallModel.Shipcall?.TidalWindowTo))
{
if (this.datePickerTidalWindowFrom.Value.IsTooOld() && this.datePickerTidalWindowTo.Value.IsTooOld())
{
message = BreCalClient.Resources.Resources.textTideTimesInThePast;
return false;
}
}
if (this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue && this.datePickerTidalWindowFrom.Value > this.datePickerTidalWindowTo.Value)
{
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
return false;
}
if ((this.datePickerTidalWindowFrom.Value.HasValue && !this.datePickerTidalWindowTo.Value.HasValue) || (!this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textTidalBothValues;
return false;
}
if (this.datePickerETA.Value.IsTooFar() || this.datePickerETA_End.Value.IsTooFar() || this.datePickerTidalWindowFrom.Value.IsTooFar() || this.datePickerTidalWindowTo.Value.IsTooFar() ||
this.datePickerETD.Value.IsTooFar() || this.datePickerETD_End.Value.IsTooFar())
{
message = BreCalClient.Resources.Resources.textTooFarInTheFuture;
return false;
}
if((this.datePickerETA_End.Value.HasValue && !this.datePickerETA.Value.HasValue) ||
(this.datePickerETD_End.Value.HasValue && !this.datePickerETD.Value.HasValue) ||
(this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textStartTimeMissing;
return false;
}
return true;
}
private void CopyToModel() private void CopyToModel()
{ {
if (this.ShipcallModel.Shipcall != null) if (this.ShipcallModel.Shipcall != null)
@ -394,7 +302,7 @@ namespace BreCalClient
private void CheckOKButton() private void CheckOKButton()
{ {
this.buttonOK.IsEnabled = _editing && RequiredFieldsSet() && !(this.ShipcallModel.Shipcall?.Canceled ?? false); this.buttonOK.IsEnabled = _editing && RequiredFieldsSet();
} }
#endregion #endregion

View File

@ -7,7 +7,7 @@
xmlns:p = "clr-namespace:BreCalClient.Resources" xmlns:p = "clr-namespace:BreCalClient.Resources"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d" Left="{local:SettingBinding W1Left}" Top="{local:SettingBinding W1Top}" mc:Ignorable="d" Left="{local:SettingBinding W1Left}" Top="{local:SettingBinding W1Top}"
Title="{x:Static p:Resources.textEditTimes}" Height="415" Width="500" Loaded="Window_Loaded" ResizeMode="CanResizeWithGrip" Icon="Resources/containership.ico"> Title="{x:Static p:Resources.textEditTimes}" Height="331" Width="500" Loaded="Window_Loaded" ResizeMode="CanResizeWithGrip" Icon="Resources/containership.ico">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width=".20*" /> <ColumnDefinition Width=".20*" />
@ -22,9 +22,6 @@
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" x:Name="rowt1" />
<RowDefinition Height="28" x:Name="rowt2" />
<RowDefinition Height="28" x:Name="rowt3" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
@ -38,10 +35,8 @@
<Label Grid.Row="4" Grid.Column="0" Content="ATD" HorizontalContentAlignment="Right" x:Name="labelATD" /> <Label Grid.Row="4" Grid.Column="0" Content="ATD" HorizontalContentAlignment="Right" x:Name="labelATD" />
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static p:Resources.textLockTime}" HorizontalContentAlignment="Right" /> <Label Grid.Row="5" Grid.Column="0" Content="{x:Static p:Resources.textLockTime}" HorizontalContentAlignment="Right" />
<Label Grid.Row="6" Grid.Column="0" Content="{x:Static p:Resources.textZoneEntryTime}" HorizontalContentAlignment="Right" /> <Label Grid.Row="6" Grid.Column="0" Content="{x:Static p:Resources.textZoneEntryTime}" HorizontalContentAlignment="Right" />
<Label Grid.Row="7" Grid.Column="0" Content="{x:Static p:Resources.textTidalWindow}" HorizontalContentAlignment="Right" />
<Label Grid.Row="8" Grid.Column="0" Content="{x:Static p:Resources.textFrom}" HorizontalContentAlignment="Right" /> <Label Grid.Row="7" Grid.Column="0" Content="{x:Static p:Resources.textRemarks}" HorizontalContentAlignment="Right" />
<Label Grid.Row="9" Grid.Column="0" Content="{x:Static p:Resources.textTo}" HorizontalContentAlignment="Right" />
<Label Grid.Row="10" Grid.Column="0" Content="{x:Static p:Resources.textRemarks}" HorizontalContentAlignment="Right" />
<Grid Grid.Row="1" Grid.Column="1"> <Grid Grid.Row="1" Grid.Column="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@ -49,7 +44,7 @@
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<local:DateTimePickerExt IsEnabled="False" Grid.Row="0" Grid.Column="0" Margin="2" x:Name="datePickerETABerth" Format="Custom" FormatString="dd.MM. yyyy HH:mm"> <local:DateTimePickerExt IsEnabled="False" Grid.Row="0" Grid.Column="0" Margin="4,2,0,2" x:Name="datePickerETABerth" Format="Custom" FormatString="dd.MM. yyyy HH:mm">
<xctk:DateTimePicker.ContextMenu> <xctk:DateTimePicker.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearETA" Click="contextMenuItemClearETA_Click" > <MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearETA" Click="contextMenuItemClearETA_Click" >
@ -142,7 +137,7 @@
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</local:DateTimePickerExt> </local:DateTimePickerExt>
<!--CheckBox IsEnabled="False" Grid.Row="3" Grid.Column="2" Margin="4,0,0,0" Name="checkBoxLockTimeFixed" VerticalAlignment="Center" /-->
<local:DateTimePickerExt IsEnabled="False" Grid.Row="6" Grid.Column="1" Margin="2" x:Name="datePickerZoneEntry" Format="Custom" FormatString="dd.MM. yyyy HH:mm"> <local:DateTimePickerExt IsEnabled="False" Grid.Row="6" Grid.Column="1" Margin="2" x:Name="datePickerZoneEntry" Format="Custom" FormatString="dd.MM. yyyy HH:mm">
<xctk:DateTimePicker.ContextMenu> <xctk:DateTimePicker.ContextMenu>
<ContextMenu> <ContextMenu>
@ -154,12 +149,10 @@
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</local:DateTimePickerExt> </local:DateTimePickerExt>
<!--CheckBox IsEnabled="False" Grid.Row="4" Grid.Column="2" Margin="4,0,0,0" Name="checkBoxZoneEntryFixed" VerticalAlignment="Center" /-->
<xctk:DateTimePicker Name="datePickerTidalWindowFrom" Grid.Column="1" Grid.Row="8" Margin="2" IsEnabled="False" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/> <TextBox Grid.Row="7" Grid.Column="1" Margin="2" Name="textBoxRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="False" IsReadOnly="True" MaxLength="512"/>
<xctk:DateTimePicker Name="datePickerTidalWindowTo" Grid.Column="1" Grid.Row="9" Margin="2" IsEnabled="False" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/> <StackPanel Grid.Row="8" Grid.Column="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
<TextBox Grid.Row="10" Grid.Column="1" Margin="2" Name="textBoxRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="False" IsReadOnly="True" MaxLength="512"/>
<StackPanel Grid.Row="11" Grid.Column="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" /> <Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" />
<Button Width="80" Margin="2" Content="{x:Static p:Resources.textCancel}" x:Name="buttonCancel" Click="buttonCancel_Click"/> <Button Width="80" Margin="2" Content="{x:Static p:Resources.textCancel}" x:Name="buttonCancel" Click="buttonCancel_Click"/>
<Button Width="28" x:Name="buttonClearAll" Click="buttonClearAll_Click" Margin="2" IsEnabled="False"> <Button Width="28" x:Name="buttonClearAll" Click="buttonClearAll_Click" Margin="2" IsEnabled="False">

View File

@ -45,19 +45,11 @@ namespace BreCalClient
} }
private void buttonOK_Click(object sender, RoutedEventArgs e) private void buttonOK_Click(object sender, RoutedEventArgs e)
{
if (!CheckValues(out string message))
{
System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning,
MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{ {
this.CopyToModel(); this.CopyToModel();
this.DialogResult = true; this.DialogResult = true;
this.Close(); this.Close();
} }
}
private void buttonCancel_Click(object sender, RoutedEventArgs e) private void buttonCancel_Click(object sender, RoutedEventArgs e)
{ {
@ -86,8 +78,6 @@ namespace BreCalClient
this.datePickerLockTime.Value = null; this.datePickerLockTime.Value = null;
this.datePickerZoneEntry.Value = null; this.datePickerZoneEntry.Value = null;
this.textBoxRemarks.Text = null; this.textBoxRemarks.Text = null;
this.datePickerTidalWindowFrom.Value = null;
this.datePickerTidalWindowTo.Value = null;
} }
} }
@ -95,93 +85,6 @@ namespace BreCalClient
#region private methods #region private methods
private bool CheckValues(out string message)
{
message = "";
if ((this.datePickerETABerth.Value != this.Times.EtaBerth) || (this.datePickerETABerth_End.Value != this.Times.EtaIntervalEnd))
{
if (this.datePickerETABerth.Value.IsTooOld() || this.datePickerETABerth_End.Value.IsTooOld())
{
message = BreCalClient.Resources.Resources.textETAInThePast;
return false;
}
}
if (this.datePickerETABerth.Value.HasValue && this.datePickerETABerth_End.Value.HasValue && this.datePickerETABerth.Value > this.datePickerETABerth_End.Value)
{
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
return false;
}
if((this.datePickerETDBerth.Value != this.Times.EtdBerth) || (this.datePickerETDBerth_End.Value != this.Times.EtdIntervalEnd))
{
if(this.datePickerETDBerth.Value.IsTooOld() || this.datePickerETDBerth_End.Value.IsTooOld())
{
message = BreCalClient.Resources.Resources.textETDInThePast;
return false;
}
}
if (this.datePickerETDBerth.Value.HasValue && this.datePickerETDBerth_End.Value.HasValue && this.datePickerETDBerth.Value > this.datePickerETDBerth_End.Value)
{
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
return false;
}
if (this.datePickerLockTime.Value.IsTooOld() && (this.datePickerLockTime.Value != this.Times.LockTime))
{
message = BreCalClient.Resources.Resources.textLockTimeInThePast;
return false;
}
if (this.datePickerZoneEntry.Value.IsTooOld() && (this.datePickerZoneEntry.Value != this.Times.ZoneEntry))
{
message = BreCalClient.Resources.Resources.textZoneEntryInThePast;
return false;
}
if(this.datePickerATA.Value.IsTooFar() || this.datePickerATD.Value.IsTooFar() || this.datePickerETABerth.Value.IsTooFar() || this.datePickerETABerth_End.Value.IsTooFar() ||
this.datePickerETDBerth.Value.IsTooFar() || this.datePickerETDBerth_End.Value.IsTooFar() || this.datePickerLockTime.Value.IsTooFar() || this.datePickerZoneEntry.Value.IsTooFar())
{
message = BreCalClient.Resources.Resources.textTooFarInTheFuture;
return false;
}
if((this.datePickerETABerth_End.Value.HasValue && !this.datePickerETABerth.Value.HasValue) || (this.datePickerETDBerth_End.Value.HasValue && !this.datePickerETDBerth.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textStartTimeMissing;
return false;
}
if ((Extensions.ParticipantType)this.Times.ParticipantType == Extensions.ParticipantType.PILOT)
{
if ((this.datePickerTidalWindowFrom.Value != this.ShipcallModel.Shipcall?.TidalWindowFrom) || (this.datePickerTidalWindowTo.Value != this.ShipcallModel.Shipcall?.TidalWindowTo)) // something has changed
{
if (datePickerTidalWindowTo.Value.IsTooOld() || this.datePickerTidalWindowFrom.Value.IsTooOld())
{
message = BreCalClient.Resources.Resources.textTideTimesInThePast;
return false;
}
if (this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue && this.datePickerTidalWindowFrom.Value > this.datePickerTidalWindowTo.Value)
{
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
return false;
}
if ((this.datePickerTidalWindowFrom.Value.HasValue && !this.datePickerTidalWindowTo.Value.HasValue) || (!this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textTidalBothValues;
return false;
}
}
}
return true;
}
private void CopyToModel() private void CopyToModel()
{ {
this.Times.Remarks = this.textBoxRemarks.Text.Trim().Truncate(512); this.Times.Remarks = this.textBoxRemarks.Text.Trim().Truncate(512);
@ -193,26 +96,19 @@ namespace BreCalClient
this.Times.ZoneEntry = this.datePickerZoneEntry.Value; this.Times.ZoneEntry = this.datePickerZoneEntry.Value;
this.Times.Ata = this.datePickerATA.Value; this.Times.Ata = this.datePickerATA.Value;
this.Times.Atd = this.datePickerATD.Value; this.Times.Atd = this.datePickerATD.Value;
Extensions.ParticipantType pType = (Extensions.ParticipantType)this.Times.ParticipantType;
if ((pType == Extensions.ParticipantType.PILOT) && this.ShipcallModel.Shipcall != null)
{
this.ShipcallModel.Shipcall.TidalWindowFrom = this.datePickerTidalWindowFrom.Value;
this.ShipcallModel.Shipcall.TidalWindowTo = this.datePickerTidalWindowTo.Value;
}
} }
private void CopyToControls() private void CopyToControls()
{ {
this.textBoxRemarks.Text = this.Times.Remarks; this.textBoxRemarks.Text = this.Times.Remarks;
this.datePickerETABerth.Value = this.Times.EtaBerth; this.datePickerETABerth.Value = this.Times.EtaBerth;
if(this.datePickerETABerth.IsEnabled && (this.Times.EtaBerth == null) && (this.AgencyTimes?.EtaBerth != null) && (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival) && (this.AgencyTimes?.EtaBerth > DateTime.Now)) if(this.datePickerETABerth.IsEnabled && (this.Times.EtaBerth == null) && (this.AgencyTimes?.EtaBerth != null) && (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival))
{ {
this.datePickerETABerth.Value = this.AgencyTimes.EtaBerth; this.datePickerETABerth.Value = this.AgencyTimes.EtaBerth;
if (this.datePickerETABerth.Template.FindName("PART_TextBox", this.datePickerETABerth) is WatermarkTextBox tb) { tb.Focus(); tb.SelectAll(); } if (this.datePickerETABerth.Template.FindName("PART_TextBox", this.datePickerETABerth) is WatermarkTextBox tb) { tb.Focus(); tb.SelectAll(); }
} }
this.datePickerETDBerth.Value = this.Times.EtdBerth; this.datePickerETDBerth.Value = this.Times.EtdBerth;
if(this.datePickerETDBerth.IsEnabled && (this.Times.EtdBerth == null) && (this.AgencyTimes?.EtdBerth != null) && ((ShipcallModel.Shipcall?.Type == ShipcallType.Departure) || (ShipcallModel.Shipcall?.Type == ShipcallType.Shifting)) && (this.AgencyTimes?.EtdBerth > DateTime.Now)) if(this.datePickerETDBerth.IsEnabled && (this.Times.EtdBerth == null) && (this.AgencyTimes?.EtdBerth != null) && ((ShipcallModel.Shipcall?.Type == ShipcallType.Departure) || (ShipcallModel.Shipcall?.Type == ShipcallType.Shifting)))
{ {
this.datePickerETDBerth.Value = this.AgencyTimes.EtdBerth; this.datePickerETDBerth.Value = this.AgencyTimes.EtdBerth;
if (this.datePickerETDBerth.Template.FindName("PART_TextBox", this.datePickerETDBerth) is WatermarkTextBox tb) tb.SelectAll(); if (this.datePickerETDBerth.Template.FindName("PART_TextBox", this.datePickerETDBerth) is WatermarkTextBox tb) tb.SelectAll();
@ -251,19 +147,6 @@ namespace BreCalClient
} }
} }
Extensions.ParticipantType pType = (Extensions.ParticipantType)this.Times.ParticipantType;
if ((pType == Extensions.ParticipantType.PILOT) && this.ShipcallModel.Shipcall != null)
{
this.datePickerTidalWindowFrom.Value = this.ShipcallModel.Shipcall.TidalWindowFrom;
this.datePickerTidalWindowTo.Value = this.ShipcallModel.Shipcall.TidalWindowTo;
}
else
{
this.rowt1.Height = new(0);
this.rowt2.Height = new(0);
this.rowt3.Height = new(0);
}
this.SetLockButton(this.Times.EtaBerthFixed ?? false); this.SetLockButton(this.Times.EtaBerthFixed ?? false);
} }
@ -305,10 +188,9 @@ namespace BreCalClient
// setting en/dis-abled // setting en/dis-abled
if ((this.Times.ParticipantId != App.Participant.Id) || (this.ShipcallModel.Shipcall?.Canceled ?? false)) if (this.Times.ParticipantId != App.Participant.Id)
{ {
this.buttonFixedOrder.IsEnabled = false; this.buttonFixedOrder.IsEnabled = false;
this.buttonOK.IsEnabled = false;
return; // if this is not "my" entry, there is no editing! return; // if this is not "my" entry, there is no editing!
} }
@ -329,12 +211,8 @@ namespace BreCalClient
this.datePickerLockTime.IsEnabled = true; this.datePickerLockTime.IsEnabled = true;
break; break;
case Extensions.ParticipantType.TUG: case Extensions.ParticipantType.TUG:
this.datePickerZoneEntry.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival);
break;
case Extensions.ParticipantType.PILOT: case Extensions.ParticipantType.PILOT:
this.datePickerZoneEntry.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival); this.datePickerZoneEntry.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival);
this.datePickerTidalWindowFrom.IsEnabled = true;
this.datePickerTidalWindowTo.IsEnabled = true;
break; break;
} }
} }

View File

@ -3,7 +3,6 @@
// //
using BreCalClient.misc.Model; using BreCalClient.misc.Model;
using System;
using System.Windows; using System.Windows;
namespace BreCalClient namespace BreCalClient
@ -30,9 +29,6 @@ namespace BreCalClient
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
if ((this.ShipcallModel != null) && (this.ShipcallModel.Shipcall != null))
this.comboBoxBerth.ItemsSource = BreCalLists.GetBerthsByPort(this.ShipcallModel.Shipcall.PortId);
else
this.comboBoxBerth.ItemsSource = BreCalLists.Berths; this.comboBoxBerth.ItemsSource = BreCalLists.Berths;
this.CopyToControls(); this.CopyToControls();
this.EnableControls(); this.EnableControls();
@ -64,18 +60,11 @@ namespace BreCalClient
} }
private void buttonOK_Click(object sender, RoutedEventArgs e) private void buttonOK_Click(object sender, RoutedEventArgs e)
{
if (!CheckValues(out string message))
{
System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{ {
this.CopyToModel(); this.CopyToModel();
this.DialogResult = true; this.DialogResult = true;
this.Close(); this.Close();
} }
}
private void buttonCancel_Click(object sender, RoutedEventArgs e) private void buttonCancel_Click(object sender, RoutedEventArgs e)
{ {
@ -106,56 +95,6 @@ namespace BreCalClient
#region private methods #region private methods
private bool CheckValues(out string message)
{
message = "";
if((this.datePickerOperationStart.Value != this.Times.OperationsStart) || (this.datePickerOperationStart_End.Value != this.Times.EtaIntervalEnd))
{
if(this.datePickerOperationStart.Value.IsTooOld() || this.datePickerOperationStart_End.Value.IsTooOld())
{
message = BreCalClient.Resources.Resources.textOperationStartInThePast;
return false;
}
}
if (this.datePickerOperationStart.Value.HasValue && this.datePickerOperationStart_End.Value.HasValue && this.datePickerOperationStart.Value > this.datePickerOperationStart_End.Value)
{
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
return false;
}
if ((this.datePickerOperationEnd.Value != this.Times.OperationsEnd) || (this.datePickerOperationEnd_End.Value != this.Times.EtdIntervalEnd))
{
if(this.datePickerOperationEnd.Value.IsTooOld() || this.datePickerOperationEnd_End.Value.IsTooOld())
{
message = BreCalClient.Resources.Resources.textOperationEndInThePast;
return false;
}
}
if (this.datePickerOperationEnd.Value.HasValue && this.datePickerOperationEnd_End.Value.HasValue && this.datePickerOperationEnd.Value > this.datePickerOperationEnd_End.Value)
{
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
return false;
}
if(this.datePickerOperationEnd.Value.IsTooFar() || this.datePickerOperationEnd_End.Value.IsTooFar() || this.datePickerOperationStart.Value.IsTooFar() || this.datePickerOperationStart_End.Value.IsTooFar())
{
message = BreCalClient.Resources.Resources.textTooFarInTheFuture;
return false;
}
if((this.datePickerOperationEnd_End.Value.HasValue && !this.datePickerOperationEnd.Value.HasValue) || (this.datePickerOperationStart_End.Value.HasValue && !this.datePickerOperationStart.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textStartTimeMissing;
return false;
}
return true;
}
private void CopyToModel() private void CopyToModel()
{ {
this.Times.PierSide = this.comboBoxPierside.SelectedIndex switch this.Times.PierSide = this.comboBoxPierside.SelectedIndex switch
@ -217,11 +156,7 @@ namespace BreCalClient
private void EnableControls() private void EnableControls()
{ {
if ((this.Times.ParticipantId != App.Participant.Id) || (this.ShipcallModel.Shipcall?.Canceled ?? false)) if (this.Times.ParticipantId != App.Participant.Id) return;
{
this.buttonOK.IsEnabled = false;
return;
}
this.datePickerOperationStart.IsEnabled = ShipcallModel.Shipcall?.Type == ShipcallType.Arrival; this.datePickerOperationStart.IsEnabled = ShipcallModel.Shipcall?.Type == ShipcallType.Arrival;
this.datePickerOperationStart_End.IsEnabled = ShipcallModel.Shipcall?.Type == ShipcallType.Arrival; this.datePickerOperationStart_End.IsEnabled = ShipcallModel.Shipcall?.Type == ShipcallType.Arrival;

View File

@ -3,8 +3,10 @@
// //
using BreCalClient.misc.Model; using BreCalClient.misc.Model;
using Newtonsoft.Json.Linq;
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace BreCalClient namespace BreCalClient
{ {
@ -58,25 +60,6 @@ namespace BreCalClient
#region public helper #region public helper
public static bool IsTooFar(this DateTime datetime)
{
return datetime > DateTime.Now.AddYears(1);
}
public static bool IsTooFar(this DateTime? datetime)
{
if (datetime == null) return false;
return datetime > DateTime.Now.AddYears(1);
}
public static bool IsTooOld(this DateTime? datetime)
{
if (datetime == null) return false;
{
return datetime < DateTime.Now.AddDays(-1);
}
}
public static bool IsTypeFlagSet(this Participant participant, ParticipantType flag) public static bool IsTypeFlagSet(this Participant participant, ParticipantType flag)
{ {
return (participant.Type & (uint)flag) != 0; return (participant.Type & (uint)flag) != 0;

View File

@ -1,5 +1,5 @@
// Copyright (c) 2024- schick Informatik // Copyright (c) 2024- schick Informatik
// Description: // Description: Window to show (complete) list of current shipcall histories
// //
using BreCalClient.misc.Api; using BreCalClient.misc.Api;

View File

@ -73,19 +73,9 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Button Margin="2" Grid.Column="0" Content="{x:Static p:Resources.textNewDots}" x:Name="buttonNew" Visibility="Hidden" Click="buttonNew_Click" Background="Transparent"/> <Button Margin="2" Grid.Column="0" Content="{x:Static p:Resources.textNewDots}" x:Name="buttonNew" Visibility="Hidden" Click="buttonNew_Click" Background="Transparent"/>
<Label Content="{x:Static p:Resources.textSortOrder}" Grid.Column="1" HorizontalContentAlignment="Right"/> <Label Content="{x:Static p:Resources.textSortOrder}" Grid.Column="1" HorizontalContentAlignment="Right"/>
<Grid Grid.Column="2" Grid.Row="1"> <ComboBox x:Name="comboBoxSortOrder" Margin="2" Grid.Column="2" SelectionChanged="comboBoxSortOrder_SelectionChanged" />
<Grid.ColumnDefinitions> <CheckBox x:Name="checkboxShowCancelledCalls" Grid.Column="3" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="2" Checked="checkboxShowCancelledCalls_Checked" Unchecked="checkboxShowCancelledCalls_Checked" />
<ColumnDefinition Width=".5*" /> <Label Content="{x:Static p:Resources.textShowCancelledShipcalls}" Grid.Column="4" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions>
<ComboBox x:Name="comboBoxSortOrder" Margin="2" Grid.Column="0" SelectionChanged="comboBoxSortOrder_SelectionChanged" />
<CheckBox x:Name="checkboxShowCancelledCalls" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="2" Checked="checkboxShowCancelledCalls_Checked" Unchecked="checkboxShowCancelledCalls_Checked" />
<Label Content="{x:Static p:Resources.textShowCancelledShipcalls}" Grid.Column="2" />
</Grid>
<Label Content="{x:Static p:Resources.textHarbour}" Grid.Column="3" HorizontalAlignment="Right" />
<xctk:CheckComboBox x:Name="comboBoxPorts" Margin="2" Grid.Column="4" ItemSelectionChanged="comboBoxPorts_ItemSelectionChanged" DisplayMemberPath="Name" />
<Button Margin="2" Grid.Column="6" Content="{x:Static p:Resources.textClearFilters}" x:Name="buttonClearFilter" Click="buttonClearFilter_Click" Background="Transparent" /> <Button Margin="2" Grid.Column="6" Content="{x:Static p:Resources.textClearFilters}" x:Name="buttonClearFilter" Click="buttonClearFilter_Click" Background="Transparent" />
</Grid> </Grid>
<Grid Grid.Row="2"> <Grid Grid.Row="2">
@ -124,8 +114,6 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="26" /> <ColumnDefinition Width="26" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="26" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="26" /> <ColumnDefinition Width="26" />
@ -159,25 +147,19 @@
</StatusBarItem> </StatusBarItem>
<Separator Grid.Column="9"/> <Separator Grid.Column="9"/>
<StatusBarItem Grid.Column="10"> <StatusBarItem Grid.Column="10">
<Button x:Name="buttonNotifications" Click="buttonNotifications_Click" Width="20" ToolTip="{x:Static p:Resources.textShowNotifications}">
<Image Source="./Resources/bell3.png"/>
</Button>
</StatusBarItem>
<Separator Grid.Column="9"/>
<StatusBarItem Grid.Column="12">
<TextBlock Name="labelStatusBar"></TextBlock> <TextBlock Name="labelStatusBar"></TextBlock>
</StatusBarItem> </StatusBarItem>
<Separator Grid.Column="13"/> <Separator Grid.Column="11"/>
<StatusBarItem Grid.Column="14"> <StatusBarItem Grid.Column="12">
<Button x:Name="buttonManualRefresh" Width="20" Click="buttonManualRefresh_Click" ToolTip="{x:Static p:Resources.textTriggerManualRefresh}"> <Button x:Name="buttonManualRefresh" Width="20" Click="buttonManualRefresh_Click" ToolTip="{x:Static p:Resources.textTriggerManualRefresh}">
<Image Source="./Resources/nav_refresh_green.png"/> <Image Source="./Resources/nav_refresh_green.png"/>
</Button> </Button>
</StatusBarItem> </StatusBarItem>
<StatusBarItem Grid.Column="15"> <StatusBarItem Grid.Column="13">
<TextBlock x:Name="labelLatestUpdate" /> <TextBlock x:Name="labelLatestUpdate" />
</StatusBarItem> </StatusBarItem>
<Separator Grid.Column="16"/> <Separator Grid.Column="14"/>
<StatusBarItem Grid.Column="17"> <StatusBarItem Grid.Column="15">
<ProgressBar Name="generalProgressStatus" Width="90" Height="16" Foreground="LightGray"/> <ProgressBar Name="generalProgressStatus" Width="90" Height="16" Foreground="LightGray"/>
</StatusBarItem> </StatusBarItem>
</StatusBar> </StatusBar>

View File

@ -23,9 +23,6 @@ using Polly;
using System.Net.Http; using System.Net.Http;
using System.Net; using System.Net;
using System.Windows.Input; using System.Windows.Input;
using System.Text.RegularExpressions;
using Newtonsoft.Json.Linq;
using System.Linq;
namespace BreCalClient namespace BreCalClient
@ -36,11 +33,8 @@ namespace BreCalClient
public partial class MainWindow : Window public partial class MainWindow : Window
{ {
private readonly ILog _log = LogManager.GetLogger(typeof(MainWindow)); private readonly ILog _log = LogManager.GetLogger(typeof(MainWindow));
private readonly ToastViewModel _vm;
private const int SHIPCALL_UPDATE_INTERVAL_SECONDS = 30; private const int SHIPCALL_UPDATE_INTERVAL_SECONDS = 30;
private const int SHIPS_UPDATE_INTERVAL_SECONDS = 120; private const int SHIPS_UPDATE_INTERVAL_SECONDS = 120;
private const int CHECK_NOTIFICATIONS_INTERVAL_SECONDS = 5;
private const int PROGRESS_STEPS = 50; private const int PROGRESS_STEPS = 50;
#region Fields #region Fields
@ -52,7 +46,7 @@ namespace BreCalClient
private readonly ConcurrentDictionary<int, ShipcallControlModel> _allShipcallsDict = new(); private readonly ConcurrentDictionary<int, ShipcallControlModel> _allShipcallsDict = new();
private readonly ConcurrentDictionary<int, ShipcallControl> _allShipCallsControlDict = new(); private readonly ConcurrentDictionary<int, ShipcallControl> _allShipCallsControlDict = new();
private readonly List<ShipcallControlModel> _visibleControlModels = []; private readonly List<ShipcallControlModel> _visibleControlModels = new();
private readonly ShipcallApi _shipcallApi; private readonly ShipcallApi _shipcallApi;
private readonly UserApi _userApi; private readonly UserApi _userApi;
@ -71,7 +65,6 @@ namespace BreCalClient
// private bool _filterChanged = false; // private bool _filterChanged = false;
// private bool _sequenceChanged = false; // private bool _sequenceChanged = false;
private HistoryDialog? _historyDialog; private HistoryDialog? _historyDialog;
private NotificationDialog? _notificationDialog;
#endregion #endregion
@ -126,13 +119,6 @@ namespace BreCalClient
RetryConfiguration.AsyncRetryPolicy = retryPolicy; RetryConfiguration.AsyncRetryPolicy = retryPolicy;
this.generalProgressStatus.Maximum = PROGRESS_STEPS; this.generalProgressStatus.Maximum = PROGRESS_STEPS;
_vm = new ToastViewModel();
this.Unloaded += MainWindow_Unloaded;
}
private void MainWindow_Unloaded(object sender, RoutedEventArgs e)
{
_vm.OnUnloaded();
} }
#endregion #endregion
@ -152,7 +138,6 @@ namespace BreCalClient
}; };
this.comboBoxSortOrder.ItemsSource = Enum.GetValues(typeof(Extensions.SortOrder)); this.comboBoxSortOrder.ItemsSource = Enum.GetValues(typeof(Extensions.SortOrder));
this.comboBoxSortOrder.SelectedIndex = (int)_sortOrder; this.comboBoxSortOrder.SelectedIndex = (int)_sortOrder;
AppNotification.LoadFromSettings();
} }
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
@ -195,12 +180,12 @@ namespace BreCalClient
} }
catch (ApiException ex) catch (ApiException ex)
{ {
if ((ex.ErrorContent != null && ((string)ex.ErrorContent).StartsWith('{'))) { if ((ex.ErrorContent != null && ((string)ex.ErrorContent).StartsWith("{"))) {
Error? anError = JsonConvert.DeserializeObject<Error>((string)ex.ErrorContent); Error? anError = JsonConvert.DeserializeObject<Error>((string)ex.ErrorContent);
if ((anError != null) && anError.ErrorField.Equals("invalid credentials")) if ((anError != null) && anError.Message.Equals("invalid credentials"))
this.labelLoginResult.Content = BreCalClient.Resources.Resources.textWrongCredentials; this.labelLoginResult.Content = BreCalClient.Resources.Resources.textWrongCredentials;
else else
this.labelLoginResult.Content = anError?.ErrorField ?? ex.Message; this.labelLoginResult.Content = anError?.Message ?? ex.Message;
} }
else { else {
this.labelLoginResult.Content = ex.Message; this.labelLoginResult.Content = ex.Message;
@ -259,8 +244,7 @@ namespace BreCalClient
EditShipcallControl esc = new() EditShipcallControl esc = new()
{ {
ShipEditingEnabled = App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD), ShipEditingEnabled = App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD),
ShipApi = _shipApi, ShipApi = _shipApi
IsCreate = true
}; };
if (model != null) if (model != null)
esc.ShipcallModel = model; esc.ShipcallModel = model;
@ -301,15 +285,13 @@ namespace BreCalClient
} }
}; };
scmOut.Shipcall.ShipId = esc.ShipcallModel.Shipcall.ShipId; scmOut.Shipcall.ShipId = esc.ShipcallModel.Shipcall.ShipId;
scmOut.Shipcall.PortId = esc.ShipcallModel.Shipcall.PortId;
scmOut.Ship = esc.ShipcallModel.Ship; scmOut.Ship = esc.ShipcallModel.Ship;
scmOut.AllowPortChange = false;
DateTime eta = esc.ShipcallModel.Shipcall?.Eta ?? DateTime.Now; DateTime eta = esc.ShipcallModel.Shipcall?.Eta ?? DateTime.Now;
scmOut.Shipcall.Etd = eta.AddDays(2); scmOut.Shipcall.Etd = eta.AddDays(2);
scmOut.Shipcall.DepartureBerthId = esc.ShipcallModel.Shipcall?.ArrivalBerthId; scmOut.Shipcall.DepartureBerthId = esc.ShipcallModel.Shipcall?.ArrivalBerthId;
if (esc.ShipcallModel.Shipcall != null) if (esc.ShipcallModel.Shipcall != null)
{ {
scmOut.Shipcall.Participants = []; scmOut.Shipcall.Participants = new();
scmOut.Shipcall.Participants.AddRange(esc.ShipcallModel.Shipcall.Participants); scmOut.Shipcall.Participants.AddRange(esc.ShipcallModel.Shipcall.Participants);
foreach(ParticipantType pType in esc.ShipcallModel.AssignedParticipants.Keys) foreach(ParticipantType pType in esc.ShipcallModel.AssignedParticipants.Keys)
scmOut.AssignedParticipants[pType] = esc.ShipcallModel.AssignedParticipants[pType]; scmOut.AssignedParticipants[pType] = esc.ShipcallModel.AssignedParticipants[pType];
@ -337,6 +319,10 @@ namespace BreCalClient
UserDetails ud = new() UserDetails ud = new()
{ {
Id = _loginResult.Id, Id = _loginResult.Id,
FirstName = _loginResult.FirstName,
LastName = _loginResult.LastName,
UserPhone = _loginResult.UserPhone,
UserEmail = _loginResult.UserEmail,
OldPassword = oldPw, OldPassword = oldPw,
NewPassword = newPw NewPassword = newPw
}; };
@ -349,42 +335,7 @@ namespace BreCalClient
{ {
this.Dispatcher.Invoke(new Action(() => this.Dispatcher.Invoke(new Action(() =>
{ {
ShowErrorDialog(ex.Message, "Error saving user information"); MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}));
}
}
};
ad.ChangeUserSettingsRequested += async () =>
{
if (_loginResult != null)
{
UserDetails ud = new()
{
Id = _loginResult.Id,
FirstName = _loginResult.FirstName,
LastName = _loginResult.LastName,
UserPhone = _loginResult.UserPhone,
UserEmail = _loginResult.UserEmail,
NotifyEmail = _loginResult.NotifyEmail,
NotifyPopup = _loginResult.NotifyPopup,
NotifySignal = _loginResult.NotifySignal,
NotifyWhatsapp = _loginResult.NotifyWhatsapp,
};
if (_loginResult.NotifyOn != null)
{
ud.NotifyOn = new(_loginResult.NotifyOn);
}
try
{
await _userApi.UserUpdateAsync(ud);
MessageBox.Show(BreCalClient.Resources.Resources.textInformationUpdated, BreCalClient.Resources.Resources.textConfirmation, MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
this.Dispatcher.Invoke(new Action(() =>
{
ShowErrorDialog(ex.Message, "Error saving user information");
})); }));
} }
} }
@ -396,7 +347,6 @@ namespace BreCalClient
{ {
this.searchFilterControl.ClearFilters(); this.searchFilterControl.ClearFilters();
this.checkboxShowCancelledCalls.IsChecked = false; this.checkboxShowCancelledCalls.IsChecked = false;
this.comboBoxPorts.UnSelectAll();
this.FilterShipcalls(); this.FilterShipcalls();
} }
@ -414,24 +364,6 @@ namespace BreCalClient
this.SearchFilterControl_SearchFilterChanged(); this.SearchFilterControl_SearchFilterChanged();
} }
private void comboBoxPorts_ItemSelectionChanged(object sender, Xceed.Wpf.Toolkit.Primitives.ItemSelectionChangedEventArgs e)
{
this.searchFilterControl.SearchFilter.Ports.Clear();
List<Berth> berths = [];
foreach (Port port in comboBoxPorts.SelectedItems)
{
this.searchFilterControl.SearchFilter.Ports.Add(port.Id);
berths.AddRange(BreCalLists.GetBerthsByPort(port.Id));
}
// create list of berths from selected port(s) or return all berths
if (berths.Count == 0)
berths = BreCalLists.AllBerths;
this.searchFilterControl.SetBerths(berths);
this.SearchFilterControl_SearchFilterChanged();
}
private async void comboBoxSortOrder_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) private async void comboBoxSortOrder_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{ {
_sortOrder = (Extensions.SortOrder) this.comboBoxSortOrder.SelectedIndex; _sortOrder = (Extensions.SortOrder) this.comboBoxSortOrder.SelectedIndex;
@ -449,8 +381,8 @@ namespace BreCalClient
_historyDialog.Closed += (sender, e) => { this._historyDialog = null; }; _historyDialog.Closed += (sender, e) => { this._historyDialog = null; };
_historyDialog.HistoryItemSelected += (x) => _historyDialog.HistoryItemSelected += (x) =>
{ {
if(_allShipCallsControlDict.TryGetValue(x, out ShipcallControl? value)) if(_allShipCallsControlDict.ContainsKey(x))
value.BringIntoView(); _allShipCallsControlDict[x].BringIntoView();
}; };
_historyDialog.Show(); _historyDialog.Show();
} }
@ -460,21 +392,6 @@ namespace BreCalClient
} }
} }
private void buttonNotifications_Click(object sender, RoutedEventArgs e)
{
if (_notificationDialog == null)
{
_notificationDialog = new NotificationDialog();
_notificationDialog.AppNotifications = AppNotification.AppNotifications;
_notificationDialog.Closed += (sender, e) => { this._notificationDialog = null; };
_notificationDialog.Show();
}
else
{
_notificationDialog.Activate();
}
}
private void buttonManualRefresh_Click(object sender, RoutedEventArgs e) private void buttonManualRefresh_Click(object sender, RoutedEventArgs e)
{ {
_refreshImmediately = true; // set flag to avoid timer loop termination _refreshImmediately = true; // set flag to avoid timer loop termination
@ -490,7 +407,6 @@ namespace BreCalClient
{ {
if (_loginResult == null) return; if (_loginResult == null) return;
BreCalLists.InitializePorts(await _staticApi.GetPortsAsync());
BreCalLists.InitializeBerths(await _staticApi.BerthsGetAsync()); BreCalLists.InitializeBerths(await _staticApi.BerthsGetAsync());
BreCalLists.InitializeShips(await _shipApi.ShipsGetAsync()); BreCalLists.InitializeShips(await _shipApi.ShipsGetAsync());
BreCalLists.InitializeParticipants(await _staticApi.ParticipantsGetAsync()); BreCalLists.InitializeParticipants(await _staticApi.ParticipantsGetAsync());
@ -514,14 +430,14 @@ namespace BreCalClient
SearchFilterModel? currentFilter = null; SearchFilterModel? currentFilter = null;
if (SearchFilterModel.filterMap != null) if (SearchFilterModel.filterMap != null)
{ {
if((_loginResult != null) && SearchFilterModel.filterMap.TryGetValue(_loginResult.Id, out SearchFilterModel? value)) if((_loginResult != null) && SearchFilterModel.filterMap.ContainsKey(_loginResult.Id))
{ {
currentFilter = value; currentFilter = SearchFilterModel.filterMap[_loginResult.Id];
} }
} }
else else
{ {
SearchFilterModel.filterMap = []; SearchFilterModel.filterMap = new();
} }
if (currentFilter == null) if (currentFilter == null)
{ {
@ -530,19 +446,10 @@ namespace BreCalClient
SearchFilterModel.filterMap[_loginResult.Id] = currentFilter; SearchFilterModel.filterMap[_loginResult.Id] = currentFilter;
} }
this.searchFilterControl.SetFilterFromModel(currentFilter); this.searchFilterControl.SetFilterFromModel(currentFilter);
if (currentFilter.Ports != null)
{
foreach (Port p in this.comboBoxPorts.ItemsSource)
{
if (currentFilter.Ports.Contains(p.Id)) this.comboBoxPorts.SelectedItems.Add(p);
}
}
} }
_ = Task.Run(() => RefreshShipcalls()); _ = Task.Run(() => RefreshShipcalls());
_ = Task.Run(() => RefreshShips()); _ = Task.Run(() => RefreshShips());
_ = Task.Run(() => CheckNotifications());
} }
@ -607,7 +514,7 @@ namespace BreCalClient
// load times for each shipcall // load times for each shipcall
List<Times> currentTimes = await _timesApi.TimesGetAsync(shipcall.Id); List<Times> currentTimes = await _timesApi.TimesGetAsync(shipcall.Id);
if (!_allShipcallsDict.TryGetValue(shipcall.Id, out ShipcallControlModel? value)) if (!_allShipcallsDict.ContainsKey(shipcall.Id))
{ {
// add entry // add entry
ShipcallControlModel scm = new() ShipcallControlModel scm = new()
@ -619,9 +526,10 @@ namespace BreCalClient
} }
else else
{ {
value.Shipcall = shipcall; // update entry
value.Times = currentTimes; _allShipcallsDict[shipcall.Id].Shipcall = shipcall;
UpdateShipcall(value); _allShipcallsDict[shipcall.Id].Times = currentTimes;
UpdateShipcall(_allShipcallsDict[shipcall.Id]);
} }
} }
@ -665,19 +573,6 @@ namespace BreCalClient
} }
} }
public async Task CheckNotifications()
{
while (true)
{
Thread.Sleep(CHECK_NOTIFICATIONS_INTERVAL_SECONDS * 1000);
if (_loginResult?.NotifyPopup ?? false)
{
List<Notification> notifications = await _staticApi.NotificationsGetAsync();
AppNotification.UpdateNotifications(notifications, _allShipcallsDict, _vm, _loginResult);
}
}
}
#endregion #endregion
#region basic operations #region basic operations
@ -688,8 +583,8 @@ namespace BreCalClient
_allShipcallsDict[scm.Shipcall.Id] = scm; _allShipcallsDict[scm.Shipcall.Id] = scm;
Shipcall shipcall = scm.Shipcall; Shipcall shipcall = scm.Shipcall;
if (BreCalLists.ShipLookupDict.TryGetValue(shipcall.ShipId, out ShipModel? value)) if (BreCalLists.ShipLookupDict.ContainsKey(shipcall.ShipId))
scm.Ship = value.Ship; scm.Ship = BreCalLists.ShipLookupDict[shipcall.ShipId].Ship;
if (shipcall.Type == ShipcallType.Arrival) if (shipcall.Type == ShipcallType.Arrival)
{ {
@ -707,7 +602,7 @@ namespace BreCalClient
{ {
ShipcallControl sc = new() ShipcallControl sc = new()
{ {
Height = 145, Height = 135,
ShipcallControlModel = scm ShipcallControlModel = scm
}; };
sc.EditTimesRequested += Sc_EditTimesRequested; sc.EditTimesRequested += Sc_EditTimesRequested;
@ -722,8 +617,8 @@ namespace BreCalClient
{ {
if(scm.Shipcall == null) return; if(scm.Shipcall == null) return;
Shipcall shipcall = scm.Shipcall; Shipcall shipcall = scm.Shipcall;
if (BreCalLists.ShipLookupDict.TryGetValue(shipcall.ShipId, out ShipModel? value)) if (BreCalLists.ShipLookupDict.ContainsKey(shipcall.ShipId))
scm.Ship = value.Ship; scm.Ship = BreCalLists.ShipLookupDict[shipcall.ShipId].Ship;
if (shipcall.Type == ShipcallType.Arrival) if (shipcall.Type == ShipcallType.Arrival)
{ {
@ -803,11 +698,6 @@ namespace BreCalClient
_ = this._visibleControlModels.RemoveAll(x => { if (x.Shipcall == null) return false; else return !sfm.Categories.Contains(x.Shipcall.Type); }); _ = this._visibleControlModels.RemoveAll(x => { if (x.Shipcall == null) return false; else return !sfm.Categories.Contains(x.Shipcall.Type); });
} }
if(sfm.Ports.Count > 0 )
{
_ = this._visibleControlModels.RemoveAll(x => { if(x.Shipcall == null) return false; else return !sfm.Ports.Contains(x.Shipcall.PortId); });
}
if(!string.IsNullOrEmpty(sfm.SearchString)) if(!string.IsNullOrEmpty(sfm.SearchString))
{ {
_ = this._visibleControlModels.RemoveAll(x => !(x.ContainsRemarkText(sfm.SearchString) || (x.Ship?.Name.Contains(sfm.SearchString, StringComparison.InvariantCultureIgnoreCase) ?? false))); _ = this._visibleControlModels.RemoveAll(x => !(x.ContainsRemarkText(sfm.SearchString) || (x.Ship?.Name.Contains(sfm.SearchString, StringComparison.InvariantCultureIgnoreCase) ?? false)));
@ -968,10 +858,10 @@ namespace BreCalClient
foreach (ShipcallControlModel visibleModel in this._visibleControlModels) foreach (ShipcallControlModel visibleModel in this._visibleControlModels)
{ {
if (visibleModel.Shipcall == null) continue; // should not happen if (visibleModel.Shipcall == null) continue; // should not happen
if (this._allShipCallsControlDict.TryGetValue(visibleModel.Shipcall.Id, out ShipcallControl? value)) if (this._allShipCallsControlDict.ContainsKey(visibleModel.Shipcall.Id))
{ {
value.RefreshData(); this._allShipCallsControlDict[visibleModel.Shipcall.Id].RefreshData();
this.stackPanel.Children.Add(value); this.stackPanel.Children.Add(this._allShipCallsControlDict[visibleModel.Shipcall.Id]);
} }
} }
} }
@ -1007,10 +897,7 @@ namespace BreCalClient
try try
{ {
obj.ShipcallControlModel.Shipcall?.Participants.Clear(); obj.ShipcallControlModel.Shipcall?.Participants.Clear();
if(! await obj.ShipcallControlModel.UpdateTimesAssignments(this._timesApi)) obj.ShipcallControlModel.UpdateTimesAssignments(this._timesApi);
{
ShowErrorDialog(obj.ShipcallControlModel.LastErrorMessage, "Update assignments error");
}
foreach(ParticipantAssignment pa in obj.ShipcallControlModel.AssignedParticipants.Values) foreach(ParticipantAssignment pa in obj.ShipcallControlModel.AssignedParticipants.Values)
obj.ShipcallControlModel.Shipcall?.Participants.Add(pa); obj.ShipcallControlModel.Shipcall?.Participants.Add(pa);
await _shipcallApi.ShipcallUpdateAsync(obj.ShipcallControlModel.Shipcall); await _shipcallApi.ShipcallUpdateAsync(obj.ShipcallControlModel.Shipcall);
@ -1077,13 +964,6 @@ namespace BreCalClient
etc.Times.Id = apiResultId.VarId; etc.Times.Id = apiResultId.VarId;
obj.ShipcallControlModel?.Times.Add(etc.Times); obj.ShipcallControlModel?.Times.Add(etc.Times);
} }
// a pilot may have changed the tidal window so we update the shipcall too in this case
if(((Extensions.ParticipantType)etc.Times.ParticipantType) == ParticipantType.PILOT)
{
await _shipcallApi.ShipcallUpdateAsync(obj.ShipcallControlModel?.Shipcall);
}
_refreshImmediately = true; _refreshImmediately = true;
_tokenSource.Cancel(); _tokenSource.Cancel();
} }
@ -1121,26 +1001,20 @@ namespace BreCalClient
} }
else else
{ {
if(editControl.ShipcallModel.AssignedParticipants.TryGetValue(ParticipantType.AGENCY, out ParticipantAssignment? value)) if(editControl.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
editControl.Times.ParticipantId = value.ParticipantId; editControl.Times.ParticipantId = editControl.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId;
} }
editControl.Times.ParticipantType = (int)ParticipantType.AGENCY; editControl.Times.ParticipantType = (int)ParticipantType.AGENCY;
if(editControl.ShowDialog() ?? false) if(editControl.ShowDialog() ?? false)
{ {
try try
{ {
if (sc.ShipcallControlModel != null) sc.ShipcallControlModel?.UpdateTimesAssignments(_timesApi); // if the agent changed the assignment of the participant to another
{
if (!await sc.ShipcallControlModel.UpdateTimesAssignments(_timesApi)) // if the agent changed the assignment of the participant to another
{
ShowErrorDialog(sc.ShipcallControlModel.LastErrorMessage, "Error updating times assignment");
}
}
// always try to be the agent, even if we are BSMD // always try to be the agent, even if we are BSMD
if (editControl.ShipcallModel.AssignedParticipants.TryGetValue(ParticipantType.AGENCY, out ParticipantAssignment? value)) if (editControl.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
{ {
editControl.Times.ParticipantId = value.ParticipantId; editControl.Times.ParticipantId = editControl.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId;
} }
else else
{ {
@ -1184,48 +1058,19 @@ namespace BreCalClient
private void ShowErrorDialog(string message, string caption) private void ShowErrorDialog(string message, string caption)
{ {
// Example:
// Error calling ShipcallUpdate: {\"message\": \"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users
// (if the special-flag is enabled). Assigned Agency: ShipcallParticipantMap(id=628, shipcall_id=115, participant_id=10,
// type=8, created=datetime.datetime(2024, 8, 28, 15, 13, 14), modified=None) with Flags: 42\"}
Match m = ErrorRegex().Match(message);
if ((m != null) && m.Success)
{
try
{
dynamic? msg = JsonConvert.DeserializeObject(m.Value);
if (msg != null)
{
if (msg.error_field != null)
{
caption = $"{caption}: {msg.error_field}";
}
if(msg.error_description != null)
{
message = msg.error_description;
}
}
}
catch (Exception) { }
}
_log.ErrorFormat("{0} - {1}", caption, message); _log.ErrorFormat("{0} - {1}", caption, message);
/*
Dispatcher.Invoke(new Action(() => Dispatcher.Invoke(new Action(() =>
{ {
MessageBox.Show(message, caption, MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(message, caption, MessageBoxButton.OK, MessageBoxImage.Error);
})); }));
*/
} }
private void EnableControlsForParticipant() private void EnableControlsForParticipant()
{ {
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD)) if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD))
this.buttonNew.Visibility = Visibility.Visible; this.buttonNew.Visibility = Visibility.Visible;
this.comboBoxPorts.ItemsSource = BreCalLists.AllPorts.Where(x => App.Participant.Ports.Contains(x.Id));
} }
private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
@ -1234,9 +1079,6 @@ namespace BreCalClient
e.Handled = true; e.Handled = true;
} }
[GeneratedRegex("\\{(.*)\\}")]
private static partial Regex ErrorRegex();
#endregion #endregion
} }

View File

@ -1,68 +0,0 @@
<Window x:Class="BreCalClient.NotificationDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BreCalClient"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:p = "clr-namespace:BreCalClient.Resources"
mc:Ignorable="d" Left="{local:SettingBinding W5Left}" Top="{local:SettingBinding W5Top}"
Title="{x:Static p:Resources.textNotifications}" Height="450" Width="800" Loaded="Window_Loaded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<local:ENIDataGrid x:Name="dataGridNotifications" Grid.Row="0" SelectionMode="Single" IsReadOnly="True" AutoGenerateColumns="False"
CanUserAddRows="False" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<local:ENIDataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding NotificationType}" Value="Assignment">
<Setter Property="Foreground" Value="Blue"/>
</DataTrigger>
<DataTrigger Binding="{Binding NotificationType}" Value="Next24h">
<Setter Property="Foreground" Value="DarkOrange"/>
</DataTrigger>
<DataTrigger Binding="{Binding NotificationType}" Value="TimeConflict">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding NotificationType}" Value="TimeConflictResolved">
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding NotificationType}" Value="Unassigned">
<Setter Property="Foreground" Value="DarkGray"/>
</DataTrigger>
<DataTrigger Binding="{Binding NotificationType}" Value="MissingData">
<Setter Property="Foreground" Value="Yellow" />
<Setter Property="Background" Value="DarkGray" />
</DataTrigger>
<DataTrigger Binding="{Binding NotificationType}" Value="Cancelled">
<Setter Property="Background" Value="LightGray" />
</DataTrigger>
</Style.Triggers>
</Style>
</local:ENIDataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Path=Id}" IsReadOnly="True"/>
<DataGridTextColumn Header="{x:Static p:Resources.textType}" Binding="{Binding Path=NotificationDisplay}" IsReadOnly="True"/>
<DataGridTextColumn Header="{x:Static p:Resources.textDate}" Binding="{Binding Path=NotificationDate}" IsReadOnly="True"/>
<DataGridTextColumn Header="{x:Static p:Resources.textShip}" Binding="{Binding Path=Ship}" IsReadOnly="True"/>
<DataGridTextColumn Header="{x:Static p:Resources.textShipcall}" Binding="{Binding Path=ShipcallType}" IsReadOnly="True"/>
<DataGridTextColumn Header="ETA/ETD" Binding="{Binding Path=ETA}" IsReadOnly="True"/>
<DataGridTextColumn Header="{x:Static p:Resources.textBerth}" Binding="{Binding Path=Berth}" IsReadOnly="True"/>
</DataGrid.Columns>
</local:ENIDataGrid>
<Grid Grid.Row="1" Grid.Column="0" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="22" />
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width=".2*" />
</Grid.ColumnDefinitions>
<Button x:Name="buttonClose" Click="buttonClose_Click" Content="{x:Static p:Resources.textClose}" Width="80" Margin="2" Grid.Row="0" Grid.Column="3" HorizontalAlignment="Right" />
</Grid>
</Grid>
</Window>

View File

@ -1,44 +0,0 @@
// Copyright (c) 2024- schick Informatik
// Description:
//
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace BreCalClient
{
/// <summary>
/// Interaction logic for NotificationDialog.xaml
/// </summary>
public partial class NotificationDialog : Window
{
public NotificationDialog()
{
InitializeComponent();
}
internal ObservableCollection<AppNotification>? AppNotifications { get; set; }
private void buttonClose_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.dataGridNotifications.ItemsSource = AppNotifications;
}
}
}

View File

@ -4,8 +4,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
--> -->
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<ApplicationRevision>0</ApplicationRevision> <ApplicationRevision>1</ApplicationRevision>
<ApplicationVersion>1.8.0.0</ApplicationVersion> <ApplicationVersion>1.4.0.0</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled> <BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Debug</Configuration> <Configuration>Debug</Configuration>
<CreateDesktopShortcut>True</CreateDesktopShortcut> <CreateDesktopShortcut>True</CreateDesktopShortcut>
@ -21,7 +21,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<OpenBrowserOnPublish>False</OpenBrowserOnPublish> <OpenBrowserOnPublish>False</OpenBrowserOnPublish>
<Platform>Any CPU</Platform> <Platform>Any CPU</Platform>
<ProductName>Bremen calling development client</ProductName> <ProductName>Bremen calling development client</ProductName>
<PublishDir>bin\Debug\net8.0-windows7.0\win-x64\app.publish\</PublishDir> <PublishDir>bin\Debug\net6.0-windows\win-x64\app.publish\</PublishDir>
<PublishUrl>bin\publish.devel\</PublishUrl> <PublishUrl>bin\publish.devel\</PublishUrl>
<PublisherName>Informatikbüro Daniel Schick</PublisherName> <PublisherName>Informatikbüro Daniel Schick</PublisherName>
<PublishProtocol>ClickOnce</PublishProtocol> <PublishProtocol>ClickOnce</PublishProtocol>
@ -33,13 +33,11 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<SignManifests>False</SignManifests> <SignManifests>False</SignManifests>
<SuiteName>Bremen calling</SuiteName> <SuiteName>Bremen calling</SuiteName>
<SupportUrl>https://www.textbausteine.net/</SupportUrl> <SupportUrl>https://www.textbausteine.net/</SupportUrl>
<TargetFramework>net8.0-windows7.0</TargetFramework> <TargetFramework>net6.0-windows</TargetFramework>
<UpdateEnabled>True</UpdateEnabled> <UpdateEnabled>True</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode> <UpdateMode>Foreground</UpdateMode>
<UpdateRequired>True</UpdateRequired> <UpdateRequired>False</UpdateRequired>
<WebPageFileName>Publish.html</WebPageFileName> <WebPageFileName>Publish.html</WebPageFileName>
<MinimumRequiredVersion>1.8.0.0</MinimumRequiredVersion>
<SkipPublishVerification>false</SkipPublishVerification>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PublishFile Include="containership.ico"> <PublishFile Include="containership.ico">

View File

@ -4,8 +4,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
--> -->
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<ApplicationRevision>5</ApplicationRevision> <ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.7.0.7</ApplicationVersion> <ApplicationVersion>1.4.0.0</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled> <BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Debug</Configuration> <Configuration>Debug</Configuration>
<CreateDesktopShortcut>True</CreateDesktopShortcut> <CreateDesktopShortcut>True</CreateDesktopShortcut>
@ -27,10 +27,10 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<SelfContained>True</SelfContained> <SelfContained>True</SelfContained>
<SignatureAlgorithm>(none)</SignatureAlgorithm> <SignatureAlgorithm>(none)</SignatureAlgorithm>
<SignManifests>False</SignManifests> <SignManifests>False</SignManifests>
<TargetFramework>net8.0-windows7.0</TargetFramework> <TargetFramework>net6.0-windows</TargetFramework>
<UpdateEnabled>True</UpdateEnabled> <UpdateEnabled>True</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode> <UpdateMode>Foreground</UpdateMode>
<UpdateRequired>True</UpdateRequired> <UpdateRequired>False</UpdateRequired>
<WebPageFileName>Publish.html</WebPageFileName> <WebPageFileName>Publish.html</WebPageFileName>
<CreateDesktopShortcut>True</CreateDesktopShortcut> <CreateDesktopShortcut>True</CreateDesktopShortcut>
<ErrorReportUrl>https://www.bsmd-emswe.eu/</ErrorReportUrl> <ErrorReportUrl>https://www.bsmd-emswe.eu/</ErrorReportUrl>
@ -38,10 +38,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<PublisherName>Informatikbüro Daniel Schick</PublisherName> <PublisherName>Informatikbüro Daniel Schick</PublisherName>
<SuiteName>Bremen Calling</SuiteName> <SuiteName>Bremen Calling</SuiteName>
<SupportUrl>http://www.textbausteine.net/</SupportUrl> <SupportUrl>http://www.textbausteine.net/</SupportUrl>
<PublishDir>bin\Debug\net8.0-windows7.0\win-x64\app.publish\</PublishDir> <PublishDir>bin\Debug\net6.0-windows\win-x64\app.publish\</PublishDir>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SkipPublishVerification>false</SkipPublishVerification>
<MinimumRequiredVersion>1.7.0.7</MinimumRequiredVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PublishFile Include="containership.ico"> <PublishFile Include="containership.ico">

View File

@ -12,7 +12,7 @@ namespace BreCalClient.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.12.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.10.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@ -25,7 +25,7 @@ namespace BreCalClient.Properties {
[global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("#751D1F")] [global::System.Configuration.DefaultSettingValueAttribute("#1D751F")]
public string BG_COLOR { public string BG_COLOR {
get { get {
return ((string)(this["BG_COLOR"])); return ((string)(this["BG_COLOR"]));
@ -34,7 +34,7 @@ namespace BreCalClient.Properties {
[global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("!!Bremen calling Entwicklungsversion!!")] [global::System.Configuration.DefaultSettingValueAttribute("!!Bremen calling Testversion!!")]
public string APP_TITLE { public string APP_TITLE {
get { get {
return ((string)(this["APP_TITLE"])); return ((string)(this["APP_TITLE"]));
@ -64,7 +64,7 @@ namespace BreCalClient.Properties {
[global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("https://brecaltest.bsmd-emswe.eu")] [global::System.Configuration.DefaultSettingValueAttribute("https://brecaldevel.bsmd-emswe.eu")]
public string API_URL { public string API_URL {
get { get {
return ((string)(this["API_URL"])); return ((string)(this["API_URL"]));
@ -226,40 +226,5 @@ namespace BreCalClient.Properties {
this["FilterCriteriaMap"] = value; this["FilterCriteriaMap"] = value;
} }
} }
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::System.Collections.Specialized.StringCollection Notifications {
get {
return ((global::System.Collections.Specialized.StringCollection)(this["Notifications"]));
}
set {
this["Notifications"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("0")]
public double W5Top {
get {
return ((double)(this["W5Top"]));
}
set {
this["W5Top"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("0")]
public double W5Left {
get {
return ((double)(this["W5Left"]));
}
set {
this["W5Left"] = value;
}
}
} }
} }

View File

@ -3,10 +3,10 @@
<Profiles /> <Profiles />
<Settings> <Settings>
<Setting Name="BG_COLOR" Type="System.String" Scope="Application"> <Setting Name="BG_COLOR" Type="System.String" Scope="Application">
<Value Profile="(Default)">#751D1F</Value> <Value Profile="(Default)">#1D751F</Value>
</Setting> </Setting>
<Setting Name="APP_TITLE" Type="System.String" Scope="Application"> <Setting Name="APP_TITLE" Type="System.String" Scope="Application">
<Value Profile="(Default)">!!Bremen calling Entwicklungsversion!!</Value> <Value Profile="(Default)">!!Bremen calling Testversion!!</Value>
</Setting> </Setting>
<Setting Name="LOGO_IMAGE_URL" Type="System.String" Scope="Application"> <Setting Name="LOGO_IMAGE_URL" Type="System.String" Scope="Application">
<Value Profile="(Default)">https://www.textbausteine.net/</Value> <Value Profile="(Default)">https://www.textbausteine.net/</Value>
@ -15,7 +15,7 @@
<Value Profile="(Default)" /> <Value Profile="(Default)" />
</Setting> </Setting>
<Setting Name="API_URL" Type="System.String" Scope="Application"> <Setting Name="API_URL" Type="System.String" Scope="Application">
<Value Profile="(Default)">https://brecaltest.bsmd-emswe.eu</Value> <Value Profile="(Default)">https://brecaldevel.bsmd-emswe.eu</Value>
</Setting> </Setting>
<Setting Name="Width" Type="System.Double" Scope="User"> <Setting Name="Width" Type="System.Double" Scope="User">
<Value Profile="(Default)">800</Value> <Value Profile="(Default)">800</Value>
@ -56,14 +56,5 @@
<Setting Name="FilterCriteriaMap" Type="System.String" Scope="User"> <Setting Name="FilterCriteriaMap" Type="System.String" Scope="User">
<Value Profile="(Default)" /> <Value Profile="(Default)" />
</Setting> </Setting>
<Setting Name="Notifications" Type="System.Collections.Specialized.StringCollection" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="W5Top" Type="System.Double" Scope="User">
<Value Profile="(Default)">0</Value>
</Setting>
<Setting Name="W5Left" Type="System.Double" Scope="User">
<Value Profile="(Default)">0</Value>
</Setting>
</Settings> </Settings>
</SettingsFile> </SettingsFile>

View File

@ -119,15 +119,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to .
/// </summary>
public static string arrow_right_blue {
get {
return ResourceManager.GetString("arrow_right_blue", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Byte[]. /// Looks up a localized resource of type System.Byte[].
/// </summary> /// </summary>
@ -148,15 +139,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to .
/// </summary>
public static string arrow_up_green {
get {
return ResourceManager.GetString("arrow_up_green", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Byte[]. /// Looks up a localized resource of type System.Byte[].
/// </summary> /// </summary>
@ -167,16 +149,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
public static byte[] bell3 {
get {
object obj = ResourceManager.GetObject("bell3", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Byte[]. /// Looks up a localized resource of type System.Byte[].
/// </summary> /// </summary>
@ -390,15 +362,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Participant assigned to shipcall.
/// </summary>
public static string textAssignment {
get {
return ResourceManager.GetString("textAssignment", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Berth. /// Looks up a localized string similar to Berth.
/// </summary> /// </summary>
@ -426,15 +389,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Both tide times are required.
/// </summary>
public static string textBothTideTimesNecessary {
get {
return ResourceManager.GetString("textBothTideTimesNecessary", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to BSMD right to edit granted. /// Looks up a localized string similar to BSMD right to edit granted.
/// </summary> /// </summary>
@ -579,15 +533,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Date.
/// </summary>
public static string textDate {
get {
return ResourceManager.GetString("textDate", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Delete. /// Looks up a localized string similar to Delete.
/// </summary> /// </summary>
@ -624,15 +569,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Draft.
/// </summary>
public static string textDraftNoUnit {
get {
return ResourceManager.GetString("textDraftNoUnit", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Edit. /// Looks up a localized string similar to Edit.
/// </summary> /// </summary>
@ -687,15 +623,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Some interval end times are before start times.
/// </summary>
public static string textEndValueBeforeStartValue {
get {
return ResourceManager.GetString("textEndValueBeforeStartValue", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Ship / remark. /// Looks up a localized string similar to Ship / remark.
/// </summary> /// </summary>
@ -705,15 +632,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Error.
/// </summary>
public static string textError {
get {
return ResourceManager.GetString("textError", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to ETA berth. /// Looks up a localized string similar to ETA berth.
/// </summary> /// </summary>
@ -723,15 +641,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to ETA value is in the past.
/// </summary>
public static string textETAInThePast {
get {
return ResourceManager.GetString("textETAInThePast", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to ETD berth. /// Looks up a localized string similar to ETD berth.
/// </summary> /// </summary>
@ -741,15 +650,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to ETD value is in the past.
/// </summary>
public static string textETDInThePast {
get {
return ResourceManager.GetString("textETDInThePast", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Exit. /// Looks up a localized string similar to Exit.
/// </summary> /// </summary>
@ -786,15 +686,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Harbour.
/// </summary>
public static string textHarbour {
get {
return ResourceManager.GetString("textHarbour", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Incoming. /// Looks up a localized string similar to Incoming.
/// </summary> /// </summary>
@ -813,15 +704,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Information successfully updated.
/// </summary>
public static string textInformationUpdated {
get {
return ResourceManager.GetString("textInformationUpdated", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Interval. /// Looks up a localized string similar to Interval.
/// </summary> /// </summary>
@ -858,15 +740,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Lock time is in the past.
/// </summary>
public static string textLockTimeInThePast {
get {
return ResourceManager.GetString("textLockTimeInThePast", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Login. /// Looks up a localized string similar to Login.
/// </summary> /// </summary>
@ -885,15 +758,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to The participant has not provided any info.
/// </summary>
public static string textMissingData {
get {
return ResourceManager.GetString("textMissingData", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Moored in lock. /// Looks up a localized string similar to Moored in lock.
/// </summary> /// </summary>
@ -930,51 +794,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Relevant next 24hrs.
/// </summary>
public static string textNext24h {
get {
return ResourceManager.GetString("textNext24h", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Notifications.
/// </summary>
public static string textNotifications {
get {
return ResourceManager.GetString("textNotifications", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Notify by e-mail.
/// </summary>
public static string textNotifyEmail {
get {
return ResourceManager.GetString("textNotifyEmail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Notify on.
/// </summary>
public static string textNotifyOn {
get {
return ResourceManager.GetString("textNotifyOn", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Notify by push notification in app.
/// </summary>
public static string textNotifyPush {
get {
return ResourceManager.GetString("textNotifyPush", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Not rotated. /// Looks up a localized string similar to Not rotated.
/// </summary> /// </summary>
@ -1011,15 +830,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Operation end is in the past.
/// </summary>
public static string textOperationEndInThePast {
get {
return ResourceManager.GetString("textOperationEndInThePast", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Operations end. /// Looks up a localized string similar to Operations end.
/// </summary> /// </summary>
@ -1038,15 +848,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Operation start is in the past.
/// </summary>
public static string textOperationStartInThePast {
get {
return ResourceManager.GetString("textOperationStartInThePast", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Outgoing. /// Looks up a localized string similar to Outgoing.
/// </summary> /// </summary>
@ -1155,15 +956,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Position.
/// </summary>
public static string textPosition {
get {
return ResourceManager.GetString("textPosition", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Rain sensitive cargo. /// Looks up a localized string similar to Rain sensitive cargo.
/// </summary> /// </summary>
@ -1281,24 +1073,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Shipcall.
/// </summary>
public static string textShipcall {
get {
return ResourceManager.GetString("textShipcall", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The shipcall was cancelled.
/// </summary>
public static string textShipcallCancelled {
get {
return ResourceManager.GetString("textShipcallCancelled", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Ship length. /// Looks up a localized string similar to Ship length.
/// </summary> /// </summary>
@ -1335,15 +1109,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Show notificiations.
/// </summary>
public static string textShowNotifications {
get {
return ResourceManager.GetString("textShowNotifications", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Sort order. /// Looks up a localized string similar to Sort order.
/// </summary> /// </summary>
@ -1362,15 +1127,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to If an end time is set, a start time is also required.
/// </summary>
public static string textStartTimeMissing {
get {
return ResourceManager.GetString("textStartTimeMissing", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Terminal. /// Looks up a localized string similar to Terminal.
/// </summary> /// </summary>
@ -1380,15 +1136,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to For a tidal window both values must be set.
/// </summary>
public static string textTidalBothValues {
get {
return ResourceManager.GetString("textTidalBothValues", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Tidal window. /// Looks up a localized string similar to Tidal window.
/// </summary> /// </summary>
@ -1398,33 +1145,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Times for the tide window are in the past.
/// </summary>
public static string textTideTimesInThePast {
get {
return ResourceManager.GetString("textTideTimesInThePast", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Participants disagree on times.
/// </summary>
public static string textTimeConflict {
get {
return ResourceManager.GetString("textTimeConflict", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Times conflict resolved.
/// </summary>
public static string textTimeConflictResolved {
get {
return ResourceManager.GetString("textTimeConflictResolved", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Timestamp. /// Looks up a localized string similar to Timestamp.
/// </summary> /// </summary>
@ -1443,15 +1163,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to A time value is too far in the future.
/// </summary>
public static string textTooFarInTheFuture {
get {
return ResourceManager.GetString("textTooFarInTheFuture", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Set as a fixed order. /// Looks up a localized string similar to Set as a fixed order.
/// </summary> /// </summary>
@ -1515,15 +1226,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Participant unassigned from shipcall.
/// </summary>
public static string textUnassigned {
get {
return ResourceManager.GetString("textUnassigned", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to User login. /// Looks up a localized string similar to User login.
/// </summary> /// </summary>
@ -1560,15 +1262,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Warning.
/// </summary>
public static string textWarning {
get {
return ResourceManager.GetString("textWarning", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Width. /// Looks up a localized string similar to Width.
/// </summary> /// </summary>
@ -1587,15 +1280,6 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Zone entry time is in the past.
/// </summary>
public static string textZoneEntryInThePast {
get {
return ResourceManager.GetString("textZoneEntryInThePast", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Zone entry. /// Looks up a localized string similar to Zone entry.
/// </summary> /// </summary>

View File

@ -117,13 +117,16 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Arrival" xml:space="preserve">
<value>Einkommend</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="arrow_down_red" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="arrow_down_red" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>arrow_down_red.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>arrow_down_red.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data> </data>
<data name="arrow_right_blue" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>arrow_right_blue.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="arrow_up_green" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>arrow_up_green.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="clipboard" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="clipboard" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>clipboard.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>clipboard.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data> </data>
@ -133,24 +136,15 @@
<data name="containership" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="containership" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>containership.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>containership.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data> </data>
<data name="Departure" xml:space="preserve">
<value>Ausgehend</value>
</data>
<data name="emergency_stop_button" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="emergency_stop_button" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>emergency_stop_button.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>emergency_stop_button.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data> </data>
<data name="logo_bremen_calling" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="logo_bremen_calling" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>logo_bremen_calling.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>logo_bremen_calling.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data> </data>
<data name="Shifting" xml:space="preserve">
<value>Verholung</value>
</data>
<data name="ship2" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="ship2" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>ship2.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>ship2.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data> </data>
<data name="textAdd" xml:space="preserve">
<value>Hinzufügen</value>
</data>
<data name="textAgencies" xml:space="preserve"> <data name="textAgencies" xml:space="preserve">
<value>Agenturen</value> <value>Agenturen</value>
</data> </data>
@ -166,18 +160,9 @@
<data name="textBerth" xml:space="preserve"> <data name="textBerth" xml:space="preserve">
<value>Liegeplatz</value> <value>Liegeplatz</value>
</data> </data>
<data name="textBerthRemarks" xml:space="preserve">
<value>Liegeplatz Informationen</value>
</data>
<data name="textBerths" xml:space="preserve"> <data name="textBerths" xml:space="preserve">
<value>Liegeplätze</value> <value>Liegeplätze</value>
</data> </data>
<data name="textBothTideTimesNecessary" xml:space="preserve">
<value>Beide Tidenzeiten sollten angegeben werden (von - bis)</value>
</data>
<data name="textBSMDGranted" xml:space="preserve">
<value>Freigabe zur Bearb. f. BSMD erteilt</value>
</data>
<data name="textBunkering" xml:space="preserve"> <data name="textBunkering" xml:space="preserve">
<value>Bunkeraufnahme</value> <value>Bunkeraufnahme</value>
</data> </data>
@ -196,128 +181,59 @@
<data name="textChange" xml:space="preserve"> <data name="textChange" xml:space="preserve">
<value>Ändern</value> <value>Ändern</value>
</data> </data>
<data name="textChangeContactInfo" xml:space="preserve">
<value>Kontaktdaten bearbeiten</value>
</data>
<data name="textChangeHistory" xml:space="preserve">
<value>Verlauf</value>
</data>
<data name="textChangePassword" xml:space="preserve"> <data name="textChangePassword" xml:space="preserve">
<value>Passwort ändern</value> <value>Passwort ändern</value>
</data> </data>
<data name="textClearAll" xml:space="preserve">
<value>Alle Eintragungen zurücksetzen?</value>
</data>
<data name="textClearAssignment" xml:space="preserve">
<value>Zuordnung entfernen</value>
</data>
<data name="textClearFilters" xml:space="preserve">
<value>Filter löschen</value>
</data>
<data name="textClearValue" xml:space="preserve">
<value>Eingabe löschen</value>
</data>
<data name="textClose" xml:space="preserve"> <data name="textClose" xml:space="preserve">
<value>Schliessen</value> <value>Schliessen</value>
</data> </data>
<data name="textConfirmation" xml:space="preserve">
<value>Bestätigung</value>
</data>
<data name="textDelete" xml:space="preserve">
<value>Löschen</value>
</data>
<data name="textDeleted" xml:space="preserve">
<value>Gelöscht</value>
</data>
<data name="textDepartureTerminal" xml:space="preserve"> <data name="textDepartureTerminal" xml:space="preserve">
<value>Terminal Abfahrt</value> <value>Terminal Abfahrt</value>
</data> </data>
<data name="textDraft" xml:space="preserve"> <data name="textDraft" xml:space="preserve">
<value>Tiefgang (m)</value> <value>Tiefgang (m)</value>
</data> </data>
<data name="textDraftNoUnit" xml:space="preserve">
<value>Tiefgang</value>
</data>
<data name="textEdit" xml:space="preserve">
<value>Bearbeiten</value>
</data>
<data name="textEditShip" xml:space="preserve">
<value>Schiff bearbeiten</value>
</data>
<data name="textEditShipcall" xml:space="preserve"> <data name="textEditShipcall" xml:space="preserve">
<value>Schiffsanlauf bearbeiten</value> <value>Schiffsanlauf bearbeiten</value>
</data> </data>
<data name="textEditShips" xml:space="preserve">
<value>Schiffe anlegen / bearbeiten</value>
</data>
<data name="textEditTimes" xml:space="preserve"> <data name="textEditTimes" xml:space="preserve">
<value>Zeiten bearbeiten</value> <value>Zeiten bearbeiten</value>
</data> </data>
<data name="textEmail" xml:space="preserve">
<value>E-Mail</value>
</data>
<data name="textEndValueBeforeStartValue" xml:space="preserve">
<value>Endzeit liegt vor Startzeit</value>
</data>
<data name="textEnterKeyword" xml:space="preserve">
<value>Schiff / Bemerkung</value>
</data>
<data name="textError" xml:space="preserve">
<value>Error</value>
</data>
<data name="textETABerth" xml:space="preserve"> <data name="textETABerth" xml:space="preserve">
<value>ETA Liegeplatz</value> <value>ETA Liegeplatz</value>
</data> </data>
<data name="textETAInThePast" xml:space="preserve">
<value>Zeitpunkt ETA liegt in der Vergangenheit</value>
</data>
<data name="textETDBerth" xml:space="preserve"> <data name="textETDBerth" xml:space="preserve">
<value>ETD Liegeplatz</value> <value>ETD Liegeplatz</value>
</data> </data>
<data name="textETDInThePast" xml:space="preserve"> <data name="textEnterKeyword" xml:space="preserve">
<value>Zeitpunkt ETD liegt in der Vergangenheit</value> <value>Schiff / Bemerkung</value>
</data> </data>
<data name="textExit" xml:space="preserve"> <data name="textExit" xml:space="preserve">
<value>Verlassen</value> <value>Verlassen</value>
</data> </data>
<data name="textFixed" xml:space="preserve">
<value>Fest</value>
</data>
<data name="textFixedOrder" xml:space="preserve">
<value>Als feste Bestellung vermerkt</value>
</data>
<data name="textFrom" xml:space="preserve"> <data name="textFrom" xml:space="preserve">
<value>von</value> <value>von</value>
</data> </data>
<data name="textHarbour" xml:space="preserve">
<value>Hafen</value>
</data>
<data name="textIncoming" xml:space="preserve">
<value>Einkommend</value>
</data>
<data name="textInfoChangePW" xml:space="preserve">
<value>App Info anzeigen und Passwort ändern</value>
</data>
<data name="textInterval" xml:space="preserve"> <data name="textInterval" xml:space="preserve">
<value>Zeitraum</value> <value>Zeitraum</value>
</data> </data>
<data name="textLength" xml:space="preserve">
<value>Länge</value>
</data>
<data name="textLengthWidth" xml:space="preserve"> <data name="textLengthWidth" xml:space="preserve">
<value>L/B (m)</value> <value>L/B (m)</value>
</data> </data>
<data name="textLockTime" xml:space="preserve">
<value>Zeit Schleuse</value>
</data>
<data name="textLockTimeInThePast" xml:space="preserve">
<value>Schleusenzeit liegt in der Vergangenheit</value>
</data>
<data name="textLogin" xml:space="preserve"> <data name="textLogin" xml:space="preserve">
<value>Anmelden</value> <value>Anmelden</value>
</data> </data>
<data name="textMineOnly" xml:space="preserve"> <data name="textFixed" xml:space="preserve">
<value>nur eigene</value> <value>Fest</value>
</data>
<data name="textLockTime" xml:space="preserve">
<value>Zeit Schleuse</value>
</data>
<data name="textOperationsEnd" xml:space="preserve">
<value>Operation Ende</value>
</data>
<data name="textOperationsStart" xml:space="preserve">
<value>Operation Start</value>
</data> </data>
<data name="textMooredLock" xml:space="preserve"> <data name="textMooredLock" xml:space="preserve">
<value>auch in Schleuse</value> <value>auch in Schleuse</value>
@ -334,45 +250,21 @@
<data name="textNotRotated" xml:space="preserve"> <data name="textNotRotated" xml:space="preserve">
<value>Ungedreht</value> <value>Ungedreht</value>
</data> </data>
<data name="textZoneEntryTime" xml:space="preserve">
<value>Reviereintritt</value>
</data>
<data name="textOK" xml:space="preserve"> <data name="textOK" xml:space="preserve">
<value>OK</value> <value>OK</value>
</data> </data>
<data name="textOldPassword" xml:space="preserve"> <data name="textOldPassword" xml:space="preserve">
<value>Altes Passwort</value> <value>Altes Passwort</value>
</data> </data>
<data name="textOperation" xml:space="preserve">
<value>Vorgang</value>
</data>
<data name="textOperationEndInThePast" xml:space="preserve">
<value>Operation Endzeit liegt in der Vergangenheit</value>
</data>
<data name="textOperationsEnd" xml:space="preserve">
<value>Operation Ende</value>
</data>
<data name="textOperationsStart" xml:space="preserve">
<value>Operation Start</value>
</data>
<data name="textOperationStartInThePast" xml:space="preserve">
<value>Operation Startzeit liegt in der Vergangenheit</value>
</data>
<data name="textOutgoing" xml:space="preserve">
<value>Ausgehend</value>
</data>
<data name="textParticipant" xml:space="preserve">
<value>Teilnehmer</value>
</data>
<data name="textParticipants" xml:space="preserve"> <data name="textParticipants" xml:space="preserve">
<value>Teilnehmer</value> <value>Teilnehmer</value>
</data> </data>
<data name="textPassword" xml:space="preserve"> <data name="textPassword" xml:space="preserve">
<value>Passwort</value> <value>Passwort</value>
</data> </data>
<data name="textPasswordChanged" xml:space="preserve">
<value>Passwort geändert.</value>
</data>
<data name="textPhone" xml:space="preserve">
<value>Telefon</value>
</data>
<data name="textPierside" xml:space="preserve"> <data name="textPierside" xml:space="preserve">
<value>Anlegeseite</value> <value>Anlegeseite</value>
</data> </data>
@ -382,24 +274,12 @@
<data name="textPilotRequired" xml:space="preserve"> <data name="textPilotRequired" xml:space="preserve">
<value>Lotsorder</value> <value>Lotsorder</value>
</data> </data>
<data name="textPilots" xml:space="preserve">
<value>Flusslotsen</value>
</data>
<data name="textPort" xml:space="preserve">
<value>Backbord</value>
</data>
<data name="textPortAuthority" xml:space="preserve">
<value>Hafenamt</value>
</data>
<data name="textRainSensitiveCargo" xml:space="preserve"> <data name="textRainSensitiveCargo" xml:space="preserve">
<value>Regensensitive Ladung</value> <value>Regensensitive Ladung</value>
</data> </data>
<data name="textRecommendedTugs" xml:space="preserve"> <data name="textRecommendedTugs" xml:space="preserve">
<value>Anzahl Schlepper</value> <value>Anzahl Schlepper</value>
</data> </data>
<data name="textRemarks" xml:space="preserve">
<value>Info</value>
</data>
<data name="textRepeatNewPassword" xml:space="preserve"> <data name="textRepeatNewPassword" xml:space="preserve">
<value>Neues Passwort wiederholen</value> <value>Neues Passwort wiederholen</value>
</data> </data>
@ -415,75 +295,27 @@
<data name="textSearch" xml:space="preserve"> <data name="textSearch" xml:space="preserve">
<value>Suche</value> <value>Suche</value>
</data> </data>
<data name="textShifting" xml:space="preserve">
<value>Verholung</value>
</data>
<data name="textShiftingFrom" xml:space="preserve">
<value>Verholung von</value>
</data>
<data name="textShiftingSequence" xml:space="preserve">
<value>Verhol. Nr.</value>
</data>
<data name="textShiftingTo" xml:space="preserve">
<value>Verholung nach</value>
</data>
<data name="textShip" xml:space="preserve"> <data name="textShip" xml:space="preserve">
<value>Schiff</value> <value>Schiff</value>
</data> </data>
<data name="textShipLength" xml:space="preserve"> <data name="textShipLength" xml:space="preserve">
<value>Schiffslänge</value> <value>Schiffslänge</value>
</data> </data>
<data name="textShips" xml:space="preserve">
<value>Schiffe</value>
</data>
<data name="textShowCancelledShipcalls" xml:space="preserve">
<value>Stornierte anzeigen</value>
</data>
<data name="textShowHistory" xml:space="preserve">
<value>Änderungshistorie der Anläufe anzeigen</value>
</data>
<data name="textSortOrder" xml:space="preserve"> <data name="textSortOrder" xml:space="preserve">
<value>Sortierung</value> <value>Sortierung</value>
</data> </data>
<data name="textStarboard" xml:space="preserve">
<value>Steuerbord</value>
</data>
<data name="textTerminal" xml:space="preserve"> <data name="textTerminal" xml:space="preserve">
<value>Terminal</value> <value>Terminal</value>
</data> </data>
<data name="textTidalBothValues" xml:space="preserve">
<value>Für das Tidenfenster müssen beide Zeiten angegeben werden</value>
</data>
<data name="textTidalWindow" xml:space="preserve"> <data name="textTidalWindow" xml:space="preserve">
<value>Tidenfenster</value> <value>Tidenfenster</value>
</data> </data>
<data name="textTideTimesInThePast" xml:space="preserve">
<value>Tidenzeit liegt in der Vergangenheit</value>
</data>
<data name="textTimestamp" xml:space="preserve">
<value>Zeitpunkt</value>
</data>
<data name="textTo" xml:space="preserve"> <data name="textTo" xml:space="preserve">
<value>bis</value> <value>bis</value>
</data> </data>
<data name="textTooFarInTheFuture" xml:space="preserve">
<value>Eine Zeiteingabe ist zu weit in der Zukunft</value>
</data>
<data name="textTooltipSetFixedOrder" xml:space="preserve">
<value>Als feste Bestellung vermerken</value>
</data>
<data name="textTooltipUnSetFixedOrder" xml:space="preserve">
<value>Feste Bestellung zurücknehmen</value>
</data>
<data name="textTriggerManualRefresh" xml:space="preserve">
<value>Manuelle Aktualisierung der Anläufe auslösen</value>
</data>
<data name="textTug" xml:space="preserve"> <data name="textTug" xml:space="preserve">
<value>Schlepper</value> <value>Schlepper</value>
</data> </data>
<data name="textTugCompany" xml:space="preserve">
<value>Schlepper-Reederei</value>
</data>
<data name="textTugRequired" xml:space="preserve"> <data name="textTugRequired" xml:space="preserve">
<value>Schlepperorder</value> <value>Schlepperorder</value>
</data> </data>
@ -496,27 +328,9 @@
<data name="textUsername" xml:space="preserve"> <data name="textUsername" xml:space="preserve">
<value>Benutzername</value> <value>Benutzername</value>
</data> </data>
<data name="textUserNamePasswordEmpty" xml:space="preserve">
<value>Benutzername / Passwort leer!</value>
</data>
<data name="textVoyage" xml:space="preserve"> <data name="textVoyage" xml:space="preserve">
<value>Reise</value> <value>Reise</value>
</data> </data>
<data name="textWarning" xml:space="preserve">
<value>Warnung</value>
</data>
<data name="textWidth" xml:space="preserve">
<value>Breite</value>
</data>
<data name="textWrongCredentials" xml:space="preserve">
<value>Benutzername / Passwort falsch</value>
</data>
<data name="textZoneEntryInThePast" xml:space="preserve">
<value>Zeit Reviereintritt liegt in der Vergangenheit</value>
</data>
<data name="textZoneEntryTime" xml:space="preserve">
<value>Reviereintritt</value>
</data>
<data name="trafficlight_green" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="trafficlight_green" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>trafficlight_green.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>trafficlight_green.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data> </data>
@ -541,64 +355,157 @@
<data name="umbrella_open" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="umbrella_open" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>umbrella_open.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>umbrella_open.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data> </data>
<data name="Undefined" xml:space="preserve">
<value>Unbekannt</value>
</data>
<data name="worker2" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="worker2" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>worker2.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>worker2.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data> </data>
<data name="arrow_right_blue" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="textClearAssignment" xml:space="preserve">
<value>arrow_right_blue.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>Zuordnung entfernen</value>
</data> </data>
<data name="arrow_up_green" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="textClearValue" xml:space="preserve">
<value>arrow_up_green.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>Eingabe löschen</value>
</data> </data>
<data name="textStartTimeMissing" xml:space="preserve"> <data name="textConfirmation" xml:space="preserve">
<value>Wenn eine Ende-Zeit angegeben wird, muss auch eine Start-Zeit angegeben werden</value> <value>Bestätigung</value>
</data> </data>
<data name="textInformationUpdated" xml:space="preserve"> <data name="textPasswordChanged" xml:space="preserve">
<value>Einstellungen erfolgreich aktualisiert</value> <value>Passwort geändert.</value>
</data> </data>
<data name="textNotifications" xml:space="preserve"> <data name="textClearFilters" xml:space="preserve">
<value>Benachrichtigungen</value> <value>Filter löschen</value>
</data> </data>
<data name="textNotifyEmail" xml:space="preserve"> <data name="textShowCancelledShipcalls" xml:space="preserve">
<value>E-Mail Benachrichtigung</value> <value>Stornierte anzeigen</value>
</data> </data>
<data name="textNotifyPush" xml:space="preserve"> <data name="textBerthRemarks" xml:space="preserve">
<value>Banner / Push Benachrichtigung in App</value> <value>Liegeplatz Informationen</value>
</data> </data>
<data name="textAssignment" xml:space="preserve"> <data name="textBSMDGranted" xml:space="preserve">
<value>Teilnehmer wurde nominiert</value> <value>Freigabe zur Bearb. f. BSMD erteilt</value>
</data> </data>
<data name="textDate" xml:space="preserve"> <data name="textRemarks" xml:space="preserve">
<value>Datum</value> <value>Info</value>
</data> </data>
<data name="textNext24h" xml:space="preserve"> <data name="textIncoming" xml:space="preserve">
<value>Relevant für Morgenrunde (24hrs)</value> <value>Einkommend</value>
</data> </data>
<data name="textShipcall" xml:space="preserve"> <data name="textOutgoing" xml:space="preserve">
<value>Anlauf</value> <value>Ausgehend</value>
</data> </data>
<data name="textShowNotifications" xml:space="preserve"> <data name="textShifting" xml:space="preserve">
<value>Benachrichtigungen anzeigen</value> <value>Verholung</value>
</data> </data>
<data name="textTimeConflict" xml:space="preserve"> <data name="textLength" xml:space="preserve">
<value>"Ampel"-Regel(n) wurde verletzt</value> <value>Länge</value>
</data> </data>
<data name="textTimeConflictResolved" xml:space="preserve"> <data name="textShiftingFrom" xml:space="preserve">
<value>Zeitliche Konflikte aufgelöst</value> <value>Verholung von</value>
</data> </data>
<data name="textUnassigned" xml:space="preserve"> <data name="textShiftingTo" xml:space="preserve">
<value>Nominierung des Teilnehmer entfernt</value> <value>Verholung nach</value>
</data> </data>
<data name="textMissingData" xml:space="preserve"> <data name="textWidth" xml:space="preserve">
<value>Der Teilnehmer hat keine Daten eingetragen</value> <value>Breite</value>
</data> </data>
<data name="textShipcallCancelled" xml:space="preserve"> <data name="textChangeContactInfo" xml:space="preserve">
<value>Der Anlauf wurde storniert</value> <value>Kontaktdaten bearbeiten</value>
</data> </data>
<data name="textNotifyOn" xml:space="preserve"> <data name="textEmail" xml:space="preserve">
<value>Benachrichtigung bei</value> <value>E-Mail</value>
</data>
<data name="textPhone" xml:space="preserve">
<value>Telefon</value>
</data>
<data name="textWrongCredentials" xml:space="preserve">
<value>Benutzername / Passwort falsch</value>
</data>
<data name="textUserNamePasswordEmpty" xml:space="preserve">
<value>Benutzername / Passwort leer!</value>
</data>
<data name="textPort" xml:space="preserve">
<value>Backbord</value>
</data>
<data name="textStarboard" xml:space="preserve">
<value>Steuerbord</value>
</data>
<data name="textAdd" xml:space="preserve">
<value>Hinzufügen</value>
</data>
<data name="textDelete" xml:space="preserve">
<value>Löschen</value>
</data>
<data name="textEdit" xml:space="preserve">
<value>Bearbeiten</value>
</data>
<data name="textEditShips" xml:space="preserve">
<value>Schiffe anlegen / bearbeiten</value>
</data>
<data name="textDeleted" xml:space="preserve">
<value>Gelöscht</value>
</data>
<data name="textEditShip" xml:space="preserve">
<value>Schiff bearbeiten</value>
</data>
<data name="textTugCompany" xml:space="preserve">
<value>Schlepper-Reederei</value>
</data>
<data name="textChangeHistory" xml:space="preserve">
<value>Verlauf</value>
</data>
<data name="textInfoChangePW" xml:space="preserve">
<value>App Info anzeigen und Passwort ändern</value>
</data>
<data name="textShowHistory" xml:space="preserve">
<value>Änderungshistorie der Anläufe anzeigen</value>
</data>
<data name="textMineOnly" xml:space="preserve">
<value>nur eigene</value>
</data>
<data name="textOperation" xml:space="preserve">
<value>Vorgang</value>
</data>
<data name="textParticipant" xml:space="preserve">
<value>Teilnehmer</value>
</data>
<data name="textTimestamp" xml:space="preserve">
<value>Zeitpunkt</value>
</data>
<data name="textFixedOrder" xml:space="preserve">
<value>Als feste Bestellung vermerkt</value>
</data>
<data name="textTooltipSetFixedOrder" xml:space="preserve">
<value>Als feste Bestellung vermerken</value>
</data>
<data name="textTooltipUnSetFixedOrder" xml:space="preserve">
<value>Feste Bestellung zurücknehmen</value>
</data>
<data name="textTriggerManualRefresh" xml:space="preserve">
<value>Manuelle Aktualisierung der Anläufe auslösen</value>
</data>
<data name="Arrival" xml:space="preserve">
<value>Einkommend</value>
</data>
<data name="Departure" xml:space="preserve">
<value>Ausgehend</value>
</data>
<data name="Shifting" xml:space="preserve">
<value>Verholung</value>
</data>
<data name="Undefined" xml:space="preserve">
<value>Unbekannt</value>
</data>
<data name="textShips" xml:space="preserve">
<value>Schiffe</value>
</data>
<data name="textPilots" xml:space="preserve">
<value>Flusslotsen</value>
</data>
<data name="textPortAuthority" xml:space="preserve">
<value>Hafenamt</value>
</data>
<data name="textShiftingSequence" xml:space="preserve">
<value>Verhol. Nr.</value>
</data>
<data name="textClearAll" xml:space="preserve">
<value>Alle Eintragungen zurücksetzen?</value>
</data> </data>
</root> </root>

View File

@ -214,9 +214,6 @@
<data name="textBerths" xml:space="preserve"> <data name="textBerths" xml:space="preserve">
<value>Berths</value> <value>Berths</value>
</data> </data>
<data name="textBothTideTimesNecessary" xml:space="preserve">
<value>Both tide times are required</value>
</data>
<data name="textBSMDGranted" xml:space="preserve"> <data name="textBSMDGranted" xml:space="preserve">
<value>BSMD right to edit granted</value> <value>BSMD right to edit granted</value>
</data> </data>
@ -295,27 +292,15 @@
<data name="textEmail" xml:space="preserve"> <data name="textEmail" xml:space="preserve">
<value>E-mail</value> <value>E-mail</value>
</data> </data>
<data name="textEndValueBeforeStartValue" xml:space="preserve">
<value>Some interval end times are before start times</value>
</data>
<data name="textEnterKeyword" xml:space="preserve"> <data name="textEnterKeyword" xml:space="preserve">
<value>Ship / remark</value> <value>Ship / remark</value>
</data> </data>
<data name="textError" xml:space="preserve">
<value>Error</value>
</data>
<data name="textETABerth" xml:space="preserve"> <data name="textETABerth" xml:space="preserve">
<value>ETA berth</value> <value>ETA berth</value>
</data> </data>
<data name="textETAInThePast" xml:space="preserve">
<value>ETA value is in the past</value>
</data>
<data name="textETDBerth" xml:space="preserve"> <data name="textETDBerth" xml:space="preserve">
<value>ETD berth</value> <value>ETD berth</value>
</data> </data>
<data name="textETDInThePast" xml:space="preserve">
<value>ETD value is in the past</value>
</data>
<data name="textExit" xml:space="preserve"> <data name="textExit" xml:space="preserve">
<value>Exit</value> <value>Exit</value>
</data> </data>
@ -328,9 +313,6 @@
<data name="textFrom" xml:space="preserve"> <data name="textFrom" xml:space="preserve">
<value>from</value> <value>from</value>
</data> </data>
<data name="textHarbour" xml:space="preserve">
<value>Harbour</value>
</data>
<data name="textIncoming" xml:space="preserve"> <data name="textIncoming" xml:space="preserve">
<value>Incoming</value> <value>Incoming</value>
</data> </data>
@ -349,9 +331,6 @@
<data name="textLockTime" xml:space="preserve"> <data name="textLockTime" xml:space="preserve">
<value>Lock time</value> <value>Lock time</value>
</data> </data>
<data name="textLockTimeInThePast" xml:space="preserve">
<value>Lock time is in the past</value>
</data>
<data name="textLogin" xml:space="preserve"> <data name="textLogin" xml:space="preserve">
<value>Login</value> <value>Login</value>
</data> </data>
@ -382,18 +361,12 @@
<data name="textOperation" xml:space="preserve"> <data name="textOperation" xml:space="preserve">
<value>Operation</value> <value>Operation</value>
</data> </data>
<data name="textOperationEndInThePast" xml:space="preserve">
<value>Operation end is in the past</value>
</data>
<data name="textOperationsEnd" xml:space="preserve"> <data name="textOperationsEnd" xml:space="preserve">
<value>Operations end</value> <value>Operations end</value>
</data> </data>
<data name="textOperationsStart" xml:space="preserve"> <data name="textOperationsStart" xml:space="preserve">
<value>Operations start</value> <value>Operations start</value>
</data> </data>
<data name="textOperationStartInThePast" xml:space="preserve">
<value>Operation start is in the past</value>
</data>
<data name="textOutgoing" xml:space="preserve"> <data name="textOutgoing" xml:space="preserve">
<value>Outgoing</value> <value>Outgoing</value>
</data> </data>
@ -490,24 +463,15 @@
<data name="textTerminal" xml:space="preserve"> <data name="textTerminal" xml:space="preserve">
<value>Terminal</value> <value>Terminal</value>
</data> </data>
<data name="textTidalBothValues" xml:space="preserve">
<value>For a tidal window both values must be set</value>
</data>
<data name="textTidalWindow" xml:space="preserve"> <data name="textTidalWindow" xml:space="preserve">
<value>Tidal window</value> <value>Tidal window</value>
</data> </data>
<data name="textTideTimesInThePast" xml:space="preserve">
<value>Times for the tide window are in the past</value>
</data>
<data name="textTimestamp" xml:space="preserve"> <data name="textTimestamp" xml:space="preserve">
<value>Timestamp</value> <value>Timestamp</value>
</data> </data>
<data name="textTo" xml:space="preserve"> <data name="textTo" xml:space="preserve">
<value>to</value> <value>to</value>
</data> </data>
<data name="textTooFarInTheFuture" xml:space="preserve">
<value>A time value is too far in the future</value>
</data>
<data name="textTooltipSetFixedOrder" xml:space="preserve"> <data name="textTooltipSetFixedOrder" xml:space="preserve">
<value>Set as a fixed order</value> <value>Set as a fixed order</value>
</data> </data>
@ -541,18 +505,12 @@
<data name="textVoyage" xml:space="preserve"> <data name="textVoyage" xml:space="preserve">
<value>Voyage</value> <value>Voyage</value>
</data> </data>
<data name="textWarning" xml:space="preserve">
<value>Warning</value>
</data>
<data name="textWidth" xml:space="preserve"> <data name="textWidth" xml:space="preserve">
<value>Width</value> <value>Width</value>
</data> </data>
<data name="textWrongCredentials" xml:space="preserve"> <data name="textWrongCredentials" xml:space="preserve">
<value>Wrong username or password</value> <value>Wrong username or password</value>
</data> </data>
<data name="textZoneEntryInThePast" xml:space="preserve">
<value>Zone entry time is in the past</value>
</data>
<data name="textZoneEntryTime" xml:space="preserve"> <data name="textZoneEntryTime" xml:space="preserve">
<value>Zone entry</value> <value>Zone entry</value>
</data> </data>
@ -592,67 +550,4 @@
<data name="_lock" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="_lock" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>lock.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>lock.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data> </data>
<data name="arrow_right_blue" xml:space="preserve">
<value />
</data>
<data name="arrow_up_green" xml:space="preserve">
<value />
</data>
<data name="textStartTimeMissing" xml:space="preserve">
<value>If an end time is set, a start time is also required</value>
</data>
<data name="textInformationUpdated" xml:space="preserve">
<value>Information successfully updated</value>
</data>
<data name="textNotifications" xml:space="preserve">
<value>Notifications</value>
</data>
<data name="textNotifyEmail" xml:space="preserve">
<value>Notify by e-mail</value>
</data>
<data name="textNotifyPush" xml:space="preserve">
<value>Notify by push notification in app</value>
</data>
<data name="bell3" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>bell3.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="textAssignment" xml:space="preserve">
<value>Participant assigned to shipcall</value>
</data>
<data name="textDate" xml:space="preserve">
<value>Date</value>
</data>
<data name="textNext24h" xml:space="preserve">
<value>Relevant next 24hrs</value>
</data>
<data name="textShipcall" xml:space="preserve">
<value>Shipcall</value>
</data>
<data name="textShowNotifications" xml:space="preserve">
<value>Show notificiations</value>
</data>
<data name="textTimeConflict" xml:space="preserve">
<value>Participants disagree on times</value>
</data>
<data name="textTimeConflictResolved" xml:space="preserve">
<value>Times conflict resolved</value>
</data>
<data name="textUnassigned" xml:space="preserve">
<value>Participant unassigned from shipcall</value>
</data>
<data name="textMissingData" xml:space="preserve">
<value>The participant has not provided any info</value>
</data>
<data name="textShipcallCancelled" xml:space="preserve">
<value>The shipcall was cancelled</value>
</data>
<data name="textNotifyOn" xml:space="preserve">
<value>Notify on</value>
</data>
<data name="textPosition" xml:space="preserve">
<value>Position</value>
</data>
<data name="textDraftNoUnit" xml:space="preserve">
<value>Draft</value>
</data>
</root> </root>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -89,7 +89,8 @@
<ColumnDefinition Width="30" /> <ColumnDefinition Width="30" />
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<xctk:WatermarkTextBox x:Name="textBoxSearch" Grid.Column="0" Margin="2" Watermark="{x:Static p:Resources.textEnterKeyword}" TextChanged="textBoxSearch_TextChanged" /> <xctk:WatermarkTextBox x:Name="textBoxSearch" Grid.Column="0" Margin="2" Watermark="{x:Static p:Resources.textEnterKeyword}" PreviewTextInput="textBoxSearch_PreviewTextInput"
DataObject.Pasting="textBoxSearch_Pasting" TextChanged="textBoxSearch_TextChanged" />
<CheckBox x:Name="checkBoxOwn" VerticalAlignment="Center" Grid.Column="1" HorizontalAlignment="Right" Margin="2" Checked="checkBoxOwn_Checked" Unchecked="checkBoxOwn_Checked" /> <CheckBox x:Name="checkBoxOwn" VerticalAlignment="Center" Grid.Column="1" HorizontalAlignment="Right" Margin="2" Checked="checkBoxOwn_Checked" Unchecked="checkBoxOwn_Checked" />
<Label Content="{x:Static p:Resources.textMineOnly}" Grid.Column="2" /> <Label Content="{x:Static p:Resources.textMineOnly}" Grid.Column="2" />
</Grid> </Grid>

View File

@ -185,9 +185,21 @@ namespace BreCalClient
SearchFilterChanged?.Invoke(); SearchFilterChanged?.Invoke();
} }
private void textBoxSearch_Pasting(object sender, System.Windows.DataObjectPastingEventArgs e)
{
this.SearchFilter.SearchString = e.SourceDataObject.ToString();
SearchFilterChanged?.Invoke();
}
private void textBoxSearch_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
this.SearchFilter.SearchString = this.textBoxSearch.Text + e.Text;
SearchFilterChanged?.Invoke();
}
private void textBoxSearch_TextChanged(object sender, TextChangedEventArgs e) private void textBoxSearch_TextChanged(object sender, TextChangedEventArgs e)
{ {
this.SearchFilter.SearchString = this.textBoxSearch.Text.Trim(); this.SearchFilter.SearchString = this.textBoxSearch.Text;
SearchFilterChanged?.Invoke(); SearchFilterChanged?.Invoke();
} }

View File

@ -25,8 +25,6 @@ namespace BreCalClient
public List<int> Berths { get; set; } = new(); public List<int> Berths { get; set; } = new();
public List<int> Ports { get; set; } = new();
public string? SearchString { get; set; } public string? SearchString { get; set; }
public double? ShipLengthFrom { get; set; } public double? ShipLengthFrom { get; set; }

View File

@ -48,16 +48,9 @@ namespace BreCalClient
if (obj is ShipModel shipmodel) if (obj is ShipModel shipmodel)
{ {
if (!shipmodel.Ship.Deleted) if (!shipmodel.Ship.Deleted)
{
try
{ {
if (this.ShipApi != null) if (this.ShipApi != null)
await this.ShipApi.ShipDeleteAsync(shipmodel.Ship.Id); await this.ShipApi.ShipDeleteAsync(shipmodel.Ship.Id);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
BreCalLists.Ships.Remove(shipmodel); // remove from "selectable" ships BreCalLists.Ships.Remove(shipmodel); // remove from "selectable" ships
shipmodel.Ship.Deleted = true; // set deleted marker on working instance shipmodel.Ship.Deleted = true; // set deleted marker on working instance
this.dataGridShips.ItemsSource = null; this.dataGridShips.ItemsSource = null;
@ -97,9 +90,7 @@ namespace BreCalClient
private async void DataGridShips_CreateRequested() private async void DataGridShips_CreateRequested()
{ {
ShipModel shipModel = new(new Ship());
ShipModel shipModel = new(ShipModel.LastEditShip ?? new Ship());
EditShipDialog esd = new() EditShipDialog esd = new()
{ {
Ship = shipModel.Ship Ship = shipModel.Ship
@ -120,15 +111,14 @@ namespace BreCalClient
if(!BreCalLists.ShipLookupDict.TryAdd(id.VarId, shipModel)) if(!BreCalLists.ShipLookupDict.TryAdd(id.VarId, shipModel))
BreCalLists.ShipLookupDict[id.VarId] = shipModel; BreCalLists.ShipLookupDict[id.VarId] = shipModel;
this.dataGridShips.ItemsSource = BreCalLists.AllShips; this.dataGridShips.ItemsSource = BreCalLists.AllShips;
ShipModel.LastEditShip = null;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
ShipModel.LastEditShip = shipModel.Ship;
MessageBox.Show(ex.Message); MessageBox.Show(ex.Message);
} }
} }
} }
#endregion #endregion

View File

@ -16,8 +16,6 @@ namespace BreCalClient
public Ship Ship { get; private set; } public Ship Ship { get; private set; }
public static Ship? LastEditShip { get; set; }
public string TugCompany public string TugCompany
{ {
get { if(this.Ship.ParticipantId.HasValue) get { if(this.Ship.ParticipantId.HasValue)

View File

@ -5,7 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:p = "clr-namespace:BreCalClient.Resources" xmlns:p = "clr-namespace:BreCalClient.Resources"
xmlns:sets="clr-namespace:BreCalClient.Properties" xmlns:sets="clr-namespace:BreCalClient.Properties"
xmlns:db="clr-namespace:BreCalClient;assembly=BreCalTestClient" xmlns:db="clr-namespace:BreCalClient;assembly=BreCalDevelClient"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="135" d:DesignWidth="800"> d:DesignHeight="135" d:DesignWidth="800">
<Border BorderBrush="LightGray" Margin="1" BorderThickness="1"> <Border BorderBrush="LightGray" Margin="1" BorderThickness="1">
@ -20,15 +20,16 @@
<ColumnDefinition Width=".15*" /> <ColumnDefinition Width=".15*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="42" /> <RowDefinition Height="32" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" > <Grid Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" >
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="42"/> <RowDefinition Height="32"/>
<RowDefinition Height=".125*"/> <RowDefinition Height=".125*"/>
<RowDefinition Height=".125*"/> <RowDefinition Height=".125*"/>
<RowDefinition Height=".125*"/> <RowDefinition Height=".125*"/>
<RowDefinition Height=".125*"/> <RowDefinition Height=".125*"/>
<RowDefinition Height=".125*"/> <RowDefinition Height=".125*"/>
<RowDefinition Height=".125*"/> <RowDefinition Height=".125*"/>
@ -63,87 +64,51 @@
<TextBlock x:Name="textBlockIMO" Padding="0" FontWeight="DemiBold" /> <TextBlock x:Name="textBlockIMO" Padding="0" FontWeight="DemiBold" />
</Viewbox> </Viewbox>
<Viewbox Grid.Row="2" Grid.Column="0" HorizontalAlignment="Left"> <Viewbox Grid.Row="2" Grid.Column="0" HorizontalAlignment="Left">
<TextBlock Text="{x:Static p:Resources.textLengthWidth}" Padding="0" /> <TextBlock Text="{x:Static p:Resources.textCallsign}" />
</Viewbox> </Viewbox>
<Viewbox Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left"> <Viewbox Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left">
<TextBlock x:Name="textBlockLengthWidth" Padding="0"/> <TextBlock x:Name="textBlockCallsign" Padding="0"/>
</Viewbox> </Viewbox>
<Viewbox Grid.Row="3" Grid.Column="0" HorizontalAlignment="Left"> <Viewbox Grid.Row="3" Grid.Column="0" HorizontalAlignment="Left">
<TextBlock Text="{x:Static p:Resources.textDraftNoUnit}" /> <TextBlock Text="{x:Static p:Resources.textLengthWidth}" Padding="0" />
</Viewbox> </Viewbox>
<Viewbox Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left"> <Viewbox Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left">
<TextBlock x:Name="textBlockDraft" Padding="0"/> <TextBlock x:Name="textBlockLengthWidth" Padding="0"/>
</Viewbox> </Viewbox>
<Viewbox Grid.Row="4" Grid.Column="0" HorizontalAlignment="Left"> <Viewbox Grid.Row="4" Grid.Column="0" HorizontalAlignment="Left">
<TextBlock Text="ETA" x:Name="labelETA"/> <TextBlock Text="{x:Static p:Resources.textDraft}" Padding="0" />
</Viewbox> </Viewbox>
<Viewbox Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left"> <Viewbox Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left">
<TextBlock x:Name="textBlockETA" Padding="0" FontWeight="DemiBold"/> <TextBlock x:Name="textBlockDraft" Padding="0"/>
</Viewbox> </Viewbox>
<Viewbox Grid.Row="5" Grid.Column="0" HorizontalAlignment="Left"> <Viewbox Grid.Row="5" Grid.Column="0" HorizontalAlignment="Left">
<TextBlock Text="{x:Static p:Resources.textBerth}"/> <TextBlock Text="ETA" x:Name="labelETA"/>
</Viewbox> </Viewbox>
<Viewbox Grid.Row="5" Grid.Column="1" HorizontalAlignment="Left"> <Viewbox Grid.Row="5" Grid.Column="1" HorizontalAlignment="Left">
<TextBlock x:Name="textBlockETA" Padding="0" FontWeight="DemiBold"/>
</Viewbox>
<Viewbox Grid.Row="6" Grid.Column="0" HorizontalAlignment="Left">
<TextBlock Text="{x:Static p:Resources.textBerth}"/>
</Viewbox>
<Viewbox Grid.Row="6" Grid.Column="1" HorizontalAlignment="Left">
<TextBlock x:Name="textBlockBerth" Padding="0" FontWeight="DemiBold" /> <TextBlock x:Name="textBlockBerth" Padding="0" FontWeight="DemiBold" />
</Viewbox> </Viewbox>
<Viewbox Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Left">
<TextBlock x:Name="textBlockHarbour" Padding="0" FontWeight="DemiBold" />
</Viewbox>
</Grid> </Grid>
<Grid Grid.Row="0" Grid.Column="1">
<Grid.RowDefinitions> <Label Grid.Row="0" Grid.Column="1" Grid.RowSpan="1" FontSize="12" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
<RowDefinition Height="*" />
<RowDefinition Height="14" />
</Grid.RowDefinitions>
<Label Padding="0" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" FontSize="13" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelAgent" PreviewMouseUp="labelAgent_PreviewMouseUp"/> HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelAgent" PreviewMouseUp="labelAgent_PreviewMouseUp"/>
<Label Name="labelLastChangeAgency" FontSize="9" Padding="0" VerticalContentAlignment="Center" Grid.Row="1" Foreground="WhiteSmoke" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" HorizontalContentAlignment="Center" /> <Label Grid.Row="0" Grid.Column="2" Grid.RowSpan="1" FontSize="12" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
</Grid>
<Grid Grid.Row="0" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="14" />
</Grid.RowDefinitions>
<Label Padding="0" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" FontSize="13" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelMooring" PreviewMouseUp="labelMooring_PreviewMouseUp"/> HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelMooring" PreviewMouseUp="labelMooring_PreviewMouseUp"/>
<Label Name="labelLastChangeMooring" FontSize="9" Padding="0" VerticalContentAlignment="Center" Grid.Row="1" Foreground="WhiteSmoke" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" HorizontalContentAlignment="Center" /> <Label Grid.Row="0" Grid.Column="3" Grid.RowSpan="1" FontSize="12" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
</Grid> HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelPortAuthority" PreviewMouseUp="labelPortAuthority_PreviewMouseUp" />
<Grid Grid.Row="0" Grid.Column="3"> <Label Grid.Row="0" Grid.Column="4" Grid.RowSpan="1" FontSize="12" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="14" />
</Grid.RowDefinitions>
<Label Padding="0" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" FontSize="13" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelPortAuthority" PreviewMouseUp="labelPortAuthority_PreviewMouseUp"/>
<Label Name="labelLastChangePortAuthority" FontSize="9" Padding="0" VerticalContentAlignment="Center" Grid.Row="1" Foreground="WhiteSmoke" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" HorizontalContentAlignment="Center" />
</Grid>
<Grid Grid.Row="0" Grid.Column="4">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="14" />
</Grid.RowDefinitions>
<Label Padding="0" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" FontSize="13" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelPilot" PreviewMouseUp="labelPilot_PreviewMouseUp"/> HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelPilot" PreviewMouseUp="labelPilot_PreviewMouseUp"/>
<Label Name="labelLastChangePilot" FontSize="9" Padding="0" VerticalContentAlignment="Center" Grid.Row="1" Foreground="WhiteSmoke" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" HorizontalContentAlignment="Center" /> <Label Grid.Row="0" Grid.Column="5" Grid.RowSpan="1" FontSize="12" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
</Grid>
<Grid Grid.Row="0" Grid.Column="5">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="14" />
</Grid.RowDefinitions>
<Label Padding="0" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" FontSize="13" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelTug" PreviewMouseUp="labelTug_PreviewMouseUp"/> HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelTug" PreviewMouseUp="labelTug_PreviewMouseUp"/>
<Label Name="labelLastChangeTug" FontSize="9" Padding="0" VerticalContentAlignment="Center" Grid.Row="1" Foreground="WhiteSmoke" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" HorizontalContentAlignment="Center" /> <Label Grid.Row="0" Grid.Column="6" Grid.RowSpan="1" FontSize="12" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
</Grid> HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelTerminal" PreviewMouseUp="labelTerminal_PreviewMouseUp" />
<Grid Grid.Row="0" Grid.Column="6">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="14" />
</Grid.RowDefinitions>
<Label Padding="0" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" FontSize="13" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelTerminal" PreviewMouseUp="labelTerminal_PreviewMouseUp"/>
<Label Name="labelLastChangeTerminal" FontSize="9" Padding="0" VerticalContentAlignment="Center" Grid.Row="1" Foreground="WhiteSmoke" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" HorizontalContentAlignment="Center" />
</Grid>
<!-- AGENCY --> <!-- AGENCY -->
<Border Grid.Row="2" Grid.Column="1" BorderThickness="1, 0, 0, 0" BorderBrush="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Padding="3,0,0,0"> <Border Grid.Row="2" Grid.Column="1" BorderThickness="1, 0, 0, 0" BorderBrush="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Padding="3,0,0,0">

View File

@ -72,7 +72,7 @@ namespace BreCalClient
string agentName = ""; string agentName = "";
string? name; string? name;
_agency = this.ShipcallControlModel?.GetParticipantForType(Extensions.ParticipantType.AGENCY); _agency = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.AGENCY);
name = _agency?.Name; name = _agency?.Name;
if (name != null) agentName = name; if (name != null) agentName = name;
this.labelAgent.Content = name ?? "- / -"; this.labelAgent.Content = name ?? "- / -";
@ -83,9 +83,10 @@ namespace BreCalClient
this.labelAgencyETAETDValue.Content = ""; this.labelAgencyETAETDValue.Content = "";
this.textBlockAgencyRemarks.Text = ""; this.textBlockAgencyRemarks.Text = "";
this.textBlockAgencyBerthRemarks.Text = ""; this.textBlockAgencyBerthRemarks.Text = "";
this.textBlockDraft.Text = "";
} }
_mooring = this.ShipcallControlModel?.GetParticipantForType(Extensions.ParticipantType.MOORING); _mooring = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.MOORING);
name = _mooring?.Name; name = _mooring?.Name;
this.labelMooring.Content = name ?? "- / -"; this.labelMooring.Content = name ?? "- / -";
if(_mooring == null) if(_mooring == null)
@ -94,7 +95,7 @@ namespace BreCalClient
this.textBlockMooringRemarks.Text = ""; this.textBlockMooringRemarks.Text = "";
} }
_pilot = this.ShipcallControlModel?.GetParticipantForType(Extensions.ParticipantType.PILOT); _pilot = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.PILOT);
name = _pilot?.Name; name = _pilot?.Name;
this.labelPilot.Content = name ?? "- / - "; this.labelPilot.Content = name ?? "- / - ";
if(_pilot == null) if(_pilot == null)
@ -103,7 +104,7 @@ namespace BreCalClient
this.textBlockPilotRemarks.Text = ""; this.textBlockPilotRemarks.Text = "";
} }
_tug = this.ShipcallControlModel?.GetParticipantForType(Extensions.ParticipantType.TUG); _tug = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.TUG);
name = _tug?.Name; name = _tug?.Name;
this.labelTug.Content = name ?? "- / - "; this.labelTug.Content = name ?? "- / - ";
if(_tug == null) if(_tug == null)
@ -112,7 +113,7 @@ namespace BreCalClient
this.textBlockTugRemarks.Text = ""; this.textBlockTugRemarks.Text = "";
} }
_port_administration = this.ShipcallControlModel?.GetParticipantForType(Extensions.ParticipantType.PORT_ADMINISTRATION); _port_administration = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.PORT_ADMINISTRATION);
name = _port_administration?.Name; name = _port_administration?.Name;
this.labelPortAuthority.Content = name ?? "- / - "; this.labelPortAuthority.Content = name ?? "- / - ";
if(_port_administration == null) if(_port_administration == null)
@ -121,7 +122,7 @@ namespace BreCalClient
this.textBlockPortAuthorityRemarks.Text = ""; this.textBlockPortAuthorityRemarks.Text = "";
} }
_terminal = this.ShipcallControlModel?.GetParticipantForType(Extensions.ParticipantType.TERMINAL); _terminal = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.TERMINAL);
name = _terminal?.Name; name = _terminal?.Name;
this.labelTerminal.Content = name ?? "- / - "; this.labelTerminal.Content = name ?? "- / - ";
if(_terminal == null) if(_terminal == null)
@ -215,13 +216,13 @@ namespace BreCalClient
switch (this.ShipcallControlModel?.Shipcall?.Type) switch (this.ShipcallControlModel?.Shipcall?.Type)
{ {
case ShipcallType.Arrival: // incoming case ShipcallType.Arrival: // incoming
this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalTestClient;component/Resources/arrow_down_red.png")); this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalDevelClient;component/Resources/arrow_down_red.png"));
break; break;
case ShipcallType.Departure: // outgoing case ShipcallType.Departure: // outgoing
this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalTestClient;component/Resources/arrow_up_blue.png")); this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalDevelClient;component/Resources/arrow_up_blue.png"));
break; break;
case ShipcallType.Shifting: // shifting case ShipcallType.Shifting: // shifting
this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalTestClient;component/Resources/arrow_right_green.png")); this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalDevelClient;component/Resources/arrow_right_green.png"));
break; break;
default: default:
break; break;
@ -230,13 +231,13 @@ namespace BreCalClient
switch(this.ShipcallControlModel?.LightMode) switch(this.ShipcallControlModel?.LightMode)
{ {
case EvaluationType.Green: case EvaluationType.Green:
this.imageEvaluation.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalTestClient;component/Resources/check.png")); this.imageEvaluation.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalDevelClient;component/Resources/check.png"));
break; break;
case EvaluationType.Yellow: case EvaluationType.Yellow:
this.imageEvaluation.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalTestClient;component/Resources/sign_warning.png")); this.imageEvaluation.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalDevelClient;component/Resources/sign_warning.png"));
break; break;
case EvaluationType.Red: case EvaluationType.Red:
this.imageEvaluation.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalTestClient;component/Resources/delete2.png")); this.imageEvaluation.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalDevelClient;component/Resources/delete2.png"));
break; break;
default: default:
break; break;
@ -268,9 +269,22 @@ namespace BreCalClient
else else
this.imageEvaluation.ToolTip = null; this.imageEvaluation.ToolTip = null;
//Times? bsmdTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.BSMD);
//if (bsmdTimes != null)
this.textBlockBerth.Text = this.ShipcallControlModel?.GetBerthText(null); this.textBlockBerth.Text = this.ShipcallControlModel?.GetBerthText(null);
this.textBlockDraft.Text = (this.ShipcallControlModel?.Shipcall?.Draft != null) ? $"{this.ShipcallControlModel?.Shipcall?.Draft.Value.ToString("N2")} m" : "-"; //else
this.textBlockETA.Text = this.ShipcallControlModel?.GetETAETD(true); // this.textBlockBerth.Text = this.ShipcallControlModel?.Berth;
this.textBlockCallsign.Text = this.ShipcallControlModel?.Ship?.Callsign;
if (this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival)
{
this.textBlockETA.Text = this.ShipcallControlModel?.Shipcall?.Eta?.ToString("dd.MM. HH:mm");
}
if ((this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Departure) || (this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Shifting))
{
this.labelETA.Text = "ETD";
this.textBlockETA.Text = this.ShipcallControlModel?.Shipcall?.Etd?.ToString("dd.MM. HH:mm");
}
this.textBlockIMO.Text = this.ShipcallControlModel?.Ship?.Imo.ToString(); this.textBlockIMO.Text = this.ShipcallControlModel?.Ship?.Imo.ToString();
this.textBlockLengthWidth.Text = $"{this.ShipcallControlModel?.Ship?.Length} / {this.ShipcallControlModel?.Ship?.Width}"; this.textBlockLengthWidth.Text = $"{this.ShipcallControlModel?.Ship?.Length} / {this.ShipcallControlModel?.Ship?.Width}";
@ -280,7 +294,6 @@ namespace BreCalClient
if (this.ShipcallControlModel?.Shipcall?.Type != ShipcallType.Arrival) if (this.ShipcallControlModel?.Shipcall?.Type != ShipcallType.Arrival)
{ {
this.labelETA.Text = "ETD";
this.labelETAETDAgent.Content = "ETD"; this.labelETAETDAgent.Content = "ETD";
this.labelETAETDMooring.Content = "ETD"; this.labelETAETDMooring.Content = "ETD";
this.labelETAETDPilot.Content = "ETD"; this.labelETAETDPilot.Content = "ETD";
@ -302,9 +315,6 @@ namespace BreCalClient
if (this.ShipcallControlModel != null) if (this.ShipcallControlModel != null)
{ {
this.textBlockHarbour.Text = ((ShipcallControlModel != null) && (ShipcallControlModel.Shipcall != null) && BreCalLists.PortLookupDict.ContainsKey(ShipcallControlModel.Shipcall.PortId)) ?
BreCalLists.PortLookupDict[ShipcallControlModel.Shipcall.PortId].Name : "";
Extensions.ParticipantType? lastToUpdate = this.ShipcallControlModel?.LastParticipantTypeToUpdate();
Times? agencyTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.AGENCY); Times? agencyTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.AGENCY);
if (agencyTimes != null) if (agencyTimes != null)
@ -313,19 +323,7 @@ namespace BreCalClient
this.labelAgencyETAETDValue.Content = agencyTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival); this.labelAgencyETAETDValue.Content = agencyTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival);
this.textBlockAgencyRemarks.Text = agencyTimes.Remarks.TruncateDots(50); this.textBlockAgencyRemarks.Text = agencyTimes.Remarks.TruncateDots(50);
this.textBlockAgencyBerthRemarks.Text = agencyTimes.BerthInfo.TruncateDots(50); this.textBlockAgencyBerthRemarks.Text = agencyTimes.BerthInfo.TruncateDots(50);
DateTime? lc = this.ShipcallControlModel?.GetLastChangeForType(Extensions.ParticipantType.AGENCY); this.textBlockDraft.Text = ShipcallControlModel?.Shipcall?.Draft?.ToString("N2");
this.labelLastChangeAgency.Content = lc.HasValue ? lc.Value.ToString("dd.MM.yyyy HH:mm") : string.Empty;
Grid.SetRowSpan(this.labelAgent, 1);
if(lastToUpdate == Extensions.ParticipantType.AGENCY)
{
this.labelLastChangeAgency.Foreground = Brushes.White;
this.labelLastChangeAgency.FontWeight = FontWeights.DemiBold;
}
else
{
this.labelLastChangeAgency.Foreground = Brushes.LightGray;
this.labelLastChangeAgency.FontWeight = FontWeights.Normal;
}
} }
else else
{ {
@ -334,7 +332,7 @@ namespace BreCalClient
this.labelAgencyETAETDValue.Content = "- / -"; this.labelAgencyETAETDValue.Content = "- / -";
this.textBlockAgencyRemarks.Text = ""; this.textBlockAgencyRemarks.Text = "";
this.textBlockAgencyBerthRemarks.Text = ""; this.textBlockAgencyBerthRemarks.Text = "";
Grid.SetRowSpan(this.labelAgent, 2); this.textBlockDraft.Text = "";
} }
Times? mooringTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.MOORING); Times? mooringTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.MOORING);
@ -358,26 +356,12 @@ namespace BreCalClient
labelTimesMooringATD.Content = mooringTimes.Atd.Value.ToString("dd.MM.yyyy HH:mm"); labelTimesMooringATD.Content = mooringTimes.Atd.Value.ToString("dd.MM.yyyy HH:mm");
} }
DateTime? lc = this.ShipcallControlModel?.GetLastChangeForType(Extensions.ParticipantType.MOORING);
this.labelLastChangeMooring.Content = lc.HasValue ? lc.Value.ToString("dd.MM.yyyy HH:mm") : string.Empty;
Grid.SetRowSpan(this.labelMooring, 1);
if (lastToUpdate == Extensions.ParticipantType.MOORING)
{
this.labelLastChangeMooring.Foreground = Brushes.White;
this.labelLastChangeMooring.FontWeight = FontWeights.DemiBold;
}
else
{
this.labelLastChangeMooring.Foreground = Brushes.LightGray;
this.labelLastChangeMooring.FontWeight = FontWeights.Normal;
}
} }
else else
{ {
this.labelMooringETAETDValue.Content = "- / "; this.labelMooringETAETDValue.Content = "- / ";
this.textBlockMooringRemarks.Text = ""; this.textBlockMooringRemarks.Text = "";
this.imageMooringLocked.Visibility = Visibility.Hidden; this.imageMooringLocked.Visibility = Visibility.Hidden;
Grid.SetRowSpan(this.labelMooring, 2);
} }
Times? portAuthorityTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.PORT_ADMINISTRATION); Times? portAuthorityTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.PORT_ADMINISTRATION);
@ -391,27 +375,12 @@ namespace BreCalClient
{ {
labelPortAuthorityLockTime.Content = portAuthorityTimes.LockTime.Value.ToString("dd.MM.yyyy HH:mm"); labelPortAuthorityLockTime.Content = portAuthorityTimes.LockTime.Value.ToString("dd.MM.yyyy HH:mm");
} }
DateTime? lc = this.ShipcallControlModel?.GetLastChangeForType(Extensions.ParticipantType.PORT_ADMINISTRATION);
this.labelLastChangePortAuthority.Content = lc.HasValue ? lc.Value.ToString("dd.MM.yyyy HH:mm") : string.Empty;
Grid.SetRowSpan(this.labelPortAuthority, 1);
if (lastToUpdate == Extensions.ParticipantType.PORT_ADMINISTRATION)
{
this.labelLastChangePortAuthority.Foreground = Brushes.White;
this.labelLastChangePortAuthority.FontWeight = FontWeights.DemiBold;
}
else
{
this.labelLastChangePortAuthority.Foreground = Brushes.LightGray;
this.labelLastChangePortAuthority.FontWeight = FontWeights.Normal;
}
} }
else else
{ {
this.labelPortAuthorityETAETDValue.Content = "- / -"; this.labelPortAuthorityETAETDValue.Content = "- / -";
this.textBlockPortAuthorityRemarks.Text = ""; this.textBlockPortAuthorityRemarks.Text = "";
this.imagePortAuthorityLocked.Visibility = Visibility.Hidden; this.imagePortAuthorityLocked.Visibility = Visibility.Hidden;
Grid.SetRowSpan(this.labelPortAuthority, 2);
} }
Times? pilotTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.PILOT); Times? pilotTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.PILOT);
@ -420,27 +389,12 @@ namespace BreCalClient
this.labelPilotETAETDValue.Content = pilotTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival); this.labelPilotETAETDValue.Content = pilotTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival);
this.textBlockPilotRemarks.Text = pilotTimes.Remarks.TruncateDots(50); this.textBlockPilotRemarks.Text = pilotTimes.Remarks.TruncateDots(50);
this.imagePilotLocked.Visibility = (pilotTimes.EtaBerthFixed ?? false) ? Visibility.Visible : Visibility.Hidden; this.imagePilotLocked.Visibility = (pilotTimes.EtaBerthFixed ?? false) ? Visibility.Visible : Visibility.Hidden;
DateTime? lc = this.ShipcallControlModel?.GetLastChangeForType(Extensions.ParticipantType.PILOT);
this.labelLastChangePilot.Content = lc.HasValue ? lc.Value.ToString("dd.MM.yyyy HH:mm") : string.Empty;
Grid.SetRowSpan(this.labelPilot, 1);
if (lastToUpdate == Extensions.ParticipantType.PILOT)
{
this.labelLastChangePilot.Foreground = Brushes.White;
this.labelLastChangePilot.FontWeight = FontWeights.DemiBold;
}
else
{
this.labelLastChangePilot.Foreground = Brushes.LightGray;
this.labelLastChangePilot.FontWeight = FontWeights.Normal;
}
} }
else else
{ {
this.labelPilotETAETDValue.Content = "- / -"; this.labelPilotETAETDValue.Content = "- / -";
this.textBlockPilotRemarks.Text = ""; this.textBlockPilotRemarks.Text = "";
this.imagePilotLocked.Visibility = Visibility.Hidden; this.imagePilotLocked.Visibility = Visibility.Hidden;
Grid.SetRowSpan(this.labelPilot, 2);
} }
Times? tugTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.TUG); Times? tugTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.TUG);
@ -449,27 +403,12 @@ namespace BreCalClient
this.labelTugETAETDValue.Content = tugTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival); this.labelTugETAETDValue.Content = tugTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival);
this.textBlockTugRemarks.Text = tugTimes.Remarks.TruncateDots(50); this.textBlockTugRemarks.Text = tugTimes.Remarks.TruncateDots(50);
this.imageTugLocked.Visibility = (tugTimes.EtaBerthFixed ?? false) ? Visibility.Visible : Visibility.Hidden; this.imageTugLocked.Visibility = (tugTimes.EtaBerthFixed ?? false) ? Visibility.Visible : Visibility.Hidden;
DateTime? lc = this.ShipcallControlModel?.GetLastChangeForType(Extensions.ParticipantType.TUG);
this.labelLastChangeTug.Content = lc.HasValue ? lc.Value.ToString("dd.MM.yyyy HH:mm") : string.Empty;
Grid.SetRowSpan(this.labelTug, 1);
if (lastToUpdate == Extensions.ParticipantType.TUG)
{
this.labelLastChangeTug.Foreground = Brushes.White;
this.labelLastChangeTug.FontWeight = FontWeights.DemiBold;
}
else
{
this.labelLastChangeTug.Foreground = Brushes.LightGray;
this.labelLastChangeTug.FontWeight = FontWeights.Normal;
}
} }
else else
{ {
this.labelTugETAETDValue.Content = "- / -"; this.labelTugETAETDValue.Content = "- / -";
this.textBlockTugRemarks.Text = ""; this.textBlockTugRemarks.Text = "";
this.imageTugLocked.Visibility = Visibility.Hidden; this.imageTugLocked.Visibility = Visibility.Hidden;
Grid.SetRowSpan(this.labelTug, 2);
} }
this.rowDefinitionTerminalBerth.Height = (this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival) ? new(14) : new(0); this.rowDefinitionTerminalBerth.Height = (this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival) ? new(14) : new(0);
@ -483,19 +422,6 @@ namespace BreCalClient
this.labelOperationsStart.Content = terminalTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival); this.labelOperationsStart.Content = terminalTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival);
this.textBlockTerminalRemarks.Text = terminalTimes.Remarks.TruncateDots(40); this.textBlockTerminalRemarks.Text = terminalTimes.Remarks.TruncateDots(40);
this.textBlockTerminalBerthRemarks.Text = terminalTimes.BerthInfo.TruncateDots(40); this.textBlockTerminalBerthRemarks.Text = terminalTimes.BerthInfo.TruncateDots(40);
DateTime? lc = this.ShipcallControlModel?.GetLastChangeForType(Extensions.ParticipantType.TERMINAL);
this.labelLastChangeTerminal.Content = lc.HasValue ? lc.Value.ToString("dd.MM.yyyy HH:mm") : string.Empty;
Grid.SetRowSpan(this.labelTerminal, 1);
if (lastToUpdate == Extensions.ParticipantType.TERMINAL)
{
this.labelLastChangeTerminal.Foreground = Brushes.White;
this.labelLastChangeTerminal.FontWeight = FontWeights.DemiBold;
}
else
{
this.labelLastChangeTerminal.Foreground = Brushes.LightGray;
this.labelLastChangeTerminal.FontWeight = FontWeights.Normal;
}
} }
else else
{ {
@ -503,7 +429,6 @@ namespace BreCalClient
this.labelOperationsStart.Content = ""; this.labelOperationsStart.Content = "";
this.textBlockTerminalRemarks.Text = ""; this.textBlockTerminalRemarks.Text = "";
this.textBlockTerminalBerthRemarks.Text = ""; this.textBlockTerminalBerthRemarks.Text = "";
Grid.SetRowSpan(this.labelTerminal, 2);
} }
} }

View File

@ -6,7 +6,6 @@ using BreCalClient.misc.Api;
using BreCalClient.misc.Model; using BreCalClient.misc.Model;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
namespace BreCalClient namespace BreCalClient
{ {
@ -39,8 +38,6 @@ namespace BreCalClient
public string? Berth { get; set; } public string? Berth { get; set; }
public string LastErrorMessage { get; private set; } = "";
internal Dictionary<Extensions.ParticipantType, ParticipantAssignment> AssignedParticipants { get; } = new(); internal Dictionary<Extensions.ParticipantType, ParticipantAssignment> AssignedParticipants { get; } = new();
public List<Times> Times { get; set; } = new(); public List<Times> Times { get; set; } = new();
@ -118,8 +115,6 @@ namespace BreCalClient
} }
} }
public bool AllowPortChange { get; set; } = true;
#endregion #endregion
#region public methods #region public methods
@ -219,7 +214,7 @@ namespace BreCalClient
return berthText; return berthText;
} }
public string GetETAETD(bool useShortVersion = false) public string GetETAETD()
{ {
DateTime theDate = DateTime.Now; DateTime theDate = DateTime.Now;
if(this.Shipcall != null) if(this.Shipcall != null)
@ -243,8 +238,6 @@ namespace BreCalClient
theDate = agentTimes.EtdBerth.Value; theDate = agentTimes.EtdBerth.Value;
} }
} }
if(useShortVersion)
return theDate.ToString("dd.MM. HH:mm");
return theDate.ToString(); return theDate.ToString();
} }
@ -254,9 +247,8 @@ namespace BreCalClient
/// This function updates the assignments for existing times records accordingly and saves them. /// This function updates the assignments for existing times records accordingly and saves them.
/// </summary> /// </summary>
/// <param name="_api">API reference to PUT eidted times</param> /// <param name="_api">API reference to PUT eidted times</param>
internal async Task<bool> UpdateTimesAssignments(TimesApi api) internal async void UpdateTimesAssignments(TimesApi api)
{ {
foreach (Extensions.ParticipantType participantType in this.AssignedParticipants.Keys) foreach (Extensions.ParticipantType participantType in this.AssignedParticipants.Keys)
{ {
Times? times = this.GetTimesForParticipantType(participantType); Times? times = this.GetTimesForParticipantType(participantType);
@ -264,16 +256,8 @@ namespace BreCalClient
if(times.ParticipantId != this.AssignedParticipants[participantType].ParticipantId) if(times.ParticipantId != this.AssignedParticipants[participantType].ParticipantId)
{ {
times.ParticipantId = this.AssignedParticipants[participantType].ParticipantId; times.ParticipantId = this.AssignedParticipants[participantType].ParticipantId;
try
{
await api.TimesUpdateAsync(times); await api.TimesUpdateAsync(times);
} }
catch (Exception ex)
{
LastErrorMessage = ex.Message;
return false;
}
}
} }
// if somebody just removed an assignment it is gone fom AssignedParticipants, but there is still a // if somebody just removed an assignment it is gone fom AssignedParticipants, but there is still a
@ -298,55 +282,10 @@ namespace BreCalClient
} }
foreach(Times times in deleteTimes) foreach(Times times in deleteTimes)
{
try
{ {
api.TimesDelete(times.Id); api.TimesDelete(times.Id);
this.Times.Remove(times); this.Times.Remove(times);
} }
catch (Exception ex)
{
LastErrorMessage = ex.Message;
return false;
}
}
return true;
}
public DateTime? GetLastChangeForType(Extensions.ParticipantType type)
{
DateTime? lastChange = null;
Times? times = GetTimesForParticipantType(type);
if(times != null)
{
if (times.Modified.HasValue) lastChange = times.Modified.Value;
else lastChange = times.Created;
}
return lastChange;
}
public Extensions.ParticipantType? LastParticipantTypeToUpdate()
{
Extensions.ParticipantType? last = null;
DateTime min = DateTime.MinValue;
foreach(Times times in this.Times)
{
if (times.Modified.HasValue)
{
if (times.Modified.Value > min)
{
min = times.Modified.Value;
last = (Extensions.ParticipantType)times.ParticipantType;
continue;
}
}
if(times.Created > min)
{
min = times.Created;
last = (Extensions.ParticipantType)times.ParticipantType;
}
}
return last;
} }
#endregion #endregion

View File

@ -1,73 +0,0 @@
// Copyright (c) 2024- schick Informatik
// Description:
//
using System;
using System.ComponentModel;
using System.Windows;
using ToastNotifications;
using ToastNotifications.Core;
using ToastNotifications.Lifetime;
using ToastNotifications.Lifetime.Clear;
using ToastNotifications.Messages;
using ToastNotifications.Position;
namespace BreCalClient
{
internal class ToastViewModel : INotifyPropertyChanged
{
private readonly Notifier _notifier;
public ToastViewModel()
{
_notifier = new Notifier(cfg =>
{
cfg.PositionProvider = new WindowPositionProvider(
parentWindow: Application.Current.MainWindow,
corner: Corner.BottomRight,
offsetX: 25,
offsetY: 100);
cfg.LifetimeSupervisor = new TimeAndCountBasedLifetimeSupervisor(
notificationLifetime: TimeSpan.FromSeconds(30),
maximumNotificationCount: MaximumNotificationCount.FromCount(6));
cfg.Dispatcher = Application.Current.Dispatcher;
cfg.DisplayOptions.TopMost = false;
cfg.DisplayOptions.Width = 250;
});
_notifier.ClearMessages(new ClearAll());
}
public void OnUnloaded()
{
_notifier.Dispose();
}
public void ShowAppNotification(string message, MessageOptions options)
{
_notifier?.ShowAppNotification(message, options);
}
public void ShowAppNotification(string message)
{
_notifier?.ShowAppNotification(message);
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string? propertyName = null)
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void ClearAll()
{
_notifier.ClearMessages(new ClearAll());
}
}
}

View File

@ -8,7 +8,7 @@
<applicationSettings> <applicationSettings>
<RoleEditor.Properties.Settings> <RoleEditor.Properties.Settings>
<setting name="ConnectionString" serializeAs="String"> <setting name="ConnectionString" serializeAs="String">
<value>Server=localhost;User ID=ds;Password=HalloWach_2323XXL!!;Database=bremen_calling_test;Port=33307</value> <value>Server=localhost;User ID=ds;Password=HalloWach_2323XXL!!;Database=bremen_calling_devel;Port=33306</value>
</setting> </setting>
</RoleEditor.Properties.Settings> </RoleEditor.Properties.Settings>
</applicationSettings> </applicationSettings>

View File

@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RoleEditor" xmlns:local="clr-namespace:RoleEditor"
mc:Ignorable="d" mc:Ignorable="d"
Title="Edit berth" Height="216" Width="450" Loaded="Window_Loaded"> Title="Edit berth" Height="188" Width="450" Loaded="Window_Loaded">
<Grid x:Name="berthGrid"> <Grid x:Name="berthGrid">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width=".3*" /> <ColumnDefinition Width=".3*" />
@ -16,7 +16,6 @@
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
@ -46,9 +45,7 @@
</Grid> </Grid>
<Label Content="Uses lock" HorizontalAlignment="Right" Grid.Row="3" /> <Label Content="Uses lock" HorizontalAlignment="Right" Grid.Row="3" />
<CheckBox x:Name="checkBoxLock" Grid.Row="3" Grid.Column="1" VerticalAlignment="Center" Margin="2" IsChecked="{Binding Path=Lock, Mode=OneWay}"/> <CheckBox x:Name="checkBoxLock" Grid.Row="3" Grid.Column="1" VerticalAlignment="Center" Margin="2" IsChecked="{Binding Path=Lock, Mode=OneWay}"/>
<Label Content="Port" HorizontalAlignment="Right" Grid.Row="4" /> <StackPanel Grid.Column="1" Grid.Row="5" Orientation="Horizontal" FlowDirection="RightToLeft">
<ComboBox Name="comboBoxPorts" Margin="2" Grid.Column="1" Grid.Row="4" SelectedItem="{Binding Port, Mode=OneWay}" />
<StackPanel Grid.Column="1" Grid.Row="6" Orientation="Horizontal" FlowDirection="RightToLeft">
<Button x:Name="buttonCancel" Width="80" Content="Cancel" Margin="2" Click="buttonCancel_Click" /> <Button x:Name="buttonCancel" Width="80" Content="Cancel" Margin="2" Click="buttonCancel_Click" />
<Button x:Name="buttonOK" Width="80" Content="OK" Margin="2" Click="buttonOK_Click"/> <Button x:Name="buttonOK" Width="80" Content="OK" Margin="2" Click="buttonOK_Click"/>
</StackPanel> </StackPanel>

View File

@ -20,8 +20,6 @@ namespace RoleEditor
public List<Participant> Authorities { get; } = new List<Participant>(); public List<Participant> Authorities { get; } = new List<Participant>();
public List<Port> Ports { get; } = new List<Port>();
private void buttonCancel_Click(object sender, RoutedEventArgs e) private void buttonCancel_Click(object sender, RoutedEventArgs e)
{ {
this.DialogResult = false; this.DialogResult = false;
@ -45,12 +43,6 @@ namespace RoleEditor
else else
this.Berth.Authority_Id = null; this.Berth.Authority_Id = null;
this.Berth.Port = this.comboBoxPorts.SelectedItem as Port;
if (this.Berth.Port != null)
this.Berth.Port_Id = this.Berth.Port.Id;
else
this.Berth.Port_Id = null;
this.DialogResult = true; this.DialogResult = true;
this.Close(); this.Close();
} }
@ -60,7 +52,6 @@ namespace RoleEditor
this.DataContext = this.Berth; this.DataContext = this.Berth;
this.comboBoxParticipants.ItemsSource = this.Owners; this.comboBoxParticipants.ItemsSource = this.Owners;
this.comboBoxAuthorities.ItemsSource = this.Authorities; this.comboBoxAuthorities.ItemsSource = this.Authorities;
this.comboBoxPorts.ItemsSource = this.Ports;
} }
private void buttonResetParticipant_Click(object sender, RoutedEventArgs e) private void buttonResetParticipant_Click(object sender, RoutedEventArgs e)

View File

@ -6,7 +6,7 @@
xmlns:local="clr-namespace:RoleEditor" xmlns:local="clr-namespace:RoleEditor"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d" mc:Ignorable="d"
Title="Bremen calling admin editor" Height="670" Width="1024" Icon="Resources/lock_preferences.ico" Loaded="Window_Loaded"> Title="Bremen calling admin editor" Height="670" Width="800" Icon="Resources/lock_preferences.ico" Loaded="Window_Loaded">
<Grid> <Grid>
<TabControl> <TabControl>
<TabItem Header="Participant, users and roles"> <TabItem Header="Participant, users and roles">
@ -16,9 +16,8 @@
<RowDefinition Height=".5*" /> <RowDefinition Height=".5*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width=".4*" /> <ColumnDefinition Width=".5*" />
<ColumnDefinition Width=".3*" /> <ColumnDefinition Width=".5*" />
<ColumnDefinition Width=".3*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<GroupBox Header="Participant" Margin="2"> <GroupBox Header="Participant" Margin="2">
<Grid> <Grid>
@ -59,7 +58,7 @@
<Label Content="Street" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right"/> <Label Content="Street" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right"/>
<Label Content="Postal code" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right"/> <Label Content="Postal code" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right"/>
<Label Content="City" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Right"/> <Label Content="City" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Right"/>
<Label Content="Deleted" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Right"/> <Label Content="Active" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Right"/>
<Label Content="Type" Grid.Row="5" Grid.Column="1" HorizontalAlignment="Right"/> <Label Content="Type" Grid.Row="5" Grid.Column="1" HorizontalAlignment="Right"/>
<Label Content="Created" Grid.Row="6" Grid.Column="1" HorizontalAlignment="Right"/> <Label Content="Created" Grid.Row="6" Grid.Column="1" HorizontalAlignment="Right"/>
<Label Content="Modified" Grid.Row="7" Grid.Column="1" HorizontalAlignment="Right"/> <Label Content="Modified" Grid.Row="7" Grid.Column="1" HorizontalAlignment="Right"/>
@ -67,7 +66,7 @@
<TextBox x:Name="textBoxParticipantStreet" Grid.Row="1" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" /> <TextBox x:Name="textBoxParticipantStreet" Grid.Row="1" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<TextBox x:Name="textBoxParticipantPostalCode" Grid.Row="2" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" /> <TextBox x:Name="textBoxParticipantPostalCode" Grid.Row="2" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<TextBox x:Name="textBoxParticipantCity" Grid.Row="3" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" /> <TextBox x:Name="textBoxParticipantCity" Grid.Row="3" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<CheckBox x:Name="checkboxParticipantDeleted" Grid.Row="4" Grid.Column="2" VerticalAlignment="Center" IsEnabled="False" /> <CheckBox x:Name="checkboxParticipantActive" Grid.Row="4" Grid.Column="2" VerticalAlignment="Center" />
<xctk:CheckComboBox x:Name="comboBoxParticipantType" Grid.Row="5" Grid.Column="2" Margin="2" SelectedValue="Key" DisplayMemberPath="Value" /> <xctk:CheckComboBox x:Name="comboBoxParticipantType" Grid.Row="5" Grid.Column="2" Margin="2" SelectedValue="Key" DisplayMemberPath="Value" />
<TextBox x:Name="textBoxParticipantCreated" Grid.Row="6" IsReadOnly="True" IsEnabled="False" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" /> <TextBox x:Name="textBoxParticipantCreated" Grid.Row="6" IsReadOnly="True" IsEnabled="False" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<StackPanel Orientation="Horizontal" Grid.Row="7" Grid.Column="0"> <StackPanel Orientation="Horizontal" Grid.Row="7" Grid.Column="0">
@ -83,69 +82,6 @@
</Button> </Button>
</Grid> </Grid>
</GroupBox> </GroupBox>
<GroupBox Header="Port Assignment" Margin="2" Grid.Row="0" Grid.Column="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<ListBox x:Name="listBoxPortAssignment" Margin="2" Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" />
<Button x:Name="buttonAddPortAssignment" Margin="2" Grid.Row="0" Grid.Column="1" Click="buttonAddPortAssignment_Click">
<Image Source="./Resources/arrow_left_green.png"/>
</Button>
<Button x:Name="buttonRemovePortAssignment" Margin="2" Grid.Row="1" Grid.Column="1" Click="buttonRemovePortAssignment_Click">
<Image Source="./Resources/delete2.png"/>
</Button>
</Grid>
</GroupBox>
<GroupBox Header="Ports" Margin="2" Grid.Row="0" Grid.Column="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width=".38*" />
<ColumnDefinition Width=".62*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="listBoxPort" Margin="2" Grid.RowSpan="4" SelectionChanged="listBoxPort_SelectionChanged">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem x:Name="menuItemNewPort" Header="New.." Click="menuItemNewPort_Click">
<MenuItem.Icon>
<Image Source="Resources/add.png" />
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="menuItemDeletePort" Header="Delete" Click="menuItemDeletePort_Click">
<MenuItem.Icon>
<Image Source="Resources/delete2.png" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
<Label Grid.Row="0" Grid.Column="1" Content="Name" HorizontalAlignment="Right"/>
<Label Grid.Row="1" Grid.Column="1" Content="Locode" HorizontalAlignment="Right"/>
<TextBox x:Name="textBoxPortName" Grid.Row="0" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" MaxLength="128"/>
<TextBox x:Name="textBoxPortLocode" Grid.Row="1" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" MaxLength="5" />
<Button x:Name="buttonPortSave" Grid.Row="2" Grid.Column="2" Click="buttonPortSave_Click" Margin="2">
<DockPanel>
<Image Source="./Resources/disk_blue.png" Margin="0,0,5,0" Height="24" DockPanel.Dock="Left" Width="16"/>
<TextBlock Text="Save" VerticalAlignment="Center" DockPanel.Dock="Right"/>
</DockPanel>
</Button>
</Grid>
</GroupBox>
<GroupBox Header="User" Margin="2" Grid.Row="1"> <GroupBox Header="User" Margin="2" Grid.Row="1">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
@ -167,7 +103,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ListBox x:Name="listBoxUser" Margin="2" Grid.RowSpan="9" SelectionChanged="listBoxUser_SelectionChanged"> <ListBox x:Name="listBoxUser" Margin="2" Grid.RowSpan="9" SelectionChanged="listBoxUser_SelectionChanged">
<ListBox.ContextMenu> <ListBox.ContextMenu>
<ContextMenu Name="contextMenuUser"> <ContextMenu>
<MenuItem x:Name="menuItemNewUser" Header="New.." Click="menuItemNewUser_Click"> <MenuItem x:Name="menuItemNewUser" Header="New.." Click="menuItemNewUser_Click">
<MenuItem.Icon> <MenuItem.Icon>
<Image Source="Resources/add.png" /> <Image Source="Resources/add.png" />
@ -209,7 +145,7 @@
</Button> </Button>
</Grid> </Grid>
</GroupBox> </GroupBox>
<GroupBox Header="Role" Margin="2" Grid.Column="1" Grid.Row="1"> <GroupBox Header="Role" Margin="2" Grid.Column="1">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="28"/> <RowDefinition Height="28"/>
@ -263,7 +199,7 @@
</Button> </Button>
</Grid> </Grid>
</GroupBox> </GroupBox>
<GroupBox Header="Securable" Margin="2" Grid.Row="1" Grid.Column="2"> <GroupBox Header="Securable" Margin="2" Grid.Row="1" Grid.Column="1">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="28"/> <RowDefinition Height="28"/>
@ -340,7 +276,6 @@
<DataGridCheckBoxColumn Header="Lock" Binding="{Binding Path=Lock}" IsReadOnly="True"/> <DataGridCheckBoxColumn Header="Lock" Binding="{Binding Path=Lock}" IsReadOnly="True"/>
<DataGridTextColumn Header="Terminal" Binding="{Binding Path=Terminal, Mode=OneWay}" IsReadOnly="True"/> <DataGridTextColumn Header="Terminal" Binding="{Binding Path=Terminal, Mode=OneWay}" IsReadOnly="True"/>
<DataGridTextColumn Header="Authority" Binding="{Binding Path=Authority_Text, Mode=OneWay}" IsReadOnly="True" /> <DataGridTextColumn Header="Authority" Binding="{Binding Path=Authority_Text, Mode=OneWay}" IsReadOnly="True" />
<DataGridTextColumn Header="Port" Binding="{Binding Path=Port}" IsReadOnly="True" />
<DataGridTextColumn Header="Deleted" Binding="{Binding Path=Deleted, Mode=OneWay}" IsReadOnly="True" /> <DataGridTextColumn Header="Deleted" Binding="{Binding Path=Deleted, Mode=OneWay}" IsReadOnly="True" />
</DataGrid.Columns> </DataGrid.Columns>
</local:ENIDataGrid> </local:ENIDataGrid>

View File

@ -38,8 +38,6 @@ namespace RoleEditor
private readonly ObservableCollection<SecurableAssignment> _assignedSecurables = new ObservableCollection<SecurableAssignment>(); private readonly ObservableCollection<SecurableAssignment> _assignedSecurables = new ObservableCollection<SecurableAssignment>();
private readonly ObservableCollection<Berth> _berths = new ObservableCollection<Berth>(); private readonly ObservableCollection<Berth> _berths = new ObservableCollection<Berth>();
private readonly ObservableCollection<Ship> _ships = new ObservableCollection<Ship>(); private readonly ObservableCollection<Ship> _ships = new ObservableCollection<Ship>();
private readonly ObservableCollection<Port> _ports = new ObservableCollection<Port>();
private readonly ObservableCollection<PortAssignment> _assignedPorts = new ObservableCollection<PortAssignment>();
private DBManager _dbManager; private DBManager _dbManager;
#endregion #endregion
@ -61,8 +59,6 @@ namespace RoleEditor
// load all participants // load all participants
List<Participant> participants = await Participant.LoadAll(_dbManager); List<Participant> participants = await Participant.LoadAll(_dbManager);
participants.Sort((x, y) => string.Compare(x.Name, y.Name));
foreach (Participant p in participants) foreach (Participant p in participants)
{ {
_participants.Add(p); _participants.Add(p);
@ -81,10 +77,6 @@ namespace RoleEditor
_securables.Add(s); _securables.Add(s);
this.listBoxSecurables.ItemsSource = _securables; this.listBoxSecurables.ItemsSource = _securables;
// load all ports
foreach (Port port in await Port.LoadAll(_dbManager)) _ports.Add(port);
this.listBoxPort.ItemsSource = _ports;
// load all berths // load all berths
foreach (Berth b in await Berth.LoadAll(_dbManager)) foreach (Berth b in await Berth.LoadAll(_dbManager))
{ {
@ -97,10 +89,6 @@ namespace RoleEditor
{ {
b.Authority = participants.Where(p => p.Id == b.Authority_Id).FirstOrDefault(); b.Authority = participants.Where(p => p.Id == b.Authority_Id).FirstOrDefault();
} }
if (b.Port_Id != null)
{
b.Port = _ports.Where(p => p.Id == b.Port_Id).FirstOrDefault();
}
} }
this.dataGridBerths.Initialize(); this.dataGridBerths.Initialize();
this.dataGridBerths.ItemsSource = _berths; this.dataGridBerths.ItemsSource = _berths;
@ -126,12 +114,10 @@ namespace RoleEditor
this.dataGridShips.EditRequested += DataGridShips_EditRequested; this.dataGridShips.EditRequested += DataGridShips_EditRequested;
this.dataGridShips.DeleteRequested += DataGridShips_DeleteRequested; this.dataGridShips.DeleteRequested += DataGridShips_DeleteRequested;
// set other item sources (filled later after selection) // set other item sources (filled later after selection)
this.listBoxUser.ItemsSource = _users; this.listBoxUser.ItemsSource = _users;
this.listBoxRoleSecurables.ItemsSource = _assignedSecurables; this.listBoxRoleSecurables.ItemsSource = _assignedSecurables;
this.listBoxUserRoles.ItemsSource = _assignedRoles; this.listBoxUserRoles.ItemsSource = _assignedRoles;
this.listBoxPortAssignment.ItemsSource = _assignedPorts;
this.comboBoxParticipantType.ItemsSource = EnumHelper.GetAllValuesAndDescription(typeof(Participant.ParticipantType)); this.comboBoxParticipantType.ItemsSource = EnumHelper.GetAllValuesAndDescription(typeof(Participant.ParticipantType));
@ -206,7 +192,6 @@ namespace RoleEditor
ebd.Berth = b; ebd.Berth = b;
ebd.Owners.AddRange(this._terminals); ebd.Owners.AddRange(this._terminals);
ebd.Authorities.AddRange(this._authorities); ebd.Authorities.AddRange(this._authorities);
ebd.Ports.AddRange(this._ports.Where(p => !p.IsDeleted));
if (ebd.ShowDialog() ?? false) if (ebd.ShowDialog() ?? false)
{ {
await b.Save(_dbManager); await b.Save(_dbManager);
@ -223,7 +208,6 @@ namespace RoleEditor
ebd.Berth = b; ebd.Berth = b;
ebd.Owners.AddRange(this._terminals); ebd.Owners.AddRange(this._terminals);
ebd.Authorities.AddRange(this._authorities); ebd.Authorities.AddRange(this._authorities);
ebd.Ports.AddRange(_ports.Where(p => !p.IsDeleted));
if (ebd.ShowDialog() ?? false) if (ebd.ShowDialog() ?? false)
{ {
_berths.Add(b); _berths.Add(b);
@ -402,59 +386,6 @@ namespace RoleEditor
} }
} }
private async void buttonPortSave_Click(object sender, RoutedEventArgs e)
{
Port? p = this.listBoxPort.SelectedItem as Port;
if (p != null)
{
p.Name = this.textBoxPortName.Text.Trim();
p.Locode = this.textBoxPortLocode.Text.Trim();
await p.Save(_dbManager);
this.listBoxPort.ItemsSource = null;
this.listBoxPort.ItemsSource = _ports;
this.listBoxPort.SelectedItem = p;
}
}
private async void buttonAddPortAssignment_Click(object sender, RoutedEventArgs e)
{
if ((this.listBoxPort.SelectedItem is Port p) && (this.listBoxParticipant.SelectedItem is Participant pa))
{
// test if assignment is already present
bool foundMatchingAssignment = false;
foreach (PortAssignment portAssignment in _assignedPorts)
{
if ((portAssignment.PortId == p.Id) && (portAssignment.ParticipantId == pa.Id))
{
foundMatchingAssignment = true;
break;
}
}
if (!foundMatchingAssignment)
{
PortAssignment portAssignment = new PortAssignment();
portAssignment.PortId = (int)p.Id;
portAssignment.ParticipantId = (int)pa.Id;
portAssignment.AssignedParticipant = pa;
portAssignment.AssignedPort = p;
await portAssignment.Save(_dbManager);
_assignedPorts.Add(portAssignment);
}
}
}
private async void buttonRemovePortAssignment_Click(object sender, RoutedEventArgs e)
{
PortAssignment? pa = this.listBoxPortAssignment.SelectedItem as PortAssignment;
if (pa != null)
{
await pa.Delete(_dbManager);
if (_assignedPorts.Contains(pa))
_assignedPorts.Remove(pa);
}
}
#endregion #endregion
#region listbox selection callbacks #region listbox selection callbacks
@ -467,8 +398,8 @@ namespace RoleEditor
this.textBoxParticipantStreet.Text = (p != null) ? p.Street : string.Empty; this.textBoxParticipantStreet.Text = (p != null) ? p.Street : string.Empty;
this.textBoxParticipantPostalCode.Text = (p != null) ? p.PostalCode : string.Empty; this.textBoxParticipantPostalCode.Text = (p != null) ? p.PostalCode : string.Empty;
this.textBoxParticipantCity.Text = (p != null) ? p.City : string.Empty; this.textBoxParticipantCity.Text = (p != null) ? p.City : string.Empty;
// this.checkboxParticipantActive.Checked = (p != null) ? p.
this.textBoxParticipantCreated.Text = (p != null) ? p.Created.ToString() : string.Empty; this.textBoxParticipantCreated.Text = (p != null) ? p.Created.ToString() : string.Empty;
this.checkboxParticipantDeleted.IsChecked = (p != null) ? p.Deleted : null;
this.textBoxParticipantModified.Text = (p != null) ? p.Modified.ToString() : string.Empty; this.textBoxParticipantModified.Text = (p != null) ? p.Modified.ToString() : string.Empty;
this.checkBoxParticipantAllowBSMD.IsChecked = (p != null) ? p.IsFlagSet(Participant.ParticipantFlags.ALLOW_BSMD) : null; this.checkBoxParticipantAllowBSMD.IsChecked = (p != null) ? p.IsFlagSet(Participant.ParticipantFlags.ALLOW_BSMD) : null;
this.comboBoxParticipantType.SelectedItems.Clear(); this.comboBoxParticipantType.SelectedItems.Clear();
@ -499,24 +430,6 @@ namespace RoleEditor
foreach (User u in await User.LoadForParticipant(p, _dbManager)) foreach (User u in await User.LoadForParticipant(p, _dbManager))
_users.Add(u); _users.Add(u);
} }
// -> load port assignments for this participant selection
this._assignedPorts.Clear();
if(p != null)
{
foreach (PortAssignment pa in await PortAssignment.LoadForParticipant(p, this._dbManager))
{
foreach (Port port in this._ports)
if (pa.PortId == port.Id)
pa.AssignedPort = port;
_assignedPorts.Add(pa);
}
}
this.contextMenuUser.IsEnabled = !p.Deleted;
this.buttonAddPortAssignment.IsEnabled = !p.Deleted;
this.buttonRemovePortAssignment.IsEnabled = !p.Deleted;
} }
private async void listBoxRoles_SelectionChanged(object sender, SelectionChangedEventArgs e) private async void listBoxRoles_SelectionChanged(object sender, SelectionChangedEventArgs e)
@ -583,13 +496,6 @@ namespace RoleEditor
this.textBoxSecurableName.Text = (s != null) ? s.Name : string.Empty; this.textBoxSecurableName.Text = (s != null) ? s.Name : string.Empty;
} }
private void listBoxPort_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Port? p = this.listBoxPort.SelectedItem as Port;
this.textBoxPortName.Text = (p != null) ? p.Name : string.Empty;
this.textBoxPortLocode.Text = (p != null) ? p.Locode : string.Empty;
}
#endregion #endregion
#region menuitem callbacks #region menuitem callbacks
@ -601,7 +507,7 @@ namespace RoleEditor
if(this.listBoxParticipant.SelectedItem is Participant p) if(this.listBoxParticipant.SelectedItem is Participant p)
{ {
await p.Delete(_dbManager); await p.Delete(_dbManager);
p.Deleted = true; this._participants.Remove(p);
} }
} }
catch (Exception ex) catch (Exception ex)
@ -635,7 +541,6 @@ namespace RoleEditor
{ {
if (this.listBoxUser.SelectedItem is User u) if (this.listBoxUser.SelectedItem is User u)
{ {
await u.ExecuteNonQuery(_dbManager); // extra history delete happens here
await u.Delete(_dbManager); await u.Delete(_dbManager);
this._users.Remove(u); this._users.Remove(u);
} }
@ -692,29 +597,6 @@ namespace RoleEditor
} }
} }
private void menuItemNewPort_Click(object sender, RoutedEventArgs e)
{
Port p = new();
this._ports.Add(p);
this.listBoxPort.SelectedItem = p;
}
private async void menuItemDeletePort_Click(object sender, RoutedEventArgs e)
{
try
{
if (this.listBoxPort.SelectedItem is Port p)
{
await p.Delete(_dbManager);
this._ports.Remove(p);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
#endregion #endregion
#region Excel import #region Excel import
@ -752,7 +634,7 @@ namespace RoleEditor
{ {
if (reader.FieldCount < 2) if (reader.FieldCount < 2)
{ {
throw new InvalidDataException("Sheet must have at least 3 Columns of data"); throw new InvalidDataException("Sheet must have at least 2 Columns of data");
} }
if (reader.IsDBNull(0) && reader.IsDBNull(1)) continue; if (reader.IsDBNull(0) && reader.IsDBNull(1)) continue;
@ -767,20 +649,8 @@ namespace RoleEditor
if (_berths.Any(predicate: x => (x.Name != null) && x.Name.Equals(berth_name, StringComparison.OrdinalIgnoreCase))) if (_berths.Any(predicate: x => (x.Name != null) && x.Name.Equals(berth_name, StringComparison.OrdinalIgnoreCase)))
continue; continue;
string port_name = "";
if (!reader.IsDBNull(2)) port_name = reader.GetString(2);
// find port in list
if(!_ports.Any(x => (x.Name != null) && x.Name.Equals(port_name, StringComparison.OrdinalIgnoreCase)))
continue;
Port port = _ports.First(x => (x.Name != null) && x.Name.Equals(port_name, StringComparison.OrdinalIgnoreCase));
Berth b = new Berth(); Berth b = new Berth();
b.Name = berth_name; b.Name = berth_name;
b.Port = port;
b.Port_Id = port.Id;
bool found_participant = false; bool found_participant = false;
foreach(Participant p in this._participants) foreach(Participant p in this._participants)

View File

@ -12,7 +12,7 @@ namespace RoleEditor.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.10.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@ -25,8 +25,8 @@ namespace RoleEditor.Properties {
[global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("Server=localhost;User ID=ds;Password=HalloWach_2323XXL!!;Database=bremen_calling_" + [global::System.Configuration.DefaultSettingValueAttribute("Server=localhost;User ID=ds;Password=HalloWach_2323XXL!!;Database=bremen_calling;" +
"test;Port=33306")] "Port=33306")]
public string ConnectionString { public string ConnectionString {
get { get {
return ((string)(this["ConnectionString"])); return ((string)(this["ConnectionString"]));

View File

@ -3,7 +3,7 @@
<Profiles /> <Profiles />
<Settings> <Settings>
<Setting Name="ConnectionString" Type="System.String" Scope="Application"> <Setting Name="ConnectionString" Type="System.String" Scope="Application">
<Value Profile="(Default)">Server=localhost;User ID=ds;Password=HalloWach_2323XXL!!;Database=bremen_calling_test;Port=33306</Value> <Value Profile="(Default)">Server=localhost;User ID=ds;Password=HalloWach_2323XXL!!;Database=bremen_calling;Port=33306</Value>
</Setting> </Setting>
</Settings> </Settings>
</SettingsFile> </SettingsFile>

View File

@ -2,12 +2,10 @@
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows7.0</TargetFramework> <TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<ApplicationIcon>Resources\lock_preferences.ico</ApplicationIcon> <ApplicationIcon>Resources\lock_preferences.ico</ApplicationIcon>
<FileVersion>1.8.0.0</FileVersion>
<AssemblyVersion>1.8.0.0</AssemblyVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -30,8 +28,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="ExcelDataReader" Version="3.8.0" /> <PackageReference Include="ExcelDataReader" Version="3.7.0-develop00385" />
<PackageReference Include="Extended.Wpf.Toolkit" Version="5.0.0" /> <PackageReference Include="Extended.Wpf.Toolkit" Version="4.5.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -13,12 +13,6 @@
"SccProvider" = "8:" "SccProvider" = "8:"
"Hierarchy" "Hierarchy"
{ {
"Entry"
{
"MsmKey" = "8:_1E7663DCE02A4D848349229A724E961A"
"OwnerKey" = "8:_UNDEFINED"
"MsmSig" = "8:_UNDEFINED"
}
"Entry" "Entry"
{ {
"MsmKey" = "8:_3E48B6E716164CC1826E094025517B3F" "MsmKey" = "8:_3E48B6E716164CC1826E094025517B3F"
@ -31,24 +25,6 @@
"OwnerKey" = "8:_UNDEFINED" "OwnerKey" = "8:_UNDEFINED"
"MsmSig" = "8:_UNDEFINED" "MsmSig" = "8:_UNDEFINED"
} }
"Entry"
{
"MsmKey" = "8:_CD20A468610C42B89F66B4D3367A5A6A"
"OwnerKey" = "8:_UNDEFINED"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_CD20A468610C42B89F66B4D3367A5A6A"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_UNDEFINED"
"OwnerKey" = "8:_1E7663DCE02A4D848349229A724E961A"
"MsmSig" = "8:_UNDEFINED"
}
} }
"Configurations" "Configurations"
{ {
@ -100,14 +76,6 @@
{ {
"LaunchCondition" "LaunchCondition"
{ {
"{A06ECF26-33A3-4562-8140-9B0E340D4F24}:_3415D375792A4611BF998D78F56CD22C"
{
"Name" = "8:.NET Framework"
"Message" = "8:[VSDNETMSG]"
"FrameworkVersion" = "8:.NETFramework,Version=v4.7.2"
"AllowLaterVersions" = "11:FALSE"
"InstallUrl" = "8:http://go.microsoft.com/fwlink/?LinkId=863262"
}
"{A06ECF26-33A3-4562-8140-9B0E340D4F24}:_7C5ED856EDF94532A041DBACD5D5C09E" "{A06ECF26-33A3-4562-8140-9B0E340D4F24}:_7C5ED856EDF94532A041DBACD5D5C09E"
{ {
"Name" = "8:.NET Core" "Name" = "8:.NET Core"
@ -122,37 +90,6 @@
} }
"File" "File"
{ {
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_1E7663DCE02A4D848349229A724E961A"
{
"AssemblyRegister" = "3:1"
"AssemblyIsInGAC" = "11:FALSE"
"AssemblyAsmDisplayName" = "8:Xceed.Wpf.AvalonDock.resources, Version=4.6.0.0, Culture=de, PublicKeyToken=3e4669d2f30244f4, processorArchitecture=MSIL"
"ScatterAssemblies"
{
"_1E7663DCE02A4D848349229A724E961A"
{
"Name" = "8:Xceed.Wpf.AvalonDock.resources.dll"
"Attributes" = "3:512"
}
}
"SourcePath" = "8:..\\BreCalClient\\bin\\Debug\\net6.0-windows\\de\\Xceed.Wpf.AvalonDock.resources.dll"
"TargetName" = "8:"
"Tag" = "8:"
"Folder" = "8:_F64284776BC0480CBF6C33B1FE00C374"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Vital" = "11:TRUE"
"ReadOnly" = "11:FALSE"
"Hidden" = "11:FALSE"
"System" = "11:FALSE"
"Permanent" = "11:FALSE"
"SharedLegacy" = "11:FALSE"
"PackageAs" = "3:1"
"Register" = "3:1"
"Exclude" = "11:FALSE"
"IsDependency" = "11:FALSE"
"IsolateTo" = "8:"
}
"{1FB2D0AE-D3B9-43D4-B9DD-F88EC61E35DE}:_4EE484EAA4A246CBBB283030A6054BC0" "{1FB2D0AE-D3B9-43D4-B9DD-F88EC61E35DE}:_4EE484EAA4A246CBBB283030A6054BC0"
{ {
"SourcePath" = "8:..\\BreCalClient\\Resources\\containership.ico" "SourcePath" = "8:..\\BreCalClient\\Resources\\containership.ico"
@ -173,37 +110,6 @@
"IsDependency" = "11:FALSE" "IsDependency" = "11:FALSE"
"IsolateTo" = "8:" "IsolateTo" = "8:"
} }
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_CD20A468610C42B89F66B4D3367A5A6A"
{
"AssemblyRegister" = "3:1"
"AssemblyIsInGAC" = "11:FALSE"
"AssemblyAsmDisplayName" = "8:BreCalClient.resources, Version=1.6.2.0, Culture=de, PublicKeyToken=9ce7b6b354e08ac9, processorArchitecture=MSIL"
"ScatterAssemblies"
{
"_CD20A468610C42B89F66B4D3367A5A6A"
{
"Name" = "8:BreCalClient.resources.dll"
"Attributes" = "3:512"
}
}
"SourcePath" = "8:..\\BreCalClient\\bin\\Debug\\net6.0-windows\\de\\BreCalClient.resources.dll"
"TargetName" = "8:"
"Tag" = "8:"
"Folder" = "8:_F64284776BC0480CBF6C33B1FE00C374"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Vital" = "11:TRUE"
"ReadOnly" = "11:FALSE"
"Hidden" = "11:FALSE"
"System" = "11:FALSE"
"Permanent" = "11:FALSE"
"SharedLegacy" = "11:FALSE"
"PackageAs" = "3:1"
"Register" = "3:1"
"Exclude" = "11:FALSE"
"IsDependency" = "11:FALSE"
"IsolateTo" = "8:"
}
} }
"FileType" "FileType"
{ {
@ -231,17 +137,6 @@
"Property" = "8:TARGETDIR" "Property" = "8:TARGETDIR"
"Folders" "Folders"
{ {
"{9EF0B969-E518-4E46-987F-47570745A589}:_F64284776BC0480CBF6C33B1FE00C374"
{
"Name" = "8:de"
"AlwaysCreate" = "11:FALSE"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Property" = "8:_319F0FD8E72443BFA3AE5E1F3F42523B"
"Folders"
{
}
}
} }
} }
"{1525181F-901A-416C-8A58-119130FE478E}:_8BBC7FE2F38E4B41A71D26CCED7D0BCB" "{1525181F-901A-416C-8A58-119130FE478E}:_8BBC7FE2F38E4B41A71D26CCED7D0BCB"

View File

@ -1,7 +1,12 @@
// Copyright (c) 2023- schick Informatik // Copyright (c) 2023- schick Informatik
// Description: Model class for berth entity // Description: Model class for berth entity
using System;
using System.Collections.Generic;
using System.Data; using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace brecal.model namespace brecal.model
{ {
@ -18,14 +23,10 @@ namespace brecal.model
public uint? Authority_Id { get; set; } public uint? Authority_Id { get; set; }
public uint? Port_Id { get; set; }
public Participant? Owner { get; set; } public Participant? Owner { get; set; }
public Participant? Authority { get; set; } public Participant? Authority { get; set; }
public Port? Port { get; set; }
public string? Terminal { get { if (Owner != null) return Owner.Name; else return "n/a"; } } public string? Terminal { get { if (Owner != null) return Owner.Name; else return "n/a"; } }
public string? Authority_Text { get { if (Authority != null) return Authority.Name; else return "n/a"; } } public string? Authority_Text { get { if (Authority != null) return Authority.Name; else return "n/a"; } }
@ -47,12 +48,12 @@ namespace brecal.model
public static void SetLoadQuery(IDbCommand cmd, params object?[] list) public static void SetLoadQuery(IDbCommand cmd, params object?[] list)
{ {
cmd.CommandText = "SELECT id, name, owner_id, authority_id, port_id, `lock`, created, modified, deleted FROM berth"; cmd.CommandText = "SELECT id, name, owner_id, authority_id, `lock`, created, modified, deleted FROM berth";
} }
public static List<DbEntity> LoadElems(IDataReader reader) public static List<DbEntity> LoadElems(IDataReader reader)
{ {
List<DbEntity> result = new(); List<DbEntity> result = new List<DbEntity>();
while (reader.Read()) while (reader.Read())
{ {
Berth b = new(); Berth b = new();
@ -60,11 +61,10 @@ namespace brecal.model
if (!reader.IsDBNull(1)) b.Name = reader.GetString(1); if (!reader.IsDBNull(1)) b.Name = reader.GetString(1);
if (!reader.IsDBNull(2)) b.Owner_Id = (uint) reader.GetInt32(2); if (!reader.IsDBNull(2)) b.Owner_Id = (uint) reader.GetInt32(2);
if (!reader.IsDBNull(3)) b.Authority_Id = (uint) reader.GetInt32(3); if (!reader.IsDBNull(3)) b.Authority_Id = (uint) reader.GetInt32(3);
if (!reader.IsDBNull(4)) b.Port_Id = (uint) reader.GetInt32(4); if (!reader.IsDBNull(4)) b.Lock = reader.GetBoolean(4);
if (!reader.IsDBNull(5)) b.Lock = reader.GetBoolean(5); if (!reader.IsDBNull(5)) b.Created = reader.GetDateTime(5);
if (!reader.IsDBNull(6)) b.Created = reader.GetDateTime(6); if (!reader.IsDBNull(6)) b.Modified = reader.GetDateTime(6);
if (!reader.IsDBNull(7)) b.Modified = reader.GetDateTime(7); if (!reader.IsDBNull(7)) b.Deleted = reader.GetInt16(7);
if (!reader.IsDBNull(8)) b.Deleted = reader.GetInt16(8);
result.Add(b); result.Add(b);
} }
return result; return result;
@ -76,7 +76,7 @@ namespace brecal.model
public override void SetCreate(IDbCommand cmd) public override void SetCreate(IDbCommand cmd)
{ {
cmd.CommandText = "INSERT INTO berth (owner_id, authority_id, port_id, name, `lock`) VALUES ( @PID, @AID, @PO_ID, @NAME, @LOCK)"; cmd.CommandText = "INSERT INTO berth (owner_id, authority_id, name, `lock`) VALUES ( @PID, @AID, @NAME, @LOCK)";
this.SetParameters(cmd); this.SetParameters(cmd);
} }
@ -92,7 +92,7 @@ namespace brecal.model
public override void SetUpdate(IDbCommand cmd) public override void SetUpdate(IDbCommand cmd)
{ {
cmd.CommandText = "UPDATE berth SET name = @NAME, owner_id = @PID, authority_id = @AID, port_id = @PO_ID, `lock` = @LOCK WHERE id = @ID"; cmd.CommandText = "UPDATE berth SET name = @NAME, owner_id = @PID, authority_id = @AID, `lock` = @LOCK WHERE id = @ID";
this.SetParameters(cmd); this.SetParameters(cmd);
} }
@ -109,19 +109,14 @@ namespace brecal.model
IDbDataParameter pid = cmd.CreateParameter(); IDbDataParameter pid = cmd.CreateParameter();
pid.ParameterName = "PID"; pid.ParameterName = "PID";
pid.Value = this.Owner_Id.HasValue ? this.Owner_Id.Value : DBNull.Value; pid.Value = this.Owner_Id;
cmd.Parameters.Add(pid); cmd.Parameters.Add(pid);
IDbDataParameter aid = cmd.CreateParameter(); IDbDataParameter aid = cmd.CreateParameter();
aid.ParameterName = "AID"; aid.ParameterName = "AID";
aid.Value = this.Authority_Id.HasValue ? this.Authority_Id.Value : DBNull.Value; aid.Value = this.Authority_Id;
cmd.Parameters.Add(aid); cmd.Parameters.Add(aid);
IDbDataParameter poid = cmd.CreateParameter();
poid.ParameterName = "PO_ID";
poid.Value = this.Port_Id.HasValue ? this.Port_Id.Value : DBNull.Value;
cmd.Parameters.Add(poid);
IDbDataParameter name = cmd.CreateParameter(); IDbDataParameter name = cmd.CreateParameter();
name.ParameterName = "NAME"; name.ParameterName = "NAME";
name.Value = this.Name; name.Value = this.Name;

View File

@ -1,4 +1,8 @@
using System.Data; using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace brecal.model namespace brecal.model
@ -38,11 +42,6 @@ namespace brecal.model
/// <param name="cmd">CMD created by DB manager</param> /// <param name="cmd">CMD created by DB manager</param>
public abstract void SetDelete(IDbCommand cmd); public abstract void SetDelete(IDbCommand cmd);
public virtual void SetNonQuery(IDbCommand cmd)
{
// default: do nothing
}
/// <summary> /// <summary>
/// Each database entity must be able to save itself to the database /// Each database entity must be able to save itself to the database
/// </summary> /// </summary>
@ -66,10 +65,5 @@ namespace brecal.model
await manager.ExecuteNonQuery(this.SetDelete); await manager.ExecuteNonQuery(this.SetDelete);
} }
public async Task ExecuteNonQuery(IDBManager manager)
{
await manager.ExecuteNonQuery(this.SetNonQuery);
}
} }
} }

View File

@ -55,8 +55,6 @@ namespace brecal.model
public uint Flags { get; set; } public uint Flags { get; set; }
public bool Deleted { get; set; } = false;
#endregion #endregion
#region public static methods #region public static methods
@ -72,7 +70,7 @@ namespace brecal.model
public static List<DbEntity> LoadElems(IDataReader reader) public static List<DbEntity> LoadElems(IDataReader reader)
{ {
List<DbEntity> result = new(); List<DbEntity> result = new List<DbEntity>();
while (reader.Read()) while (reader.Read())
{ {
Participant p = new(); Participant p = new();
@ -85,7 +83,6 @@ namespace brecal.model
if (!reader.IsDBNull(6)) p.Flags = (uint)reader.GetInt32(6); if (!reader.IsDBNull(6)) p.Flags = (uint)reader.GetInt32(6);
if (!reader.IsDBNull(7)) p.Created = reader.GetDateTime(7); if (!reader.IsDBNull(7)) p.Created = reader.GetDateTime(7);
if (!reader.IsDBNull(8)) p.Modified = reader.GetDateTime(8); if (!reader.IsDBNull(8)) p.Modified = reader.GetDateTime(8);
if (!reader.IsDBNull(9)) p.Deleted = reader.GetBoolean(9);
result.Add(p); result.Add(p);
} }
return result; return result;
@ -93,7 +90,7 @@ namespace brecal.model
public static void SetLoadQuery(IDbCommand cmd, params object?[] list) public static void SetLoadQuery(IDbCommand cmd, params object?[] list)
{ {
cmd.CommandText = "SELECT id, name, street, postal_code, city, type, flags, created, modified, deleted FROM participant"; cmd.CommandText = "SELECT id, name, street, postal_code, city, type, flags, created, modified FROM participant";
} }
#endregion #endregion
@ -114,7 +111,7 @@ namespace brecal.model
public override void SetDelete(IDbCommand cmd) public override void SetDelete(IDbCommand cmd)
{ {
cmd.CommandText = "UPDATE participant SET deleted = 1 WHERE id = @ID"; cmd.CommandText = "DELETE FROM participant WHERE id = @ID";
IDataParameter idParam = cmd.CreateParameter(); IDataParameter idParam = cmd.CreateParameter();
idParam.ParameterName = "ID"; idParam.ParameterName = "ID";

View File

@ -1,115 +0,0 @@
// Copyright (c) 2023- schick Informatik
// Description: Port entity
using System.Data;
namespace brecal.model
{
public class Port : DbEntity
{
#region Properties
public string? Name { get; set; }
public string? Locode { get; set; }
public bool IsDeleted { get; set; } = false;
#endregion
#region overrides
public override void SetCreate(IDbCommand cmd)
{
cmd.CommandText = "INSERT INTO port (name, locode) VALUES ( @NAME, @LOCODE)";
this.SetParameters(cmd);
}
public override void SetDelete(IDbCommand cmd)
{
cmd.CommandText = "UPDATE port SET deleted = 1 WHERE id = @ID";
IDataParameter idParam = cmd.CreateParameter();
idParam.ParameterName = "ID";
idParam.Value = this.Id;
cmd.Parameters.Add(idParam);
}
public override void SetUpdate(IDbCommand cmd)
{
cmd.CommandText = "UPDATE port set name = @NAME, locode = @LOCODE WHERE id = @ID";
this.SetParameters(cmd);
}
#endregion
#region public static methods
public static async Task<List<Port>> LoadAll(IDBManager manager)
{
List<DbEntity> loadResultList = await manager.Load(SetLoadQuery, LoadElems);
List<Port> result = new();
foreach (Port p in loadResultList.Cast<Port>())
result.Add(p);
return result;
}
public static void SetLoadQuery(IDbCommand cmd, params object?[] list)
{
cmd.CommandText = "SELECT id, name, locode, created, modified, deleted FROM port WHERE deleted = 0";
}
public static List<DbEntity> LoadElems(IDataReader reader)
{
List<DbEntity> result = new();
while (reader.Read())
{
Port p = new();
p.Id = (uint)reader.GetInt32(0);
if (!reader.IsDBNull(1)) p.Name = reader.GetString(1);
if (!reader.IsDBNull(2)) p.Locode = reader.GetString(2);
if (!reader.IsDBNull(3)) p.Created = reader.GetDateTime(3);
if (!reader.IsDBNull(4)) p.Modified = reader.GetDateTime(4);
if (!reader.IsDBNull(5)) p.IsDeleted = reader.GetBoolean(5);
result.Add(p);
}
return result;
}
#endregion
#region private methods
private void SetParameters(IDbCommand cmd)
{
IDbDataParameter name = cmd.CreateParameter();
name.ParameterName = "NAME";
name.Value = this.Name;
cmd.Parameters.Add(name);
IDbDataParameter desc = cmd.CreateParameter();
desc.ParameterName = "LOCODE";
desc.Value = this.Locode;
cmd.Parameters.Add(desc);
IDataParameter idParam = cmd.CreateParameter();
idParam.ParameterName = "ID";
idParam.Value = this.Id;
cmd.Parameters.Add(idParam);
}
#endregion
#region overrides
public override string ToString()
{
return $"{Name} ({Locode})";
}
#endregion
}
}

View File

@ -1,111 +0,0 @@
// Copyright (c) 2023- schick Informatik
// Description: Participant Port Map Entity
using System.Data;
namespace brecal.model
{
public class PortAssignment : DbEntity
{
#region Properties
public int? ParticipantId { get; set; }
public int? PortId { get; set; }
public Participant? AssignedParticipant { get; set; }
public Port? AssignedPort { get; set; }
#endregion
#region public static methods
public static async Task<List<PortAssignment>> LoadForParticipant(Participant? p, IDBManager manager)
{
List<DbEntity> loadResultList = await manager.Load(SetLoadQuery, LoadElems, args: p);
List<PortAssignment> result = new();
foreach (PortAssignment pa in loadResultList.Cast<PortAssignment>())
{
pa.AssignedParticipant = p;
result.Add(pa);
}
return result;
}
public static void SetLoadQuery(IDbCommand cmd, params object?[] args)
{
cmd.CommandText = "SELECT id, participant_id, port_id FROM participant_port_map WHERE participant_id = @PID";
if (args.Length != 1 || args[0] is not Participant)
throw new ArgumentException("loader needs single participant as argument");
IDataParameter pid = cmd.CreateParameter();
pid.ParameterName = "PID";
if (args[0] is Participant p)
pid.Value = p.Id;
cmd.Parameters.Add(pid);
}
public static List<DbEntity> LoadElems(IDataReader reader)
{
List<DbEntity> result = new();
while (reader.Read())
{
PortAssignment ra = new();
ra.Id = (uint)reader.GetInt32(0);
if (!reader.IsDBNull(1)) ra.ParticipantId = reader.GetInt32(1);
if (!reader.IsDBNull(2)) ra.PortId = reader.GetInt32(2);
result.Add(ra);
}
return result;
}
#endregion
#region overrides
public override void SetUpdate(IDbCommand cmd)
{
throw new NotImplementedException();
}
public override void SetCreate(IDbCommand cmd)
{
cmd.CommandText = "INSERT INTO participant_port_map (participant_id, port_id) VALUES (@PID, @PORTID)";
IDbDataParameter participantId = cmd.CreateParameter();
participantId.ParameterName = "pID";
participantId.Value = this.ParticipantId;
cmd.Parameters.Add(participantId);
IDbDataParameter portId = cmd.CreateParameter();
portId.ParameterName = "PORTID";
portId.Value = this.PortId;
cmd.Parameters.Add(portId);
}
public override void SetDelete(IDbCommand cmd)
{
cmd.CommandText = "DELETE FROM participant_port_map WHERE id = @ID";
IDataParameter idParam = cmd.CreateParameter();
idParam.ParameterName = "ID";
idParam.Value = this.Id;
cmd.Parameters.Add(idParam);
}
public override string ToString()
{
if (this.AssignedPort == null)
{
return $"{Id}: <defunct port>";
}
else
{
return AssignedPort.Name ?? AssignedPort.Id.ToString();
}
}
#endregion
}
}

View File

@ -1,7 +1,9 @@
// Copyright (c) 2023- schick Informatik using System;
// Description: Role Entity using System.Collections.Generic;
using System.Data; using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace brecal.model namespace brecal.model
{ {

View File

@ -101,16 +101,6 @@ namespace brecal.model
return this.Username ?? $"{base.Id} - {this.GetType().Name}"; return this.Username ?? $"{base.Id} - {this.GetType().Name}";
} }
public override void SetNonQuery(IDbCommand cmd)
{
cmd.CommandText = "UPDATE history set user_id = NULL WHERE user_id = @ID";
IDataParameter idParam = cmd.CreateParameter();
idParam.ParameterName = "ID";
idParam.Value = this.Id;
cmd.Parameters.Add(idParam);
}
#endregion #endregion
#region private methods #region private methods

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

View File

@ -17,7 +17,7 @@ namespace brecal.mysql
public async Task<List<DbEntity>> Load(QueryFunc prepareAction, LoadFunc<IDataReader> loadAction, params object?[] args) public async Task<List<DbEntity>> Load(QueryFunc prepareAction, LoadFunc<IDataReader> loadAction, params object?[] args)
{ {
await using MySqlConnection connection = new(_connectionString); await using MySqlConnection connection = new MySqlConnection(_connectionString);
await connection.OpenAsync(); await connection.OpenAsync();
using MySqlCommand cmd = new(); using MySqlCommand cmd = new();
@ -31,7 +31,7 @@ namespace brecal.mysql
public async Task<object?> ExecuteScalar(Action<IDbCommand> prepareAction) public async Task<object?> ExecuteScalar(Action<IDbCommand> prepareAction)
{ {
await using MySqlConnection connection = new(_connectionString); await using MySqlConnection connection = new MySqlConnection(_connectionString);
await connection.OpenAsync(); await connection.OpenAsync();
using MySqlCommand cmd = new(); using MySqlCommand cmd = new();

View File

@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MySqlConnector" Version="2.4.0" /> <PackageReference Include="MySqlConnector" Version="2.3.0-beta.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,7 +1,6 @@
from flask import Flask from flask import Flask
import os import os
import sys
import logging import logging
from . import local_db from . import local_db
@ -14,7 +13,6 @@ from .api import ships
from .api import login from .api import login
from .api import user from .api import user
from .api import history from .api import history
from .api import ports
from BreCal.brecal_utils.file_handling import get_project_root, ensure_path from BreCal.brecal_utils.file_handling import get_project_root, ensure_path
from BreCal.brecal_utils.test_handling import execute_test_with_pytest, execute_coverage_test from BreCal.brecal_utils.test_handling import execute_test_with_pytest, execute_coverage_test
@ -37,6 +35,7 @@ from BreCal.stubs.df_times import get_df_times
from BreCal.services.schedule_routines import setup_schedule, run_schedule_permanently_in_background from BreCal.services.schedule_routines import setup_schedule, run_schedule_permanently_in_background
def create_app(test_config=None, instance_path=None): def create_app(test_config=None, instance_path=None):
app = Flask(__name__, instance_relative_config=True) app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping( app.config.from_mapping(
SECRET_KEY='dev' SECRET_KEY='dev'
@ -48,13 +47,10 @@ def create_app(test_config=None, instance_path=None):
if instance_path is not None: if instance_path is not None:
app.instance_path = instance_path app.instance_path = instance_path
elif app.config.get("INSTANCE_PATH"):
app.instance_path = app.config["INSTANCE_PATH"]
try: try:
import os import os
print(f'Instance path = {app.instance_path}') print(f'Instance path = {app.instance_path}')
if not os.path.exists(app.instance_path):
os.makedirs(app.instance_path) os.makedirs(app.instance_path)
except OSError: except OSError:
pass pass
@ -69,25 +65,14 @@ def create_app(test_config=None, instance_path=None):
app.register_blueprint(login.bp) app.register_blueprint(login.bp)
app.register_blueprint(user.bp) app.register_blueprint(user.bp)
app.register_blueprint(history.bp) app.register_blueprint(history.bp)
app.register_blueprint(ports.bp)
log_level = getattr(logging, app.config.get("LOG_LEVEL", "DEBUG")) logging.basicConfig(filename='brecaldevel.log', level=logging.DEBUG, format='%(asctime)s | %(name)s | %(levelname)s | %(message)s')
log_kwargs = {"format": "%(asctime)s | %(name)s | %(levelname)s | %(message)s"} local_db.initPool(os.path.dirname(app.instance_path))
if app.config.get("LOG_TO_STDERR"):
log_kwargs["stream"] = sys.stderr
else:
log_kwargs["filename"] = app.config.get("LOG_FILE", "brecaltest.log")
logging.basicConfig(level=log_level, **log_kwargs)
if app.config.get("SECRET_KEY"):
os.environ["SECRET_KEY"] = app.config["SECRET_KEY"]
local_db.initPool(os.path.dirname(app.instance_path), config=app.config)
logging.info('App started') logging.info('App started')
# Setup Routine jobs (e.g., reevaluation of shipcalls) # Setup Routine jobs (e.g., reevaluation of shipcalls)
setup_schedule(update_shipcalls_interval_in_minutes=app.config.get("SCHEDULE_UPDATE_SHIPCALLS_MINUTES", 60)) setup_schedule(update_shipcalls_interval_in_minutes=60)
run_schedule_permanently_in_background(latency=app.config.get("SCHEDULE_BACKGROUND_LATENCY_SECONDS", 30)) run_schedule_permanently_in_background(latency=30)
logging.info('Routine Jobs are defined.') logging.info('Routine Jobs are defined.')
return app return app

View File

@ -1,15 +0,0 @@
import json
import logging
from flask import request
def verify_if_request_is_json(request):
"""
when a request contains invalid JSON data, this function raises a 400 error (bad request) and returns an error description.
this function avoids less precise 500 Internal Server Error messages.
"""
if request.is_json:
# when invalid json data is posted, a JSONDecodeError will be raised
json.loads(request.data)
return

View File

@ -1,11 +1,8 @@
import logging
from flask import Blueprint, request from flask import Blueprint, request
from webargs.flaskparser import parser from webargs.flaskparser import parser
from .. import impl from .. import impl
from ..services.auth_guard import auth_guard from ..services.auth_guard import auth_guard
from ..services.jwt_handler import decode_jwt
import json import json
from BreCal.validators.validation_error import create_dynamic_exception_response
bp = Blueprint('berths', __name__) bp = Blueprint('berths', __name__)
@ -14,15 +11,8 @@ bp = Blueprint('berths', __name__)
@auth_guard() # no restriction by role @auth_guard() # no restriction by role
def GetBerths(): def GetBerths():
try:
if 'Authorization' in request.headers: if 'Authorization' in request.headers:
token = request.headers.get('Authorization') token = request.headers.get('Authorization')
payload = decode_jwt(token.split("Bearer ")[-1]) return impl.berths.GetBerths(token)
options = {}
options["participant_id"] = payload["participant_id"]
return impl.berths.GetBerths(options)
else: else:
return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated") return json.dumps("not authenticated"), 403
except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400)

View File

@ -1,26 +1,21 @@
import logging
from flask import Blueprint, request from flask import Blueprint, request
from .. import impl from .. import impl
from ..services.auth_guard import auth_guard from ..services.auth_guard import auth_guard
import json import json
from BreCal.validators.validation_error import create_dynamic_exception_response
bp = Blueprint('history', __name__) bp = Blueprint('history', __name__)
@bp.route('/history', methods=['get']) @bp.route('/history', methods=['get'])
@auth_guard() # no restriction by role @auth_guard() # no restriction by role
def GetHistory(): def GetParticipant():
try:
if 'Authorization' in request.headers: if 'Authorization' in request.headers:
token = request.headers.get('Authorization') token = request.headers.get('Authorization')
options = {} options = {}
if not 'shipcall_id' in request.args: if not 'shipcall_id' in request.args:
return create_dynamic_exception_response(ex=None, status_code=400, message="missing parameter: shipcall_id") return json.dumps("missing parameter"), 400
options["shipcall_id"] = request.args.get("shipcall_id") options["shipcall_id"] = request.args.get("shipcall_id")
return impl.history.GetHistory(options) return impl.history.GetHistory(options)
else: else:
return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated") return json.dumps("not authenticated"), 403
except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400)

View File

@ -3,7 +3,6 @@ from .. import impl
from ..services.auth_guard import auth_guard from ..services.auth_guard import auth_guard
import logging import logging
import json import json
from BreCal.validators.validation_error import create_dynamic_exception_response
bp = Blueprint('notifications', __name__) bp = Blueprint('notifications', __name__)
@ -11,19 +10,10 @@ bp = Blueprint('notifications', __name__)
@bp.route('/notifications', methods=['get']) @bp.route('/notifications', methods=['get'])
@auth_guard() # no restriction by role @auth_guard() # no restriction by role
def GetNotifications(): def GetNotifications():
try: if 'shipcall_id' in request.args:
if 'Authorization' in request.headers: options = {}
token = request.headers.get('Authorization') options["shipcall_id"] = request.args.get("shipcall_id")
participant_id = None return impl.notifications.GetNotifications(options)
if 'participant_id' in request.args:
try:
participant_id = int(request.args.get('participant_id'))
except (TypeError, ValueError):
return create_dynamic_exception_response(ex=None, status_code=400, message="participant_id must be an integer")
return impl.notifications.GetNotifications(token, participant_id=participant_id)
else: else:
return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated") logging.warning("attempt to load notifications without shipcall id")
return json.dumps("missing argument"), 400
except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400)

View File

@ -1,10 +1,7 @@
import logging
from flask import Blueprint, request from flask import Blueprint, request
from .. import impl from .. import impl
from ..services.auth_guard import auth_guard from ..services.auth_guard import auth_guard
from ..services.jwt_handler import decode_jwt
import json import json
from BreCal.validators.validation_error import create_dynamic_exception_response
bp = Blueprint('participants', __name__) bp = Blueprint('participants', __name__)
@ -12,19 +9,11 @@ bp = Blueprint('participants', __name__)
@auth_guard() # no restriction by role @auth_guard() # no restriction by role
def GetParticipant(): def GetParticipant():
try:
if 'Authorization' in request.headers: if 'Authorization' in request.headers:
payload = decode_jwt(request.headers.get("Authorization").split("Bearer ")[-1]) token = request.headers.get('Authorization')
options = {} options = {}
options["user_id"] = request.args.get("user_id") options["user_id"] = request.args.get("user_id")
if "participant_id" in payload:
options["participant_id"] = payload["participant_id"]
else:
return create_dynamic_exception_response(ex=None, status_code=403, message="not authorized")
return impl.participant.GetParticipant(options) return impl.participant.GetParticipant(options)
else: else:
return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated") return json.dumps("not authenticated"), 403
except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400)

View File

@ -1,24 +0,0 @@
import logging
from flask import Blueprint, request
from webargs.flaskparser import parser
from .. import impl
from ..services.auth_guard import auth_guard
import json
from BreCal.validators.validation_error import create_dynamic_exception_response
bp = Blueprint('ports', __name__)
@bp.route('/ports', methods=['get'])
@auth_guard() # no restriction by role
def GetPorts():
try:
if 'Authorization' in request.headers:
token = request.headers.get('Authorization')
return impl.ports.GetPorts(token)
else:
return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated")
except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400)

View File

@ -7,9 +7,6 @@ from ..services.auth_guard import auth_guard, check_jwt
from BreCal.validators.input_validation import validate_posted_shipcall_data, check_if_user_is_bsmd_type from BreCal.validators.input_validation import validate_posted_shipcall_data, check_if_user_is_bsmd_type
from BreCal.validators.input_validation_shipcall import InputValidationShipcall from BreCal.validators.input_validation_shipcall import InputValidationShipcall
from BreCal.database.sql_handler import execute_sql_query_standalone from BreCal.database.sql_handler import execute_sql_query_standalone
from BreCal.services.jwt_handler import decode_jwt
from BreCal.validators.validation_error import create_validation_error_response, create_werkzeug_error_response, create_dynamic_exception_response
from . import verify_if_request_is_json
import logging import logging
import json import json
@ -21,7 +18,6 @@ bp = Blueprint('shipcalls', __name__)
@bp.route('/shipcalls', methods=['get']) @bp.route('/shipcalls', methods=['get'])
@auth_guard() # no restriction by role @auth_guard() # no restriction by role
def GetShipcalls(): def GetShipcalls():
try:
if 'Authorization' in request.headers: if 'Authorization' in request.headers:
token = request.headers.get('Authorization') # see impl/login to see the token encoding, which is a JWT token. token = request.headers.get('Authorization') # see impl/login to see the token encoding, which is a JWT token.
@ -33,17 +29,13 @@ def GetShipcalls():
# oneline: # oneline:
payload = decode_jwt(request.headers.get("Authorization").split("Bearer ")[-1]) payload = decode_jwt(request.headers.get("Authorization").split("Bearer ")[-1])
""" """
payload = decode_jwt(request.headers.get("Authorization").split("Bearer ")[-1])
options = {} options = {}
options["participant_id"] = request.args.get("participant_id")
options["past_days"] = request.args.get("past_days", default=1, type=int) options["past_days"] = request.args.get("past_days", default=1, type=int)
options["participant_id"] = payload["participant_id"]
return impl.shipcalls.GetShipcalls(options) return impl.shipcalls.GetShipcalls(options)
else: else:
return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated") return json.dumps("not authenticated"), 403
except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400)
@bp.route('/shipcalls', methods=['post']) @bp.route('/shipcalls', methods=['post'])
@ -51,8 +43,6 @@ def GetShipcalls():
def PostShipcalls(): def PostShipcalls():
try: try:
verify_if_request_is_json(request)
content = request.get_json(force=True) content = request.get_json(force=True)
loadedModel = model.ShipcallSchema().load(data=content, many=False, partial=True) loadedModel = model.ShipcallSchema().load(data=content, many=False, partial=True)
@ -61,14 +51,19 @@ def PostShipcalls():
# validate the posted shipcall data & the user's authority # validate the posted shipcall data & the user's authority
InputValidationShipcall.evaluate_post_data(user_data, loadedModel, content) InputValidationShipcall.evaluate_post_data(user_data, loadedModel, content)
return impl.shipcalls.PostShipcalls(loadedModel)
except ValidationError as ex: except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400) logging.error(ex)
print(ex)
return json.dumps({"message":f"bad format. \nError Messages: {ex.messages}. \nValid Data: {ex.valid_data}"}), 400
except Exception as ex: except Exception as ex:
logging.error(ex)
logging.error(traceback.format_exc()) logging.error(traceback.format_exc())
return create_dynamic_exception_response(ex=ex, status_code=400, message="bad format") print(ex)
return json.dumps("bad format"), 400
return impl.shipcalls.PostShipcalls(loadedModel)
@bp.route('/shipcalls', methods=['put']) @bp.route('/shipcalls', methods=['put'])
@ -76,28 +71,28 @@ def PostShipcalls():
def PutShipcalls(): def PutShipcalls():
try: try:
verify_if_request_is_json(request)
content = request.get_json(force=True) content = request.get_json(force=True)
loadedModel = model.ShipcallSchema().load(data=content, many=False, partial=True) loadedModel = model.ShipcallSchema().load(data=content, many=False, partial=True)
# read the user data from the JWT token (set when login is performed) # read the user data from the JWT token (set when login is performed)
user_data = check_jwt() user_data = check_jwt()
if not InputValidationShipcall.exists_shipcall_by_id(loadedModel.get("id")):
return create_dynamic_exception_response(ex=None, status_code=404, message="no shipcall found with the provided id")
# validate the PUT shipcall data and the user's authority # validate the PUT shipcall data and the user's authority
InputValidationShipcall.evaluate_put_data(user_data, loadedModel, content) InputValidationShipcall.evaluate_put_data(user_data, loadedModel, content)
return impl.shipcalls.PutShipcalls(loadedModel, content)
except ValidationError as ex: except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400) logging.error(ex)
print(ex)
return json.dumps({"message":f"bad format. \nError Messages: {ex.messages}. \nValid Data: {ex.valid_data}"}), 400
except werkzeug.exceptions.Forbidden as ex: except werkzeug.exceptions.Forbidden as ex:
return create_werkzeug_error_response(ex=ex, status_code=403) logging.error(ex)
print(ex)
return json.dumps({"message":ex.description}), 403
except Exception as ex: except Exception as ex:
logging.error(traceback.format_exc()) logging.error(ex)
return create_dynamic_exception_response(ex=None, status_code=400, message="bad format") print(ex)
return json.dumps("bad format"), 400
return impl.shipcalls.PutShipcalls(loadedModel)

View File

@ -5,8 +5,6 @@ from marshmallow import EXCLUDE, ValidationError
from ..schemas import model from ..schemas import model
import json import json
import logging import logging
from . import verify_if_request_is_json
from BreCal.validators.validation_error import create_validation_error_response, create_dynamic_exception_response
from BreCal.validators.input_validation import check_if_user_is_bsmd_type from BreCal.validators.input_validation import check_if_user_is_bsmd_type
from BreCal.validators.input_validation_ship import InputValidationShip from BreCal.validators.input_validation_ship import InputValidationShip
@ -17,15 +15,11 @@ bp = Blueprint('ships', __name__)
@auth_guard() # no restriction by role @auth_guard() # no restriction by role
def GetShips(): def GetShips():
try:
if 'Authorization' in request.headers: if 'Authorization' in request.headers:
token = request.headers.get('Authorization') token = request.headers.get('Authorization')
return impl.ships.GetShips(token) return impl.ships.GetShips(token)
else: else:
return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated") return json.dumps("not authenticated"), 403
except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400)
@bp.route('/ships', methods=['post']) @bp.route('/ships', methods=['post'])
@ -33,8 +27,6 @@ def GetShips():
def PostShip(): def PostShip():
try: try:
verify_if_request_is_json(request)
# read the user data from the JWT token (set when login is performed) # read the user data from the JWT token (set when login is performed)
user_data = check_jwt() user_data = check_jwt()
@ -42,20 +34,20 @@ def PostShip():
# as ParticipantType is an IntFlag, a user belonging to multiple groups is properly evaluated. # as ParticipantType is an IntFlag, a user belonging to multiple groups is properly evaluated.
is_bsmd = check_if_user_is_bsmd_type(user_data) is_bsmd = check_if_user_is_bsmd_type(user_data)
if not is_bsmd: if not is_bsmd:
raise ValidationError({"participant_type":f"current user does not belong to BSMD. Cannot post shipcalls. Found user data: {user_data}"}) raise ValidationError(f"current user does not belong to BSMD. Cannot post shipcalls. Found user data: {user_data}")
content = request.get_json(force=True) content = request.get_json(force=True)
loadedModel = model.ShipSchema().load(data=content, many=False, partial=True) loadedModel = model.ShipSchema().load(data=content, many=False, partial=True)
# validate the request data & user permissions # validate the request data & user permissions
InputValidationShip.evaluate_post_data(user_data, loadedModel, content) InputValidationShip.evaluate_post_data(user_data, loadedModel, content)
return impl.ships.PostShip(loadedModel)
except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400)
except Exception as ex: except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400, message=None) logging.error(ex)
print(ex)
return json.dumps(repr(ex)), 400
return impl.ships.PostShip(loadedModel)
@bp.route('/ships', methods=['put']) @bp.route('/ships', methods=['put'])
@ -63,26 +55,21 @@ def PostShip():
def PutShip(): def PutShip():
try: try:
verify_if_request_is_json(request)
# read the user data from the JWT token (set when login is performed) # read the user data from the JWT token (set when login is performed)
user_data = check_jwt() user_data = check_jwt()
content = request.get_json(force=True) content = request.get_json(force=True)
loadedModel = model.ShipSchema().load(data=content, many=False, partial=True, unknown=EXCLUDE) loadedModel = model.Ship().load(data=content, many=False, partial=True, unknown=EXCLUDE)
if not InputValidationShip.exists_ship_by_dict(model=loadedModel):
return create_dynamic_exception_response(ex=None, status_code=404, message="no ship found with the provided id")
# validate the request data & user permissions # validate the request data & user permissions
InputValidationShip.evaluate_put_data(user_data, loadedModel, content) InputValidationShip.evaluate_put_data(user_data, loadedModel, content)
return impl.ships.PutShip(loadedModel)
except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400)
except Exception as ex: except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400) logging.error(ex)
print(ex)
return json.dumps(repr(ex)), 400
return impl.ships.PutShip(loadedModel)
@bp.route('/ships', methods=['delete']) @bp.route('/ships', methods=['delete'])
@ -90,29 +77,22 @@ def PutShip():
def DeleteShip(): def DeleteShip():
try: try:
verify_if_request_is_json(request)
# read the user data from the JWT token (set when login is performed) # read the user data from the JWT token (set when login is performed)
user_data = check_jwt() user_data = check_jwt()
ship_id = request.args.get("id")
if 'id' in request.args: if 'id' in request.args:
options = {} options = {}
options["id"] = request.args.get("id") options["id"] = request.args.get("id")
else: else:
return create_dynamic_exception_response(ex=None, status_code=400, message="no id provided") return json.dumps("no id provided"), 400
# validate the request data & user permissions # validate the request data & user permissions
ship_id = request.args.get("id")
if not InputValidationShip.exists_ship_by_id(id=ship_id):
return create_dynamic_exception_response(ex=None, status_code=404, message="no ship found with the provided id")
InputValidationShip.evaluate_delete_data(user_data, ship_id) InputValidationShip.evaluate_delete_data(user_data, ship_id)
return impl.ships.DeleteShip(options)
except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400)
except Exception as ex: except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400) logging.error(ex)
print(ex)
return json.dumps(repr(ex)), 400
return impl.ships.DeleteShip(options)

View File

@ -6,8 +6,6 @@ import json
import logging import logging
from marshmallow import ValidationError from marshmallow import ValidationError
from BreCal.validators.input_validation_times import InputValidationTimes from BreCal.validators.input_validation_times import InputValidationTimes
from . import verify_if_request_is_json
from BreCal.validators.validation_error import create_validation_error_response, create_werkzeug_error_response, create_dynamic_exception_response
bp = Blueprint('times', __name__) bp = Blueprint('times', __name__)
@ -16,13 +14,10 @@ bp = Blueprint('times', __name__)
@auth_guard() # no restriction by role @auth_guard() # no restriction by role
def GetTimes(): def GetTimes():
try:
options = {} options = {}
options["shipcall_id"] = request.args.get("shipcall_id") options["shipcall_id"] = request.args.get("shipcall_id")
return impl.times.GetTimes(options)
except Exception as ex: return impl.times.GetTimes(options)
return create_dynamic_exception_response(ex=ex, status_code=400)
@bp.route('/times', methods=['post']) @bp.route('/times', methods=['post'])
@ -30,8 +25,6 @@ def GetTimes():
def PostTimes(): def PostTimes():
try: try:
verify_if_request_is_json(request)
# print (request.is_json) # print (request.is_json)
content = request.get_json(force=True) # force gets us json even if the content-type was wrong content = request.get_json(force=True) # force gets us json even if the content-type was wrong
@ -44,14 +37,18 @@ def PostTimes():
# validate the request # validate the request
InputValidationTimes.evaluate_post_data(user_data, loadedModel, content) InputValidationTimes.evaluate_post_data(user_data, loadedModel, content)
return impl.times.PostTimes(loadedModel)
except ValidationError as ex: except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400) logging.error(ex)
print(ex)
return json.dumps(f"bad format. \nError Messages: {ex.messages}. \nValid Data: {ex.valid_data}"), 400
except Exception as ex: except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400, message="bad format") logging.error(ex)
print(ex)
return json.dumps("bad format"), 400
return impl.times.PostTimes(loadedModel)
@bp.route('/times', methods=['put']) @bp.route('/times', methods=['put'])
@ -59,34 +56,32 @@ def PostTimes():
def PutTimes(): def PutTimes():
try: try:
verify_if_request_is_json(request)
content = request.get_json(force=True) content = request.get_json(force=True)
loadedModel = model.TimesSchema().load(data=content, many=False, partial=True) loadedModel = model.TimesSchema().load(data=content, many=False, partial=True)
# read the user data from the JWT token (set when login is performed) # read the user data from the JWT token (set when login is performed)
user_data = check_jwt() user_data = check_jwt()
if not InputValidationTimes.exists_times_by_id(loadedModel.get("id")):
return create_dynamic_exception_response(ex=None, status_code=404, message="no times found with the provided id")
# validate the request # validate the request
InputValidationTimes.evaluate_put_data(user_data, loadedModel, content) InputValidationTimes.evaluate_put_data(user_data, loadedModel, content)
return impl.times.PutTimes(loadedModel, content)
except ValidationError as ex: except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400) logging.error(ex)
print(ex)
return json.dumps(f"bad format. \nError Messages: {ex.messages}. \nValid Data: {ex.valid_data}"), 400
except Exception as ex: except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400, message="bad format") logging.error(ex)
print(ex)
return json.dumps("bad format"), 400
return impl.times.PutTimes(loadedModel)
@bp.route('/times', methods=['delete']) @bp.route('/times', methods=['delete'])
@auth_guard() # no restriction by role @auth_guard() # no restriction by role
def DeleteTimes(): def DeleteTimes():
try:
if 'id' in request.args: if 'id' in request.args:
options = {} options = {}
options["id"] = request.args.get("id") options["id"] = request.args.get("id")
@ -94,18 +89,10 @@ def DeleteTimes():
# read the user data from the JWT token (set when login is performed) # read the user data from the JWT token (set when login is performed)
user_data = check_jwt() user_data = check_jwt()
if not InputValidationTimes.exists_times_by_id(options["id"]):
return create_dynamic_exception_response(ex=None, status_code=404, message="no times found with the provided id")
# validate the request # validate the request
InputValidationTimes.evaluate_delete_data(user_data, times_id = request.args.get("id")) InputValidationTimes.evaluate_delete_data(user_data, times_id = request.args.get("id"))
return impl.times.DeleteTimes(options) return impl.times.DeleteTimes(options)
else: else:
return create_dynamic_exception_response(ex=None, status_code=400, message="Times delete missing argument: id") logging.warning("Times delete missing id argument")
return json.dumps("missing argument"), 400
except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400)
except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400, message="bad format")

View File

@ -2,14 +2,9 @@ from flask import Blueprint, request
from ..schemas import model from ..schemas import model
from .. import impl from .. import impl
from ..services.auth_guard import auth_guard from ..services.auth_guard import auth_guard
from marshmallow import ValidationError
from . import verify_if_request_is_json
from BreCal.validators.validation_error import create_dynamic_exception_response, create_validation_error_response
import json import json
import logging import logging
import traceback from marshmallow import ValidationError
bp = Blueprint('user', __name__) bp = Blueprint('user', __name__)
@ -17,20 +12,19 @@ bp = Blueprint('user', __name__)
@auth_guard() # no restriction by role @auth_guard() # no restriction by role
def PutUser(): def PutUser():
content = None
try: try:
verify_if_request_is_json(request)
content = request.get_json(force=True) content = request.get_json(force=True)
loadedModel = model.UserSchema().load(data=content, many=False, partial=True) loadedModel = model.UserSchema().load(data=content, many=False, partial=True)
return impl.user.PutUser(loadedModel)
except ValidationError as ex: except ValidationError as ex:
logging.warning("UserSchema validation failed. Payload=%s", json.dumps(content, default=str)) logging.error(ex)
return create_validation_error_response(ex=ex, status_code=400) print(ex)
return json.dumps(f"bad format. \nError Messages: {ex.messages}. \nValid Data: {ex.valid_data}"), 400
except Exception as ex: except Exception as ex:
logging.error("UserSchema load failed. Payload=%s\n%s", json.dumps(content, default=str), traceback.format_exc()) logging.error(ex)
return create_dynamic_exception_response(ex=None, status_code=400, message="bad format") print(ex)
return json.dumps("bad format"), 400
return impl.user.PutUser(loadedModel)

View File

@ -94,7 +94,7 @@ class RequestCode_HTTP_403_FORBIDDEN(RequestStatusCode):
def response(self, message="invalid credentials"): def response(self, message="invalid credentials"):
result = {} result = {}
result["error_field"] = message result["message"] = message
return json.dumps(result) return json.dumps(result)
@ -110,7 +110,7 @@ class RequestCode_HTTP_404_NOT_FOUND(RequestStatusCode):
def response(self, message="no such record"): def response(self, message="no such record"):
result = {} result = {}
result["error_field"] = message result["message"] = message
return json.dumps(result) return json.dumps(result)
@ -126,6 +126,6 @@ class RequestCode_HTTP_500_INTERNAL_SERVER_ERROR(RequestStatusCode):
def response(self, message="credential lookup mismatch"): def response(self, message="credential lookup mismatch"):
result = {} result = {}
result["error_field"] = message result["message"] = message
return json.dumps(result) return json.dumps(result)

View File

@ -48,6 +48,13 @@ class PierSide(IntEnum):
PORTSIDE = 0 # Port/Backbord PORTSIDE = 0 # Port/Backbord
STARBOARD_SIDE = 1 # Starboard / Steuerbord STARBOARD_SIDE = 1 # Starboard / Steuerbord
class NotificationType(IntFlag):
"""determines the method by which a notification is distributed to users. Flagging allows selecting multiple notification types."""
UNDEFINED = 0
EMAIL = 1
POPUP = 2
MESSENGER = 4
class ParticipantFlag(IntFlag): class ParticipantFlag(IntFlag):
""" """
| 1 | If this flag is set on a shipcall record with participant type Agency (8), | 1 | If this flag is set on a shipcall record with participant type Agency (8),
@ -56,3 +63,6 @@ class ParticipantFlag(IntFlag):
undefined = 0 undefined = 0
BSMD = 1 BSMD = 1
@classmethod
def _missing_(cls, value):
return cls.undefined

View File

@ -6,9 +6,6 @@ import typing
from BreCal.schemas.model import Shipcall, Ship, Participant, Berth, User, Times, ShipcallParticipantMap from BreCal.schemas.model import Shipcall, Ship, Participant, Berth, User, Times, ShipcallParticipantMap
from BreCal.database.enums import ParticipantType from BreCal.database.enums import ParticipantType
from BreCal.local_db import getPoolConnection from BreCal.local_db import getPoolConnection
from BreCal.database.sql_queries import SQLQuery
from BreCal.schemas import model
def pandas_series_to_data_model(): def pandas_series_to_data_model():
return return
@ -36,9 +33,7 @@ def get_synchronous_shipcall_times_standalone(query_time:pd.Timestamp, all_df_ti
returns: counts returns: counts
""" """
assert (isinstance(query_time,pd.Timestamp)) or (pd.isnull(query_time)), f"expected a timestamp. Found type: {type(query_time)} with value: {query_time}" assert isinstance(query_time,pd.Timestamp)
if pd.isnull(query_time):
return 0
# get a timedelta for each valid (not Null) time entry # get a timedelta for each valid (not Null) time entry
time_deltas_eta = [(query_time.to_pydatetime()-time_.to_pydatetime()) for time_ in all_df_times.loc[:,"eta_berth"] if not pd.isnull(time_)] time_deltas_eta = [(query_time.to_pydatetime()-time_.to_pydatetime()) for time_ in all_df_times.loc[:,"eta_berth"] if not pd.isnull(time_)]
@ -92,16 +87,6 @@ def execute_sql_query_standalone(query, param={}, pooledConnection=None, model=N
schemas = commands.query_single_or_default(query, sentinel, param=param) if model is None else commands.query_single_or_default(query, sentinel, param=param, model=model) schemas = commands.query_single_or_default(query, sentinel, param=param) if model is None else commands.query_single_or_default(query, sentinel, param=param, model=model)
if schemas is sentinel: if schemas is sentinel:
raise Exception("no such record") raise Exception("no such record")
elif command_type=="single_or_none":
sentinel = object()
# pulls a *single* row from the query. Typically, these queries require an ID within the param dictionary.
# when providing a model, such as model.Shipcall, the dataset is immediately translated into a data model.
schemas = commands.query_single_or_default(query, sentinel, param=param) if model is None else commands.query_single_or_default(query, sentinel, param=param, model=model)
schemas = None if schemas is sentinel else schemas
elif command_type=="execute_scalar":
schemas = commands.execute_scalar(query)
else: else:
raise ValueError(command_type) raise ValueError(command_type)
@ -111,26 +96,6 @@ def execute_sql_query_standalone(query, param={}, pooledConnection=None, model=N
pooledConnection.close() pooledConnection.close()
return schemas return schemas
def get_assigned_participant_of_type(shipcall_id:int, participant_type:typing.Union[int,model.ParticipantType])->typing.Optional[model.Participant]:
"""obtains the ShipcallParticipantMap of a given shipcall and finds the participant id of a desired type. Finally, returns the respective Participant"""
spm_shipcall_data = execute_sql_query_standalone(
query=SQLQuery.get_shipcall_participant_map_by_shipcall_id_and_type(),
param={"id":shipcall_id, "type":int(participant_type)},
command_type="query") # returns a list of matches
if len(spm_shipcall_data)==0:
return None
query = 'SELECT * FROM participant WHERE id=?participant_id?'
assigned_participant = execute_sql_query_standalone(
query=query,
param={"participant_id":spm_shipcall_data[0]["participant_id"]},
model=model.Participant,
command_type="single_or_none"
) # returns a list of matches
return assigned_participant
class SQLHandler(): class SQLHandler():
""" """
An object that reads SQL queries from the sql_connection and stores it in pandas DataFrames. The object can read all available tables An object that reads SQL queries from the sql_connection and stores it in pandas DataFrames. The object can read all available tables
@ -474,6 +439,4 @@ class SQLHandler():
def count_synchronous_shipcall_times(self, query_time:pd.Timestamp, all_df_times:pd.DataFrame, delta_threshold=900)->int: def count_synchronous_shipcall_times(self, query_time:pd.Timestamp, all_df_times:pd.DataFrame, delta_threshold=900)->int:
"""count all times entries, which are too close to the query_time. The {delta_threshold} determines the threshold. returns counts (int)""" """count all times entries, which are too close to the query_time. The {delta_threshold} determines the threshold. returns counts (int)"""
if all_df_times is None:
all_df_times = self.df_dict.get("times")
return get_synchronous_shipcall_times_standalone(query_time, all_df_times, delta_threshold) return get_synchronous_shipcall_times_standalone(query_time, all_df_times, delta_threshold)

View File

@ -10,8 +10,7 @@ def create_sql_query_shipcall_get(options:dict)->str:
options : dict. A dictionary, which must contains the 'past_days' key (int). Determines the range options : dict. A dictionary, which must contains the 'past_days' key (int). Determines the range
by which shipcalls are filtered. by which shipcalls are filtered.
""" """
if "participant_id" not in options: # if no participant_id is given, all shipcalls are selected query = ("SELECT s.id as id, ship_id, type, eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, " +
query = ("SELECT s.id as id, ship_id, port_id, type, eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, " +
"flags, s.pier_side, bunkering, replenishing_terminal, replenishing_lock, draft, tidal_window_from, " + "flags, s.pier_side, bunkering, replenishing_terminal, replenishing_lock, draft, tidal_window_from, " +
"tidal_window_to, rain_sensitive_cargo, recommended_tugs, anchored, moored_lock, canceled, evaluation, " + "tidal_window_to, rain_sensitive_cargo, recommended_tugs, anchored, moored_lock, canceled, evaluation, " +
"evaluation_message, evaluation_time, evaluation_notifications_sent, s.created as created, s.modified as modified, time_ref_point " + "evaluation_message, evaluation_time, evaluation_notifications_sent, s.created as created, s.modified as modified, time_ref_point " +
@ -25,23 +24,6 @@ def create_sql_query_shipcall_get(options:dict)->str:
"((t.id IS NOT NULL AND t.etd_berth >= DATE(NOW() - INTERVAL %d DAY)) OR " + "((t.id IS NOT NULL AND t.etd_berth >= DATE(NOW() - INTERVAL %d DAY)) OR " +
"(etd >= DATE(NOW() - INTERVAL %d DAY)))) " + "(etd >= DATE(NOW() - INTERVAL %d DAY)))) " +
"ORDER BY eta") % (options["past_days"], options["past_days"], options["past_days"], options["past_days"]) "ORDER BY eta") % (options["past_days"], options["past_days"], options["past_days"], options["past_days"])
else:
query = ("SELECT s.id as id, ship_id, port_id, type, eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, " +
"flags, s.pier_side, bunkering, replenishing_terminal, replenishing_lock, draft, tidal_window_from, " +
"tidal_window_to, rain_sensitive_cargo, recommended_tugs, anchored, moored_lock, canceled, evaluation, " +
"evaluation_message, evaluation_time, evaluation_notifications_sent, s.created as created, s.modified as modified, time_ref_point " +
"FROM shipcall s " +
"LEFT JOIN times t ON t.shipcall_id = s.id AND t.participant_type = 8 " +
"WHERE " +
"port_id in (SELECT port_id FROM participant_port_map WHERE participant_id = %d)" +
" AND (" +
"(type = 1 AND " +
"((t.id IS NOT NULL AND t.eta_berth >= DATE(NOW() - INTERVAL %d DAY)) OR " +
"(eta >= DATE(NOW() - INTERVAL %d DAY)))) OR " +
"((type = 2 OR type = 3) AND " +
"((t.id IS NOT NULL AND t.etd_berth >= DATE(NOW() - INTERVAL %d DAY)) OR " +
"(etd >= DATE(NOW() - INTERVAL %d DAY))))) " +
"ORDER BY eta") % (options["participant_id"], options["past_days"], options["past_days"], options["past_days"], options["past_days"])
return query return query
@ -237,7 +219,7 @@ class SQLQuery():
@staticmethod @staticmethod
def get_user()->str: def get_user()->str:
query = "SELECT id, participant_id, first_name, last_name, user_name, user_email, user_phone, password_hash, " +\ query = "SELECT id, participant_id, first_name, last_name, user_name, user_email, user_phone, password_hash, " +\
"api_key, notify_email, notify_whatsapp, notify_signal, notify_popup, notify_event, created, modified FROM user " +\ "api_key, notify_email, notify_whatsapp, notify_signal, notify_popup, created, modified FROM user " +\
"WHERE user_name = ?username? OR user_email = ?username?" "WHERE user_name = ?username? OR user_email = ?username?"
return query return query
@ -279,23 +261,11 @@ class SQLQuery():
"ORDER BY eta") "ORDER BY eta")
return query return query
def get_next24hrs_shipcalls()->str:
query = ("SELECT s.id as id, ship.name as name FROM shipcall s INNER JOIN ship ON s.ship_id = ship.id LEFT JOIN times t on t.shipcall_id = s.id AND t.participant_type = 8 " + \
"WHERE (type = 1 AND (COALESCE(t.eta_berth, eta) >= NOW() AND COALESCE(t.eta_berth, eta) < (NOW() + INTERVAL 1 DAY)))" + \
"OR ((type = 2 OR type = 3) AND (COALESCE(t.etd_berth, etd) >= NOW() AND COALESCE(t.etd_berth, etd) < (NOW() + INTERVAL 1 DAY)))"
"AND s.canceled = 0")
return query
@staticmethod @staticmethod
def get_ships()->str: def get_ships()->str:
query = "SELECT id, name, imo, callsign, participant_id, length, width, is_tug, bollard_pull, eni, created, modified, deleted FROM ship ORDER BY name" query = "SELECT id, name, imo, callsign, participant_id, length, width, is_tug, bollard_pull, eni, created, modified, deleted FROM ship ORDER BY name"
return query return query
@staticmethod
def get_ship_by_id()->str:
query = "SELECT * FROM ship where id = ?id?"
return query
@staticmethod @staticmethod
def get_times()->str: def get_times()->str:
query = "SELECT id, eta_berth, eta_berth_fixed, etd_berth, etd_berth_fixed, lock_time, lock_time_fixed, " + \ query = "SELECT id, eta_berth, eta_berth_fixed, etd_berth, etd_berth_fixed, lock_time, lock_time_fixed, " + \

Some files were not shown because too many files have changed in this diff Show More