Created an HTML-based Email notification template, which is adaptive to error messages. It includes a logo file from the local repository (logo_bremen_calling.png). Refactoring methods and clearing old methods. Created a one-line function to connect to the Email server. Included the Notifier's sending method in the routine will be executed every 15 minutes and includes those shipcalls, which have been evaluated at least 10 minutes ago. Located the logo file and email template in a resources/ folder within the library. Manually overwriting the Emailaddress of all notification recipients, so the BSMD mail is used throughout testing.

This commit is contained in:
Max Metz 2024-07-31 18:58:39 +02:00
parent cea615fe63
commit d8d75afc6b
59 changed files with 2364 additions and 420 deletions

View File

@ -18,7 +18,8 @@
"pm.environment.set(\"LOGON_TOKEN\", responseData.token)\r", "pm.environment.set(\"LOGON_TOKEN\", responseData.token)\r",
"console.log(\"Id: \" + responseData.id)" "console.log(\"Id: \" + responseData.id)"
], ],
"type": "text/javascript" "type": "text/javascript",
"packages": {}
} }
} }
], ],
@ -76,6 +77,39 @@
}, },
"response": [] "response": []
}, },
{
"name": "History GET",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{LOGON_TOKEN}}",
"type": "string"
}
]
},
"method": "GET",
"header": [],
"url": {
"raw": "{{SCHEMA}}{{PATH}}/history?shipcall_id=79",
"host": [
"{{SCHEMA}}{{PATH}}"
],
"path": [
"history"
],
"query": [
{
"key": "shipcall_id",
"value": "79"
}
]
}
},
"response": []
},
{ {
"name": "Shipcalls GET", "name": "Shipcalls GET",
"request": { "request": {
@ -120,7 +154,7 @@
"header": [], "header": [],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{\r\n \"ship_id\" : 1,\r\n \"type\" : 1,\r\n \"eta\" : \"2023-07-23T07:18:19\",\r\n \"voyage\" : \"43B\",\r\n \"tug_required\" : false,\r\n \"pilot_required\" : true,\r\n \"flags\" : 0,\r\n \"pier_side\" : false,\r\n \"bunkering\" : true,\r\n \"recommended_tugs\" : 2\r\n}", "raw": "{\r\n \"anchored\": null,\r\n \"arrival_berth_id\": 144,\r\n \"bunkering\": null,\r\n \"canceled\": false,\r\n \"created\": \"0001-01-01T00:00:00\",\r\n \"departure_berth_id\": null,\r\n \"draft\": null,\r\n \"eta\": \"2024-04-10T12:29:09.174\",\r\n \"etd\": null,\r\n \"evaluation\": null,\r\n \"evaluation_message\": null,\r\n \"flags\": null,\r\n \"id\": 0,\r\n \"modified\": null,\r\n \"moored_lock\": null,\r\n \"participants\": [\r\n {\r\n \"participant_id\": 136,\r\n \"type\": 8\r\n },\r\n {\r\n \"participant_id\": 11,\r\n \"type\": 32\r\n },\r\n {\r\n \"participant_id\": 1,\r\n \"type\": 1\r\n }\r\n ],\r\n \"pier_side\": null,\r\n \"pilot_required\": null,\r\n \"rain_sensitive_cargo\": null,\r\n \"recommended_tugs\": null,\r\n \"replenishing_lock\": null,\r\n \"replenishing_terminal\": null,\r\n \"ship_id\": 14,\r\n \"tidal_window_from\": null,\r\n \"tidal_window_to\": null,\r\n \"time_ref_point\": 0,\r\n \"tug_required\": null,\r\n \"type\": \"arrival\",\r\n \"voyage\": null\r\n}",
"options": { "options": {
"raw": { "raw": {
"language": "json" "language": "json"
@ -156,7 +190,7 @@
"header": [], "header": [],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{\r\n \"id\" : 2, \r\n \"recommended_tugs\" : 3\r\n}", "raw": "{\"type\": \"shifting\", \"evaluation\": \"green\", \"id\": 33, \"ship_id\": 2, \"eta\": \"2024-01-27T18:00:21\", \"etd\": \"2024-01-25T17:00:45\", \"arrival_berth_id\": 168, \"departure_berth_id\": 184, \"rain_sensitive_cargo\": \"False\", \"time_ref_point\": 1, \"participants\": [{\"participant_id\": 6, \"type\": 8}, {\"participant_id\": 11, \"type\": 32}, {\"participant_id\": 9, \"type\": 64}, {\"participant_id\": 1, \"type\": 1}], \"created\": \"2023-10-24T11:41:16\", \"modified\": \"2024-02-23T14:50:07\"}",
"options": { "options": {
"raw": { "raw": {
"language": "json" "language": "json"
@ -218,7 +252,7 @@
"method": "GET", "method": "GET",
"header": [], "header": [],
"url": { "url": {
"raw": "{{SCHEMA}}{{PATH}}/notifications?participant_id=1", "raw": "{{SCHEMA}}{{PATH}}/notifications?shipcall_id=4",
"host": [ "host": [
"{{SCHEMA}}{{PATH}}" "{{SCHEMA}}{{PATH}}"
], ],
@ -227,8 +261,8 @@
], ],
"query": [ "query": [
{ {
"key": "participant_id", "key": "shipcall_id",
"value": "1" "value": "4"
} }
] ]
} }
@ -262,6 +296,99 @@
}, },
"response": [] "response": []
}, },
{
"name": "Ships PUT",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{LOGON_TOKEN}}",
"type": "string"
}
]
},
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"id\": 17,\r\n \"name\": \"Testschiff 1\",\r\n \"imo\": 1231231,\r\n \"callsign\": \"TEST1\",\r\n \"participant_id\": null,\r\n \"length\": 202.0,\r\n \"width\": 25.0,\r\n \"is_tug\": 0,\r\n \"bollard_pull\": null,\r\n \"eni\": null,\r\n \"created\": \"2024-04-03T07:49:29\",\r\n \"modified\": null,\r\n \"deleted\": 0\r\n}"
},
"url": {
"raw": "{{SCHEMA}}{{PATH}}/ships",
"host": [
"{{SCHEMA}}{{PATH}}"
],
"path": [
"ships"
]
}
},
"response": []
},
{
"name": "Ships POST",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{LOGON_TOKEN}}",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"name\": \"Testschiff 02\",\r\n \"imo\": 9999992,\r\n \"length\": 100.2,\r\n \"width\": 16.5,\r\n \"is_tug\": 0,\r\n \"bollard_pull\": 42,\r\n \"callsign\": \"9992\",\r\n \"participant_id\": null,\r\n \"eni\": 1\r\n }"
},
"url": {
"raw": "{{SCHEMA}}{{PATH}}/ships",
"host": [
"{{SCHEMA}}{{PATH}}"
],
"path": [
"ships"
]
}
},
"response": []
},
{
"name": "Ships DELETE",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{LOGON_TOKEN}}",
"type": "string"
}
]
},
"method": "DELETE",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"id\": 15,\r\n \"name\": \"Testschiff 01\",\r\n \"imo\": 9999991\r\n }"
},
"url": {
"raw": "{{SCHEMA}}{{PATH}}/ships",
"host": [
"{{SCHEMA}}{{PATH}}"
],
"path": [
"ships"
]
}
},
"response": []
},
{ {
"name": "Times GET", "name": "Times GET",
"request": { "request": {
@ -278,7 +405,7 @@
"method": "GET", "method": "GET",
"header": [], "header": [],
"url": { "url": {
"raw": "{{SCHEMA}}{{PATH}}/times?shipcall_id=3", "raw": "{{SCHEMA}}{{PATH}}/times?shipcall_id=112",
"host": [ "host": [
"{{SCHEMA}}{{PATH}}" "{{SCHEMA}}{{PATH}}"
], ],
@ -288,7 +415,7 @@
"query": [ "query": [
{ {
"key": "shipcall_id", "key": "shipcall_id",
"value": "3" "value": "112"
} }
] ]
} }
@ -348,7 +475,12 @@
"header": [], "header": [],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{\r\n \"start_planned\" : \"2023-05-18T07:18:19\",\r\n \"end_planned\" : \"2023-05-18T09:18:19\", \r\n \"id\" : 1\r\n}" "raw": "{\r\n \"etd_berth\" : \"2023-01-09T05:00:39\", \r\n \"id\" : 11,\r\n \"participant_id\": 2,\r\n \"remarks\": \"test 23\",\r\n \"shipcall_id\" : 4,\r\n \"pier_side\" : 0\r\n \r\n}\r\n\r\n",
"options": {
"raw": {
"language": "json"
}
}
}, },
"url": { "url": {
"raw": "{{SCHEMA}}{{PATH}}/times", "raw": "{{SCHEMA}}{{PATH}}/times",
@ -378,7 +510,7 @@
"method": "DELETE", "method": "DELETE",
"header": [], "header": [],
"url": { "url": {
"raw": "{{SCHEMA}}{{PATH}}/times?id=3", "raw": "{{SCHEMA}}{{PATH}}/times?id=118",
"host": [ "host": [
"{{SCHEMA}}{{PATH}}" "{{SCHEMA}}{{PATH}}"
], ],
@ -388,12 +520,43 @@
"query": [ "query": [
{ {
"key": "id", "key": "id",
"value": "3" "value": "118"
} }
] ]
} }
}, },
"response": [] "response": []
},
{
"name": "User PUT",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{LOGON_TOKEN}}",
"type": "string"
}
]
},
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"id\": 2, \r\n \"old_password\" : \"Gurkensalat\",\r\n \"new_password\" : \"Hallowach\"\r\n}"
},
"url": {
"raw": "{{SCHEMA}}{{PATH}}/user",
"host": [
"{{SCHEMA}}{{PATH}}"
],
"path": [
"user"
]
}
},
"response": []
} }
], ],
"auth": { "auth": {

View File

@ -46,7 +46,7 @@ using System.Web;
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -850,7 +850,7 @@ namespace BreCalClient.misc.Api
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -1505,7 +1505,7 @@ namespace BreCalClient.misc.Api
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -2290,7 +2290,7 @@ namespace BreCalClient.misc.Api
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -3109,7 +3109,7 @@ namespace BreCalClient.misc.Api
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -3594,7 +3594,7 @@ namespace BreCalClient.misc.Api
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -4330,7 +4330,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -4390,7 +4390,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -4530,7 +4530,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -4748,7 +4748,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -5207,7 +5207,7 @@ namespace BreCalClient.misc.Client
string report = "C# SDK (BreCalClient.misc) Debug Report:\n"; string report = "C# SDK (BreCalClient.misc) Debug Report:\n";
report += " OS: " + System.Environment.OSVersion + "\n"; report += " OS: " + System.Environment.OSVersion + "\n";
report += " .NET Framework Version: " + System.Environment.Version + "\n"; report += " .NET Framework Version: " + System.Environment.Version + "\n";
report += " Version of the API: 1.3.0\n"; report += " Version of the API: 1.4.0\n";
report += " SDK Package Version: 1.0.0\n"; report += " SDK Package Version: 1.0.0\n";
return report; return report;
} }
@ -5276,7 +5276,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -5296,7 +5296,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -5353,7 +5353,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -5386,7 +5386,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -5419,7 +5419,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -5510,7 +5510,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -5627,7 +5627,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -5711,7 +5711,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -5970,7 +5970,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -5998,7 +5998,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -6068,7 +6068,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -6095,7 +6095,7 @@ namespace BreCalClient.misc.Client
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -6162,7 +6162,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -6288,7 +6288,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -6373,7 +6373,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -6446,7 +6446,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -6486,7 +6486,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -6596,7 +6596,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -6658,7 +6658,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -6784,7 +6784,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -6888,7 +6888,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -6923,7 +6923,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -6953,7 +6953,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -6993,7 +6993,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -7150,7 +7150,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -7226,7 +7226,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -7396,7 +7396,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -7701,7 +7701,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -7741,7 +7741,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */
@ -8023,7 +8023,7 @@ namespace BreCalClient.misc.Model
* *
* Administer DEBRE ship calls, times and notifications * Administer DEBRE ship calls, times and notifications
* *
* The version of the OpenAPI document: 1.3.0 * The version of the OpenAPI document: 1.4.0
* Contact: info@textbausteine.net * Contact: info@textbausteine.net
* Generated by: https://github.com/openapitools/openapi-generator.git * Generated by: https://github.com/openapitools/openapi-generator.git
*/ */

View File

@ -2,7 +2,7 @@ openapi: 3.0.0
x-stoplight: x-stoplight:
id: mwv4y8vcnopwr id: mwv4y8vcnopwr
info: info:
version: 1.3.0 version: 1.4.0
title: Bremen calling API title: Bremen calling API
description: 'Administer DEBRE ship calls, times and notifications' description: 'Administer DEBRE ship calls, times and notifications'
termsOfService: 'https://www.bsmd.de/' termsOfService: 'https://www.bsmd.de/'

67
misc/disclaimer.html Normal file
View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>Information gemäß Art. 13 und Art. 14 DSGVO für Auftraggeber Software BremenCalling</title>
<meta charset="UTF-8">
</head>
<body>
<h1>Information gemäß Art. 13 und Art. 14 DSGVO für Auftraggeber
Software BremenCalling</h1>
<hr />
Hier erhalten Sie Informationen gem. Art. 13 DSGVO über unseren Umgang mit Ihren Daten, wenn Sie die von uns betriebene Software „Bremen Calling“ nutzen.
<p>
<span style="text-decoration:underline;">Verantwortlicher:</span>
</p>
<p>
BREMER SCHIFFSMELDEDIENST Kapt. P. Langbein e.K.<br />
Vertreten durch die Geschäftsführer Bastian Güttner und Benjamin Wiese<br />
Hafenkopf II / Überseetor 20, 28217 Bremen / Germany<br />
Tel. +49 (0) 421 38 48 27<br />
E-Mail : report@bsmd.de<br />
</p>
<h2>Zweck der Datenverarbeitung:</h2>
<ul>
<li>Bereitstellung der Software „Bremen Calling“ zur Nutzung für Kunden und Auftraggeber</li>
</ul>
<h2>Rechtsgrundlage für die Datenverarbeitung:</h2>
Art. 6 Abs. 1 lit. b DSGVO (Vertrag, vorvertragliche Maßnahmen auf Anfrage der betroffenen Person)
<h2>Berechtigte Interesse des Verantwortlichen:</h2>
Hier nicht einschlägig.
<h3>Wozu benötigen wir Ihre Daten? („Hintergründe für die Bereitstellung der Daten“):</h3>
Wenn Sie unsere Software nutzen möchten, müssen wir Sie als Benutzer einrichten. Dies geschieht auf der Grundlage Ihrer (firmenbezogenen) E-Mail-Adresse und Ihres Namens.
<h3>Holen wir bei anderen Stellen außer bei Ihnen selber Informationen über Sie ein?</h3>
Nein.
<h3>Empfänger der Daten:</h3>
Wir verarbeiten Ihre Daten nur innerhalb des BSMD.
<h3>Übermittlung in Drittländer:</h3>
Eine Übermittlung Ihrer Daten in Staaten außerhalb der EU findet nicht statt.
<h3>Dauer der Speicherung:</h3>
Wir speichern Ihre Daten, solange wie Ihr Vertrag zur Nutzung unserer Software mit Ihnen besteht. Nach dem Ende unserer Geschäftsbeziehung stellen wir Ihre Benutzerdaten inaktiv und löschen sie nach sechs Monaten. Wir erarbeiten derzeit ein Löschkonzept, das ein systematisches Löschen von personenbezogenen Daten ermöglicht.
<h3>Ihre Rechte in Bezug auf Ihre Daten:</h3>
<p>
Sie können von uns Auskunft verlangen ob wir persönliche Daten von Ihnen speichern, und wenn ja, welche das sind und was wir damit tun (Art. 15 DSGVO).
</p>
<p>
Sollten wir unrichtige oder unvollständige Daten von Ihnen haben, können Sie die Berichtigung dieser Daten verlangen (Art. 16 DSGVO).
</p>
<p>
Sie können auch die Löschung Ihrer Daten verlangen (Art. 17 DSGVO). Es kann jedoch Gründe geben, aus denen wir Ihre Daten trotz Ihres Wunsches nicht löschen dürfen oder löschen müssen. Diese Gründe bestimmt das Gesetz. Wenn Sie von uns die Löschung Ihrer Daten verlangen, werden wir prüfen, ob möglicherweise solche Gründe vorliegen. Wenn nicht, löschen wir Ihre Daten. Die Alternative zur Löschung Ihrer Daten ist in bestimmten Fällen die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten (Art. 18 DSGVO). Auch dabei gilt: lassen Sie uns wissen, wie Sie verfahren wollen, dann prüfen wir die gesetzlichen Vorgaben und finden einen Weg, der Ihre und unsere Interessen ausgleicht.
</p>
<p>
Art. 20 DSGVO sieht vor, dass wir Ihnen in bestimmten Fällen Ihre persönlichen Daten in einem strukturierten, gängigen und maschinenlesbaren Format zur Verfügung stellen müssen, wenn Sie es wünschen.
</p>
<p>
Möchten Sie von Ihren hier beschriebenen Rechten Gebrauch machen, genügt eine E-Mail an bremencalling@bsmd.de.
</p>
<p>
Wenn Sie der Meinung sind, dass wir die Datenschutzvorgaben für die Verarbeitung Ihrer Daten auf diesen Webseiten nicht einhalten, können Sie sich bei einer Datenschutz-Aufsichtsbehörde beschweren. Eine Liste der in Deutschland zuständigen Datenschutz-Aufsichtsbehörden finden Sie hier:
<a href="https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html">https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html</a>.
</p>
<hr />
<h4>
Stand dieser Datenschutzinformationen: April 2024
</h4>
</body>

14
misc/product_codes.txt Normal file
View File

@ -0,0 +1,14 @@
Bremen calling Installer Upgrade Codes:
======================================
Test: 1C7FA3E4-BAB9-4911-9348-73094357FC7C
Prod: 81A329F1-C663-48DA-9E15-DAF19F99B5AE
Product codes v1.2.2.3:
Test: 6F89CBAA-2189-456F-A347-0C0158325B61
Prod: 0ED342DD-DC00-4CE4-8348-96BB3AB726B1
Note:
If you want the MSI to "upgrade" from one version to the other, the
upgrade code must stay the same and the product code must change.

View File

@ -1 +1 @@
1.3.0.0 1.4.0.0

View File

@ -83,6 +83,9 @@
<setting name="W4Top" serializeAs="String"> <setting name="W4Top" serializeAs="String">
<value>0</value> <value>0</value>
</setting> </setting>
<setting name="FilterCriteriaMap" serializeAs="String">
<value />
</setting>
</BreCalClient.Properties.Settings> </BreCalClient.Properties.Settings>
</userSettings> </userSettings>
</configuration> </configuration>

View File

@ -8,8 +8,8 @@
<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.3.0.0</AssemblyVersion> <AssemblyVersion>1.4.0.0</AssemblyVersion>
<FileVersion>1.3.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>
@ -17,6 +17,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="Resources\24hours.png" />
<None Remove="Resources\add.png" /> <None Remove="Resources\add.png" />
<None Remove="Resources\arrow_down_green.png" /> <None Remove="Resources\arrow_down_green.png" />
<None Remove="Resources\arrow_down_red.png" /> <None Remove="Resources\arrow_down_red.png" />
@ -38,6 +39,7 @@
<None Remove="Resources\lock_open.png" /> <None Remove="Resources\lock_open.png" />
<None Remove="Resources\logo_bremen_calling.png" /> <None Remove="Resources\logo_bremen_calling.png" />
<None Remove="Resources\nav_refresh_green.png" /> <None Remove="Resources\nav_refresh_green.png" />
<None Remove="Resources\nav_undo_red.png" />
<None Remove="Resources\ship2.png" /> <None Remove="Resources\ship2.png" />
<None Remove="Resources\sign_warning.png" /> <None Remove="Resources\sign_warning.png" />
<None Remove="Resources\trafficlight_green.png" /> <None Remove="Resources\trafficlight_green.png" />
@ -73,6 +75,7 @@
<Generator>OpenApiCodeGenerator</Generator> <Generator>OpenApiCodeGenerator</Generator>
<LastGenOutput>BreCalApi.cs</LastGenOutput> <LastGenOutput>BreCalApi.cs</LastGenOutput>
</None> </None>
<Resource Include="Resources\24hours.png" />
<Resource Include="Resources\add.png" /> <Resource Include="Resources\add.png" />
<Resource Include="Resources\arrow_down_green.png" /> <Resource Include="Resources\arrow_down_green.png" />
<Resource Include="Resources\arrow_down_red.png" /> <Resource Include="Resources\arrow_down_red.png" />
@ -94,6 +97,7 @@
<Resource Include="Resources\lock_open.png" /> <Resource Include="Resources\lock_open.png" />
<Resource Include="Resources\logo_bremen_calling.png" /> <Resource Include="Resources\logo_bremen_calling.png" />
<Resource Include="Resources\nav_refresh_green.png" /> <Resource Include="Resources\nav_refresh_green.png" />
<Resource Include="Resources\nav_undo_red.png" />
<Resource Include="Resources\ship2.png" /> <Resource Include="Resources\ship2.png" />
<Resource Include="Resources\sign_warning.png" /> <Resource Include="Resources\sign_warning.png" />
<Resource Include="Resources\StringResources.de.xaml"> <Resource Include="Resources\StringResources.de.xaml">

View File

@ -10,6 +10,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig .editorconfig = .editorconfig
EndProjectSection EndProjectSection
EndProject EndProject
Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "Setup", "..\Setup\Setup.vdproj", "{CDC1EC53-8D75-4F8B-B627-A76B126380D4}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -20,6 +22,8 @@ Global
{FA9E0A87-FBFB-4F2B-B5FA-46DE2E5E4BCB}.Debug|Any CPU.Build.0 = Debug|Any CPU {FA9E0A87-FBFB-4F2B-B5FA-46DE2E5E4BCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA9E0A87-FBFB-4F2B-B5FA-46DE2E5E4BCB}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA9E0A87-FBFB-4F2B-B5FA-46DE2E5E4BCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA9E0A87-FBFB-4F2B-B5FA-46DE2E5E4BCB}.Release|Any CPU.Build.0 = Release|Any CPU {FA9E0A87-FBFB-4F2B-B5FA-46DE2E5E4BCB}.Release|Any CPU.Build.0 = Release|Any CPU
{CDC1EC53-8D75-4F8B-B627-A76B126380D4}.Debug|Any CPU.ActiveCfg = Debug
{CDC1EC53-8D75-4F8B-B627-A76B126380D4}.Release|Any CPU.ActiveCfg = Release
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -144,9 +144,12 @@ namespace BreCalClient
internal static void InitializeShips(List<Ship> ships) internal static void InitializeShips(List<Ship> ships)
{ {
_ships.Clear();
_allShips.Clear();
foreach (var ship in ships) foreach (var ship in ships)
{ {
ShipModel sm = new ShipModel(ship); ShipModel sm = new(ship);
_shipLookupDict[ship.Id] = sm; _shipLookupDict[ship.Id] = sm;
if (!ship.Deleted) if (!ship.Deleted)
_ships.Add(sm); _ships.Add(sm);

View File

@ -0,0 +1,41 @@
using System.Text.RegularExpressions;
using System.Windows.Input;
using Xceed.Wpf.Toolkit;
namespace BreCalClient
{
public class DateTimePickerExt : DateTimePicker
{
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
base.OnPreviewTextInput(e);
if (this.Template.FindName("PART_TextBox", this) is not WatermarkTextBox tb) return;
// strip input after caret
string subText = (tb.CaretIndex > 0) ? this.Text[..tb.CaretIndex] : tb.Text; // Range operator instead of Substring(0, tb.CaretIndex)
string text = subText + e.Text;
// System.Diagnostics.Debug.WriteLine("C:" + this.Text + " E: " + e.Text + " Caret: " + tb.CaretIndex + " Subt: " + subText);
// 10 char eingabe "am Stück"
if (Regex.IsMatch(text, @"^\d{10}"))
{
e.Handled = true;
tb.Text = Regex.Replace(text, @"(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})", "$1.$2.20$3 $4:$5");
tb.CaretIndex = tb.Text.Length;
tb.SelectedText = "";
}
// nur die Zeit wird markiert und mit 4 Zahlen befüllt, diese Regel setzt den Doppelpunkt
else if (Regex.IsMatch(text, @"^(\d{2}\.\d{2}\. \d{4} \d{3})"))
{
e.Handled = true;
tb.Text = Regex.Replace(text, @"(\d{2}\.\d{2}\. \d{4} \d{2})(\d)(.*)", "$1:$2");
// System.Diagnostics.Trace.WriteLine("Replaced: " + tb.Text);
tb.CaretIndex = tb.Text.Length;
tb.Select(tb.Text.Length, 0);
}
}
}
}

View File

@ -4,11 +4,11 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BreCalClient" xmlns:local="clr-namespace:BreCalClient"
xmlns:p = "clr-namespace:BreCalClient.Resources" xmlns:p = "clr-namespace:BreCalClient.Resources"
xmlns:api="clr-namespace:BreCalClient.misc.Model" xmlns:api="clr-namespace:BreCalClient.misc.Model"
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.textEditShipcall}" Height="270" Width="800" Loaded="Window_Loaded" ResizeMode="NoResize" Icon="Resources/containership.ico"> Title="{x:Static p:Resources.textEditShipcall}" Height="298" Width="800" Loaded="Window_Loaded" ResizeMode="NoResize" Icon="Resources/containership.ico">
<Window.Resources> <Window.Resources>
<local:BoolToIndexConverter x:Key="boolToIndexConverter" /> <local:BoolToIndexConverter x:Key="boolToIndexConverter" />
<local:EnumToStringConverter x:Key="enumToStringConverter" /> <local:EnumToStringConverter x:Key="enumToStringConverter" />
@ -29,6 +29,7 @@
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Label Content="{x:Static p:Resources.textShip}" Grid.Column="0" Grid.Row="0" HorizontalContentAlignment="Right"/> <Label Content="{x:Static p:Resources.textShip}" Grid.Column="0" Grid.Row="0" HorizontalContentAlignment="Right"/>
@ -46,7 +47,7 @@
<Button x:Name="buttonEditShips" Grid.Column="1" Grid.Row="6" Margin="2" Content="{x:Static p:Resources.textEditShips}" Click="buttonEditShips_Click" Visibility="Hidden" /> <Button x:Name="buttonEditShips" Grid.Column="1" Grid.Row="6" 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" />
<ComboBox ItemsSource="{local:Enumerate {x:Type api:ShipcallType}}" Grid.Column="3" Margin="2" Grid.Row="0" SelectionChanged="comboBoxCategories_SelectionChanged" x:Name="comboBoxCategories" /> <ComboBox ItemsSource="{local:Enumerate {x:Type api:ShipcallType}}" Grid.Column="3" Margin="2" Grid.Row="0" SelectionChanged="comboBoxCategories_SelectionChanged" x:Name="comboBoxCategories" />
<Label Content="{x:Static p:Resources.textBerth}" Grid.Column="2" Grid.Row="1" HorizontalContentAlignment="Right"/> <Label Content="{x:Static p:Resources.textBerth}" Grid.Column="2" Grid.Row="1" HorizontalContentAlignment="Right"/>
@ -64,12 +65,13 @@
<Label Content="Zeit Ref." Grid.Column="2" Grid.Row="2" HorizontalContentAlignment="Right" /> <Label Content="Zeit Ref." Grid.Column="2" Grid.Row="2" HorizontalContentAlignment="Right" />
<Label Content="ETA" Grid.Column="2" Grid.Row="3" HorizontalContentAlignment="Right" Margin="0,2,0,26" Grid.RowSpan="2"/> <Label Content="ETA" Grid.Column="2" Grid.Row="4" HorizontalContentAlignment="Right" Margin="0,2,0,26" Grid.RowSpan="2" x:Name="labelETA"/>
<Label Content="ETD" Grid.Column="2" Grid.Row="4" HorizontalContentAlignment="Right"/> <Label Content="ETD" Grid.Column="2" Grid.Row="3" HorizontalContentAlignment="Right" x:Name="labelETD"/>
<ComboBox x:Name="comboBoxTimeRef" Grid.Column="3" Margin="2" Grid.Row="2" /> <ComboBox x:Name="comboBoxTimeRef" Grid.Column="3" Margin="2" Grid.Row="2" />
<xctk:DateTimePicker x:Name="datePickerETA" Grid.Column="3" Grid.Row="3" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False" ValueChanged="datePickerETA_ValueChanged"/>
<xctk:DateTimePicker x:Name="datePickerETD" Grid.Column="3" Grid.Row="4" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False" ValueChanged="datePickerETD_ValueChanged"/> <local:DateTimePickerExt x:Name="datePickerETD" Grid.Column="3" Grid.Row="3" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False" ValueChanged="datePickerETD_ValueChanged"/>
<local:DateTimePickerExt x:Name="datePickerETA" Grid.Column="3" Grid.Row="4" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False" ValueChanged="datePickerETA_ValueChanged"/>
<Label Content="{x:Static p:Resources.textAgency}" Grid.Column="2" Grid.Row="5" HorizontalContentAlignment="Right"/> <Label Content="{x:Static p:Resources.textAgency}" Grid.Column="2" Grid.Row="5" HorizontalContentAlignment="Right"/>
<ComboBox Name="comboBoxAgency" Grid.Column="3" Grid.Row="5" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxAgency_SelectionChanged"> <ComboBox Name="comboBoxAgency" Grid.Column="3" Grid.Row="5" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxAgency_SelectionChanged">
@ -80,8 +82,12 @@
</ComboBox.ContextMenu> </ComboBox.ContextMenu>
</ComboBox> </ComboBox>
<Label x:Name="labelBSMDGranted" Grid.Row="6" 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" />
<StackPanel Grid.Row="7" Grid.Column="3" Orientation="Horizontal" HorizontalAlignment="Right">
<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="255" />
<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" />
<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"/>
</StackPanel> </StackPanel>

View File

@ -64,6 +64,8 @@ namespace BreCalClient
this.comboBoxTimeRef.ItemsSource = BreCalLists.TimeRefs; this.comboBoxTimeRef.ItemsSource = BreCalLists.TimeRefs;
this.integerUpDownShiftingCount.Value = this.ShipcallModel.ShiftSequence;
if (this.ShipcallModel.Shipcall == null) this.ShipcallModel.Shipcall = new(); if (this.ShipcallModel.Shipcall == null) this.ShipcallModel.Shipcall = new();
this.CopyToControls(); this.CopyToControls();
@ -116,32 +118,48 @@ namespace BreCalClient
{ {
switch (type) switch (type)
{ {
case ShipcallType.Arrival: case ShipcallType.Arrival:
this.datePickerETA.IsEnabled = true; this.datePickerETD.Visibility = Visibility.Hidden;
this.datePickerETD.IsEnabled = false; this.labelETD.Visibility = Visibility.Hidden;
this.datePickerETA.Visibility = Visibility.Visible;
this.labelETA.Visibility = Visibility.Visible;
Grid.SetRow(datePickerETA, 3);
Grid.SetRow(labelETA, 3);
this.datePickerETD.Value = null; this.datePickerETD.Value = null;
this.comboBoxDepartureBerth.SelectedIndex = -1; this.comboBoxDepartureBerth.SelectedIndex = -1;
this.comboBoxDepartureBerth.IsEnabled = false; this.comboBoxDepartureBerth.IsEnabled = false;
this.comboBoxArrivalBerth.IsEnabled = true; this.comboBoxArrivalBerth.IsEnabled = true;
this.comboBoxTimeRef.IsEnabled = true; this.comboBoxTimeRef.IsEnabled = true;
this.labelShiftingCount.Visibility = Visibility.Hidden;
this.integerUpDownShiftingCount.Visibility = Visibility.Hidden;
break; break;
case ShipcallType.Departure: case ShipcallType.Departure:
this.datePickerETA.IsEnabled = false; this.datePickerETD.Visibility = Visibility.Visible;
this.datePickerETD.IsEnabled = true; this.labelETD.Visibility = Visibility.Visible;
this.datePickerETA.Visibility = Visibility.Hidden;
this.labelETA.Visibility = Visibility.Hidden;
this.datePickerETA.Value = null; this.datePickerETA.Value = null;
this.comboBoxArrivalBerth.SelectedIndex = -1; this.comboBoxArrivalBerth.SelectedIndex = -1;
this.comboBoxArrivalBerth.IsEnabled = false; this.comboBoxArrivalBerth.IsEnabled = false;
this.comboBoxDepartureBerth.IsEnabled = true; this.comboBoxDepartureBerth.IsEnabled = true;
this.comboBoxTimeRef.IsEnabled = false; this.comboBoxTimeRef.IsEnabled = false;
this.comboBoxTimeRef.SelectedIndex = 0; this.comboBoxTimeRef.SelectedIndex = 0;
this.labelShiftingCount.Visibility = Visibility.Hidden;
this.integerUpDownShiftingCount.Visibility = Visibility.Hidden;
break; break;
case ShipcallType.Shifting: case ShipcallType.Shifting:
this.datePickerETA.IsEnabled = true; Grid.SetRow(datePickerETA, 4);
this.datePickerETD.IsEnabled = true; Grid.SetRow(labelETA, 4);
this.datePickerETA.Visibility = Visibility.Visible;
this.labelETA.Visibility = Visibility.Visible;
this.datePickerETD.Visibility = Visibility.Visible;
this.labelETD.Visibility = Visibility.Visible;
this.comboBoxArrivalBerth.IsEnabled = true; this.comboBoxArrivalBerth.IsEnabled = true;
this.comboBoxDepartureBerth.IsEnabled = true; this.comboBoxDepartureBerth.IsEnabled = true;
this.comboBoxTimeRef.IsEnabled = false; this.comboBoxTimeRef.IsEnabled = false;
this.comboBoxTimeRef.SelectedIndex = 0; this.comboBoxTimeRef.SelectedIndex = 0;
this.labelShiftingCount.Visibility = Visibility.Visible;
this.integerUpDownShiftingCount.Visibility = Visibility.Visible;
break; break;
} }
} }
@ -226,6 +244,7 @@ namespace BreCalClient
{ {
this.ShipcallModel.Shipcall.DepartureBerthId = (this.comboBoxArrivalBerth.SelectedItem != null) ? ((Berth)this.comboBoxArrivalBerth.SelectedItem).Id : null; this.ShipcallModel.Shipcall.DepartureBerthId = (this.comboBoxArrivalBerth.SelectedItem != null) ? ((Berth)this.comboBoxArrivalBerth.SelectedItem).Id : null;
this.ShipcallModel.Shipcall.ArrivalBerthId = (this.comboBoxDepartureBerth.SelectedItem != null) ? ((Berth)this.comboBoxDepartureBerth.SelectedItem).Id : null; this.ShipcallModel.Shipcall.ArrivalBerthId = (this.comboBoxDepartureBerth.SelectedItem != null) ? ((Berth)this.comboBoxDepartureBerth.SelectedItem).Id : null;
this.ShipcallModel.ShiftSequence = (byte?) this.integerUpDownShiftingCount.Value;
} }
Participant? participant; Participant? participant;
@ -289,7 +308,7 @@ namespace BreCalClient
if (this.ShipcallModel == null) return; if (this.ShipcallModel == null) return;
if (this.ShipcallModel.Shipcall != null) if (this.ShipcallModel.Shipcall != null)
{ {
this.comboBoxTimeRef.SelectedIndex = this.ShipcallModel.Shipcall.TimeRefPoint ?? 0; this.comboBoxTimeRef.SelectedIndex = this.ShipcallModel.Shipcall.TimeRefPoint ?? 1;
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;
@ -354,7 +373,7 @@ namespace BreCalClient
this.comboBoxDepartureBerth.IsEnabled = isBsmd || isAgency; this.comboBoxDepartureBerth.IsEnabled = isBsmd || isAgency;
this.comboBoxShip.IsEnabled = isBsmd; this.comboBoxShip.IsEnabled = isBsmd;
this.datePickerETA.IsEnabled = isAgency || isBsmd; this.datePickerETA.IsEnabled = isAgency || isBsmd;
this.datePickerETD.IsEnabled = isAgency; this.datePickerETD.IsEnabled = isAgency || isBsmd;
this.labelBSMDGranted.Visibility = editRightGrantedForBSMD ? Visibility.Visible : Visibility.Hidden; this.labelBSMDGranted.Visibility = editRightGrantedForBSMD ? Visibility.Visible : Visibility.Hidden;

View File

@ -5,7 +5,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BreCalClient" xmlns:local="clr-namespace:BreCalClient"
xmlns:p = "clr-namespace:BreCalClient.Resources" xmlns:p = "clr-namespace:BreCalClient.Resources"
xmlns:db="clr-namespace:BreCalClient;assembly=BreCalClient"
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.textEditShipcall}" Height="403" Width="900" Loaded="Window_Loaded" ResizeMode="CanResizeWithGrip" Icon="Resources/containership.ico"> Title="{x:Static p:Resources.textEditShipcall}" Height="403" Width="900" Loaded="Window_Loaded" ResizeMode="CanResizeWithGrip" Icon="Resources/containership.ico">
@ -42,19 +41,19 @@
<Label Grid.Column="0" Grid.Row="0" Content="{x:Static p:Resources.textIncoming}" FontWeight="DemiBold"/> <Label Grid.Column="0" Grid.Row="0" Content="{x:Static p:Resources.textIncoming}" FontWeight="DemiBold"/>
<Image Margin="2" Grid.Column="1" Source="Resources/arrow_down_red.png" /> <Image Margin="2" Grid.Column="1" Source="Resources/arrow_down_red.png" />
</Grid> </Grid>
<Label Content="ETA" x:Name="labelETA" Grid.Column="0" Grid.Row="1" HorizontalContentAlignment="Right" FontWeight="Bold"/> <Label Content="ETA" x:Name="labelETA" Grid.Column="0" Grid.Row="1" HorizontalContentAlignment="Right" FontWeight="Bold"/>
<Grid Grid.Column="1" Grid.Row="1"> <Grid Grid.Column="1" Grid.Row="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<xctk:DateTimePicker x:Name="datePickerETA" Grid.Column="0" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETA_ValueChanged"/> <local:DateTimePickerExt x:Name="datePickerETA" Grid.Column="0" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETA_ValueChanged"/>
<xctk:DateTimePicker x:Name="datePickerETA_End" Grid.Column="1" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETA_End_ValueChanged"/> <local:DateTimePickerExt x:Name="datePickerETA_End" Grid.Column="1" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETA_End_ValueChanged"/>
</Grid> </Grid>
<Label Content="{x:Static p:Resources.textBerth}" Grid.Column="0" Grid.Row="2" HorizontalContentAlignment="Right" FontWeight="Bold"/> <Label Content="{x:Static p:Resources.textBerth}" Grid.Column="0" Grid.Row="2" HorizontalContentAlignment="Right" FontWeight="Bold"/>
<ComboBox Name="comboBoxArrivalBerth" Grid.Column="1" Grid.Row="2" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxArrivalBerth_SelectionChanged"> <ComboBox Name="comboBoxArrivalBerth" Grid.Column="1" Grid.Row="2" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxArrivalBerth_SelectionChanged">
</ComboBox> </ComboBox>
<Label Content="{x:Static p:Resources.textPierside}" Grid.Column="0" Grid.Row="3" HorizontalContentAlignment="Right" /> <Label Content="{x:Static p:Resources.textPierside}" Grid.Column="0" Grid.Row="3" HorizontalContentAlignment="Right" />
<ComboBox x:Name="comboBoxPierside" Grid.Column="1" Grid.Row="3" Margin="2" > <ComboBox x:Name="comboBoxPierside" Grid.Column="1" Grid.Row="3" Margin="2" >

View File

@ -94,7 +94,7 @@ namespace BreCalClient
if (this.comboBoxPierside.SelectedIndex >= 0) if (this.comboBoxPierside.SelectedIndex >= 0)
{ {
this.ShipcallModel.Shipcall.PierSide = (this.comboBoxPierside.SelectedIndex == 0) ? true : false; this.ShipcallModel.Shipcall.PierSide = (this.comboBoxPierside.SelectedIndex == 0);
} }
else else
{ {
@ -209,7 +209,10 @@ namespace BreCalClient
this.checkBoxReplenishingLock.IsChecked = this.ShipcallModel.Shipcall.ReplenishingLock ?? false; this.checkBoxReplenishingLock.IsChecked = this.ShipcallModel.Shipcall.ReplenishingLock ?? false;
this.checkBoxReplenishingTerminal.IsChecked = this.ShipcallModel.Shipcall.ReplenishingTerminal ?? false; this.checkBoxReplenishingTerminal.IsChecked = this.ShipcallModel.Shipcall.ReplenishingTerminal ?? false;
this.labelETA.Content = string.Format("ETA {0}", BreCalLists.TimeRefs[this.ShipcallModel.Shipcall.TimeRefPoint ?? 0]); if ((this.ShipcallModel.Shipcall.TimeRefPoint ?? 0) == 0)
this.labelETA.Content = BreCalClient.Resources.Resources.textETABerth;
else
this.labelETA.Content = string.Format("ETA {0}", BreCalLists.TimeRefs[this.ShipcallModel.Shipcall.TimeRefPoint ?? 0]);
if(!string.IsNullOrEmpty(this.Times.Remarks)) if(!string.IsNullOrEmpty(this.Times.Remarks))
this.textBoxRemarks.Text = this.Times.Remarks; this.textBoxRemarks.Text = this.Times.Remarks;

View File

@ -5,7 +5,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BreCalClient" xmlns:local="clr-namespace:BreCalClient"
xmlns:p = "clr-namespace:BreCalClient.Resources" xmlns:p = "clr-namespace:BreCalClient.Resources"
xmlns:db="clr-namespace:BreCalClient;assembly=BreCalClient"
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.textEditShipcall}" Height="375" Width="900" Loaded="Window_Loaded" ResizeMode="CanResizeWithGrip" Icon="Resources/containership.ico"> Title="{x:Static p:Resources.textEditShipcall}" Height="375" Width="900" Loaded="Window_Loaded" ResizeMode="CanResizeWithGrip" Icon="Resources/containership.ico">
@ -39,15 +38,15 @@
<Label Grid.Column="0" Grid.Row="0" Content="{x:Static p:Resources.textOutgoing}" FontWeight="DemiBold"/> <Label Grid.Column="0" Grid.Row="0" Content="{x:Static p:Resources.textOutgoing}" FontWeight="DemiBold"/>
<Image Margin="2" Grid.Column="1" Source="Resources/arrow_up_blue.png" /> <Image Margin="2" Grid.Column="1" Source="Resources/arrow_up_blue.png" />
</Grid> </Grid>
<Label Content="ETD" x:Name="labelETD" Grid.Column="0" Grid.Row="1" HorizontalContentAlignment="Right" FontWeight="Bold"/> <Label Content="ETD" x:Name="labelETD" Grid.Column="0" Grid.Row="1" HorizontalContentAlignment="Right" FontWeight="Bold"/>
<Grid Grid.Row="1" Grid.Column="1" > <Grid Grid.Row="1" Grid.Column="1" >
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<xctk:DateTimePicker x:Name="datePickerETD" Grid.Column="0" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETD_ValueChanged"/> <local:DateTimePickerExt x:Name="datePickerETD" Grid.Column="0" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETD_ValueChanged" AllowTextInput="True" />
<xctk:DateTimePicker x:Name="datePickerETD_End" Grid.Column="1" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETD_ValueChanged"/> <local:DateTimePickerExt x:Name="datePickerETD_End" Grid.Column="1" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETD_ValueChanged"/>
</Grid> </Grid>
<Label Content="{x:Static p:Resources.textBerth}" Grid.Column="0" Grid.Row="2" HorizontalContentAlignment="Right" FontWeight="Bold"/> <Label Content="{x:Static p:Resources.textBerth}" Grid.Column="0" Grid.Row="2" HorizontalContentAlignment="Right" FontWeight="Bold"/>
<ComboBox Name="comboBoxDepartureBerth" Grid.Column="1" Grid.Row="2" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxDepartureBerth_SelectionChanged" /> <ComboBox Name="comboBoxDepartureBerth" Grid.Column="1" Grid.Row="2" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxDepartureBerth_SelectionChanged" />

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
{ {
@ -66,9 +73,11 @@ namespace BreCalClient
(App.Participant.IsTypeFlagSet(ParticipantType.BSMD) && allowBSMD); (App.Participant.IsTypeFlagSet(ParticipantType.BSMD) && allowBSMD);
this.EnableControls(); this.EnableControls();
} }
private void buttonOK_Click(object sender, RoutedEventArgs e) private void buttonOK_Click(object sender, RoutedEventArgs e)
{ {
this.CopyToModel(); this.CopyToModel();
@ -95,7 +104,7 @@ namespace BreCalClient
if (this.comboBoxPierside.SelectedIndex >= 0) if (this.comboBoxPierside.SelectedIndex >= 0)
{ {
this.ShipcallModel.Shipcall.PierSide = (this.comboBoxPierside.SelectedIndex == 0) ? true : false; this.ShipcallModel.Shipcall.PierSide = (this.comboBoxPierside.SelectedIndex == 0);
} }
else else
{ {
@ -202,7 +211,10 @@ namespace BreCalClient
this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false; this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false;
this.checkBoxRainsensitiveCargo.IsChecked = this.ShipcallModel.Shipcall.RainSensitiveCargo ?? false; this.checkBoxRainsensitiveCargo.IsChecked = this.ShipcallModel.Shipcall.RainSensitiveCargo ?? false;
this.labelETD.Content = string.Format("ETD {0}", BreCalLists.TimeRefs[this.ShipcallModel.Shipcall.TimeRefPoint ?? 0]); if ((this.ShipcallModel.Shipcall.TimeRefPoint ?? 0) == 0)
this.labelETD.Content = BreCalClient.Resources.Resources.textETDBerth;
else
this.labelETD.Content = string.Format("ETD {0}", BreCalLists.TimeRefs[this.ShipcallModel.Shipcall.TimeRefPoint ?? 0]);
if (!string.IsNullOrEmpty(this.Times.Remarks)) if (!string.IsNullOrEmpty(this.Times.Remarks))
this.textBoxRemarks.Text = this.Times.Remarks; this.textBoxRemarks.Text = this.Times.Remarks;
@ -337,7 +349,7 @@ namespace BreCalClient
CheckOKButton(); CheckOKButton();
} }
#endregion #endregion
} }
} }

View File

@ -5,7 +5,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BreCalClient" xmlns:local="clr-namespace:BreCalClient"
xmlns:p = "clr-namespace:BreCalClient.Resources" xmlns:p = "clr-namespace:BreCalClient.Resources"
xmlns:db="clr-namespace:BreCalClient;assembly=BreCalClient"
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.textEditShipcall}" Height="490" Width="900" Loaded="Window_Loaded" ResizeMode="CanResizeWithGrip" Icon="Resources/containership.ico"> Title="{x:Static p:Resources.textEditShipcall}" Height="490" Width="900" Loaded="Window_Loaded" ResizeMode="CanResizeWithGrip" Icon="Resources/containership.ico">
@ -33,7 +32,7 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid Grid.Row="0" Grid.Column="0"> <Grid Grid.Row="0" Grid.Column="0">
@ -44,14 +43,14 @@
<Label Grid.Column="0" Grid.Row="0" Content="{x:Static p:Resources.textShiftingFrom}" FontWeight="DemiBold"/> <Label Grid.Column="0" Grid.Row="0" Content="{x:Static p:Resources.textShiftingFrom}" FontWeight="DemiBold"/>
<Image Margin="2" Grid.Column="1" Source="Resources/arrow_right_green.png" /> <Image Margin="2" Grid.Column="1" Source="Resources/arrow_right_green.png" />
</Grid> </Grid>
<Label Content="ETD" x:Name="labelETD" Grid.Column="0" Grid.Row="1" HorizontalContentAlignment="Right" FontWeight="Bold"/> <Label Content="{x:Static p:Resources.textETDBerth}" Grid.Column="0" Grid.Row="1" HorizontalContentAlignment="Right" FontWeight="Bold"/>
<Grid Grid.Column="1" Grid.Row="1"> <Grid Grid.Column="1" Grid.Row="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<xctk:DateTimePicker x:Name="datePickerETD" Grid.Column="0" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETD_ValueChanged"/> <local:DateTimePickerExt x:Name="datePickerETD" Grid.Column="0" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETD_ValueChanged"/>
<xctk:DateTimePicker x:Name="datePickerETD_End" Grid.Column="1" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETD_ValueChanged"/> <local:DateTimePickerExt x:Name="datePickerETD_End" Grid.Column="1" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETD_ValueChanged"/>
</Grid> </Grid>
<Label Content="{x:Static p:Resources.textTerminal}" Grid.Column="0" Grid.Row="2" HorizontalContentAlignment="Right"/> <Label Content="{x:Static p:Resources.textTerminal}" Grid.Column="0" Grid.Row="2" HorizontalContentAlignment="Right"/>
@ -82,16 +81,16 @@
<Label Grid.Column="0" Grid.Row="0" Content="{x:Static p:Resources.textShiftingTo}" FontWeight="DemiBold"/> <Label Grid.Column="0" Grid.Row="0" Content="{x:Static p:Resources.textShiftingTo}" FontWeight="DemiBold"/>
<Image Margin="2" Grid.Column="1" Source="Resources/arrow_right_green.png" /> <Image Margin="2" Grid.Column="1" Source="Resources/arrow_right_green.png" />
</Grid> </Grid>
<Label Content="ETA" x:Name="labelETA" Grid.Column="0" Grid.Row="9" HorizontalContentAlignment="Right" FontWeight="Bold"/> <Label Content="{x:Static p:Resources.textETABerth}" Grid.Column="0" Grid.Row="9" HorizontalContentAlignment="Right" FontWeight="Bold"/>
<Grid Grid.Column="1" Grid.Row="9"> <Grid Grid.Column="1" Grid.Row="9">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<xctk:DateTimePicker x:Name="datePickerETA" Grid.Column="0" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETA_ValueChanged"/> <local:DateTimePickerExt x:Name="datePickerETA" Grid.Column="0" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETA_ValueChanged"/>
<xctk:DateTimePicker x:Name="datePickerETA_End" Grid.Column="1" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETA_ValueChanged"/> <local:DateTimePickerExt x:Name="datePickerETA_End" Grid.Column="1" Grid.Row="0" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETA_ValueChanged"/>
</Grid> </Grid>
<Label Content="{x:Static p:Resources.textBerth}" Grid.Column="0" Grid.Row="10" HorizontalContentAlignment="Right" FontWeight="Bold"/> <Label Content="{x:Static p:Resources.textBerth}" Grid.Column="0" Grid.Row="10" HorizontalContentAlignment="Right" FontWeight="Bold"/>
<ComboBox Name="comboBoxArrivalBerth" Grid.Column="1" Grid.Row="10" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxArrivalBerth_SelectionChanged" /> <ComboBox Name="comboBoxArrivalBerth" Grid.Column="1" Grid.Row="10" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxArrivalBerth_SelectionChanged" />
@ -143,7 +142,7 @@
</ComboBox> </ComboBox>
<Label Content="{x:Static p:Resources.textMooredLock}" Grid.Column="2" Grid.Row="5" HorizontalContentAlignment="Right" /> <Label Content="{x:Static p:Resources.textMooredLock}" Grid.Column="2" Grid.Row="5" HorizontalContentAlignment="Right" />
<CheckBox x:Name="checkBoxMooredLock" Grid.Column="3" Grid.Row="5" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,4,0" /> <CheckBox x:Name="checkBoxMooredLock" Grid.Column="3" Grid.Row="5" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,4,0" />
<Label Content="{x:Static p:Resources.textRainSensitiveCargo}" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="6" HorizontalAlignment="Right" /> <Label Content="{x:Static p:Resources.textRainSensitiveCargo}" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="6" HorizontalAlignment="Right" />
<CheckBox x:Name="checkBoxRainsensitiveCargo" Grid.Column="3" Grid.Row="6" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,4,0" /> <CheckBox x:Name="checkBoxRainsensitiveCargo" Grid.Column="3" Grid.Row="6" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,4,0" />
<Label Content="{x:Static p:Resources.textRemarks}" Grid.Column="2" Grid.Row="7" HorizontalContentAlignment="Right" /> <Label Content="{x:Static p:Resources.textRemarks}" Grid.Column="2" Grid.Row="7" HorizontalContentAlignment="Right" />

View File

@ -98,7 +98,7 @@ namespace BreCalClient
this.ShipcallModel.Shipcall.DepartureBerthId = (int)this.comboBoxDepartureBerth.SelectedValue; this.ShipcallModel.Shipcall.DepartureBerthId = (int)this.comboBoxDepartureBerth.SelectedValue;
if (this.comboBoxPiersideArrival.SelectedIndex >= 0) if (this.comboBoxPiersideArrival.SelectedIndex >= 0)
{ {
this.ShipcallModel.Shipcall.PierSide = (this.comboBoxPiersideArrival.SelectedIndex == 0) ? true : false; this.ShipcallModel.Shipcall.PierSide = (this.comboBoxPiersideArrival.SelectedIndex == 0);
} }
else else
{ {
@ -217,10 +217,7 @@ namespace BreCalClient
this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false; this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false;
this.checkBoxRainsensitiveCargo.IsChecked = this.ShipcallModel.Shipcall.RainSensitiveCargo ?? false; this.checkBoxRainsensitiveCargo.IsChecked = this.ShipcallModel.Shipcall.RainSensitiveCargo ?? false;
this.labelETA.Content = string.Format("ETA {0}", BreCalLists.TimeRefs[this.ShipcallModel.Shipcall.TimeRefPoint ?? 0]);
this.labelETD.Content = string.Format("ETD {0}", BreCalLists.TimeRefs[this.ShipcallModel.Shipcall.TimeRefPoint ?? 0]);
if (!string.IsNullOrEmpty(this.Times.Remarks)) if (!string.IsNullOrEmpty(this.Times.Remarks))
this.textBoxRemarks.Text = this.Times.Remarks; this.textBoxRemarks.Text = this.Times.Remarks;

View File

@ -5,7 +5,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BreCalClient" xmlns:local="clr-namespace:BreCalClient"
xmlns:p = "clr-namespace:BreCalClient.Resources" xmlns:p = "clr-namespace:BreCalClient.Resources"
xmlns:db="clr-namespace:BreCalClient;assembly=BreCalClient"
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="331" 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">
@ -17,8 +16,8 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" x:Name="rowETA" />
<RowDefinition Height="28" /> <RowDefinition Height="28" x:Name="rowETD" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
@ -36,7 +35,7 @@
<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.textRemarks}" HorizontalContentAlignment="Right" /> <Label Grid.Row="7" 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">
@ -45,7 +44,7 @@
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<xctk:DateTimePicker IsEnabled="False" Grid.Row="0" Grid.Column="0" Margin="2" Name="datePickerETABerth" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETABerth_ValueChanged"> <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" >
@ -55,9 +54,9 @@
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</xctk:DateTimePicker> </local:DateTimePickerExt>
<xctk:DateTimePicker IsEnabled="False" Grid.Row="0" Grid.Column="1" Margin="2" Name="datePickerETABerth_End" Format="Custom" FormatString="dd.MM. yyyy HH:mm"> <local:DateTimePickerExt IsEnabled="False" Grid.Row="0" Grid.Column="1" Margin="2" x:Name="datePickerETABerth_End" 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_End" Click="contextMenuItemClearETA_End_Click" > <MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearETA_End" Click="contextMenuItemClearETA_End_Click" >
@ -67,7 +66,7 @@
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</xctk:DateTimePicker> </local:DateTimePickerExt>
</Grid> </Grid>
@ -77,7 +76,7 @@
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<xctk:DateTimePicker IsEnabled="False" Grid.Row="0" Grid.Column="0" Margin="2" Name="datePickerETDBerth" Format="Custom" FormatString="dd.MM. yyyy HH:mm" ValueChanged="datePickerETDBerth_ValueChanged"> <local:DateTimePickerExt IsEnabled="False" Grid.Row="0" Grid.Column="0" Margin="2" x:Name="datePickerETDBerth" Format="Custom" FormatString="dd.MM. yyyy HH:mm">
<xctk:DateTimePicker.ContextMenu> <xctk:DateTimePicker.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearETD" Click="contextMenuItemClearETD_Click" > <MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearETD" Click="contextMenuItemClearETD_Click" >
@ -87,9 +86,9 @@
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</xctk:DateTimePicker> </local:DateTimePickerExt>
<xctk:DateTimePicker IsEnabled="False" Grid.Row="0" Grid.Column="1" Margin="2" Name="datePickerETDBerth_End" Format="Custom" FormatString="dd.MM. yyyy HH:mm"> <local:DateTimePickerExt IsEnabled="False" Grid.Row="0" Grid.Column="1" Margin="2" x:Name="datePickerETDBerth_End" Format="Custom" FormatString="dd.MM. yyyy HH:mm">
<xctk:DateTimePicker.ContextMenu> <xctk:DateTimePicker.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearETD_End" Click="contextMenuItemClearETD_End_Click" > <MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearETD_End" Click="contextMenuItemClearETD_End_Click" >
@ -99,11 +98,11 @@
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</xctk:DateTimePicker> </local:DateTimePickerExt>
</Grid> </Grid>
<xctk:DateTimePicker IsEnabled="False" Grid.Row="3" Grid.Column="1" Margin="2" Name="datePickerATA" Format="Custom" FormatString="dd.MM. yyyy HH:mm"> <local:DateTimePickerExt IsEnabled="False" Grid.Row="3" Grid.Column="1" Margin="2" x:Name="datePickerATA" Format="Custom" FormatString="dd.MM. yyyy HH:mm">
<xctk:DateTimePicker.ContextMenu> <xctk:DateTimePicker.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearATA" Click="contextMenuItemClearATA_Click" > <MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearATA" Click="contextMenuItemClearATA_Click" >
@ -113,9 +112,9 @@
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</xctk:DateTimePicker> </local:DateTimePickerExt>
<xctk:DateTimePicker IsEnabled="False" Grid.Row="4" Grid.Column="1" Margin="2" Name="datePickerATD" Format="Custom" FormatString="dd.MM. yyyy HH:mm"> <local:DateTimePickerExt IsEnabled="False" Grid.Row="4" Grid.Column="1" Margin="2" x:Name="datePickerATD" Format="Custom" FormatString="dd.MM. yyyy HH:mm">
<xctk:DateTimePicker.ContextMenu> <xctk:DateTimePicker.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearATD" Click="contextMenuItemClearATD_Click" > <MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearATD" Click="contextMenuItemClearATD_Click" >
@ -125,9 +124,9 @@
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</xctk:DateTimePicker> </local:DateTimePickerExt>
<xctk:DateTimePicker IsEnabled="False" Grid.Row="5" Grid.Column="1" Margin="2" Name="datePickerLockTime" Format="Custom" FormatString="dd.MM. yyyy HH:mm"> <local:DateTimePickerExt IsEnabled="False" Grid.Row="5" Grid.Column="1" Margin="2" x:Name="datePickerLockTime" Format="Custom" FormatString="dd.MM. yyyy HH:mm">
<xctk:DateTimePicker.ContextMenu> <xctk:DateTimePicker.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearLockTime" Click="contextMenuItemClearLockTime_Click" > <MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearLockTime" Click="contextMenuItemClearLockTime_Click" >
@ -137,9 +136,9 @@
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</xctk:DateTimePicker> </local:DateTimePickerExt>
<!--CheckBox IsEnabled="False" Grid.Row="3" Grid.Column="2" Margin="4,0,0,0" Name="checkBoxLockTimeFixed" VerticalAlignment="Center" /--> <!--CheckBox IsEnabled="False" Grid.Row="3" Grid.Column="2" Margin="4,0,0,0" Name="checkBoxLockTimeFixed" VerticalAlignment="Center" /-->
<xctk:DateTimePicker IsEnabled="False" Grid.Row="6" Grid.Column="1" Margin="2" 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>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearZoneEntry" Click="contextMenuItemClearZoneEntry_Click" > <MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearZoneEntry" Click="contextMenuItemClearZoneEntry_Click" >
@ -149,13 +148,16 @@
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</xctk:DateTimePicker> </local:DateTimePickerExt>
<!--CheckBox IsEnabled="False" Grid.Row="4" Grid.Column="2" Margin="4,0,0,0" Name="checkBoxZoneEntryFixed" VerticalAlignment="Center" /--> <!--CheckBox IsEnabled="False" Grid.Row="4" Grid.Column="2" Margin="4,0,0,0" Name="checkBoxZoneEntryFixed" VerticalAlignment="Center" /-->
<TextBox Grid.Row="7" Grid.Column="1" Margin="2" Name="textBoxRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="False" IsReadOnly="True" MaxLength="512"/> <TextBox Grid.Row="7" Grid.Column="1" Margin="2" Name="textBoxRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="False" IsReadOnly="True" MaxLength="512"/>
<StackPanel Grid.Row="8" Grid.Column="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right"> <StackPanel Grid.Row="8" 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" IsEnabled="False" /> <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">
<Image Source="Resources\nav_undo_red.png"/>
</Button>
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@ -7,6 +7,7 @@ using BreCalClient.misc.Model;
using System; using System;
using System.Windows; using System.Windows;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using Xceed.Wpf.Toolkit;
namespace BreCalClient namespace BreCalClient
{ {
@ -27,7 +28,9 @@ namespace BreCalClient
#region Properties #region Properties
public Times Times { get; set; } = new(); public Times Times { get; set; } = new();
public Times? AgencyTimes { get; set; } = new();
public ShipcallControlModel ShipcallModel { get; set; } = new(); public ShipcallControlModel ShipcallModel { get; set; } = new();
@ -36,9 +39,9 @@ namespace BreCalClient
#region event handler #region event handler
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
this.CopyToControls();
this.EnableControls(); this.EnableControls();
this.CopyToControls();
} }
private void buttonOK_Click(object sender, RoutedEventArgs e) private void buttonOK_Click(object sender, RoutedEventArgs e)
@ -62,14 +65,20 @@ namespace BreCalClient
SetLockButton(newValue); SetLockButton(newValue);
} }
private void datePickerETABerth_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e) private void buttonClearAll_Click(object sender, RoutedEventArgs e)
{ {
CheckOKButton(); if (System.Windows.MessageBox.Show(BreCalClient.Resources.Resources.textClearAll, BreCalClient.Resources.Resources.textConfirmation, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.Yes)
} {
this.datePickerETABerth.Value = null;
private void datePickerETDBerth_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e) this.datePickerETABerth_End.Value = null;
{ this.datePickerETDBerth.Value = null;
CheckOKButton(); this.datePickerETDBerth_End.Value = null;
this.datePickerATA.Value = null;
this.datePickerATD.Value = null;
this.datePickerLockTime.Value = null;
this.datePickerZoneEntry.Value = null;
this.textBoxRemarks.Text = null;
}
} }
#endregion #endregion
@ -93,30 +102,51 @@ namespace BreCalClient
{ {
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.datePickerETABerth.Value = this.AgencyTimes.EtaBerth;
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.datePickerETDBerth.Value = this.AgencyTimes.EtdBerth;
if (this.datePickerETDBerth.Template.FindName("PART_TextBox", this.datePickerETDBerth) is WatermarkTextBox tb) tb.SelectAll();
}
this.datePickerLockTime.Value = this.Times.LockTime; this.datePickerLockTime.Value = this.Times.LockTime;
this.datePickerZoneEntry.Value = this.Times.ZoneEntry; this.datePickerZoneEntry.Value = this.Times.ZoneEntry;
this.datePickerATA.Value = this.Times.Ata; this.datePickerATA.Value = this.Times.Ata;
this.datePickerATD.Value = this.Times.Atd; this.datePickerATD.Value = this.Times.Atd;
this.datePickerETABerth_End.Value = this.Times.EtaIntervalEnd; this.datePickerETABerth_End.Value = this.Times.EtaIntervalEnd;
this.datePickerETDBerth_End.Value = this.Times.EtdIntervalEnd; if (this.datePickerETABerth_End.IsEnabled && (this.Times.EtaIntervalEnd == null) && (this.Times.EtaBerth == null) && (this.AgencyTimes?.EtaIntervalEnd != null) && (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival))
this.labelETA.Content = string.Format("ETA {0}", BreCalLists.TimeRefs[this.ShipcallModel.Shipcall?.TimeRefPoint ?? 0]);
this.labelETD.Content = string.Format("ETD {0}", BreCalLists.TimeRefs[this.ShipcallModel.Shipcall?.TimeRefPoint ?? 0]);
switch (ShipcallModel.Shipcall?.Type)
{ {
case ShipcallType.Arrival: this.datePickerETABerth_End.Value = this.AgencyTimes.EtaIntervalEnd;
this.labelETA.FontWeight = FontWeights.Bold; //if (this.datePickerETABerth_End.Template.FindName("PART_TextBox", this.datePickerETABerth_End) is WatermarkTextBox tb) { tb.Focus(); tb.SelectAll(); }
this.datePickerETABerth.ContextMenu.IsEnabled = false;
break;
case ShipcallType.Departure:
case ShipcallType.Shifting:
this.labelETD.FontWeight = FontWeights.Bold;
this.datePickerETDBerth.ContextMenu.IsEnabled = false;
break;
} }
this.datePickerETDBerth_End.Value = this.Times.EtdIntervalEnd;
if (this.datePickerETDBerth_End.IsEnabled && (this.Times.EtdIntervalEnd == null) && (this.Times.EtdBerth == null) && (this.AgencyTimes?.EtdIntervalEnd != null) && ((ShipcallModel.Shipcall?.Type == ShipcallType.Departure) || (ShipcallModel.Shipcall?.Type == ShipcallType.Shifting)))
{
this.datePickerETDBerth_End.Value = this.AgencyTimes.EtdIntervalEnd;
//if (this.datePickerETDBerth_End.Template.FindName("PART_TextBox", this.datePickerETDBerth_End) is WatermarkTextBox tb) { tb.Focus(); tb.SelectAll(); }
}
if (this.ShipcallModel.Shipcall?.Type != ShipcallType.Shifting)
{
int displayIndex = this.ShipcallModel.Shipcall?.TimeRefPoint ?? 0;
if (displayIndex > 0)
{
this.labelETA.Content = string.Format("ETA {0}", BreCalLists.TimeRefs[displayIndex]);
this.labelETD.Content = string.Format("ETD {0}", BreCalLists.TimeRefs[displayIndex]);
}
else
{
this.labelETA.Content = BreCalClient.Resources.Resources.textETABerth;
this.labelETD.Content = BreCalClient.Resources.Resources.textETDBerth;
}
}
this.SetLockButton(this.Times.EtaBerthFixed ?? false); this.SetLockButton(this.Times.EtaBerthFixed ?? false);
} }
@ -147,6 +177,15 @@ namespace BreCalClient
} }
} }
if (ShipcallModel.Shipcall?.Type != ShipcallType.Arrival)
{
this.rowETA.Height = new(0);
}
else
{
this.rowETD.Height = new(0);
}
// setting en/dis-abled // setting en/dis-abled
if (this.Times.ParticipantId != App.Participant.Id) if (this.Times.ParticipantId != App.Participant.Id)
@ -160,6 +199,7 @@ namespace BreCalClient
this.datePickerETDBerth.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Departure || ShipcallModel.Shipcall?.Type == ShipcallType.Shifting); this.datePickerETDBerth.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Departure || ShipcallModel.Shipcall?.Type == ShipcallType.Shifting);
this.datePickerETDBerth_End.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Departure || ShipcallModel.Shipcall?.Type == ShipcallType.Shifting); this.datePickerETDBerth_End.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Departure || ShipcallModel.Shipcall?.Type == ShipcallType.Shifting);
this.textBoxRemarks.IsReadOnly = false; this.textBoxRemarks.IsReadOnly = false;
this.buttonClearAll.IsEnabled = true;
switch (pType) switch (pType)
{ {
@ -174,10 +214,7 @@ namespace BreCalClient
case Extensions.ParticipantType.PILOT: case Extensions.ParticipantType.PILOT:
this.datePickerZoneEntry.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival); this.datePickerZoneEntry.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival);
break; break;
} }
CheckOKButton();
} }
private void SetLockButton(bool newValue) private void SetLockButton(bool newValue)
@ -194,17 +231,7 @@ namespace BreCalClient
this.imageFixedOrder.Source = new BitmapImage(new Uri(@"pack://application:,,,/Resources/lock_open.png", UriKind.RelativeOrAbsolute)); this.imageFixedOrder.Source = new BitmapImage(new Uri(@"pack://application:,,,/Resources/lock_open.png", UriKind.RelativeOrAbsolute));
this.buttonFixedOrder.ToolTip = BreCalClient.Resources.Resources.textTooltipSetFixedOrder; this.buttonFixedOrder.ToolTip = BreCalClient.Resources.Resources.textTooltipSetFixedOrder;
} }
} }
private void CheckOKButton()
{
Extensions.ParticipantType pType = (Extensions.ParticipantType)this.Times.ParticipantType;
if (pType != Extensions.ParticipantType.PORT_ADMINISTRATION)
this.buttonOK.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival) ?
this.datePickerETABerth.Value.HasValue : this.datePickerETDBerth.Value.HasValue;
else
this.buttonOK.IsEnabled = true;
}
#endregion #endregion

View File

@ -12,11 +12,11 @@
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width=".3*" /> <ColumnDefinition Width=".3*" />
<ColumnDefinition Width=".7*" /> <ColumnDefinition Width=".7*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="28" /> <RowDefinition Height="28" x:Name="rowStart"/>
<RowDefinition Height="28" /> <RowDefinition Height="28" x:Name="rowEnd"/>
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="56" /> <RowDefinition Height="56" />
@ -26,9 +26,9 @@
<Label Grid.Row="0" Grid.Column="0" Content="{x:Static p:Resources.textOperationsStart}" HorizontalContentAlignment="Right" x:Name="labelStart" /> <Label Grid.Row="0" Grid.Column="0" Content="{x:Static p:Resources.textOperationsStart}" HorizontalContentAlignment="Right" x:Name="labelStart" />
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static p:Resources.textOperationsEnd}" HorizontalContentAlignment="Right" x:Name="labelEnd" /> <Label Grid.Row="1" Grid.Column="0" Content="{x:Static p:Resources.textOperationsEnd}" HorizontalContentAlignment="Right" x:Name="labelEnd" />
<Label Grid.Row="2" Grid.Column="0" Content="{x:Static p:Resources.textBerth}" HorizontalAlignment="Right" /> <Label Grid.Row="2" Grid.Column="0" Content="{x:Static p:Resources.textBerth}" HorizontalAlignment="Right" x:Name="labelBerth"/>
<Label Grid.Row="3" Grid.Column="0" Content="{x:Static p:Resources.textPierside}" HorizontalContentAlignment="Right" /> <Label Grid.Row="3" Grid.Column="0" Content="{x:Static p:Resources.textPierside}" HorizontalContentAlignment="Right" x:Name="labelPierside" />
<Label Grid.Row="4" Grid.Column="0" Content="{x:Static p:Resources.textBerthRemarks}" HorizontalContentAlignment="Right" /> <Label Grid.Row="4" Grid.Column="0" Content="{x:Static p:Resources.textBerthRemarks}" HorizontalContentAlignment="Right" x:Name="labelBerthRemarks"/>
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static p:Resources.textRemarks}" HorizontalContentAlignment="Right" /> <Label Grid.Row="5" Grid.Column="0" Content="{x:Static p:Resources.textRemarks}" HorizontalContentAlignment="Right" />
@ -37,8 +37,8 @@
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<xctk:DateTimePicker Grid.Row="0" Grid.Column="0" Margin="2" Name="datePickerOperationStart" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False" ValueChanged="datePickerOperationStart_ValueChanged"> <local:DateTimePickerExt Grid.Row="0" Grid.Column="0" Margin="2" x:Name="datePickerOperationStart" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False" >
<xctk:DateTimePicker.ContextMenu> <xctk:DateTimePicker.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearOperationStart" Click="contextMenuItemClearOperationStart_Click" > <MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearOperationStart" Click="contextMenuItemClearOperationStart_Click" >
@ -48,8 +48,8 @@
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</xctk:DateTimePicker> </local:DateTimePickerExt>
<xctk:DateTimePicker Grid.Row="0" Grid.Column="1" Margin="2" Name="datePickerOperationStart_End" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False"> <local:DateTimePickerExt Grid.Row="0" Grid.Column="1" Margin="2" x:Name="datePickerOperationStart_End" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False">
<xctk:DateTimePicker.ContextMenu> <xctk:DateTimePicker.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearOperationStart_End" Click="contextMenuItemClearOperationStart_End_Click"> <MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearOperationStart_End" Click="contextMenuItemClearOperationStart_End_Click">
@ -59,8 +59,8 @@
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</xctk:DateTimePicker> </local:DateTimePickerExt>
</Grid> </Grid>
<Grid Grid.Row="1" Grid.Column="1" > <Grid Grid.Row="1" Grid.Column="1" >
@ -68,8 +68,8 @@
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<xctk:DateTimePicker Grid.Row="0" Grid.Column="0" Margin="2" Name="datePickerOperationEnd" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False" ValueChanged="datePickerOperationEnd_ValueChanged"> <local:DateTimePickerExt Grid.Row="0" Grid.Column="0" Margin="2" x:Name="datePickerOperationEnd" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False" >
<xctk:DateTimePicker.ContextMenu> <xctk:DateTimePicker.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearOperationEnd" Click="contextMenuItemClearOperationEnd_Click" > <MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearOperationEnd" Click="contextMenuItemClearOperationEnd_Click" >
@ -79,9 +79,9 @@
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</xctk:DateTimePicker> </local:DateTimePickerExt>
<xctk:DateTimePicker Grid.Row="0" Grid.Column="1" Margin="2" Name="datePickerOperationEnd_End" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False"> <local:DateTimePickerExt Grid.Row="0" Grid.Column="1" Margin="2" x:Name="datePickerOperationEnd_End" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False">
<xctk:DateTimePicker.ContextMenu> <xctk:DateTimePicker.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearOperationEnd_End" Click="contextMenuItemClearOperationEnd_End_Click"> <MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearOperationEnd_End" Click="contextMenuItemClearOperationEnd_End_Click">
@ -91,11 +91,11 @@
</MenuItem> </MenuItem>
</ContextMenu> </ContextMenu>
</xctk:DateTimePicker.ContextMenu> </xctk:DateTimePicker.ContextMenu>
</xctk:DateTimePicker> </local:DateTimePickerExt>
</Grid> </Grid>
<ComboBox Name="comboBoxBerth" Grid.Column="1" Grid.Row="2" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" IsEnabled="False"> <ComboBox Name="comboBoxBerth" Grid.Column="1" Grid.Row="2" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" IsEnabled="False">
<ComboBox.ContextMenu> <ComboBox.ContextMenu>
<ContextMenu> <ContextMenu>
@ -115,8 +115,11 @@
<TextBox Grid.Row="4" Grid.Column="1" Margin="2" Name="textBoxBerthRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="False" IsReadOnly="True" MaxLength="512" /> <TextBox Grid.Row="4" Grid.Column="1" Margin="2" Name="textBoxBerthRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="False" IsReadOnly="True" MaxLength="512" />
<TextBox Grid.Row="5" Grid.Column="1" Margin="2" Name="textBoxRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="False" IsReadOnly="True" MaxLength="512" /> <TextBox Grid.Row="5" Grid.Column="1" Margin="2" Name="textBoxRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="False" IsReadOnly="True" MaxLength="512" />
<StackPanel Grid.Row="6" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right"> <StackPanel Grid.Row="6" Grid.Column="1" 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="True"/>
<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">
<Image Source="Resources\nav_undo_red.png"/>
</Button>
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@ -76,14 +76,19 @@ namespace BreCalClient
this.comboBoxPierside.SelectedIndex = -1; this.comboBoxPierside.SelectedIndex = -1;
} }
private void datePickerOperationStart_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e) private void buttonClearAll_Click(object sender, RoutedEventArgs e)
{ {
this.CheckOKButton(); if (MessageBox.Show(BreCalClient.Resources.Resources.textClearAll, BreCalClient.Resources.Resources.textConfirmation, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.Yes)
} {
this.datePickerOperationStart.Value = null;
private void datePickerOperationEnd_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e) this.datePickerOperationStart_End.Value = null;
{ this.datePickerOperationEnd.Value = null;
this.CheckOKButton(); this.datePickerOperationEnd_End.Value = null;
this.comboBoxBerth.SelectedIndex = -1;
this.comboBoxPierside.SelectedIndex = -1;
this.textBoxRemarks.Text = null;
this.textBoxBerthRemarks.Text = null;
}
} }
#endregion #endregion
@ -122,13 +127,28 @@ namespace BreCalClient
switch (ShipcallModel.Shipcall?.Type) switch (ShipcallModel.Shipcall?.Type)
{ {
case ShipcallType.Arrival: case ShipcallType.Arrival:
this.labelStart.FontWeight = FontWeights.Bold; this.labelEnd.Visibility = Visibility.Hidden;
this.datePickerOperationStart.ContextMenu.IsEnabled = false; this.datePickerOperationEnd.Visibility = Visibility.Hidden;
this.datePickerOperationEnd_End.Visibility = Visibility.Hidden;
this.rowEnd.Height = new(0);
break; break;
case ShipcallType.Departure: case ShipcallType.Departure:
this.rowStart.Height = new(0);
this.labelBerth.Visibility = Visibility.Hidden;
this.comboBoxBerth.Visibility= Visibility.Hidden;
this.labelPierside.Visibility = Visibility.Hidden;
this.comboBoxPierside.Visibility = Visibility.Hidden;
this.labelBerthRemarks.Visibility = Visibility.Hidden;
this.textBoxBerthRemarks.Visibility = Visibility.Hidden;
break;
case ShipcallType.Shifting: case ShipcallType.Shifting:
this.labelEnd.FontWeight = FontWeights.Bold; this.rowStart.Height = new(0);
this.datePickerOperationEnd.ContextMenu.IsEnabled = false; this.labelBerth.Visibility = Visibility.Hidden;
this.comboBoxBerth.Visibility = Visibility.Hidden;
this.labelPierside.Visibility = Visibility.Hidden;
this.comboBoxPierside.Visibility = Visibility.Hidden;
this.labelBerthRemarks.Visibility = Visibility.Hidden;
this.textBoxBerthRemarks.Visibility = Visibility.Hidden;
break; break;
} }
@ -146,13 +166,8 @@ namespace BreCalClient
this.comboBoxPierside.IsEnabled = ShipcallModel.Shipcall?.Type == ShipcallType.Arrival; this.comboBoxPierside.IsEnabled = ShipcallModel.Shipcall?.Type == ShipcallType.Arrival;
this.textBoxBerthRemarks.IsReadOnly = ShipcallModel.Shipcall?.Type != ShipcallType.Arrival; this.textBoxBerthRemarks.IsReadOnly = ShipcallModel.Shipcall?.Type != ShipcallType.Arrival;
this.textBoxRemarks.IsReadOnly = false; this.textBoxRemarks.IsReadOnly = false;
this.CheckOKButton(); this.buttonClearAll.IsEnabled = true;
}
private void CheckOKButton()
{
this.buttonOK.IsEnabled = (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival) ? this.datePickerOperationStart.Value.HasValue :
this.datePickerOperationEnd.Value.HasValue;
} }
#endregion #endregion

View File

@ -27,7 +27,7 @@ namespace BreCalClient
BSMD = 1, BSMD = 1,
[Description("Terminal")] [Description("Terminal")]
TERMINAL = 2, TERMINAL = 2,
[Description("Lotsen")] [Description("Flusslotsen")]
PILOT = 4, PILOT = 4,
[Description("Agentur")] [Description("Agentur")]
AGENCY = 8, AGENCY = 8,
@ -98,7 +98,7 @@ namespace BreCalClient
if(times.OperationsStart.HasValue) if(times.OperationsStart.HasValue)
{ {
string result = times.OperationsStart.Value.ToString("dd.MM.yyyy HH:mm"); string result = times.OperationsStart.Value.ToString("dd.MM.yyyy HH:mm");
if (times.EtaIntervalEnd.HasValue) result += " - " + times.EtaIntervalEnd.Value.ToString("HH:mm"); if (times.EtaIntervalEnd.HasValue) result = times.OperationsStart.Value.ToString("d.M. HH:mm") + " - " + times.EtaIntervalEnd.Value.ToString("d.M. HH:mm");
return result; return result;
} }
else else
@ -111,7 +111,7 @@ namespace BreCalClient
if(times.EtaBerth.HasValue) if(times.EtaBerth.HasValue)
{ {
string result = times.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm"); string result = times.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm");
if (times.EtaIntervalEnd.HasValue) result += " - " + times.EtaIntervalEnd.Value.ToString("HH:mm"); if (times.EtaIntervalEnd.HasValue) result = times.EtaBerth.Value.ToString("d.M. HH:mm") + " - " + times.EtaIntervalEnd.Value.ToString("d.M. HH:mm");
return result; return result;
} }
else else
@ -127,7 +127,7 @@ namespace BreCalClient
if(times.OperationsEnd.HasValue) if(times.OperationsEnd.HasValue)
{ {
string result = times.OperationsEnd.Value.ToString("dd.MM.yyyy HH:mm"); string result = times.OperationsEnd.Value.ToString("dd.MM.yyyy HH:mm");
if (times.EtdIntervalEnd.HasValue) result += " - " + times.EtdIntervalEnd.Value.ToString("HH:mm"); if (times.EtdIntervalEnd.HasValue) result = times.OperationsEnd.Value.ToString("d.M. HH:mm") + " - " + times.EtdIntervalEnd.Value.ToString("d.M. HH:mm");
return result; return result;
} }
else else
@ -140,7 +140,7 @@ namespace BreCalClient
if(times.EtdBerth.HasValue) if(times.EtdBerth.HasValue)
{ {
string result = times.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm"); string result = times.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm");
if (times.EtdIntervalEnd.HasValue) result += " - " + times.EtdIntervalEnd.Value.ToString("HH:mm"); if (times.EtdIntervalEnd.HasValue) result = times.EtdBerth.Value.ToString("d.M. HH:mm") + " - " + times.EtdIntervalEnd.Value.ToString("d.M. HH:mm");
return result; return result;
} }
else else

View File

@ -3,12 +3,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BreCalClient" xmlns:local="clr-namespace:BreCalClient"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:sets="clr-namespace:BreCalClient.Properties" xmlns:sets="clr-namespace:BreCalClient.Properties"
xmlns:p = "clr-namespace:BreCalClient.Resources" xmlns:p = "clr-namespace:BreCalClient.Resources"
mc:Ignorable="d" mc:Ignorable="d"
Title="{DynamicResource textApplicationTitle}" Height="{local:SettingBinding Height}" Width="{local:SettingBinding Width}" Title="{DynamicResource textApplicationTitle}" Height="{local:SettingBinding Height}" Width="{local:SettingBinding Width}"
Top="{local:SettingBinding Top}" Left="{local:SettingBinding Left}" Loaded="Window_Loaded" Closing="Window_Closing" Icon="Resources/containership.ico"> Top="{local:SettingBinding Top}" Left="{local:SettingBinding Left}" Loaded="Window_Loaded" Closing="Window_Closing" Icon="Resources/containership.ico">
<Window.Resources> <Window.Resources>
<local:BoolToIndexConverter x:Key="boolToIndexConverter" /> <local:BoolToIndexConverter x:Key="boolToIndexConverter" />
@ -42,11 +42,11 @@
<Button Name="buttonLogin" Content="{x:Static p:Resources.textLogin}" Grid.Row="4" Grid.Column="0" Margin="2" Click="buttonLogin_Click" IsDefault="True" /> <Button Name="buttonLogin" Content="{x:Static p:Resources.textLogin}" Grid.Row="4" Grid.Column="0" Margin="2" Click="buttonLogin_Click" IsDefault="True" />
<Button Name="buttonExit" Content="{x:Static p:Resources.textExit}" Grid.Row="4" Grid.Column="1" Margin="2" Click="buttonExit_Click" /> <Button Name="buttonExit" Content="{x:Static p:Resources.textExit}" Grid.Row="4" Grid.Column="1" Margin="2" Click="buttonExit_Click" />
<TextBlock FontSize="10" TextWrapping="Wrap" Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2" > <TextBlock FontSize="10" TextWrapping="Wrap" Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2" >
<Underline>Hinweis</Underline>:<LineBreak /> <Underline>Hinweis</Underline>:<LineBreak />
Bei dem derzeitigen System handelt es sich um einen Testbetrieb.<LineBreak /> Mit der Anmeldung in Bremen Calling akzeptieren Sie die Bedingungen<LineBreak />
Alle angegebenen Daten sind unverbindlich und lösen damit keinerlei Auftrag oder Bestellung aus.<LineBreak/> des Systems, die Sie in der aktuell gültigen Fassung unter dem nachfolgenden Link
Bei Fragen oder Anmerkungen wenden Sie sich gern an <Hyperlink NavigateUri="mailto:bremencalling@bsmd.de" RequestNavigate="Hyperlink_RequestNavigate">bremencalling@bsmd.de</Hyperlink>. einsehen können: <Hyperlink NavigateUri="https://www.bsmd-emswe.eu/disclaimer.html" RequestNavigate="Hyperlink_RequestNavigate">Datenschutzerklärung</Hyperlink>.
</TextBlock> </TextBlock>
</Grid> </Grid>
</xctk:BusyIndicator.BusyContent> </xctk:BusyIndicator.BusyContent>
<Grid> <Grid>
@ -89,12 +89,12 @@
<ColumnDefinition Width=".15*" /> <ColumnDefinition Width=".15*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Label Grid.Column="0" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Foreground="White" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"></Label> <Label Grid.Column="0" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Foreground="White" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"></Label>
<Label Grid.Column="1" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Foreground="White" Content="Agent" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"></Label> <Label Grid.Column="1" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Foreground="White" Content="{x:Static p:Resources.textAgency}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"></Label>
<Label Grid.Column="2" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Foreground="White" Content="Festmacher" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"></Label> <Label Grid.Column="2" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Foreground="White" Content="{x:Static p:Resources.textMooring}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"></Label>
<Label Grid.Column="3" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Foreground="White" Content="Hafenamt" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"></Label> <Label Grid.Column="3" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Foreground="White" Content="{x:Static p:Resources.textPortAuthority}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"></Label>
<Label Grid.Column="4" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Foreground="White" Content="Lotsen" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"></Label> <Label Grid.Column="4" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Foreground="White" Content="{x:Static p:Resources.textPilots}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"></Label>
<Label Grid.Column="5" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Foreground="White" Content="Schlepper" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"></Label> <Label Grid.Column="5" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Foreground="White" Content="{x:Static p:Resources.textTug}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"></Label>
<Label Grid.Column="6" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Foreground="White" Content="Terminal" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"></Label> <Label Grid.Column="6" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Foreground="White" Content="{x:Static p:Resources.textTerminal}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"></Label>
</Grid> </Grid>
<ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto"> <ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="stackPanel"/> <StackPanel x:Name="stackPanel"/>

View File

@ -34,12 +34,13 @@ namespace BreCalClient
{ {
private readonly ILog _log = LogManager.GetLogger(typeof(MainWindow)); private readonly ILog _log = LogManager.GetLogger(typeof(MainWindow));
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 PROGRESS_STEPS = 50; private const int PROGRESS_STEPS = 50;
#region Fields #region Fields
//private static int _uiUpdateRunning = 0; //private static int _uiUpdateRunning = 0;
private static SemaphoreSlim uiLock = new SemaphoreSlim(1); private static readonly SemaphoreSlim uiLock = new(1);
private Credentials? _credentials; private Credentials? _credentials;
@ -142,7 +143,7 @@ namespace BreCalClient
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{ {
// serialize filter settings // serialize filter settings
Properties.Settings.Default.FilterCriteria = this.searchFilterControl.SearchFilter.Serialize(); Properties.Settings.Default.FilterCriteriaMap = SearchFilterModel.Serialize();
Properties.Settings.Default.Save(); Properties.Settings.Default.Save();
_tokenSource.Cancel(); _tokenSource.Cancel();
} }
@ -404,6 +405,8 @@ namespace BreCalClient
private async void LoadStaticLists() private async void LoadStaticLists()
{ {
if (_loginResult == null) return;
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());
@ -421,14 +424,43 @@ namespace BreCalClient
this.searchFilterControl.SetAgencies(BreCalLists.Participants_Agent); this.searchFilterControl.SetAgencies(BreCalLists.Participants_Agent);
if (!string.IsNullOrEmpty(Properties.Settings.Default.FilterCriteria)) if (!string.IsNullOrEmpty(Properties.Settings.Default.FilterCriteriaMap))
{ {
SearchFilterModel? sfm = SearchFilterModel.Deserialize(Properties.Settings.Default.FilterCriteria); SearchFilterModel.Deserialize(Properties.Settings.Default.FilterCriteriaMap);
if (sfm != null) SearchFilterModel? currentFilter = null;
this.searchFilterControl.SetFilterFromModel(sfm); if (SearchFilterModel.filterMap != null)
{
if((_loginResult != null) && SearchFilterModel.filterMap.ContainsKey(_loginResult.Id))
{
currentFilter = SearchFilterModel.filterMap[_loginResult.Id];
}
}
else
{
SearchFilterModel.filterMap = new();
}
if (currentFilter == null)
{
currentFilter = new();
if(_loginResult != null)
SearchFilterModel.filterMap[_loginResult.Id] = currentFilter;
}
this.searchFilterControl.SetFilterFromModel(currentFilter);
} }
_ = Task.Run(() => RefreshShipcalls()); _ = Task.Run(() => RefreshShipcalls());
_ = Task.Run(() => RefreshShips());
}
public async Task RefreshShips()
{
while (true)
{
Thread.Sleep(SHIPS_UPDATE_INTERVAL_SECONDS * 1000);
BreCalLists.InitializeShips(await _shipApi.ShipsGetAsync());
}
} }
public async Task RefreshShipcalls() public async Task RefreshShipcalls()
@ -513,7 +545,7 @@ namespace BreCalClient
this.FilterShipcalls(); this.FilterShipcalls();
await uiLock.WaitAsync(); await uiLock.WaitAsync();
this.UpdateUI(); this.UpdateUI();
} }
} }
catch(Exception ex) catch(Exception ex)
@ -640,7 +672,7 @@ namespace BreCalClient
// first add everything // first add everything
this._visibleControlModels.AddRange(_allShipcallsDict.Values); this._visibleControlModels.AddRange(_allShipcallsDict.Values);
// now remove elements whose filter criteria are met // now remove elements whose filter criteria are met
if(sfm.Berths.Count > 0 ) if(sfm.Berths.Count > 0 )
{ {
@ -707,7 +739,7 @@ namespace BreCalClient
_ = this._visibleControlModels.RemoveAll(x => _ = this._visibleControlModels.RemoveAll(x =>
{ {
Times? t = x.GetTimesForParticipantType(ParticipantType.AGENCY); Times? t = x.GetTimesForParticipantType(ParticipantType.AGENCY);
DateTime refValue = sfm.EtaTo.Value.AddMinutes(1439); // 23:59 DateTime refValue = sfm.EtaTo.Value.AddMinutes(1440); // including 23:59
switch (x.Shipcall?.Type) switch (x.Shipcall?.Type)
{ {
case ShipcallType.Arrival: case ShipcallType.Arrival:
@ -745,7 +777,34 @@ namespace BreCalClient
_ = this._visibleControlModels.RemoveAll(x => x.Shipcall?.Canceled ?? false); _ = this._visibleControlModels.RemoveAll(x => x.Shipcall?.Canceled ?? false);
} }
switch(this._sortOrder) // special Basti case: Wenn das ATA / ATD eingetragen ist und schon 2 Stunden in der Vergangenheit liegt
if (searchPastDays <= 3)
{
_ = this._visibleControlModels.RemoveAll(x =>
{
Times? mooringTimes = x.GetTimesForParticipantType(ParticipantType.MOORING);
if (mooringTimes != null)
{
switch (x.Shipcall?.Type)
{
case ShipcallType.Arrival:
if (mooringTimes.Ata.HasValue && ((DateTime.Now - mooringTimes.Ata.Value).TotalHours > 2))
return true;
break;
default:
if (mooringTimes.Atd.HasValue && ((DateTime.Now - mooringTimes.Atd.Value).TotalHours > 2))
return true;
break;
}
}
return false;
});
}
switch (this._sortOrder)
{ {
case Extensions.SortOrder.SHIP_NAME: case Extensions.SortOrder.SHIP_NAME:
this._visibleControlModels.Sort((x, y) => { if (x.Ship == null) return 0; if (y.Ship == null) return 0; return x.Ship.Name.CompareTo(y.Ship.Name); }); this._visibleControlModels.Sort((x, y) => { if (x.Ship == null) return 0; if (y.Ship == null) return 0; return x.Ship.Name.CompareTo(y.Ship.Name); });
@ -758,14 +817,20 @@ namespace BreCalClient
{ {
if (x.Shipcall == null) return 0; if (x.Shipcall == null) return 0;
if (y.Shipcall == null) return 0; if (y.Shipcall == null) return 0;
DateTime xDate = (x.Shipcall.Type == ShipcallType.Arrival) ? x.Eta ?? DateTime.Now : x.Etd ?? DateTime.Now; DateTime now = DateTime.Now;
DateTime xDate = (x.Shipcall.Type == ShipcallType.Arrival) ? (x.Eta ?? now) : (x.Etd ?? now);
Times? xTimes = x.GetTimesForParticipantType(ParticipantType.AGENCY); Times? xTimes = x.GetTimesForParticipantType(ParticipantType.AGENCY);
if(xTimes != null) if(xTimes != null)
xDate = (x.Shipcall.Type == ShipcallType.Arrival) ? xTimes.EtaBerth ?? DateTime.Now : xTimes.EtdBerth ?? DateTime.Now; xDate = (x.Shipcall.Type == ShipcallType.Arrival) ? (xTimes.EtaBerth ?? now) : (xTimes.EtdBerth ?? now);
DateTime yDate = (y.Shipcall.Type == ShipcallType.Arrival) ? y.Eta ?? DateTime.Now : y.Etd ?? DateTime.Now;
DateTime yDate = (y.Shipcall.Type == ShipcallType.Arrival) ? (y.Eta ?? now) : (y.Etd ?? now);
Times? yTimes = y.GetTimesForParticipantType(ParticipantType.AGENCY); Times? yTimes = y.GetTimesForParticipantType(ParticipantType.AGENCY);
if (yTimes != null) if (yTimes != null)
yDate = (y.Shipcall.Type == ShipcallType.Arrival) ? yTimes.EtaBerth ?? DateTime.Now : yTimes.EtdBerth ?? DateTime.Now; yDate = (y.Shipcall.Type == ShipcallType.Arrival) ? (yTimes.EtaBerth ?? now) : (yTimes.EtdBerth ?? now);
return DateTime.Compare(xDate, yDate); return DateTime.Compare(xDate, yDate);
}); });
break; break;
@ -854,10 +919,14 @@ namespace BreCalClient
if( obj.ShipcallControlModel == null) { return; } if( obj.ShipcallControlModel == null) { return; }
if (!obj.ShipcallControlModel.AssignedParticipants.ContainsKey(participantType)) return; // no assigment means no dialog my friend if (!obj.ShipcallControlModel.AssignedParticipants.ContainsKey(participantType)) return; // no assigment means no dialog my friend
Times? agencyTimes = obj.ShipcallControlModel.GetTimesForParticipantType(ParticipantType.AGENCY);
// show a dialog that lets the user create / update times for the given shipcall // show a dialog that lets the user create / update times for the given shipcall
IEditTimesControl etc = (participantType == ParticipantType.TERMINAL) ? new EditTimesTerminalControl() : new EditTimesControl(); IEditTimesControl etc = (participantType == ParticipantType.TERMINAL) ? new EditTimesTerminalControl() : new EditTimesControl();
etc.Title = obj.ShipcallControlModel.Title; etc.Title = obj.ShipcallControlModel.Title;
etc.ShipcallModel = obj.ShipcallControlModel; etc.ShipcallModel = obj.ShipcallControlModel;
if (etc is EditTimesControl control)
control.AgencyTimes = agencyTimes;
bool wasEdit = false; bool wasEdit = false;
if (times != null) if (times != null)

View File

@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<ApplicationRevision>1</ApplicationRevision> <ApplicationRevision>1</ApplicationRevision>
<ApplicationVersion>1.3.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>

View File

@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<ApplicationRevision>0</ApplicationRevision> <ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.3.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>

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.5.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())));
@ -214,5 +214,17 @@ namespace BreCalClient.Properties {
this["W4Top"] = value; this["W4Top"] = value;
} }
} }
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string FilterCriteriaMap {
get {
return ((string)(this["FilterCriteriaMap"]));
}
set {
this["FilterCriteriaMap"] = value;
}
}
} }
} }

View File

@ -53,5 +53,8 @@
<Setting Name="W4Top" Type="System.Double" Scope="User"> <Setting Name="W4Top" Type="System.Double" Scope="User">
<Value Profile="(Default)">0</Value> <Value Profile="(Default)">0</Value>
</Setting> </Setting>
<Setting Name="FilterCriteriaMap" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
</Settings> </Settings>
</SettingsFile> </SettingsFile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

View File

@ -60,6 +60,16 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
public static byte[] _24hours {
get {
object obj = ResourceManager.GetObject("_24hours", 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>
@ -268,6 +278,16 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
public static byte[] nav_undo_red {
get {
object obj = ResourceManager.GetObject("nav_undo_red", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Shifting. /// Looks up a localized string similar to Shifting.
/// </summary> /// </summary>
@ -459,6 +479,15 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Clear all entries?.
/// </summary>
public static string textClearAll {
get {
return ResourceManager.GetString("textClearAll", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Clear assignment. /// Looks up a localized string similar to Clear assignment.
/// </summary> /// </summary>
@ -900,6 +929,15 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Pilot.
/// </summary>
public static string textPilots {
get {
return ResourceManager.GetString("textPilots", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Port. /// Looks up a localized string similar to Port.
/// </summary> /// </summary>
@ -909,6 +947,15 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Port authority.
/// </summary>
public static string textPortAuthority {
get {
return ResourceManager.GetString("textPortAuthority", 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>
@ -999,6 +1046,15 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Shift. number.
/// </summary>
public static string textShiftingSequence {
get {
return ResourceManager.GetString("textShiftingSequence", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Shifting to. /// Looks up a localized string similar to Shifting to.
/// </summary> /// </summary>

View File

@ -269,7 +269,7 @@
<value>Anlegeseite</value> <value>Anlegeseite</value>
</data> </data>
<data name="textPilot" xml:space="preserve"> <data name="textPilot" xml:space="preserve">
<value>Lotse</value> <value>Flusslotse</value>
</data> </data>
<data name="textPilotRequired" xml:space="preserve"> <data name="textPilotRequired" xml:space="preserve">
<value>Lotsorder</value> <value>Lotsorder</value>
@ -496,4 +496,16 @@
<data name="textShips" xml:space="preserve"> <data name="textShips" xml:space="preserve">
<value>Schiffe</value> <value>Schiffe</value>
</data> </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>
</root> </root>

View File

@ -178,6 +178,9 @@
<data name="nav_refresh_green" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="nav_refresh_green" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>nav_refresh_green.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>nav_refresh_green.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data> </data>
<data name="nav_undo_red" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>nav_undo_red.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="Shifting" xml:space="preserve"> <data name="Shifting" xml:space="preserve">
<value>Shifting</value> <value>Shifting</value>
</data> </data>
@ -241,6 +244,9 @@
<data name="textChangePassword" xml:space="preserve"> <data name="textChangePassword" xml:space="preserve">
<value>Change password</value> <value>Change password</value>
</data> </data>
<data name="textClearAll" xml:space="preserve">
<value>Clear all entries?</value>
</data>
<data name="textClearAssignment" xml:space="preserve"> <data name="textClearAssignment" xml:space="preserve">
<value>Clear assignment</value> <value>Clear assignment</value>
</data> </data>
@ -388,9 +394,15 @@
<data name="textPilotRequired" xml:space="preserve"> <data name="textPilotRequired" xml:space="preserve">
<value>Pilot required</value> <value>Pilot required</value>
</data> </data>
<data name="textPilots" xml:space="preserve">
<value>Pilot</value>
</data>
<data name="textPort" xml:space="preserve"> <data name="textPort" xml:space="preserve">
<value>Port</value> <value>Port</value>
</data> </data>
<data name="textPortAuthority" xml:space="preserve">
<value>Port authority</value>
</data>
<data name="textRainSensitiveCargo" xml:space="preserve"> <data name="textRainSensitiveCargo" xml:space="preserve">
<value>Rain sensitive cargo</value> <value>Rain sensitive cargo</value>
</data> </data>
@ -421,6 +433,9 @@
<data name="textShiftingFrom" xml:space="preserve"> <data name="textShiftingFrom" xml:space="preserve">
<value>Shifting from</value> <value>Shifting from</value>
</data> </data>
<data name="textShiftingSequence" xml:space="preserve">
<value>Shift. number</value>
</data>
<data name="textShiftingTo" xml:space="preserve"> <data name="textShiftingTo" xml:space="preserve">
<value>Shifting to</value> <value>Shifting to</value>
</data> </data>
@ -529,6 +544,9 @@
<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="_24hours" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>24hours.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -8,7 +8,7 @@
xmlns:api="clr-namespace:BreCalClient.misc.Model" xmlns:api="clr-namespace:BreCalClient.misc.Model"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="56" d:DesignWidth="800" Loaded="UserControl_Loaded"> d:DesignHeight="56" d:DesignWidth="800">
<UserControl.Resources> <UserControl.Resources>
<local:EnumToStringConverter x:Key="enumToStringConverter" /> <local:EnumToStringConverter x:Key="enumToStringConverter" />
</UserControl.Resources> </UserControl.Resources>
@ -52,7 +52,8 @@
</TextBlock.Text> </TextBlock.Text>
</TextBlock> </TextBlock>
</Label> </Label>
<Label Grid.Row="1" Grid.Column="1" Content="{x:Static p:Resources.textSearch}" HorizontalContentAlignment="Right"/>
<Label Grid.Row="1" Grid.Column="1" Content="{x:Static p:Resources.textSearch}" HorizontalContentAlignment="Right"/>
<Label Grid.Row="1" Grid.Column="3" Content="{x:Static p:Resources.textBerths}" HorizontalContentAlignment="Right"/> <Label Grid.Row="1" Grid.Column="3" Content="{x:Static p:Resources.textBerths}" HorizontalContentAlignment="Right"/>
<Label Grid.Row="1" Grid.Column="5" Content="{x:Static p:Resources.textAgencies}" HorizontalContentAlignment="Right"/> <Label Grid.Row="1" Grid.Column="5" Content="{x:Static p:Resources.textAgencies}" HorizontalContentAlignment="Right"/>
@ -62,10 +63,14 @@
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
<ColumnDefinition Width="30" /> <ColumnDefinition Width="30" />
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<DatePicker x:Name="datePickerETAFrom" Grid.Column="0" Margin="2" SelectedDateChanged="datePickerETAFrom_SelectedDateChanged" SelectedDate="{Binding Path=EtaFrom}"/> <DatePicker x:Name="datePickerETAFrom" Grid.Column="0" Margin="2" SelectedDateChanged="datePickerETAFrom_SelectedDateChanged" SelectedDate="{Binding Path=EtaFrom}"/>
<Label Grid.Column="1" Content="{x:Static p:Resources.textTo}" /> <Label Grid.Column="1" Content="{x:Static p:Resources.textTo}" />
<DatePicker x:Name="datePickerETATo" Grid.Column="2" Margin="2" SelectedDateChanged="datePickerETATo_SelectedDateChanged" SelectedDate="{Binding Path=EtaTo}"/> <DatePicker x:Name="datePickerETATo" Grid.Column="2" Margin="2" SelectedDateChanged="datePickerETATo_SelectedDateChanged" SelectedDate="{Binding Path=EtaTo}"/>
<Button Grid.Column="3" Margin="2" x:Name="toggleButton24Hrs" Click="toggleButton24Hrs_Click">
<Image Source="./Resources/24hours.png"/>
</Button>
</Grid> </Grid>
<xctk:CheckComboBox x:Name="comboBoxCategories" Grid.Column="4" Margin="2" ItemSelectionChanged="comboBoxCategories_ItemSelectionChanged" ItemsSource="{local:Enumerate {x:Type api:ShipcallType}}" /> <xctk:CheckComboBox x:Name="comboBoxCategories" Grid.Column="4" Margin="2" ItemSelectionChanged="comboBoxCategories_ItemSelectionChanged" ItemsSource="{local:Enumerate {x:Type api:ShipcallType}}" />
<Grid Grid.Column="6" Grid.Row="0"> <Grid Grid.Column="6" Grid.Row="0">

View File

@ -128,22 +128,17 @@ namespace BreCalClient
private void logoImage_MouseUp(object sender, MouseButtonEventArgs e) private void logoImage_MouseUp(object sender, MouseButtonEventArgs e)
{ {
LogoImageClicked?.Invoke(); LogoImageClicked?.Invoke();
} }
private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
}
private void datePickerETAFrom_SelectedDateChanged(object sender, SelectionChangedEventArgs e) private void datePickerETAFrom_SelectedDateChanged(object sender, SelectionChangedEventArgs e)
{ {
this._model.EtaFrom = this.datePickerETAFrom.SelectedDate; this._model.EtaFrom = this.datePickerETAFrom.SelectedDate?.Date;
SearchFilterChanged?.Invoke(); SearchFilterChanged?.Invoke();
} }
private void datePickerETATo_SelectedDateChanged(object sender, SelectionChangedEventArgs e) private void datePickerETATo_SelectedDateChanged(object sender, SelectionChangedEventArgs e)
{ {
this._model.EtaTo = datePickerETATo.SelectedDate; this._model.EtaTo = datePickerETATo.SelectedDate?.Date;
SearchFilterChanged?.Invoke(); SearchFilterChanged?.Invoke();
} }
@ -212,6 +207,13 @@ namespace BreCalClient
{ {
this._model.MineOnly = this.checkBoxOwn.IsChecked; this._model.MineOnly = this.checkBoxOwn.IsChecked;
SearchFilterChanged?.Invoke(); SearchFilterChanged?.Invoke();
}
private void toggleButton24Hrs_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.datePickerETAFrom.SelectedDate = DateTime.Now;
this.datePickerETATo.SelectedDate = DateTime.Now.AddDays(1);
SearchFilterChanged?.Invoke();
} }
#endregion #endregion

View File

@ -33,18 +33,21 @@ namespace BreCalClient
public bool? MineOnly { get; set; } public bool? MineOnly { get; set; }
public static Dictionary<int, SearchFilterModel>? filterMap = new();
#endregion #endregion
#region Serialisation #region Serialisation
public static SearchFilterModel? Deserialize(string json) public static bool Deserialize(string json)
{ {
return (SearchFilterModel?) JsonConvert.DeserializeObject(json, typeof(SearchFilterModel)); filterMap = (Dictionary<int, SearchFilterModel>?) JsonConvert.DeserializeObject(json, typeof(Dictionary<int, SearchFilterModel>));
return (filterMap != null);
} }
public string Serialize() public static string Serialize()
{ {
return JsonConvert.SerializeObject(this, Formatting.Indented); return JsonConvert.SerializeObject(filterMap, Formatting.Indented);
} }
#endregion #endregion

View File

@ -41,15 +41,19 @@
<Grid Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" Grid.ColumnSpan="3"> <Grid Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" Grid.ColumnSpan="3">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="30" /> <ColumnDefinition Width="30" />
<ColumnDefinition Width="20" x:Name="columnDefinitionShiftingSequence"/>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="32" /> <ColumnDefinition Width="32" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Image Margin="2" Grid.Column="0" PreviewMouseUp="Image_PreviewMouseUp" x:Name="imageShipcallType" /> <Image Margin="2" Grid.Column="0" PreviewMouseUp="Image_PreviewMouseUp" x:Name="imageShipcallType" />
<Label Grid.Column="1" FontSize="12" x:Name="labelShipName" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch" <Viewbox Grid.Column="1">
<TextBlock x:Name="textBlockShiftingSequence"></TextBlock>
</Viewbox>
<Label Grid.Column="2" FontSize="12" x:Name="labelShipName" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" PreviewMouseUp="Image_PreviewMouseUp"> HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" PreviewMouseUp="Image_PreviewMouseUp">
<TextBlock Name="textBlockShipName" /> <TextBlock Name="textBlockShipName" />
</Label> </Label>
<Grid Grid.Column="2" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}"> <Grid Grid.Column="3" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}">
<Image Grid.Column="3" Margin="2" x:Name="imageEvaluation" /> <Image Grid.Column="3" Margin="2" x:Name="imageEvaluation" />
</Grid> </Grid>
</Grid> </Grid>
@ -226,8 +230,8 @@
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="20" /> <RowDefinition Height="20" />
<RowDefinition Height=".5*" /> <RowDefinition Height=".5*" />
<RowDefinition Height="14" /> <RowDefinition Height="14" x:Name="rowDefinitionTerminalBerth"/>
<RowDefinition Height=".5*" /> <RowDefinition Height=".5*" x:Name="rowDefinitionTerminalBerthRemarks"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="{x:Static p:Resources.textOperationsStart}" x:Name="labelETAETDTerminal" Padding="0" VerticalContentAlignment="Center" FontSize="9"/> <Label Grid.Row="0" Grid.Column="0" Content="{x:Static p:Resources.textOperationsStart}" x:Name="labelETAETDTerminal" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="0" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelOperationsStart" FontWeight="DemiBold"/> <Label Grid.Row="0" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelOperationsStart" FontWeight="DemiBold"/>

View File

@ -25,10 +25,7 @@ namespace BreCalClient
Participant? _terminal; Participant? _terminal;
Participant? _tug; Participant? _tug;
Participant? _port_administration; Participant? _port_administration;
private static readonly ILog _log = LogManager.GetLogger(typeof(ShipcallControl)); private static readonly ILog _log = LogManager.GetLogger(typeof(ShipcallControl));
bool ataAdded = false;
bool atdAdded = false;
bool lockTimeAdded = false;
#endregion #endregion
@ -68,6 +65,11 @@ namespace BreCalClient
{ {
if (this.ShipcallControlModel != null) if (this.ShipcallControlModel != null)
{ {
bool shiftingNoVisible = ((this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Shifting) && this.ShipcallControlModel.ShiftSequence.HasValue);
this.textBlockShiftingSequence.Text = shiftingNoVisible ? this.ShipcallControlModel?.ShiftSequence.ToString() : "";
this.columnDefinitionShiftingSequence.Width = shiftingNoVisible ? new(20) : new(0);
string agentName = ""; string agentName = "";
string? name; string? name;
_agency = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.AGENCY); _agency = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.AGENCY);
@ -267,7 +269,12 @@ namespace BreCalClient
else else
this.imageEvaluation.ToolTip = null; this.imageEvaluation.ToolTip = null;
this.textBlockBerth.Text = this.ShipcallControlModel?.Berth; //Times? bsmdTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.BSMD);
//if (bsmdTimes != null)
this.textBlockBerth.Text = this.ShipcallControlModel?.GetBerthText(null);
//else
// this.textBlockBerth.Text = this.ShipcallControlModel?.Berth;
this.textBlockCallsign.Text = this.ShipcallControlModel?.Ship?.Callsign; this.textBlockCallsign.Text = this.ShipcallControlModel?.Ship?.Callsign;
if (this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival) if (this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival)
{ {
@ -336,26 +343,17 @@ namespace BreCalClient
this.labelMooringETAETDValue.Content = mooringTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival); this.labelMooringETAETDValue.Content = mooringTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival);
this.textBlockMooringRemarks.Text = mooringTimes.Remarks.TruncateDots(50); this.textBlockMooringRemarks.Text = mooringTimes.Remarks.TruncateDots(50);
this.imageMooringLocked.Visibility = (mooringTimes.EtaBerthFixed ?? false) ? Visibility.Visible : Visibility.Hidden; this.imageMooringLocked.Visibility = (mooringTimes.EtaBerthFixed ?? false) ? Visibility.Visible : Visibility.Hidden;
ataRowDefinition.Height = mooringTimes.Ata.HasValue ? new(15) : new(0);
atdRowDefinition.Height = mooringTimes.Atd.HasValue ? new(15) : new(0);
if(mooringTimes.Ata.HasValue) if(mooringTimes.Ata.HasValue)
{ {
if(!ataAdded) labelTimesMooringATA.Content = mooringTimes.Ata.Value.ToString("dd.MM.yyyy HH:mm");
{
ataRowDefinition.Height = new GridLength(15);
labelTimesMooringATA.Content = mooringTimes.Ata.Value.ToString("dd.MM.yyyy HH:mm");
ataAdded = true;
}
} }
if (mooringTimes.Atd.HasValue) if (mooringTimes.Atd.HasValue)
{ {
if (!atdAdded) labelTimesMooringATD.Content = mooringTimes.Atd.Value.ToString("dd.MM.yyyy HH:mm");
{
atdRowDefinition.Height = new GridLength(15);
labelTimesMooringATD.Content = mooringTimes.Atd.Value.ToString("dd.MM.yyyy HH:mm");
atdAdded = true;
}
} }
} }
@ -372,14 +370,10 @@ namespace BreCalClient
this.labelPortAuthorityETAETDValue.Content = portAuthorityTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival); this.labelPortAuthorityETAETDValue.Content = portAuthorityTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival);
this.textBlockPortAuthorityRemarks.Text = portAuthorityTimes.Remarks.TruncateDots(50); this.textBlockPortAuthorityRemarks.Text = portAuthorityTimes.Remarks.TruncateDots(50);
this.imagePortAuthorityLocked.Visibility = (portAuthorityTimes.EtaBerthFixed ?? false) ? Visibility.Visible : Visibility.Hidden; this.imagePortAuthorityLocked.Visibility = (portAuthorityTimes.EtaBerthFixed ?? false) ? Visibility.Visible : Visibility.Hidden;
lockTimeRowDefinition.Height = portAuthorityTimes.LockTime.HasValue ? new(15) : new(0);
if(portAuthorityTimes.LockTime.HasValue) if(portAuthorityTimes.LockTime.HasValue)
{ {
if(!lockTimeAdded) labelPortAuthorityLockTime.Content = portAuthorityTimes.LockTime.Value.ToString("dd.MM.yyyy HH:mm");
{
lockTimeRowDefinition.Height = new GridLength(15);
labelPortAuthorityLockTime.Content = portAuthorityTimes.LockTime.Value.ToString("dd.MM.yyyy HH:mm");
lockTimeAdded = true;
}
} }
} }
else else
@ -417,9 +411,13 @@ namespace BreCalClient
this.imageTugLocked.Visibility = Visibility.Hidden; this.imageTugLocked.Visibility = Visibility.Hidden;
} }
Times? terminalTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.TERMINAL); this.rowDefinitionTerminalBerth.Height = (this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival) ? new(14) : new(0);
this.rowDefinitionTerminalBerthRemarks.Height = (this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival) ? new GridLength(.5, GridUnitType.Star) : new(0);
Times ? terminalTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.TERMINAL);
if (terminalTimes != null) if (terminalTimes != null)
{ {
this.labelTerminalBerth.Visibility = (this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival) ? Visibility.Visible : Visibility.Hidden;
this.labelTerminalBerth.Content = this.ShipcallControlModel?.GetBerthText(terminalTimes); this.labelTerminalBerth.Content = this.ShipcallControlModel?.GetBerthText(terminalTimes);
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);

View File

@ -16,7 +16,7 @@ namespace BreCalClient
public class ShipcallControlModel public class ShipcallControlModel
{ {
#region Enumerations #region Enumerations
[Flags] [Flags]
public enum StatusFlags public enum StatusFlags
@ -90,6 +90,31 @@ namespace BreCalClient
} }
} }
/// <summary>
/// This property attempts to store the (hopefully short) shifting sequence in the topmost
/// byte of the "flags" integer which will not be used for the forseeable future
///
/// This is a workaround to avoid adding another field to the interface and is only used in the
/// client display anyway. You could say hack as well.
/// </summary>
public byte? ShiftSequence
{
get
{
if((this.Shipcall == null) || (this.Shipcall.Flags == null)) return null;
return (byte?) (this.Shipcall?.Flags >> 24);
}
set
{
if ((value != null) && (this.Shipcall != null))
{
int currentFlag = this.Shipcall.Flags ?? 0;
int moveUp = ((value ?? 0) << 24);
this.Shipcall.Flags = (currentFlag & 0xffffff) | moveUp;
}
}
}
#endregion #endregion
#region public methods #region public methods
@ -146,28 +171,45 @@ namespace BreCalClient
return false; return false;
} }
public string? GetBerthText(Times times) /// <summary>
/// Get berth display text for columns AGENT and TERMINAL
/// </summary>
public string? GetBerthText(Times? times)
{ {
string? timesBerthText = null;
string? scArrivalBerthText = null;
string? scDepartureBerthText = null;
string? berthText = null; string? berthText = null;
if ((BreCalLists.Berths != null) && times.BerthId.HasValue && (this.Shipcall?.Type != ShipcallType.Shifting)) Berth? berth;
if((times != null) && times.BerthId.HasValue)
{ {
Berth? berth = BreCalLists.AllBerths.Find((x) => x.Id == times.BerthId); berth = BreCalLists.AllBerths.Find((x) => x.Id == times.BerthId.Value);
berthText = berth?.Name; timesBerthText = berth?.Name;
} }
if ((berthText == null) && (times.ParticipantType != (int) Extensions.ParticipantType.TERMINAL)) berth = BreCalLists.AllBerths?.Find((x) => x.Id == this.Shipcall?.ArrivalBerthId);
scArrivalBerthText = berth?.Name;
berth = BreCalLists.AllBerths?.Find((x) => x.Id == this.Shipcall?.DepartureBerthId);
scDepartureBerthText= berth?.Name;
switch(this.Shipcall?.Type)
{ {
if (this.Shipcall?.Type == ShipcallType.Arrival) case ShipcallType.Arrival:
{ berthText = timesBerthText ?? scArrivalBerthText;
Berth? berth = BreCalLists.AllBerths?.Find((x) => x.Id == this.Shipcall?.ArrivalBerthId); break;
berthText = berth?.Name; case ShipcallType.Departure:
} berthText = timesBerthText ?? scDepartureBerthText;
else break;
{ case ShipcallType.Shifting:
Berth? berth = BreCalLists.AllBerths?.Find((x) => x.Id == this.Shipcall?.DepartureBerthId); if (times?.ParticipantType != (int)Extensions.ParticipantType.TERMINAL)
berthText = berth?.Name; berthText = (scDepartureBerthText ?? "") + " / " + (timesBerthText ?? scArrivalBerthText);
} else
} berthText = scDepartureBerthText ?? "";
break;
default: break;
}
return berthText; return berthText;
} }
@ -194,7 +236,7 @@ namespace BreCalClient
{ {
if (agentTimes.EtdBerth != null) if (agentTimes.EtdBerth != null)
theDate = agentTimes.EtdBerth.Value; theDate = agentTimes.EtdBerth.Value;
} }
} }
return theDate.ToString(); return theDate.ToString();
} }

747
src/Setup/Setup.vdproj Normal file
View File

@ -0,0 +1,747 @@
"DeployProject"
{
"VSVersion" = "3:800"
"ProjectType" = "8:{978C614F-708E-4E1A-B201-565925725DBA}"
"IsWebType" = "8:FALSE"
"ProjectName" = "8:Setup"
"LanguageId" = "3:1033"
"CodePage" = "3:1252"
"UILanguageId" = "3:1033"
"SccProjectName" = "8:"
"SccLocalPath" = "8:"
"SccAuxPath" = "8:"
"SccProvider" = "8:"
"Hierarchy"
{
"Entry"
{
"MsmKey" = "8:_3E48B6E716164CC1826E094025517B3F"
"OwnerKey" = "8:_UNDEFINED"
"MsmSig" = "8:_UNDEFINED"
}
"Entry"
{
"MsmKey" = "8:_4EE484EAA4A246CBBB283030A6054BC0"
"OwnerKey" = "8:_UNDEFINED"
"MsmSig" = "8:_UNDEFINED"
}
}
"Configurations"
{
"Debug"
{
"DisplayName" = "8:Debug"
"IsDebugOnly" = "11:TRUE"
"IsReleaseOnly" = "11:FALSE"
"OutputFilename" = "8:Debug\\BremenCallingInstaller.msi"
"PackageFilesAs" = "3:2"
"PackageFileSize" = "3:-2147483648"
"CabType" = "3:1"
"Compression" = "3:2"
"SignOutput" = "11:FALSE"
"CertificateFile" = "8:"
"PrivateKeyFile" = "8:"
"TimeStampServer" = "8:"
"InstallerBootstrapper" = "3:2"
}
"Release"
{
"DisplayName" = "8:Release"
"IsDebugOnly" = "11:FALSE"
"IsReleaseOnly" = "11:TRUE"
"OutputFilename" = "8:Release\\BremenCallingInstaller.msi"
"PackageFilesAs" = "3:2"
"PackageFileSize" = "3:-2147483648"
"CabType" = "3:1"
"Compression" = "3:2"
"SignOutput" = "11:FALSE"
"CertificateFile" = "8:"
"PrivateKeyFile" = "8:"
"TimeStampServer" = "8:"
"InstallerBootstrapper" = "3:2"
}
}
"Deployable"
{
"CustomAction"
{
}
"DefaultFeature"
{
"Name" = "8:DefaultFeature"
"Title" = "8:"
"Description" = "8:"
}
"ExternalPersistence"
{
"LaunchCondition"
{
"{A06ECF26-33A3-4562-8140-9B0E340D4F24}:_7C5ED856EDF94532A041DBACD5D5C09E"
{
"Name" = "8:.NET Core"
"Message" = "8:[VSDNETCOREMSG]"
"AllowLaterVersions" = "11:FALSE"
"InstallUrl" = "8:https://dotnet.microsoft.com/download/dotnet-core/[NetCoreVerMajorDotMinor]"
"IsNETCore" = "11:TRUE"
"Architecture" = "2:0"
"Runtime" = "2:0"
}
}
}
"File"
{
"{1FB2D0AE-D3B9-43D4-B9DD-F88EC61E35DE}:_4EE484EAA4A246CBBB283030A6054BC0"
{
"SourcePath" = "8:..\\BreCalClient\\Resources\\containership.ico"
"TargetName" = "8:containership.ico"
"Tag" = "8:"
"Folder" = "8:_7C8F7547CED64B3CAE7D0296A8BCE23F"
"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"
{
}
"Folder"
{
"{1525181F-901A-416C-8A58-119130FE478E}:_42539D5BC3044EB6A0CE1EFE84F3C07F"
{
"Name" = "8:#1916"
"AlwaysCreate" = "11:FALSE"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Property" = "8:DesktopFolder"
"Folders"
{
}
}
"{3C67513D-01DD-4637-8A68-80971EB9504F}:_7C8F7547CED64B3CAE7D0296A8BCE23F"
{
"DefaultLocation" = "8:[ProgramFiles64Folder][Manufacturer]\\[ProductName]"
"Name" = "8:#1925"
"AlwaysCreate" = "11:FALSE"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Property" = "8:TARGETDIR"
"Folders"
{
}
}
"{1525181F-901A-416C-8A58-119130FE478E}:_8BBC7FE2F38E4B41A71D26CCED7D0BCB"
{
"Name" = "8:#1919"
"AlwaysCreate" = "11:FALSE"
"Condition" = "8:"
"Transitive" = "11:FALSE"
"Property" = "8:ProgramMenuFolder"
"Folders"
{
}
}
}
"LaunchCondition"
{
}
"Locator"
{
}
"MsiBootstrapper"
{
"LangId" = "3:1033"
"RequiresElevation" = "11:FALSE"
}
"Product"
{
"Name" = "8:Microsoft Visual Studio"
"ProductName" = "8:Bremen calling"
"ProductCode" = "8:{6EF71F76-3E55-483B-A032-4B29FDAFE6A4}"
"PackageCode" = "8:{8E55F30F-A9C8-41E9-BCE8-431770A951ED}"
"UpgradeCode" = "8:{1C7FA3E4-BAB9-4911-9348-73094357FC7C}"
"AspNetVersion" = "8:"
"RestartWWWService" = "11:FALSE"
"RemovePreviousVersions" = "11:TRUE"
"DetectNewerInstalledVersion" = "11:TRUE"
"InstallAllUsers" = "11:FALSE"
"ProductVersion" = "8:1.2.2"
"Manufacturer" = "8:Informatikbüro Daniel Schick"
"ARPHELPTELEPHONE" = "8:+49 (0) 421 - 38 48 27"
"ARPHELPLINK" = "8:https://www.bsmd.de/"
"Title" = "8:Bremen Calling"
"Subject" = "8:"
"ARPCONTACT" = "8:Informatikbüro Daniel Schick"
"Keywords" = "8:"
"ARPCOMMENTS" = "8:Bremen calling desktop app"
"ARPURLINFOABOUT" = "8:https://www.textbausteine.net/"
"ARPPRODUCTICON" = "8:"
"ARPIconIndex" = "3:0"
"SearchPath" = "8:"
"UseSystemSearchPath" = "11:TRUE"
"TargetPlatform" = "3:1"
"PreBuildEvent" = "8:"
"PostBuildEvent" = "8:"
"RunPostBuildEvent" = "3:0"
}
"Registry"
{
"HKLM"
{
"Keys"
{
"{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_621080AF02E34428A84F3B5008F9EFE8"
{
"Name" = "8:Software"
"Condition" = "8:"
"AlwaysCreate" = "11:FALSE"
"DeleteAtUninstall" = "11:FALSE"
"Transitive" = "11:FALSE"
"Keys"
{
"{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_E6E863A158B246CDA6DC9DF011E759D6"
{
"Name" = "8:[Manufacturer]"
"Condition" = "8:"
"AlwaysCreate" = "11:FALSE"
"DeleteAtUninstall" = "11:FALSE"
"Transitive" = "11:FALSE"
"Keys"
{
}
"Values"
{
}
}
}
"Values"
{
}
}
}
}
"HKCU"
{
"Keys"
{
"{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_4C5D672685C24D5CB9C3365480FF133D"
{
"Name" = "8:Software"
"Condition" = "8:"
"AlwaysCreate" = "11:FALSE"
"DeleteAtUninstall" = "11:FALSE"
"Transitive" = "11:FALSE"
"Keys"
{
"{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_D60142B890324967A76C15BDECC179BD"
{
"Name" = "8:[Manufacturer]"
"Condition" = "8:"
"AlwaysCreate" = "11:FALSE"
"DeleteAtUninstall" = "11:FALSE"
"Transitive" = "11:FALSE"
"Keys"
{
}
"Values"
{
}
}
}
"Values"
{
}
}
}
}
"HKCR"
{
"Keys"
{
}
}
"HKU"
{
"Keys"
{
}
}
"HKPU"
{
"Keys"
{
}
}
}
"Sequences"
{
}
"Shortcut"
{
"{970C0BB2-C7D0-45D7-ABFA-7EC378858BC0}:_5E8842D2923A4CED99FE0FF49BD7EB44"
{
"Name" = "8:Bremen calling"
"Arguments" = "8:"
"Description" = "8:"
"ShowCmd" = "3:1"
"IconIndex" = "3:0"
"Transitive" = "11:FALSE"
"Target" = "8:_3E48B6E716164CC1826E094025517B3F"
"Folder" = "8:_8BBC7FE2F38E4B41A71D26CCED7D0BCB"
"WorkingFolder" = "8:_7C8F7547CED64B3CAE7D0296A8BCE23F"
"Icon" = "8:_4EE484EAA4A246CBBB283030A6054BC0"
"Feature" = "8:"
}
"{970C0BB2-C7D0-45D7-ABFA-7EC378858BC0}:_E3C6E6AB5F9B416C8AB4CB484AB16A04"
{
"Name" = "8:Bremen calling"
"Arguments" = "8:"
"Description" = "8:"
"ShowCmd" = "3:1"
"IconIndex" = "3:0"
"Transitive" = "11:FALSE"
"Target" = "8:_3E48B6E716164CC1826E094025517B3F"
"Folder" = "8:_42539D5BC3044EB6A0CE1EFE84F3C07F"
"WorkingFolder" = "8:_7C8F7547CED64B3CAE7D0296A8BCE23F"
"Icon" = "8:_4EE484EAA4A246CBBB283030A6054BC0"
"Feature" = "8:"
}
}
"UserInterface"
{
"{2479F3F5-0309-486D-8047-8187E2CE5BA0}:_03AD68BE31A34015840F64B788AE62D3"
{
"UseDynamicProperties" = "11:FALSE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdUserInterface.wim"
}
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_03DFC57B49C04D4FAF4AFA2978E58D73"
{
"Name" = "8:#1901"
"Sequence" = "3:2"
"Attributes" = "3:2"
"Dialogs"
{
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_539DAA1B164044C3972295728D682887"
{
"Sequence" = "3:100"
"DisplayName" = "8:Progress"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminProgressDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
"ShowProgress"
{
"Name" = "8:ShowProgress"
"DisplayName" = "8:#1009"
"Description" = "8:#1109"
"Type" = "3:5"
"ContextData" = "8:1;True=1;False=0"
"Attributes" = "3:0"
"Setting" = "3:0"
"Value" = "3:1"
"DefaultValue" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
}
}
}
}
"{2479F3F5-0309-486D-8047-8187E2CE5BA0}:_1911E796FA5D489EA62710F95FAF9302"
{
"UseDynamicProperties" = "11:FALSE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdBasicDialogs.wim"
}
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_294C0DD534914D668B6B8D4963D157F5"
{
"Name" = "8:#1902"
"Sequence" = "3:2"
"Attributes" = "3:3"
"Dialogs"
{
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_047451965B9047229CFC992E233C1FA0"
{
"Sequence" = "3:100"
"DisplayName" = "8:Finished"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminFinishedDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
}
}
}
}
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_3E8E2283333C48529849E39663B8A994"
{
"Name" = "8:#1900"
"Sequence" = "3:1"
"Attributes" = "3:1"
"Dialogs"
{
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_56CB3060C3AD48EAABCC4E6F6340FD33"
{
"Sequence" = "3:100"
"DisplayName" = "8:Welcome"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdWelcomeDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
"CopyrightWarning"
{
"Name" = "8:CopyrightWarning"
"DisplayName" = "8:#1002"
"Description" = "8:#1102"
"Type" = "3:3"
"ContextData" = "8:"
"Attributes" = "3:0"
"Setting" = "3:1"
"Value" = "8:#1202"
"DefaultValue" = "8:#1202"
"UsePlugInResources" = "11:TRUE"
}
"Welcome"
{
"Name" = "8:Welcome"
"DisplayName" = "8:#1003"
"Description" = "8:#1103"
"Type" = "3:3"
"ContextData" = "8:"
"Attributes" = "3:0"
"Setting" = "3:1"
"Value" = "8:#1203"
"DefaultValue" = "8:#1203"
"UsePlugInResources" = "11:TRUE"
}
}
}
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_8CD43C22928545F8A3392FDCC650C893"
{
"Sequence" = "3:300"
"DisplayName" = "8:Confirm Installation"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdConfirmDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
}
}
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_E6254350E1FF43E7A231AC077BAB8F82"
{
"Sequence" = "3:200"
"DisplayName" = "8:Installation Folder"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdFolderDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
"InstallAllUsersVisible"
{
"Name" = "8:InstallAllUsersVisible"
"DisplayName" = "8:#1059"
"Description" = "8:#1159"
"Type" = "3:5"
"ContextData" = "8:1;True=1;False=0"
"Attributes" = "3:0"
"Setting" = "3:0"
"Value" = "3:1"
"DefaultValue" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
}
}
}
}
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_4A86B004A86E4D3DAF096262FE76805C"
{
"Name" = "8:#1901"
"Sequence" = "3:1"
"Attributes" = "3:2"
"Dialogs"
{
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_16DE803E5AB14E1F9FE98B738BA75A4A"
{
"Sequence" = "3:100"
"DisplayName" = "8:Progress"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdProgressDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
"ShowProgress"
{
"Name" = "8:ShowProgress"
"DisplayName" = "8:#1009"
"Description" = "8:#1109"
"Type" = "3:5"
"ContextData" = "8:1;True=1;False=0"
"Attributes" = "3:0"
"Setting" = "3:0"
"Value" = "3:1"
"DefaultValue" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
}
}
}
}
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_8453A720995D4255A3A1B309A36A7111"
{
"Name" = "8:#1900"
"Sequence" = "3:2"
"Attributes" = "3:1"
"Dialogs"
{
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_240EA1CF929842A7AB9F635AFA4F93EA"
{
"Sequence" = "3:100"
"DisplayName" = "8:Welcome"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminWelcomeDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
"CopyrightWarning"
{
"Name" = "8:CopyrightWarning"
"DisplayName" = "8:#1002"
"Description" = "8:#1102"
"Type" = "3:3"
"ContextData" = "8:"
"Attributes" = "3:0"
"Setting" = "3:1"
"Value" = "8:#1202"
"DefaultValue" = "8:#1202"
"UsePlugInResources" = "11:TRUE"
}
"Welcome"
{
"Name" = "8:Welcome"
"DisplayName" = "8:#1003"
"Description" = "8:#1103"
"Type" = "3:3"
"ContextData" = "8:"
"Attributes" = "3:0"
"Setting" = "3:1"
"Value" = "8:#1203"
"DefaultValue" = "8:#1203"
"UsePlugInResources" = "11:TRUE"
}
}
}
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_F5EB176F97E14A8CBBA361E5EA6BF00A"
{
"Sequence" = "3:200"
"DisplayName" = "8:Installation Folder"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminFolderDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
}
}
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_F9D0FD5521A74505AFD3AFA0A4C3B9D5"
{
"Sequence" = "3:300"
"DisplayName" = "8:Confirm Installation"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminConfirmDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
}
}
}
}
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_8BFEF97EF6EC4B3094E8982994B8C6FF"
{
"Name" = "8:#1902"
"Sequence" = "3:1"
"Attributes" = "3:3"
"Dialogs"
{
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_738B5FF6A9514E8F88896EFCE7980AF3"
{
"Sequence" = "3:100"
"DisplayName" = "8:Finished"
"UseDynamicProperties" = "11:TRUE"
"IsDependency" = "11:FALSE"
"SourcePath" = "8:<VsdDialogDir>\\VsdFinishedDlg.wid"
"Properties"
{
"BannerBitmap"
{
"Name" = "8:BannerBitmap"
"DisplayName" = "8:#1001"
"Description" = "8:#1101"
"Type" = "3:8"
"ContextData" = "8:Bitmap"
"Attributes" = "3:4"
"Setting" = "3:1"
"UsePlugInResources" = "11:TRUE"
}
"UpdateText"
{
"Name" = "8:UpdateText"
"DisplayName" = "8:#1058"
"Description" = "8:#1158"
"Type" = "3:15"
"ContextData" = "8:"
"Attributes" = "3:0"
"Setting" = "3:1"
"Value" = "8:#1258"
"DefaultValue" = "8:#1258"
"UsePlugInResources" = "11:TRUE"
}
}
}
}
}
}
"MergeModule"
{
}
"ProjectOutput"
{
"{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_3E48B6E716164CC1826E094025517B3F"
{
"SourcePath" = "8:..\\BreCalClient\\obj\\Release\\net6.0-windows\\apphost.exe"
"TargetName" = "8:"
"Tag" = "8:"
"Folder" = "8:_7C8F7547CED64B3CAE7D0296A8BCE23F"
"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:"
"ProjectOutputGroupRegister" = "3:1"
"OutputConfiguration" = "8:"
"OutputGroupCanonicalName" = "8:PublishItems"
"OutputProjectGuid" = "8:{FA9E0A87-FBFB-4F2B-B5FA-46DE2E5E4BCB}"
"ShowKeyOutput" = "11:TRUE"
"ExcludeFilters"
{
}
}
}
}
}

View File

@ -10,6 +10,7 @@ namespace brecal.model
{ {
#region Enumerations #region Enumerations
// TODO: should localize the descriptions
[Flags] [Flags]
public enum ParticipantType public enum ParticipantType
{ {
@ -19,7 +20,7 @@ namespace brecal.model
BSMD = 1, BSMD = 1,
[Description("Terminal")] [Description("Terminal")]
TERMINAL = 2, TERMINAL = 2,
[Description("Lotsen")] [Description("Flusslotsen")]
PILOT = 4, PILOT = 4,
[Description("Agentur")] [Description("Agentur")]
AGENCY = 8, AGENCY = 8,

View File

@ -33,7 +33,8 @@ def get_synchronous_shipcall_times_standalone(query_time:pd.Timestamp, all_df_ti
returns: counts returns: counts
""" """
assert isinstance(query_time,pd.Timestamp) assert isinstance(query_time,pd.Timestamp) or pd.isnull(query_time), f"expected query_time to be a pd.Timestamp or pd.NaT. Found: {type(query_time)}"
assert isinstance(all_df_times,pd.DataFrame)
# 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_)]
@ -439,4 +440,5 @@ 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)"""
assert isinstance(all_df_times, pd.DataFrame)
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

@ -222,7 +222,7 @@ class SQLQuery():
"api_key, notify_email, notify_whatsapp, notify_signal, notify_popup, 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
@staticmethod @staticmethod
def get_notifications()->str: def get_notifications()->str:
query = "SELECT id, shipcall_id, level, type, message, created, modified FROM notification " + \ query = "SELECT id, shipcall_id, level, type, message, created, modified FROM notification " + \
@ -266,6 +266,11 @@ class SQLQuery():
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 id, name, imo, callsign, participant_id, length, width, is_tug, bollard_pull, eni, created, modified, deleted 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, " + \
@ -316,6 +321,22 @@ class SQLQuery():
query = prefix+stage1+bridge+stage2+suffix query = prefix+stage1+bridge+stage2+suffix
return query return query
@staticmethod
def get_notifications_post(schemaModel:dict)->str:
param_keys = {key:key for key in schemaModel.keys()}
prefix = "INSERT INTO notification ("
bridge = ") VALUES ("
suffix = ")"
non_dynamic_keys = ["id", "created", "modified"]
stage1 = ",".join([key for key in schemaModel.keys() if not key in non_dynamic_keys])
stage2 = ",".join([f"?{param_keys.get(key)}?" for key in schemaModel.keys() if not key in non_dynamic_keys])
query = prefix+stage1+bridge+stage2+suffix
return query
@staticmethod @staticmethod
def get_last_insert_id()->str: def get_last_insert_id()->str:

View File

@ -0,0 +1,7 @@
"""This file contains login information to register into distinct notification accounts."""
mail_server = 'w01d5503.kasserver.com'
mail_port=465
mail_address="max.metz@scope-sorting.com"
mail_pwd = b'gAAAAABmqJlkXbtJTL1tFiyQNHhF_Y7sgtVI0xEx07ybwbX70Ro1Vp73CLDq49eFSYG-1SswIDQ2JBSORYlWaR-Vh2kIwPHy_lX8SxkySrRvBRzkyZP5x0I='

View File

@ -1,7 +1,19 @@
import typing import typing
import datetime
from BreCal.database.sql_handler import execute_sql_query_standalone from BreCal.database.sql_handler import execute_sql_query_standalone
from BreCal.database.sql_queries import SQLQuery from BreCal.database.sql_queries import SQLQuery
from BreCal.schemas import model from BreCal.schemas import model
from BreCal.brecal_utils.time_handling import difference_to_then
from BreCal.notifications.accounts import mail_server, mail_port, mail_address, mail_pwd
from BreCal.services.email_handling import EmailHandler, create_shipcall_evaluation_notification, send_notification, get_default_html_email
from BreCal.database.enums import ParticipantwiseTimeDelta
eta_etd_type_dict = {
model.ShipcallType.arrival : "Ankunft",
model.ShipcallType.departure : "Abfahrt",
model.ShipcallType.shifting : "Wechselnd"
}
class Notifier(): class Notifier():
""" """
@ -35,112 +47,82 @@ class Notifier():
- iterate over each remaining shipcall and apply .send_notification - iterate over each remaining shipcall and apply .send_notification
- those which are unsent, shall be sent by the respective type - those which are unsent, shall be sent by the respective type
""" """
raise NotImplementedError("skeleton") # set a threshold, when alarm event notifications become eligible
time_diff_threshold = float(ParticipantwiseTimeDelta.NOTIFICATION)*60 # m minutes, converted to seconds
debug = is_test # if is_test, the Emails will not be issued. Only a print message will be created.
update_database = True if not is_test else False # if_test, the database will not be updated.
time_diff_threshold = time_diff_threshold if not is_test else 0.0 # 0.0 delay when is_test is set.
# get all shipcalls email_handler = EmailHandler(mail_server=mail_server, mail_port=mail_port, mail_address=mail_address)
all_shipcalls = NotImplementedError
shipcalls = [shipcall for shipcall in all_shipcalls if not shipcall.evaluation_notifications_sent] # get candidates: find all eligible shipcalls, where the evaluation state is yellow or red & the notifications are not yet sent
for shipcall in shipcalls: eligible_shipcalls = Notifier.get_eligible_shipcalls(time_diff_threshold)
notification_list = Notifier.send_notification(shipcall, is_test=is_test)
# #TODO: get all notifications # get a list of tuples, where (shipcall, users) are combined for easier iterations.
# #TODO: get matching shipcall (based on shipcall_id) notification_instructions = Notifier.build_notification_tuples(eligible_shipcalls)
# #TODO: filter: consider only those, which are not yet sent if len(notification_instructions)>0: # only perform a login when there are any notification_instructions
try:
# login in advance, so the email handler uses a shared connection. It disconnects only once at the end of the call.
email_handler.login(interactive=False, pwd=mail_pwd)
for shipcall, users in notification_instructions:
assert isinstance(shipcall, model.Shipcall)
assert isinstance(users,list)
assert all([isinstance(user,model.User) for user in users])
# identify necessity # iterate over each notification type
# #TODO: get the 'evaluation_notifications_sent' field from all shipcalls (based on shipcall_id) for notification_type in Notifier.get_all_notification_types():
# if not -> return # consider only those users, which have subscribed to the respective notification type
# USE shipcall.evaluation_notifications_sent eligible_users = Notifier.get_eligible_users(users, notification_type)
# #TODO: those which are unsent, shall be created&sent by the respective type -- Note: consider the is_test argument if len(eligible_users)>0:
# iterate over the list of Notifier.build_notification_type_list Notifier.create_and_send_notification_mapper(notification_type, shipcall, eligible_users, email_handler, mail_pwd, debug=debug)
# one might use Notifier.create(..., update_database=True)
# use the History (GetHistory -- by shipcall_id) to identify all subscribed users
# #TODO: update the shipcall dataset ('evaluation_notifications_sent') -- Note: consider the is_test argument if not debug:
# populate notifications within the database to keep track
Notifier.generate_notification(shipcall, notification_type)
# #TODO_clarify: how to handle the 'evaluation_notifications_sent', when there is no recipient? # update the database entries, so notifications are only sent once.
if update_database:
Notifier.shipcall_put_update_evaluation_notifications_sent_flag(shipcall)
# #TODO: except... create log?
finally:
email_handler.close()
return return
@staticmethod
def build_notification_tuples(eligible_shipcalls)->list[typing.Tuple[model.Shipcall,list[model.User]]]:
"""
creates tuples, where shipcall and the list of attached users are grouped. One can iterate over the tuples
to perform actions, such as issueing notifications
"""
notification_instructions = []
for shipcall in eligible_shipcalls:
# get all users, which are attached to the shipcall (uses the History dataset)
users = Notifier.get_users_via_history(shipcall.id)
# create and store tuples of (shipcall, users) for each eligible shipcall
notification_instructions.append((shipcall, users))
return notification_instructions
@staticmethod @staticmethod
def send_notification(shipcall:model.Shipcall, is_test:bool=False)->list[model.Notification]: def get_eligible_users(users, notification_type):
""" eligible_users = [user for user in users if Notifier.check_user_is_subscribed_to_notification_type(user,notification_type=notification_type)]
Complex-function, which is responsible of creating notification messages, issuing them to users and optionally updating
the database. The requirement is, that the notification is required and passes through an internal set of filters.
Steps: # filter: consider only those users, where an Email is set
- get all notifications of shipcall_id # #TODO: this is Email-specific and should not be a filter for other notifications
- identify the assigned list of users eligible_users = [user for user in eligible_users if user.user_email is not None]
- apply all filters. When a filter triggers, exit. If not, create and send a notification. return eligible_users
"""
update_database = False if is_test else True
# #TODO: the concept of old state and new state must be refactored.
# old state: read shipcall_id from notifications and look for the latest finding (if None -> EvaluationType.undefined)
# new state: read shipcall_id from shipcalls and look for the *current* 'evaluation' (-> EvaluationType(value))
# get existing notifications by shipcall_id (list)
existing_notifications = Notifier.get_existing_notifications(shipcall_id=shipcall.id)
old_state = NotImplementedError
new_state = shipcall.evaluation
# get User by querying all History objects of a shipcall_id
users = Notifier.get_users_via_history(shipcall_id=shipcall.id)
# identify necessity
# state-check: Did the 'evaluation' shift to a higher level of severity?
severity_bool = Notifier.check_higher_severity(old_state, new_state)
if not severity_bool:
return None
# #TODO: time-based filter. There shall be 'enough' time between the evaluation time and NOW
evaluation_time = shipcall.evaluation_time
# latency_bool = #TODO_DIFFERENCE_FROM_NOW_TO_EVALUATION_TIME____THIS_METHOD_ALREADY_EXISTS(evaluation_time)
# careful: what is True, what is False?
# if latency_booL:
# return None
notification_list = []
for user in users:
notification = Notifier.create(
shipcall.id,
old_state,
new_state,
user,
update_database=update_database,
is_test=is_test
)
notification_list.append(notification)
return notification_list
@staticmethod
def publish(shipcall_id, old_state, new_state, user, update_database:bool=False)->typing.Optional[model.Notification]:
"""
Complex-function, which creates, sends and documents a notification. It serves as a convenience function.
The method does not apply internal filters to identify, whether a notification should be created in the first place.
options:
update_database: bool.
# #TODO: instead of update_database, one may also use is_test
"""
# 1.) create
# ... = Notifier.create(shipcall_id, old_state, new_state, user) # e.g., might return a dictionary of dict[model.NotificationType, str], where str is the message
# 2.) send
# ... = Notifier.send(...) # should contain internal 'logistics', which user the respective handlers to send notifications
# 3.) document (mysql database)
# if update_database
# ... = Notifier.document(...)
raise NotImplementedError("skeleton")
return
@staticmethod @staticmethod
def create(shipcall_id, old_state, new_state, user, update_database:bool=False)->typing.Optional[model.Notification]: def create(shipcall_id, old_state, new_state, user, update_database:bool=False)->typing.Optional[model.Notification]:
""" """
# #TODO_refactor: drastically change this method. It should only generate notifications, but not send them.
Standalone function, which creates a Notification for a specific user. Standalone function, which creates a Notification for a specific user.
Steps: Steps:
@ -173,6 +155,7 @@ class Notifier():
# get a list of all subscribed notification types and track the state (success or failure) # get a list of all subscribed notification types and track the state (success or failure)
raise NotImplementedError("skeleton")
successes = {} successes = {}
notification_type_list = Notifier.build_notification_type_list(user) notification_type_list = Notifier.build_notification_type_list(user)
for notification_type in notification_type_list: for notification_type in notification_type_list:
@ -183,7 +166,6 @@ class Notifier():
success_state = Notifier.send_notification_by_type(notification_type, message) success_state = Notifier.send_notification_by_type(notification_type, message)
successes[notification_type] = success_state successes[notification_type] = success_state
raise NotImplementedError("skeleton")
notification = ... notification = ...
return notification return notification
@ -197,11 +179,10 @@ class Notifier():
def get_users_via_history(shipcall_id:int)->list[model.User]: def get_users_via_history(shipcall_id:int)->list[model.User]:
"""using the History objects, one can infer the user_id, which allows querying the Users""" """using the History objects, one can infer the user_id, which allows querying the Users"""
histories = execute_sql_query_standalone(query=SQLQuery.get_history(), param={"shipcallid" : shipcall_id}, model=model.History, command_type="query") histories = execute_sql_query_standalone(query=SQLQuery.get_history(), param={"shipcallid" : shipcall_id}, model=model.History, command_type="query")
user_ids = [ assert isinstance(histories,list)
history.user_id assert all([isinstance(history,model.History) for history in histories])
for history in histories
] users = [Notifier.get_user(history.user_id) for history in histories]
users = [Notifier.get_user(user_id) for user_id in user_ids]
return users return users
@staticmethod @staticmethod
@ -306,8 +287,249 @@ class Notifier():
else: else:
raise ValueError(notification_type) raise ValueError(notification_type)
return return
@staticmethod
def get_eligible_shipcalls(time_diff_threshold:float):
"""
get all eligible shipcalls, which do not have a sent notification yet
criterion a) notification shall not be sent yet (evaluation_notifications_sent = 0)
criterion b) evaluation state is yellow or red (type 2 or 3)
"""
query = 'SELECT * FROM shipcall WHERE (evaluation_notifications_sent = ?evaluation_notifications_sent?) AND (evaluation = 2 OR evaluation = 3)'
evaluation_notifications_sent = 0
# filter out the shipcalls, where the notifications were already sent (ignores null values, which is as expected)
eligible_shipcalls = execute_sql_query_standalone(query=query, model=model.Shipcall, param={"evaluation_notifications_sent" : evaluation_notifications_sent})
# filter out the shipcalls, where evaluation_time is not set
eligible_shipcalls = [shipcall for shipcall in eligible_shipcalls if shipcall.evaluation_time is not None]
# filter out the shipcalls, where the evaluation_time is too recent. We expect a minimum difference, so user input errors do not cause notifications
eligible_shipcalls = [shipcall for shipcall in eligible_shipcalls if Notifier.check_shipcall_evaluation_time_exceeds_minimum_time_difference(shipcall, time_diff_threshold)]
return eligible_shipcalls
@staticmethod
def get_eligible_notifications_of_shipcall(shipcall:model.Shipcall, time_diff_threshold:float)->list[model.Notification]:
"""obtain all notifications, which belong to the shipcall id"""
query = SQLQuery.get_notifications()
eligible_notifications = execute_sql_query_standalone(query=query, model=model.Notification, param={"scid" : shipcall.id})
eligible_notifications = [notification for notification in eligible_notifications if Notifier.check_notification_level_matches_shipcall_entry(notification, shipcall)]
return eligible_notifications
@staticmethod
def check_shipcall_evaluation_time_exceeds_minimum_time_difference(shipcall:model.Shipcall, time_diff_threshold:float):
"""
a notification may only be sent, when the created notification has been created or modified {time_diff_threshold} seconds ago.
"""
assert (shipcall.evaluation_time is not None), f"must provide 'evaluation_time'"
return difference_to_then(shipcall.evaluation_time)>=time_diff_threshold
@staticmethod
def check_notification_level_matches_shipcall_entry(notification, shipcall):
"""
a notification may only be sent, when the shipcall entry matches the notification level.
otherwise, a user may have adapted the shipcall in the mean-time, so a notification would no longer be useful.
"""
return int(shipcall.evaluation) == int(notification.level)
@staticmethod
def get_eligible_notifications(shipcalls:list[model.Shipcall]):
"""obtain a list of all notifications of each element of the shipcall list."""
eligible_notifications = []
for shipcall in shipcalls:
eligible_notification = Notifier.get_eligible_notifications_of_shipcall(shipcall)
eligible_notifications.extend(eligible_notification)
raise NotImplementedError("refactoring!")
return eligible_notifications
@staticmethod
def create_notifications_for_user_list(shipcall, users:list[model.User]):
raise NotImplementedError("deprecated")
notification_type_list = []
for user in users:
user_notification_type_list = Notifier.build_notification_type_list(user)
notification_type_list.extend(user_notification_type_list)
# get the unique notification types
notification_type_list = list(set(notification_type_list))
for notification_type in notification_type_list:
schemaModel = dict(shipcall_id = shipcall.id, level = int(shipcall.evaluation), type = notification_type, message = "", created = datetime.datetime.now(), modified=None)
query = SQLQuery.get_notifications_post(schemaModel)
schemas = execute_sql_query_standalone(query=query, param=schemaModel, command_type="execute")
return
@staticmethod
def generate_notification(shipcall:model.Shipcall, notification_type:model.NotificationType):
"""
This one-line method creates a notification for the provided shipcall and notification type
"""
schemaModel = dict(
shipcall_id = shipcall.id,
level = int(shipcall.evaluation),
type = notification_type,
message = "", # #TODO_messsage: what should be stored here? The HTML-template appears to be too long.
created = datetime.datetime.now(),
modified=None)
query = SQLQuery.get_notifications_post(schemaModel)
schemas = execute_sql_query_standalone(query=query, param=schemaModel, command_type="execute")
return
@staticmethod
def DEPRECATED_generate_notifications(shipcall_id):
"""
# #TODO_delete: this method can be safely removed after 15.08.2024
This one-line method creates all notifications for the provided shipcall id. It does so by obtaining the shipcall,
looking up its history, and finding all attached users.
For each user, a notification will be created for each subscribed notification type (e.g., Email)
"""
shipcall = Notifier.get_shipcall(shipcall_id)
notifications = execute_sql_query_standalone(query=SQLQuery.get_notifications(), param={"scid" : shipcall_id}, model=model.Notification, command_type="query")
latest_notification = Notifier.find_latest_notification(notifications)
old_state = model.EvaluationType(latest_notification.level) if latest_notification is not None else model.EvaluationType.undefined
new_state = shipcall.evaluation
# identify, whether the severity of the shipcall has increased to see, whether a notification is required
severity_increase = Notifier.check_higher_severity(old_state=old_state, new_state=new_state)
# when the severity increases, set the 'evaluation_notifications_sent' argument to 0 (False)
if severity_increase:
### UPDATE Shipcall ###
# prepare and create a query
evaluation_notifications_sent = 0
schemaModel = {"id":shipcall.id, "evaluation_notifications_sent":evaluation_notifications_sent} # #TODO: should this require the 'modified' tag to be adapted?
query = SQLQuery.get_shipcall_put(schemaModel)
# execute the PUT-Request
schemas = execute_sql_query_standalone(query=query, param=schemaModel, command_type="execute")
### Generate Notifications ###
# find all attached users of the shipcall (checks the history, then reads out the user ids and builds the users)
users = Notifier.get_users_via_history(shipcall_id=shipcall.id)
# for each user, identify the notification_types, which must be generated. Finally, create those
# notifications with a POST-request
Notifier.create_notifications_for_user_list(shipcall, users)
return
@staticmethod
def create_etaetd_string(eta, etd): # #TODO_rename: function name is improvable
eta = eta.strftime("%d.%m.%Y %H:%M") if eta is not None else None
etd = etd.strftime("%d.%m.%Y %H:%M") if etd is not None else None
eta_etd = ""
if eta is not None and etd is not None:
eta_etd = f"{eta} - {etd}"
if eta is not None and etd is None:
eta_etd = f"{eta}"
if etd is None and etd is not None:
eta_etd = f"{etd}"
return eta_etd
@staticmethod
def prepare_notification_body(shipcall:model.Shipcall):
# obtain the respective shipcall and ship
shipcall = execute_sql_query_standalone(query=SQLQuery.get_shipcall_by_id(), model=model.Shipcall, param={"id" : shipcall.id}, command_type="single")
ship = execute_sql_query_standalone(query=SQLQuery.get_ship_by_id(), model=model.Ship, param={"id" : shipcall.ship_id}, command_type="single")
# use ship & shipcall data models to prepare the body
ship_name = ship.name
eta_etd = Notifier.create_etaetd_string(shipcall.eta, shipcall.etd)
eta_etd_type = eta_etd_type_dict[model.ShipcallType(shipcall.type)]
evaluation_message = shipcall.evaluation_message
return (ship_name, evaluation_message, eta_etd, eta_etd_type)
@staticmethod
def shipcall_put_update_evaluation_notifications_sent_flag(shipcall):
# change the 'evaluation_notifications_sent' flag to 1
evaluation_notifications_sent = 1
schemaModel = {"id":shipcall.id, "evaluation_notifications_sent":evaluation_notifications_sent}
query = SQLQuery.get_shipcall_put(schemaModel)
schemas = execute_sql_query_standalone(query=query, param=schemaModel, command_type="execute")
return
@staticmethod
def build_email_targets_validation_notification(users)->list[str]:
# readout the email address of all users
email_tgts = [user.user_email for user in users if user.user_email is not None]
# additionally, always inform the BSMD
email_tgts.append("bremencalling@bsmd.de") # #TODO: for testing, use "bremencalling@bsmd.de". For live system, use "report@bsmd.de"
# #TODO_development: overwrite the recipients. Only send to 'bremencalling@bsmd.de' until the testing phase has succeeded.
email_tgts = ["bremencalling@bsmd.de" for tgt in email_tgts]
# avoid multi-mails, when (for some reason) multiple users share the same email address.
email_tgts = list(set(email_tgts))
return email_tgts
@staticmethod
def create_and_send_notification_mapper(notification_type:model.NotificationType, shipcall:model.Shipcall, eligible_users:list[model.User], email_handler:EmailHandler, mail_pwd:bytes, debug:bool=False):
# #TODO_refactor: instead create a method, which contains the 'distribution-logic' for all notification types
if int(notification_type)==int(model.NotificationType.email):
# create an Email and send it to each eligible_user.
# #TODO: this method must be a distributor. It should send emails for those, who want emails, and provide placeholders for other types of notifications
Notifier.create_and_send_email_notification(email_handler, mail_pwd, eligible_users, shipcall, debug=debug)
elif int(notification_type)==int(model.NotificationType.undefined): pass
elif int(notification_type)==int(model.NotificationType.push): pass
else: pass
return
@staticmethod
def create_and_send_email_notification(email_handler:EmailHandler, pwd:bytes, users:list[model.User], shipcall:model.Shipcall, debug:bool=False):
"""
# #TODO_rename: when there is more than one type of notification, this should be renamed. This method refers to a validation-state notification
this 'naive' method creates a message and simply sends it to all users in a list of users.
Afterwards, the database will be updated, so the shipcall no longer requires a notification.
"""
# get a list of all recipients
email_tgts = Notifier.build_email_targets_validation_notification(users)
# prepare and build the Email content
content = get_default_html_email()
files = [] # optional attachments
ship_name, evaluation_message, eta_etd, eta_etd_type = Notifier.prepare_notification_body(shipcall)
msg_multipart,msg_content = create_shipcall_evaluation_notification(
email_handler, ship_name, evaluation_message, eta_etd, eta_etd_type, content, files=files
)
# send the messages via smtlib's SSL functions
send_notification(email_handler, email_tgts, msg_multipart, pwd, debug=debug)
return
@staticmethod
def check_user_is_subscribed_to_notification_type(user,notification_type):
"""given a notification, one can check, whether the current user has subscribed to the respective notification_type. Returns a boolean"""
if int(notification_type) == int(model.NotificationType.email):
return user.notify_email
elif int(notification_type) == int(model.NotificationType.push):
return user.notify_popup
elif int(notification_type) == int(model.NotificationType.undefined):
pass
### placeholders:
#elif int(notification_type) == int(model.NotificationType.whatsapp):
#return user.notify_whatsapp
#elif int(notification_type) == int(model.NotificationType.signal):
#return user.notify_signal
else: # placeholder: whatsapp/signal
raise NotImplementedError(notification_type)
@staticmethod
def get_all_notification_types():
from BreCal.schemas import model
return list(model.NotificationType._member_map_.values())
"""# build the list of evaluation times ('now', as isoformat)"""
#evaluation_times = [datetime.datetime.now().isoformat() for _i in range(len(evaluation_states_new))]

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,159 @@
<!doctype html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Simple Transactional Email</title>
<style media="all" type="text/css">
@media all {
.btn-primary table td:hover {
background-color: #ec0867 !important;
}
.btn-primary a:hover {
background-color: #ec0867 !important;
border-color: #ec0867 !important;
}
}
@media only screen and (max-width: 640px) {
.main p,
.main td,
.main span {
font-size: 16px !important;
}
.wrapper {
padding: 8px !important;
}
.content {
padding: 0 !important;
}
.container {
padding: 0 !important;
padding-top: 8px !important;
width: 100% !important;
}
.main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
.btn table {
max-width: 100% !important;
width: 100% !important;
}
.btn a {
font-size: 16px !important;
max-width: 100% !important;
width: 100% !important;
}
}
@media all {
.ExternalClass {
width: 100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
#MessageViewBody a {
color: inherit;
text-decoration: none;
font-size: inherit;
font-family: inherit;
font-weight: inherit;
line-height: inherit;
}
}
</style>
</head>
<body style="font-family: Helvetica, sans-serif; -webkit-font-smoothing: antialiased; font-size: 16px; line-height: 1.3; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; background-color: #f4f5f6; margin: 0; padding: 0;">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #f4f5f6; width: 100%;" width="100%" bgcolor="#f4f5f6">
<tr>
<td style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top;" valign="top">&nbsp;</td>
<td class="container" style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top; max-width: 600px; padding: 0; padding-top: 24px; width: 600px; margin: 0 auto;" width="600" valign="top">
<div class="content" style="box-sizing: border-box; display: block; margin: 0 auto; max-width: 600px; padding: 0;">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Ein Schiffsanlauf benötigt Ihre Aufmerksamkeit.</span>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background: #ffffff; border: 1px solid #eaebed; border-radius: 16px; width: 100%;" width="100%">
<!-- START MAIN CONTENT AREA -->
<div style="text-align: center;">
<img src="cid:LogoBremenCalling" height="100" width="100" alt="Bild kann nicht geladen werden." border="0" align="center">
</div>
<tr>
<td class="wrapper" style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top; box-sizing: border-box; padding: 24px;" valign="top">
<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">Ahoi,</p>
<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">ein Schiffsanlauf benötigt Ihre Aufmerksamkeit. Bei der Prüfung der Daten haben wir wahrgenommen, dass ein Problem aufgetreten sein könnte.</p>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; box-sizing: border-box; width: 100%; min-width: 100%;" width="100%">
<tbody>
<tr>
<td align="left" style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top; padding-bottom: 16px;" valign="top">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
<tbody>
<tr>
<td style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top; border-radius: 4px; text-align: center; background-color: #0867ec;" valign="top" align="center" bgcolor="#0867ec"> <a href="https://bsmd.de/" target="_blank" style="border: solid 2px #0867ec; border-radius: 4px; box-sizing: border-box; cursor: pointer; display: inline-block; font-size: 16px; font-weight: bold; margin: 0; padding: 12px 24px; text-decoration: none; text-transform: capitalize; background-color: #0867ec; border-color: #0867ec; color: #ffffff;">Zu Bremen Calling</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">#ADAPTIVECONTENT</p>
<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;"><br>Falls es sich hierbei um eine Fehlmeldung handelt, Verzeihung. Wir sind stets interessiert daran, die Software zu verbessern. Senden Sie uns gerne eine <a href="mailto:bsmd@bsmd.de">Email</a>.</p>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer" style="clear: both; padding-top: 24px; text-align: center; width: 100%;">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr>
<td class="content-block" style="font-family: Helvetica, sans-serif; vertical-align: top; color: #9a9ea6; font-size: 12px; text-align: center;" valign="top" align="center">
<span class="apple-link" style="color: #9a9ea6; font-size: 12px; text-align: center;">Bremer Schiffsmeldedienst, Kapt. P. Langbein e.K., Hafenkopf II / Überseetor 20, 28217 Bremen / Germany</span>
<br> Sie möchten keine Benachrichtigungen mehr erhalten? <a href="mailto:bsmd@bsmd.de" style="text-decoration: underline; color: #9a9ea6; font-size: 12px; text-align: center;">Hier abmelden</a>.
</td>
</tr>
<tr>
<td class="content-block powered-by" style="font-family: Helvetica, sans-serif; vertical-align: top; color: #9a9ea6; font-size: 12px; text-align: center;" valign="top" align="center">
Mail-Design by <a href="http://htmlemail.io" style="color: #9a9ea6; font-size:12px; text-align: center; text-decoration: none;">HTMLemail.io</a>
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER --></div>
</td>
<td style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top;" valign="top">&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -1,6 +1,8 @@
import os import os
import typing import typing
import datetime
import smtplib import smtplib
from socket import gaierror
from getpass import getpass from getpass import getpass
from email.message import EmailMessage from email.message import EmailMessage
import mimetypes import mimetypes
@ -42,7 +44,10 @@ class EmailHandler():
self.mail_port = mail_port self.mail_port = mail_port
self.mail_address = mail_address self.mail_address = mail_address
self.server = smtplib.SMTP_SSL(self.mail_server, self.mail_port) # alternatively, use smtplib.SMTP try:
self.server = smtplib.SMTP_SSL(self.mail_server, self.mail_port) # alternatively, use smtplib.SMTP
except gaierror:
raise Exception(f"'socket.gaierror' raised. This commonly happens, when there is no access to the server (e.g., by not having an internet connection)")
def check_state(self): def check_state(self):
"""check, whether the server login took place and is open.""" """check, whether the server login took place and is open."""
@ -226,4 +231,118 @@ class EmailHandler():
time.sleep(delete_after_s_seconds) time.sleep(delete_after_s_seconds)
if os.path.exists(temp_filename): if os.path.exists(temp_filename):
os.remove(temp_filename) os.remove(temp_filename)
return return
import typing
from email.mime.application import MIMEApplication
import mimetypes
import os
def find_warning_notification_email_template()->str:
"""
dynamically finds the 'default_email_template.txt' file within the module.
"""
# __file__ is BreCal/stubs/email_template.py
# parent of email_template.py is stubs
# parent of stubs is BreCal
brecal_root_folder = os.path.dirname(os.path.dirname(__file__)) # .../BreCal
resource_root_folder = os.path.join(brecal_root_folder, "resources") # .../BreCal/resources
html_filepath = os.path.join(resource_root_folder,"warning_notification_email_template.txt") # .../BreCal/resources/warning_notification_email_template.txt
assert os.path.exists(html_filepath), f"could not find default email template file at path: {html_filepath}"
return html_filepath
def get_default_html_email()->str:
"""
dynamically finds the 'default_email_template.txt' file within the module. It opens the file and returns the content.
__file__ returns to the file, where this function is stored (e.g., within BreCal.stubs.email_template)
using the dirname refers to the directory, where __file__ is stored.
finally, the 'default_email_template.txt' is stored within that folder
"""
html_filepath = find_warning_notification_email_template()
with open(html_filepath,"r", encoding="utf-8") as file: # encoding = "utf-8" allows for German Umlaute
content = file.read()
return content
def find_bremen_calling_logo():
"""
find the path towards the logo file (located at 'brecal\src\BreCalClient\Resources\logo_bremen_calling.png')
"""
# __file__ is services/email_handling.py
# parent of __file__ is services
# parent of services is BreCal
src_root_folder = os.path.dirname(os.path.dirname(__file__)) # .../BreCal
resource_root_folder = os.path.join(src_root_folder, "resources")
path = os.path.join(resource_root_folder, "logo_bremen_calling.png")
assert os.path.exists(path), f"cannot find logo of bremen calling at path: {os.path.abspath(path)}"
return path
def add_bremen_calling_logo(msg_multipart):
"""
The image is not attached automatically when it is embedded to the content. To circumvent this,
one commonly creates attachments, which are referred to in the email content.
The content body refers to 'LogoBremenCalling', which the 'Content-ID' of the logo is assigned as.
"""
path = find_bremen_calling_logo()
with open(path, 'rb') as file:
attachment = MIMEApplication(file.read(), _subtype=mimetypes.MimeTypes().guess_type(path), Name="bremen_calling.png")
attachment.add_header('Content-Disposition','attachment',filename=str(os.path.basename(path)))
attachment.add_header('Content-ID', '<LogoBremenCalling>')
msg_multipart.attach(attachment)
return msg_multipart
def create_shipcall_evaluation_notification(email_handler, ship_name:str, evaluation_message:str, eta_etd_str:str, eta_etd_type:str, content:str, files:typing.Optional[list[str]]):
"""
email_handler : EmailHandler. Contains meta-level information about the mail server and sender's Email.
ship_name : str. Name of the referenced ship, so the user knows the context.
evaluation_message : str. Brief description of the current evaluation state
eta_etd_str : str. Readable format of a datetime.datetime object, which is either ETA, ETD or both. Informs the user about when the shipcall is due.
eta_etd_type : str. Reference to the time, whether it arrives/leaves/shifts.
content : str (or filepath). Should refer to the template, which defines the content. This file contains HTML-structured text.
files: (optional). List of file paths, which are included as attachments.
"""
subject = f"{ship_name} (vorauss. {eta_etd_type}: {eta_etd_str})"
# create message_body
message_body = content # "Hello World."
evaluation_message_reformatted = evaluation_message.replace("\n", "<br>")
adaptive_content = f'<br>Betrifft: {ship_name} ({eta_etd_str})<font size="1"><br>{evaluation_message_reformatted}</font>'
message_body = message_body.replace("#ADAPTIVECONTENT", adaptive_content)
msg = email_handler.create_email(subject=subject, message_body=message_body, subtype="html")
msg_multipart = email_handler.translate_mail_to_multipart(msg=msg)
if files is not None:
for path in files:
assert os.path.exists(path), f"cannot find attachment at path: {path}"
email_handler.attach_file(path, msg=msg_multipart)
# add the bremen calling logo, which is referred to in the email body
msg_multipart = add_bremen_calling_logo(msg_multipart)
return (msg_multipart,content)
def send_notification(email_handler, email_tgts, msg, pwd, debug=False):
already_logged_in = email_handler.check_login()
if not already_logged_in:
email_handler.login(interactive=False, pwd=pwd)
try:
assert email_handler.check_login()
if not debug:
email_handler.send_email(msg, email_tgts)
else:
print(f"(send_notification INFO): debugging state. Would have sent an Email to: {email_tgts}")
finally:
if not already_logged_in:
email_handler.close()
return

View File

@ -51,7 +51,7 @@ def add_function_to_schedule__update_shipcalls(interval_in_minutes:int, options:
schedule.every(interval_in_minutes).minutes.do(UpdateShipcalls, **kwargs_) schedule.every(interval_in_minutes).minutes.do(UpdateShipcalls, **kwargs_)
return return
def add_function_to_schedule__send_notifications(vr, interval_in_minutes:int=10): def add_function_to_schedule__send_notifications(interval_in_minutes:int=15):
schedule.every(interval_in_minutes).minutes.do(Notifier.send_notifications) schedule.every(interval_in_minutes).minutes.do(Notifier.send_notifications)
return return
@ -65,8 +65,8 @@ def setup_schedule(update_shipcalls_interval_in_minutes:int=60):
# update the evaluation state in every recent shipcall # update the evaluation state in every recent shipcall
add_function_to_schedule__update_shipcalls(update_shipcalls_interval_in_minutes) add_function_to_schedule__update_shipcalls(update_shipcalls_interval_in_minutes)
# placeholder: create/send notifications # create/send notifications
# add_function_to_schedule__send_notifications(...) add_function_to_schedule__send_notifications(15)
return return

View File

@ -929,6 +929,9 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
query_time = times_agency.iloc[0].eta_berth query_time = times_agency.iloc[0].eta_berth
# count the number of times, where a times entry is very close to the query time (uses an internal threshold, such as 15 minutes) # count the number of times, where a times entry is very close to the query time (uses an internal threshold, such as 15 minutes)
if all_times_agency is None:
all_times_agency = self.sql_handler.get_times_for_agency(non_null_column="eta_berth")
counts = self.sql_handler.count_synchronous_shipcall_times(query_time, all_df_times=all_times_agency) counts = self.sql_handler.count_synchronous_shipcall_times(query_time, all_df_times=all_times_agency)
violation_state = counts > maximum_threshold violation_state = counts > maximum_threshold
@ -952,6 +955,9 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value] times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
query_time = times_agency.iloc[0].etd_berth query_time = times_agency.iloc[0].etd_berth
if all_times_agency is None:
all_times_agency = self.sql_handler.get_times_for_agency(non_null_column="etd_berth")
# count the number of times, where a times entry is very close to the query time (uses an internal threshold, such as 15 minutes) # count the number of times, where a times entry is very close to the query time (uses an internal threshold, such as 15 minutes)
counts = self.sql_handler.count_synchronous_shipcall_times(query_time, all_df_times=all_times_agency) counts = self.sql_handler.count_synchronous_shipcall_times(query_time, all_df_times=all_times_agency)
violation_state = counts > maximum_threshold violation_state = counts > maximum_threshold

View File

@ -1,3 +1,4 @@
import typing
import copy import copy
import logging import logging
import re import re
@ -74,6 +75,9 @@ class ValidationRules(ValidationRuleFunctions):
"""apply 'evaluate_shipcall_from_df' to each individual shipcall in {shipcall_df}. Returns shipcall_df ('evaluation', 'evaluation_message', 'evaluation_time' and 'evaluation_notifications_sent' are updated)""" """apply 'evaluate_shipcall_from_df' to each individual shipcall in {shipcall_df}. Returns shipcall_df ('evaluation', 'evaluation_message', 'evaluation_time' and 'evaluation_notifications_sent' are updated)"""
evaluation_states_old = [state_old for state_old in shipcall_df.loc[:,"evaluation"]] evaluation_states_old = [state_old for state_old in shipcall_df.loc[:,"evaluation"]]
evaluation_states_old = [state_old if not pd.isna(state_old) else 0 for state_old in evaluation_states_old] evaluation_states_old = [state_old if not pd.isna(state_old) else 0 for state_old in evaluation_states_old]
evaluation_notifications_sent_old = [ens for ens in shipcall_df.loc[:,"evaluation_notifications_sent"]]
results = shipcall_df.apply(lambda x: self.evaluate_shipcall_from_df(x), axis=1).values # returns tuple (state, message) results = shipcall_df.apply(lambda x: self.evaluate_shipcall_from_df(x), axis=1).values # returns tuple (state, message)
# unbundle individual results. evaluation_states becomes an integer, violation # unbundle individual results. evaluation_states becomes an integer, violation
@ -82,6 +86,15 @@ class ValidationRules(ValidationRuleFunctions):
violations = [self.concise_evaluation_message_if_too_long(violation) for violation in violations] violations = [self.concise_evaluation_message_if_too_long(violation) for violation in violations]
# build the list of evaluation times ('now', as isoformat) # build the list of evaluation times ('now', as isoformat)
evaluation_time = self.get_notification_times(evaluation_states_new)
# build the list of 'evaluation_notifications_sent'. The value is 'False', when a notification should be created
evaluation_notifications_sent = self.get_notification_states(evaluation_states_old, evaluation_states_new, evaluation_notifications_sent_old)
shipcall_df.loc[:,"evaluation"] = evaluation_states_new
shipcall_df.loc[:,"evaluation_message"] = violations
shipcall_df.loc[:,"evaluation_time"] = evaluation_time
shipcall_df.loc[:,"evaluation_notifications_sent"] = evaluation_notifications_sent
#evaluation_time = self.get_notification_times(evaluation_states_new) #evaluation_time = self.get_notification_times(evaluation_states_new)
# build the list of 'evaluation_notifications_sent'. The value is 'False', when a notification should be created # build the list of 'evaluation_notifications_sent'. The value is 'False', when a notification should be created
@ -112,16 +125,24 @@ class ValidationRules(ValidationRuleFunctions):
"""this function should apply the ValidationRules to the respective .shipcall, in regards to .times""" """this function should apply the ValidationRules to the respective .shipcall, in regards to .times"""
return (StatusFlags.GREEN, False) # (state:str, should_notify:bool) return (StatusFlags.GREEN, False) # (state:str, should_notify:bool)
def determine_notification_state(self, state_old, state_new): def determine_notification_state(self, state_old, state_new, evaluation_notifications_sent)->typing.Optional[bool]:
""" """
this method determines state changes in the notification state. When the state increases, a user is notified about it. this method determines state changes in the notification state. When the state increases, a user is notified about it.
state order: (NONE = GREEN < YELLOW < RED) state order: (NONE = GREEN < YELLOW < RED)
If a notification shall be sent, this method returns False. If no notification shall be sent, this method returns None or the prior state.
The method *never* returns True, as it shall only be called on novel shipcalls.
args:
evaluation_notifications_sent: the PREVIOUS state (if any) of this boolean. When no notification is required, the prior bool is used (e.g., None, False, True).
""" """
previous_state = evaluation_notifications_sent
# identify a state increase # identify a state increase
should_notify = self.identify_notification_state_change(state_old=state_old, state_new=state_new) should_notify = self.identify_notification_state_change(state_old=state_old, state_new=state_new)
# when a state increases, a notification must be sent. Thereby, the field should be set to False ({evaluation_notifications_sent}) # when a state increases, a notification must be sent. Thereby, the field should be set to False ({evaluation_notifications_sent})
evaluation_notifications_sent = False if bool(should_notify) else None evaluation_notifications_sent = False if bool(should_notify) else previous_state
return evaluation_notifications_sent return evaluation_notifications_sent
def identify_notification_state_change(self, state_old, state_new) -> bool: def identify_notification_state_change(self, state_old, state_new) -> bool:
@ -147,13 +168,13 @@ class ValidationRules(ValidationRuleFunctions):
return int(state_new) > int(state_old) return int(state_new) > int(state_old)
def get_notification_times(self, evaluation_states_new)->list[datetime.datetime]: def get_notification_times(self, evaluation_states_new)->list[datetime.datetime]:
"""# build the list of evaluation times ('now', as isoformat)""" """# build the list of evaluation times ('now'-datetime)"""
evaluation_times = [datetime.datetime.now().isoformat() for _i in range(len(evaluation_states_new))] evaluation_times = [datetime.datetime.now() for _i in range(len(evaluation_states_new))] # .isoformat()
return evaluation_times return evaluation_times
def get_notification_states(self, evaluation_states_old, evaluation_states_new)->list[bool]: def get_notification_states(self, evaluation_states_old, evaluation_states_new, evaluation_notifications_sent_old)->list[typing.Optional[bool]]:
"""# build the list of 'evaluation_notifications_sent'. The value is 'False', when a notification should be created""" """# build the list of 'evaluation_notifications_sent'. The value is 'False', when a notification should be created and None, when not"""
evaluation_notifications_sent = [self.determine_notification_state(state_old=int(state_old), state_new=int(state_new)) for state_old, state_new in zip(evaluation_states_old, evaluation_states_new)] evaluation_notifications_sent = [self.determine_notification_state(state_old=int(state_old), state_new=int(state_new), evaluation_notifications_sent=ens) for state_old, state_new, ens in zip(evaluation_states_old, evaluation_states_new, evaluation_notifications_sent_old)]
return evaluation_notifications_sent return evaluation_notifications_sent

View File

@ -2,7 +2,7 @@ from setuptools import find_packages, setup
setup( setup(
name='BreCal', name='BreCal',
version='1.3.0', version='1.4.0',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
zip_safe=False, zip_safe=False,

View File

@ -0,0 +1,21 @@
import pytest
from BreCal.notifications.accounts import mail_server, mail_port, mail_address, mail_pwd
def test_mail_server():
assert isinstance(mail_server, str)
assert not "@" in mail_server
return
def test_mail_port():
assert isinstance(mail_port, int)
return
def test_mail_address():
assert isinstance(mail_address, str)
assert "@" in mail_address
return
def test_mail_pwd():
assert isinstance(mail_pwd, bytes), f"must be a bytes-encoded password to protect the account"
return

View File

View File

@ -0,0 +1,14 @@
import pytest
import os
def test_find_bremen_calling_logo():
from BreCal.services.email_handling import find_bremen_calling_logo
path = find_bremen_calling_logo()
assert os.path.exists(path), f"cannot find the bremen calling logo file, which is needed for notifications (e.g., Email). Searched at path: \n\t{path}"
return
def test_find_warning_notification_email_template():
from BreCal.services.email_handling import find_warning_notification_email_template
path = find_warning_notification_email_template()
assert os.path.exists(path), f"cannot find the required email template, which is needed for warning notifications. Searched at path: \n\t{path}"
return