diff --git a/.gitignore b/.gitignore
index bbd7a3a..83afcd3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,44 +1,3 @@
-
-Skip to content
-Pull requests
-Issues
-Codespaces
-Marketplace
-Explore
-@danielschick
-github /
-gitignore
-Public
-
-Fork your own copy of github/gitignore
-
-Code
-Pull requests 371
-Actions
-Security
-
- Insights
-
-Beta Try the new code view
-gitignore/VisualStudio.gitignore
-@n0099
-n0099 [VisualStudio.gitignore] remove a trailing space
-Latest commit 491040e Jan 26, 2022
-History
-165 contributors
-@shiftkey
-@arcresu
-@aroben
-@bbodenmiller
-@HassanHashemi
-@haacked
-@niik
-@AArnott
-@sayedihashimi
-@saschanaz
-@bdougie
-@OsirisTerje
-398 lines (319 sloc) 6.7 KB
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
@@ -75,40 +34,11 @@ bld/
# Visual Studio 2015/2017 cache/options directory
.vs/
-# Uncomment if you have tasks that create the project's static files in wwwroot
-#wwwroot/
-
-# Visual Studio 2017 auto generated files
-Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
-# NUnit
-*.VisualState.xml
-TestResult.xml
-nunit-*.xml
-
-# Build Results of an ATL Project
-[Dd]ebugPS/
-[Rr]eleasePS/
-dlldata.c
-
-# Benchmark Results
-BenchmarkDotNet.Artifacts/
-
-# .NET Core
-project.lock.json
-project.fragment.lock.json
-artifacts/
-
-# ASP.NET Scaffolding
-ScaffoldingReadMe.txt
-
-# StyleCop
-StyleCopReport.xml
-
# Files built by Visual Studio
*_i.c
*_p.c
@@ -139,20 +69,6 @@ StyleCopReport.xml
*.svclog
*.scc
-# Chutzpah Test files
-_Chutzpah*
-
-# Visual C++ cache files
-ipch/
-*.aps
-*.ncb
-*.opendb
-*.opensdf
-*.sdf
-*.cachefile
-*.VC.db
-*.VC.VC.opendb
-
# Visual Studio profiler
*.psess
*.vsp
@@ -162,108 +78,9 @@ ipch/
# Visual Studio Trace Files
*.e2e
-# TFS 2012 Local Workspace
-$tf/
-
-# Guidance Automation Toolkit
-*.gpState
-
-# ReSharper is a .NET coding add-in
-_ReSharper*/
-*.[Rr]e[Ss]harper
-*.DotSettings.user
-
-# TeamCity is a build add-in
-_TeamCity*
-
-# DotCover is a Code Coverage Tool
-*.dotCover
-
-# AxoCover is a Code Coverage Tool
-.axoCover/*
-!.axoCover/settings.json
-
-# Coverlet is a free, cross platform Code Coverage Tool
-coverage*.json
-coverage*.xml
-coverage*.info
-
-# Visual Studio code coverage results
-*.coverage
-*.coveragexml
-
-# NCrunch
-_NCrunch_*
-.*crunch*.local.xml
-nCrunchTemp_*
-
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
-.sass-cache/
-
-# Installshield output folder
-[Ee]xpress/
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
# Click-Once directory
publish/
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-# Note: Comment the next line if you want to checkin your web deploy settings,
-# but database connection strings (with potential passwords) will be unencrypted
-# *.pubxml
-*.publishproj
-
-# Microsoft Azure Web App publish settings. Comment the next line if you want to
-# checkin your Azure Web App publish settings, but sensitive information contained
-# in these scripts will be unencrypted
-PublishScripts/
-
-# NuGet Packages
-*.nupkg
-# NuGet Symbol Packages
-*.snupkg
-# The packages folder can be ignored because of Package Restore
-**/[Pp]ackages/*
-# except build/, which is used as an MSBuild target.
-!**/[Pp]ackages/build/
-# Uncomment if necessary however generally it will be regenerated when needed
-#!**/[Pp]ackages/repositories.config
-# NuGet v3's project.json files produces more ignorable files
-*.nuget.props
-*.nuget.targets
-
-# Microsoft Azure Build Output
-csx/
-*.build.csdef
-
-# Microsoft Azure Emulator
-ecf/
-rcf/
-
-# Windows Store app package directories and files
-AppPackages/
-BundleArtifacts/
-Package.StoreAssociation.xml
-_pkginfo.txt
-*.appx
-*.appxbundle
-*.appxupload
-
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
@@ -279,158 +96,33 @@ ClientBin/
*.jfm
*.pfx
*.publishsettings
-orleans.codegen.cs
-
-# Including strong name files can present a security risk
-# (https://github.com/github/gitignore/pull/2483#issue-259490424)
-#*.snk
-
-# Since there are multiple workflows, uncomment next line to ignore bower_components
-# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
-#bower_components/
-
-# RIA/Silverlight projects
-Generated_Code/
-
-# Backup & report files from converting an old project file
-# to a newer Visual Studio version. Backup files are not needed,
-# because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-UpgradeLog*.htm
-ServiceFabricBackup/
-*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
-# Business Intelligence projects
-*.rdl.data
-*.bim.layout
-*.bim_*.settings
-*.rptproj.rsuser
-*- [Bb]ackup.rdl
-*- [Bb]ackup ([0-9]).rdl
-*- [Bb]ackup ([0-9][0-9]).rdl
-
-# Microsoft Fakes
-FakesAssemblies/
-
-# GhostDoc plugin setting file
-*.GhostDoc.xml
-
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
-# Visual Studio 6 build log
-*.plg
-
-# Visual Studio 6 workspace options file
-*.opt
-
-# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
-*.vbw
-
-# Visual Studio 6 auto-generated project file (contains which files were open etc.)
-*.vbp
-
-# Visual Studio 6 workspace and project file (working project files containing files to include in project)
-*.dsw
-*.dsp
-
-# Visual Studio 6 technical files
-*.ncb
-*.aps
-
-# Visual Studio LightSwitch build output
-**/*.HTMLClient/GeneratedArtifacts
-**/*.DesktopClient/GeneratedArtifacts
-**/*.DesktopClient/ModelManifest.xml
-**/*.Server/GeneratedArtifacts
-**/*.Server/ModelManifest.xml
-_Pvt_Extensions
-
-# Paket dependency manager
-.paket/paket.exe
-paket-files/
-
-# FAKE - F# Make
-.fake/
-
-# CodeRush personal settings
-.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
-# Cake - Uncomment if you are using it
-# tools/**
-# !tools/packages.config
-
-# Tabs Studio
-*.tss
-
-# Telerik's JustMock configuration file
-*.jmconfig
-
-# BizTalk build output
-*.btp.cs
-*.btm.cs
-*.odx.cs
-*.xsd.cs
-
-# OpenCover UI analysis results
-OpenCover/
-
-# Azure Stream Analytics local run output
-ASALocalRun/
-
-# MSBuild Binary and Structured Log
-*.binlog
-
-# NVidia Nsight GPU debugger configuration file
-*.nvuser
-
-# MFractors (Xamarin productivity tool) working folder
-.mfractor/
-
-# Local History for Visual Studio
-.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
-# BeatPulse healthcheck temp database
-healthchecksdb
-
-# Backup folder for Package Reference Convert tool in Visual Studio 2017
-MigrationBackup/
-
-# Ionide (cross platform F# VS Code tools) working folder
-.ionide/
-
-# Fody - auto-generated XML schema
-FodyWeavers.xsd
-
# VS Code files for those working on multiple tools
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
-*.code-workspace
+.vscode/
+
+#!.vscode/settings.json
+#!.vscode/tasks.json
+#!.vscode/launch.json
+#!.vscode/extensions.json
+#*.code-workspace
# Local History for Visual Studio Code
.history/
-
-# Windows Installer files from build outputs
-*.cab
-*.msi
-*.msix
-*.msm
-*.msp
diff --git a/misc/BreCalApi.cs b/misc/BreCalApi.cs
index c816626..e48e0b2 100644
--- a/misc/BreCalApi.cs
+++ b/misc/BreCalApi.cs
@@ -1,7 +1,7 @@
//----------------------
//
-// Generated REST API Client Code Generator v1.8.4.0 on 23.10.2023 09:06:40
+// Generated REST API Client Code Generator v1.8.4.0 on 01.11.2023 15:27:09
// Using the tool OpenAPI Generator v7.0.0
//
//----------------------
diff --git a/misc/Deployment.md b/misc/Deployment.md
index 21ee272..38a7ccd 100644
--- a/misc/Deployment.md
+++ b/misc/Deployment.md
@@ -6,6 +6,14 @@ ___
## Client
+Deployment of the productive client:
+- create a branch release/pub_ from test release branch
+- remove all text references to 'test' (changing target url in the process)
+- rename application in settings
+- change BG_COLOR in settings to #203864
+- user deployment publish xml
+
+
## Database
## Backend / Flask app
@@ -14,7 +22,7 @@ In order to not have complicated and error-prone copying manoevers a direct depl
### File structure
-### Steps
+### Installation steps
1) Created a ssh-key for the user that does the installation on the server following the Github [instructions](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent).
2) Deploy generated key to the Github user account.
@@ -27,6 +35,8 @@ ssh-add ~/.ssh/od_ed25519
4) Change to deployment folder
+e.g.
+
```bash
cd /var/www/brecal_test
```
@@ -43,6 +53,12 @@ git checkout
6) Database credentials are stored outside the web root, we are using /var/www/secure. Here the file ```connection_data.json``` is placed, a different named copy for each instance.
+### Changing Devel / Test / Prod Environment
+
+Please note that in the "develop" branch the environment and paths are set to the "devel" setting. If a deployment is made (to testing or to the production) the scripts ```copytest.sh``` and ```copyprod.sh``` have to be run. These scripts will change the environment and paths to the respective settings.
+
+There is also a script called ```bump-version.sh``` which can be used to upgrade all version entries in the repository.
+
### Installing Requirements
Python 3.11 & Pip3.11 installation (linux), virtualenv package
diff --git a/misc/bump-version.sh b/misc/bump-version.sh
new file mode 100644
index 0000000..f9105c1
--- /dev/null
+++ b/misc/bump-version.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+CURRENT_VERSION=$(cat version.txt)
+NEW_VERSION="${1}"
+
+if [ -z "${NEW_VERSION}" ]; then
+ echo "No new version given"
+ exit 1
+fi
+
+echo "Bumping version ${CURRENT_VERSION} to ${NEW_VERSION}"
+
+CURRENT_VERSION=$(printf '%s\n' "$CURRENT_VERSION" | sed -e 's/[\/&]/\\&/g')
+NEW_VERSION=$(printf '%s\n' "$NEW_VERSION" | sed -e 's/[\/&]/\\&/g')
+
+echo "Found the following matching version strings:"
+git grep -I "${CURRENT_VERSION}"
+
+echo "Proceed? [N/y]"
+read proceed
+
+if [ "${proceed}" = "y" ]; then
+ git grep -Il "${CURRENT_VERSION}" | xargs sed --in-place -e "s/${CURRENT_VERSION}/${NEW_VERSION}/g"
+ git add $(git grep -Il "${NEW_VERSION}")
+fi
diff --git a/misc/copyprod.sh b/misc/copyprod.sh
new file mode 100644
index 0000000..2083bce
--- /dev/null
+++ b/misc/copyprod.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+# This script replaces all references to the development version with the test version
+# 1) Database references and paths
+
+git grep -I "connection_data_test.json" -- '..' ':(exclude)*.sh'
+git grep -I "/var/www/brecal_test/" -- '..' ':(exclude)*.sh'
+
+# 2) Color references
+# Bar colors in client: (BG_COLOR)
+# Devel: #1D751F
+# Test: #751D1F
+# Prod: #203864
+
+git grep -I "#751D1F" -- '..' ':(exclude)*.sh'
+
+# 3) Assembly name references
+
+git grep -I "BreCalTestClient" -- '..' ':(exclude)*.sh'
+
+echo "Proceed? [N/y]"
+read proceed
+
+# for color
+
+if [ "${proceed}" = "y" ]; then
+
+ # 1. for database references and paths
+ git grep -I "connection_data_test.json" -- '..' ':(exclude)*.sh' | xargs sed --in-place -e "s/connection_data_test.json/connection_data_prod.json/g"
+ git add $(git grep -Il "connection_data_prod.json" -- '..' ':(exclude)*.sh')
+ git grep -I "/var/www/brecal_test/" -- '..' ':(exclude)*.sh' | xargs sed --in-place -e "s/\/var\/www\/brecal_test\//\/var\/www\/brecal\//g"
+ git add $(git grep -Il "/var/www/brecal/" -- '..' ':(exclude)*.sh')
+
+ # 2. for color
+ git grep -Il "#751D1F" -- '..' ':(exclude)*.sh' | xargs sed --in-place -e "s/#751D1F/#203864/g"
+ git add $(git grep -Il "#203864" -- '..' ':(exclude)*.sh')
+
+ # 3. for assembly name
+
+ git grep -I "BreCalTestClient" -- '..' ':(exclude)*.sh' | xargs sed --in-place -e "s/BreCalTestClient/BreCalClient/g"
+ git add $(git grep -Il "BreCalClient" -- '..' ':(exclude)*.sh')
+
+fi
diff --git a/misc/copytest.sh b/misc/copytest.sh
new file mode 100644
index 0000000..d7bbd87
--- /dev/null
+++ b/misc/copytest.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+# This script replaces all references to the development version with the test version
+# 1) Database references and paths
+
+git grep -I "connection_data_devel.json" -- '..' ':(exclude)*.sh'
+git grep -I "/var/www/brecal_devel/" -- '..' ':(exclude)*.sh'
+
+# 2) Color references
+# Bar colors in client: (BG_COLOR)
+# Devel: #1D751F
+# Test: #751D1F
+# Prod: #203864
+
+git grep -I "#1D751F" -- '..' ':(exclude)*.sh'
+
+# 3) Assembly name references
+
+git grep -I "BreCalDevelClient" -- '..' ':(exclude)*.sh'
+
+echo "Proceed? [N/y]"
+read proceed
+
+# for color
+
+if [ "${proceed}" = "y" ]; then
+
+ # 1. for database references and paths
+ git grep -Il "connection_data_devel.json" -- '..' ':(exclude)*.sh' | xargs sed --in-place -e "s/connection_data_devel.json/connection_data_test.json/g"
+ git add $(git grep -Il "connection_data_test.json" -- '..' ':(exclude)*.sh')
+ git grep -Il "/var/www/brecal_devel/" -- '..' ':(exclude)*.sh' | xargs sed --in-place -e "s/\/var\/www\/brecal_devel\//\/var\/www\/brecal_test\//g"
+ git add $(git grep -Il "/var/www/brecal_test/" -- '..' ':(exclude)*.sh')
+
+ # 2. for color
+ git grep -Il "#1D751F" -- '..' ':(exclude)*.sh' | xargs sed --in-place -e "s/#1D751F/#751D1F/g"
+ git add $(git grep -Il "#751D1F" -- '..' ':(exclude)*.sh')
+
+ # 3. for assembly name
+
+ git grep -Il "BreCalDevelClient" -- '..' ':(exclude)*.sh' | xargs sed --in-place -e "s/BreCalDevelClient/BreCalTestClient/g"
+ git add $(git grep -Il "BreCalTestClient" -- '..' ':(exclude)*.sh')
+
+fi
diff --git a/misc/sample_static_data.sql b/misc/sample_static_data.sql
new file mode 100644
index 0000000..5cacc46
--- /dev/null
+++ b/misc/sample_static_data.sql
@@ -0,0 +1,192 @@
+-- --------------------------------------------------------
+-- Host: 127.0.0.1
+-- Server Version: 8.0.34-0ubuntu0.22.04.1 - (Ubuntu)
+-- Server Betriebssystem: Linux
+-- HeidiSQL Version: 10.2.0.5599
+-- --------------------------------------------------------
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET NAMES utf8 */;
+/*!50503 SET NAMES utf8mb4 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+
+-- Exportiere Daten aus Tabelle bremen_calling_test.berth: ~59 rows (ungefähr)
+/*!40000 ALTER TABLE `berth` DISABLE KEYS */;
+INSERT INTO `berth` (`id`, `name`, `lock`, `owner_id`, `authority_id`, `created`, `modified`, `deleted`) VALUES
+ (1, 'Roland Mühle', NULL, NULL, 11, '2023-06-26 14:01:40', '2023-10-06 15:04:08', b'1'),
+ (2, 'Stahlwerk', NULL, NULL, 11, '2023-06-26 14:01:40', '2023-10-06 15:04:08', b'1'),
+ (3, 'Kellogs', NULL, NULL, 11, '2023-06-26 14:01:40', '2023-10-06 15:04:08', b'1'),
+ (139, 'Avangard Dalben', NULL, 110, 136, '2023-08-21 08:23:35', '2023-10-06 16:06:01', b'0'),
+ (140, 'Avangard Kaje', NULL, 110, 11, '2023-08-21 08:23:35', '2023-10-06 15:04:08', b'0'),
+ (141, 'Baustelle 2', NULL, 111, 11, '2023-08-21 08:23:35', '2023-10-06 15:04:08', b'0'),
+ (142, 'BHW', NULL, 112, 11, '2023-08-21 08:23:36', '2023-10-06 15:04:08', b'0'),
+ (143, 'Dalben 2', NULL, 111, 11, '2023-08-21 08:23:36', '2023-10-06 15:04:08', b'0'),
+ (144, 'Dalben 3', NULL, 111, 11, '2023-08-21 08:23:36', '2023-10-06 15:04:08', b'0'),
+ (145, 'Egerland Kaje', NULL, 113, 11, '2023-08-21 08:23:36', '2023-10-06 15:04:08', b'0'),
+ (146, 'Getreideanlage Pier A', NULL, 114, 11, '2023-08-21 08:23:37', '2023-10-06 15:04:08', b'0'),
+ (147, 'Getreideanlage Pier D', NULL, 114, 11, '2023-08-21 08:23:37', '2023-10-06 15:04:08', b'0'),
+ (148, 'Griepe, Bnp Paribas', NULL, 115, 11, '2023-08-21 08:23:37', '2023-10-06 15:04:08', b'0'),
+ (149, 'Hafen F', NULL, 116, 11, '2023-08-21 08:23:37', '2023-10-06 15:04:08', b'0'),
+ (150, 'Hansa Landhandel', NULL, 117, 11, '2023-08-21 08:23:38', '2023-10-06 15:04:08', b'0'),
+ (151, 'Hansa Melasse', NULL, 118, 11, '2023-08-21 08:23:38', '2023-10-06 15:04:08', b'0'),
+ (152, 'Hansa-Mühle', NULL, 119, 11, '2023-08-21 08:23:38', '2023-10-06 15:04:08', b'0'),
+ (153, 'Heidelberger Sand', NULL, 120, 11, '2023-08-21 08:23:38', '2023-10-06 15:04:08', b'0'),
+ (154, 'HGM Bunkerstation', NULL, 121, 11, '2023-08-21 08:23:39', '2023-10-06 15:04:08', b'0'),
+ (155, 'HGM Tanklager', NULL, 121, 11, '2023-08-21 08:23:39', '2023-10-06 15:04:08', b'0'),
+ (156, 'Kap Horn Innen', NULL, 122, 11, '2023-08-21 08:23:39', '2023-10-06 15:04:08', b'0'),
+ (157, 'Kap Horn Weser', NULL, 122, 11, '2023-08-21 08:23:39', '2023-10-06 15:04:08', b'0'),
+ (158, 'Kap Horn Weser Bremer Recycling', NULL, 123, 11, '2023-08-21 08:23:40', '2023-10-06 15:04:08', b'0'),
+ (159, 'Kap Horn Weser -GHK-', NULL, 124, 11, '2023-08-21 08:23:40', '2023-10-06 15:04:08', b'0'),
+ (160, 'Kohlenhafen 2', NULL, 111, 11, '2023-08-21 08:23:40', '2023-10-06 15:04:08', b'0'),
+ (161, 'Kraftwerk Farge', NULL, 125, 11, '2023-08-21 08:23:40', '2023-10-06 15:04:08', b'0'),
+ (162, 'Kraftwerk Industriehafen', NULL, 126, 11, '2023-08-21 08:23:41', '2023-10-06 15:04:08', b'0'),
+ (163, 'Lankenau B', NULL, 111, 11, '2023-08-21 08:23:41', '2023-10-06 15:04:08', b'0'),
+ (164, 'Mibau, Bnp Paribas', NULL, 127, 11, '2023-08-21 08:23:41', '2023-10-06 15:04:08', b'0'),
+ (165, 'Müller Weser', NULL, 114, 11, '2023-08-21 08:23:41', '2023-10-06 15:04:08', b'0'),
+ (166, 'Osterort 5 Aussen', NULL, 111, 11, '2023-08-21 08:23:41', '2023-10-06 15:04:08', b'0'),
+ (167, 'Pier 2 Anleger', NULL, 128, 11, '2023-08-21 08:23:42', '2023-10-06 15:04:08', b'0'),
+ (168, 'Pier III', NULL, 129, 11, '2023-08-21 08:23:42', '2023-10-06 15:04:08', b'0'),
+ (169, 'Plump', NULL, 130, 11, '2023-08-21 08:23:42', '2023-10-06 15:04:08', b'0'),
+ (170, 'Rolandmühle', NULL, 131, 11, '2023-08-21 08:23:42', '2023-10-06 15:04:08', b'0'),
+ (171, 'Schleusenvorhafen Nord', NULL, 111, 11, '2023-08-21 08:23:43', '2023-10-06 15:04:08', b'0'),
+ (172, 'Schrägpier', NULL, 4, 11, '2023-08-21 08:23:43', '2023-10-06 15:04:08', b'0'),
+ (173, 'Schuppen 19', NULL, 132, 11, '2023-08-21 08:23:43', '2023-10-06 15:04:08', b'0'),
+ (174, 'Schuppen 20', NULL, 4, 11, '2023-08-21 08:23:43', '2023-10-06 15:04:08', b'0'),
+ (175, 'Schuppen 21', NULL, 4, 11, '2023-08-21 08:23:43', '2023-10-06 15:04:08', b'0'),
+ (176, 'Schuppen 22', NULL, 4, 11, '2023-08-21 08:23:43', '2023-10-06 15:04:08', b'0'),
+ (177, 'Schuppen 23', NULL, 4, 11, '2023-08-21 08:23:44', '2023-10-06 15:04:08', b'0'),
+ (178, 'Schuppen 24', NULL, 4, 11, '2023-08-21 08:23:44', '2023-10-06 15:04:08', b'0'),
+ (179, 'Seedalben Dlg-Seite', NULL, 111, 11, '2023-08-21 08:23:44', '2023-10-06 15:04:08', b'0'),
+ (180, 'Seedalben Kw-Seite', NULL, 111, 11, '2023-08-21 08:23:44', '2023-10-06 15:04:08', b'0'),
+ (181, 'Seehausen Spüler', NULL, 111, 11, '2023-08-21 08:23:44', '2023-10-06 15:04:08', b'0'),
+ (182, 'Tankschiffliegeplatz 1', NULL, 111, 11, '2023-08-21 08:23:44', '2023-10-06 15:04:08', b'0'),
+ (183, 'Tankschiffliegeplatz 2', NULL, 111, 11, '2023-08-21 08:23:44', '2023-10-06 15:04:08', b'0'),
+ (184, 'Terminal 1', NULL, 10, 11, '2023-08-21 08:23:44', '2023-10-06 15:04:08', b'0'),
+ (185, 'Terminal 2', NULL, 10, 11, '2023-08-21 08:23:45', '2023-10-06 15:04:08', b'0'),
+ (186, 'Terminal 3', NULL, 10, 11, '2023-08-21 08:23:45', '2023-10-06 15:04:08', b'0'),
+ (187, 'Terminal 4', NULL, 10, 11, '2023-08-21 08:23:45', '2023-10-06 15:04:08', b'0'),
+ (188, 'TSR Recycling', NULL, 133, 11, '2023-08-21 08:23:45', '2023-10-06 15:04:08', b'0'),
+ (189, 'Viehbrücke', NULL, 111, 11, '2023-08-21 08:23:45', '2023-10-06 15:04:08', b'0'),
+ (190, 'Vulkan Industriegebiet', NULL, 120, 11, '2023-08-21 08:23:46', '2023-10-06 15:04:08', b'0'),
+ (191, 'Weserbahnhof', NULL, 132, 11, '2023-08-21 08:23:46', '2023-10-06 15:04:08', b'0'),
+ (192, 'Weser-Petrol Holzhafen', NULL, 134, 11, '2023-08-21 08:23:46', '2023-10-06 15:04:08', b'0'),
+ (193, 'Weser-Petrol Kalihafen', NULL, 134, 11, '2023-08-21 08:23:46', '2023-10-06 15:04:08', b'0'),
+ (194, 'Wesertanking', NULL, 135, 11, '2023-08-21 08:23:46', '2023-10-06 15:04:08', b'0');
+/*!40000 ALTER TABLE `berth` ENABLE KEYS */;
+
+-- Exportiere Daten aus Tabelle bremen_calling_test.participant: ~40 rows (ungefähr)
+/*!40000 ALTER TABLE `participant` DISABLE KEYS */;
+INSERT INTO `participant` (`id`, `name`, `street`, `postal_code`, `city`, `type`, `flags`, `created`, `modified`, `deleted`) VALUES
+ (1, 'Schick Informatik', 'Gottlieb-Daimler-Str. 8', '73614', 'Schorndorf', 1, 42, '2023-04-17 07:18:19', '2023-08-24 07:07:02', b'0'),
+ (2, 'Lotsenbrüderschaft Weser 1', '', '', '', 4, 0, '2023-08-10 07:07:41', NULL, b'0'),
+ (3, 'Bremer Schiffsmeldedienst', 'Hafenkopf II / Überseetor 20', '28217', 'Bremen', 1, 0, '2023-08-10 07:11:10', NULL, b'0'),
+ (4, 'BLG Cargo Logistics GmbH', '', '', '', 2, 0, '2023-08-10 07:14:40', NULL, b'0'),
+ (5, 'Schiffsmakler-Verband für Küsten und Seeschiffsbefrachter e.V.', '', '', '', 8, 0, '2023-08-10 07:15:56', NULL, b'0'),
+ (6, 'RMS Rhenus Maritime Services GmbH', '', '', '', 8, 1, '2023-08-10 07:19:29', '2023-09-06 09:02:53', b'0'),
+ (7, 'J.MÜLLER Weser GmbH & Co. KG', '', '', '', 10, 0, '2023-08-10 07:21:43', '2023-08-10 08:47:59', b'0'),
+ (8, 'Schiffahrtskontor Detra GmbH & Co.KG', '', '', '', 8, 0, '2023-08-10 07:23:04', NULL, b'0'),
+ (9, 'Boluda Deutschland GmbH', '', '', '', 64, 0, '2023-08-10 07:24:18', NULL, b'0'),
+ (10, 'Weserport GmbH', '', '', '', 10, 0, '2023-08-10 07:26:42', '2023-08-10 08:48:19', b'0'),
+ (11, 'Port Authority Bremen', '', '', '', 32, 0, '2023-08-10 07:28:11', NULL, b'0'),
+ (12, 'Nordenia Frachtkontor GmbH', '', '', '', 8, 0, '2023-08-21 06:52:04', NULL, b'0'),
+ (15, 'Extern', '', '', '', 0, 0, '2023-08-21 06:55:18', NULL, b'0'),
+ (16, 'FESTMA Vertäugesellschaft mbH', '', '', '', 16, 0, '2023-08-21 06:57:23', NULL, b'0'),
+ (110, 'Avangard', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:35', '2023-08-21 10:04:21', b'0'),
+ (111, 'Bremenports', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:35', '2023-08-21 10:04:21', b'0'),
+ (112, 'Bremer Holzwerke', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:36', '2023-08-21 10:04:21', b'0'),
+ (113, 'Egerland', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:36', '2023-08-21 10:04:21', b'0'),
+ (114, 'Müller J. Bremen', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:37', '2023-08-21 10:04:21', b'0'),
+ (115, 'Griepe', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:37', '2023-08-21 10:04:21', b'0'),
+ (116, 'Mseven Real Estate', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:37', '2023-08-21 10:04:21', b'0'),
+ (117, 'Hansa Landhandel', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:38', '2023-08-21 10:04:21', b'0'),
+ (118, 'Hansa Melasse', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:38', '2023-08-21 10:04:21', b'0'),
+ (119, 'Hansa-Mühle', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:38', '2023-08-21 10:04:21', b'0'),
+ (120, 'Heidelberger Sand Und Kies Gmbh', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:38', '2023-08-21 10:04:21', b'0'),
+ (121, 'HGM', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:39', '2023-08-21 10:04:21', b'0'),
+ (122, 'Kap-Horn Logistics Gmbh', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:39', '2023-08-21 10:04:21', b'0'),
+ (123, 'Bremer Recycling Kontor', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:39', '2023-08-21 10:04:21', b'0'),
+ (124, 'GHK', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:40', '2023-08-21 10:04:21', b'0'),
+ (125, 'Kraftwerk Farge Engie Gmbh & Co. KG', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:40', '2023-08-21 10:04:21', b'0'),
+ (126, 'Swb Erzeugung', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:40', '2023-08-21 10:04:21', b'0'),
+ (127, 'Mibau', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:41', '2023-08-21 10:04:21', b'0'),
+ (128, 'SWG', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:41', '2023-08-21 10:04:21', b'0'),
+ (129, 'Umweltschutz Nord Ganderkesee', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:42', '2023-08-21 10:04:21', b'0'),
+ (130, 'Nehlsen Industrieservice Gmbh & Co. KG', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:42', '2023-08-21 10:04:21', b'0'),
+ (131, 'Rolandmühle', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:42', '2023-08-21 10:04:21', b'0'),
+ (132, 'Wfb', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:43', '2023-08-21 10:04:21', b'0'),
+ (133, 'TSR', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:45', '2023-08-21 10:04:21', b'0'),
+ (134, 'Weser-Petrol', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:46', '2023-08-21 10:04:21', b'0'),
+ (135, 'Wesertanking', NULL, NULL, NULL, 2, 0, '2023-08-21 08:23:46', '2023-08-21 10:04:21', b'0'),
+ (136, 'TEST_BSMD', 'Überseetor 20', '28217', 'Bremen', 127, 1, '2023-10-04 11:54:36', '2023-10-13 11:37:51', b'0');
+/*!40000 ALTER TABLE `participant` ENABLE KEYS */;
+
+-- Exportiere Daten aus Tabelle bremen_calling_test.role: ~2 rows (ungefähr)
+/*!40000 ALTER TABLE `role` DISABLE KEYS */;
+INSERT INTO `role` (`id`, `name`, `description`, `created`, `modified`) VALUES
+ (1, 'My first role', 'A very good description', '2023-04-17 07:31:57', NULL),
+ (2, 'Another role', 'This role is very nice as well', '2023-04-17 07:32:12', NULL);
+/*!40000 ALTER TABLE `role` ENABLE KEYS */;
+
+-- Exportiere Daten aus Tabelle bremen_calling_test.securable: ~2 rows (ungefähr)
+/*!40000 ALTER TABLE `securable` DISABLE KEYS */;
+INSERT INTO `securable` (`id`, `name`, `created`, `modified`) VALUES
+ (1, 'First secure thing', '2023-04-17 07:38:12', NULL),
+ (2, 'Another secure thing', '2023-04-17 07:38:22', NULL);
+/*!40000 ALTER TABLE `securable` ENABLE KEYS */;
+
+-- Exportiere Daten aus Tabelle bremen_calling_test.ship: ~12 rows (ungefähr)
+/*!40000 ALTER TABLE `ship` DISABLE KEYS */;
+INSERT INTO `ship` (`id`, `name`, `imo`, `callsign`, `participant_id`, `length`, `width`, `is_tug`, `bollard_pull`, `eni`, `created`, `modified`, `deleted`) VALUES
+ (1, 'Dicke Berta', 1234567, 'DEBE', 1, 100, 20, b'0', NULL, NULL, '2023-06-27 10:43:02', NULL, b'0'),
+ (2, 'Maersk Neston', 9632167, '9V3532', 1, 210.07, 30.2, b'0', NULL, NULL, '2023-07-27 12:34:13', NULL, b'0'),
+ (3, 'AFRICAN HALCYON', 9343613, NULL, NULL, 177.13, 28.4, b'0', NULL, NULL, '2023-08-24 10:41:56', NULL, b'0'),
+ (4, 'AMIKO', 9125669, NULL, NULL, 99.98, 16.5, b'0', NULL, NULL, '2023-08-24 10:42:17', NULL, b'0'),
+ (5, 'ARKLOW BEACON', 9638795, NULL, NULL, 119.49, 14.99, b'0', NULL, NULL, '2023-08-24 10:42:17', NULL, b'0'),
+ (6, 'FWN ATLANTIDE', 9535620, NULL, NULL, 145.65, 18.25, b'0', NULL, NULL, '2023-08-24 10:42:17', NULL, b'0'),
+ (7, 'IONIAN SPIRIT', 9747235, NULL, NULL, 179.9, 30, b'0', NULL, NULL, '2023-08-24 10:42:17', NULL, b'0'),
+ (8, 'IRMA', 9180396, NULL, NULL, 199.9, 23.6, b'0', NULL, NULL, '2023-08-24 10:42:17', NULL, b'0'),
+ (9, 'JANA', 9330185, NULL, NULL, 69.34, 12, b'0', NULL, NULL, '2023-08-24 10:42:18', NULL, b'0'),
+ (10, 'MEDI PERTH', 9804552, NULL, NULL, 199.99, 32.24, b'0', NULL, NULL, '2023-08-24 10:42:18', NULL, b'0'),
+ (11, 'S NEPTUNE', 9634892, NULL, NULL, 169.99, 27, b'0', NULL, NULL, '2023-08-24 10:42:18', NULL, b'0'),
+ (12, 'WESER STAHL', 9186687, NULL, NULL, 192, 32.26, b'0', NULL, NULL, '2023-08-24 10:42:18', NULL, b'0'),
+ (13, 'BOTHNIABORG', 9267728, 'PBIO', NULL, 153.05, 21.8, b'0', NULL, NULL, '2023-10-04 11:52:32', NULL, b'0');
+/*!40000 ALTER TABLE `ship` ENABLE KEYS */;
+
+-- Exportiere Daten aus Tabelle bremen_calling_test.user: ~27 rows (ungefähr)
+/*!40000 ALTER TABLE `user` DISABLE KEYS */;
+INSERT INTO `user` (`id`, `participant_id`, `first_name`, `last_name`, `user_name`, `user_email`, `user_phone`, `password_hash`, `api_key`, `created`, `modified`) VALUES
+ (1, 1, 'Daniel', 'Schick', 'dani', NULL, NULL, '$2b$12$qfjw4b3XvGuu0t6HR8OYGOzF5b8gmC6PyIIBNbIXMXEayJunEEKmi', '0815', '2023-04-17 07:15:41', '2023-08-11 11:11:34'),
+ (2, 1, 'Londo', 'Mollari', 'Londo', 'l.mollari@centauri.gov', '+01 555 324 2314', '$2b$12$8r1oGQiWdiuQNoGbzm.z.OoCOc8.4YACN93k7ge7YDWKjQ8tPuTrm', NULL, '2023-06-27 08:34:55', '2023-10-28 12:04:54'),
+ (3, 2, 'Maik', 'Baudeck', 'maikb', NULL, NULL, '$2b$12$4SxGRlinOrpEVvqDZcE.wOusMZYsepdc6vj1vDpNhbPtApxU8VGPi', '', '2023-08-10 07:09:35', '2023-08-11 11:11:55'),
+ (4, 3, 'Christin', 'Hollmann', 'christinh', NULL, NULL, '$2b$12$evGJop3j19bNTkdg2GHrIeRedC7LG5SIHm8.hKhdUSrlXsp6sXBDG', '', '2023-08-10 07:12:05', '2023-10-04 11:48:13'),
+ (5, 3, 'Bastian', 'Güttner', 'bastiang', NULL, NULL, '$2b$12$0oCX3c2WyMykmxMoLqmpNubke713xhYlEEQgnxBV6Fj/TaUn.3/U6', '', '2023-08-10 07:12:26', '2023-08-11 11:11:13'),
+ (6, 3, 'Benjamin', 'Wiese', 'benjaminw', NULL, NULL, '$2b$12$RRj32KdLIf3D7z7cVWFqa.yZM5.ODOS0HqU3rdCuFrJS8HJ/rtqwy', '', '2023-08-10 07:13:01', '2023-08-11 11:11:16'),
+ (7, 1, 'Sladjan', 'Veselinovic', 'sladjanv', NULL, NULL, '$2b$12$4DctoCbZwxTvE39lXNRzneQ2kb/lXlJ5wEZ1CGbbw.rGM3nuAYjpa', '', '2023-08-10 07:13:39', '2023-08-11 11:11:45'),
+ (8, 1, 'Kersten', 'Gevers', 'kersteng', NULL, NULL, '$2b$12$zKX8iLPnXRmp5wD1Yp8P7e..U9R0A4ytbiMjd.l.IGkMzahcHPNWq', '', '2023-08-10 07:13:59', '2023-08-11 11:11:49'),
+ (9, 4, 'Dirk', 'Brunnert', 'dirkb', NULL, NULL, '$2b$12$HTeq/Fdfse6oElk7DLsQae5dtvWJloee.VtBH.THsj2kdcxxBkCDW', '', '2023-08-10 07:15:01', '2023-08-11 11:12:01'),
+ (10, 5, 'Thorsten', 'Fischer', 'thorstenf', NULL, NULL, '$2b$12$NHEpTNHuKU4ruPRIfd9yc.yv5faHGemFfRI3TISniqM7QNqHiyZpK', '', '2023-08-10 07:16:20', '2023-08-11 11:12:07'),
+ (11, 6, 'Lisa', 'Friedhoff', 'lisaf', NULL, NULL, '$2b$12$DJKJHGrQwfY9pwzgFfPds.DHGsygHyV3KDs38Hq4AUHPPs3jBPH3y', '', '2023-08-10 07:19:52', '2023-08-11 11:12:12'),
+ (12, 6, 'Dario', 'Fritschi', 'dariof', NULL, NULL, '$2b$12$MwCVTMQkN6zCAzCsE572Ye.M0nRDQNld4AgorLVyWq.DcQEmAy5lu', '', '2023-08-10 07:20:11', '2023-08-11 11:12:15'),
+ (13, 7, 'Hergen', 'Hanke', 'hergenh', NULL, NULL, '$2b$12$MKb6BDRrTbNd0qg5BdAS.upzlqxcWOgU/VEafJKSuzE9JLIWCimq6', '', '2023-08-10 07:22:09', '2023-08-11 11:12:24'),
+ (14, 8, 'Hardy', 'Paasch', 'hardyp', NULL, NULL, '$2b$12$l1lE/UqnYnOvci.N4j3zBOz6HC0z87ovnO0n6BIZYO7VN8gj.qGey', '', '2023-08-10 07:23:25', '2023-08-11 11:12:28'),
+ (15, 8, 'Marc', 'Pagel', 'marcp', NULL, NULL, '$2b$12$UCVJKzqX92Z8xZJ4kK0BRuFXMRdqcaXaGmBrqnYWARdKlPvZvLUZq', '', '2023-08-10 07:23:41', '2023-08-11 11:12:30'),
+ (16, 9, 'Andreas', 'Peukert', 'andreasp', NULL, NULL, '$2b$12$jNmciJAVR6p0IflvAthmk.j0SoOBvFHwDiEDKUHfwJq7baRsKg/LG', '', '2023-08-10 07:24:37', '2023-08-11 11:12:45'),
+ (17, 8, 'Christina', 'Rachiele', 'christinar', NULL, NULL, '$2b$12$BCsVgPRuIWPuuor07lprF.klQxvF901O3AXUhRrBJoEvYIjNQ.HKS', '', '2023-08-10 07:25:05', '2023-08-11 11:12:33'),
+ (18, 9, 'Sonia', 'Rekawek', 'soniar', NULL, NULL, '$2b$12$uHCkH6gu13yqllXBibLFIOWOpvctMC7NmojtXqDd6xsLq7bmvNOMu', '', '2023-08-10 07:25:27', '2023-08-11 11:12:48'),
+ (19, 6, 'Frank', 'Roelfs', 'frankr', NULL, NULL, '$2b$12$cEQAhUe9VJV6uTkfOY6/R.oAVfmFZQ4vS5G6BqoNEyaVHtFRDtB56', '', '2023-08-10 07:26:04', '2023-08-11 11:12:19'),
+ (20, 10, 'Vera', 'Schliedermann', 'veras', NULL, NULL, '$2b$12$FKcitW6W1HPwd.cdkZLGLeTFuzjsEIrbiKInysAKN.RibZ4gVLZHi', '', '2023-08-10 07:27:01', '2023-08-11 11:12:54'),
+ (21, 8, 'Michael', 'Strudthoff', 'michaels', NULL, NULL, '$2b$12$doTiywWpkso1UWB5eiAW1eoACP6rN4UDVt7qFFdRFvhhWUXikCmS2', '', '2023-08-10 07:27:27', '2023-08-11 11:12:37'),
+ (22, 4, 'Volker', 'Viohl', 'volkerv', NULL, NULL, '$2b$12$.YavQbWNE4eJDQA.ZNSKROYvMPWifBXyMX0IL0H2z50M720fpfTJW', '', '2023-08-10 07:27:50', '2023-08-11 11:12:04'),
+ (23, 11, 'Frauke', 'Zabel', 'fraukez', NULL, NULL, '$2b$12$rawQg6Cjl1yECGm9DOG8degdWdD.nZjEgGp8eXO98nh11QV1sEEEO', '', '2023-08-10 07:28:33', '2023-08-11 11:12:58'),
+ (24, 8, 'Jan', 'Zierow', 'janz', NULL, NULL, '$2b$12$CbnjUT42cf0mkIAqAURg3OksP9G3brmsE2GQTECTZ4.cVuhPn5D2G', '', '2023-08-10 07:28:55', '2023-08-11 11:12:39'),
+ (25, 12, 'Berit', 'Güstrau', 'beritg', NULL, NULL, '$2b$12$g8WJTEWwsrtMyqpVW/GFVuzyRjB2/n0YJJyvBx.3l51YiVEUjEQYy', '', '2023-08-21 06:52:35', NULL),
+ (26, 15, 'Ilknur', 'Colmorn', 'ilknurc', NULL, NULL, '$2b$12$tpEb0JQ8Li4YkPH28FeYk.1Jt2vK.TFn9SyhBKJ08gn7S5d8WYRlO', '', '2023-08-21 06:56:42', NULL),
+ (27, 16, 'Horst', 'Imgram', 'horsti', NULL, NULL, '$2b$12$05NFPSaP78puAa8pL39KrOKTafs/TzWwr4YfV4/Vrdu90assvNFZa', '', '2023-08-21 06:57:58', NULL),
+ (28, 136, 'Christin', 'Hollmann', 'chollmann', NULL, NULL, '$2b$12$pb1bWJ7hxOplFoqT/nIhyuRD39dxOpQ9t0LwZUI8CNOkTkE.eXiSO', '', '2023-10-04 11:55:05', NULL),
+ (29, 1, 'Max', 'Metz', 'maxm', NULL, NULL, '$2b$12$gm4EwjCF44Ls20vDHnlG/ew/cZ.DK4gcYed.OHER5J4OzZrA.9Jt.', '', '2023-10-06 13:02:56', '2023-10-13 11:53:35');
+/*!40000 ALTER TABLE `user` ENABLE KEYS */;
+
+/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
+/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
diff --git a/misc/version.txt b/misc/version.txt
new file mode 100644
index 0000000..c2e8527
--- /dev/null
+++ b/misc/version.txt
@@ -0,0 +1 @@
+0.9.6.0
\ No newline at end of file
diff --git a/src/BreCalClient/AboutDialog.xaml b/src/BreCalClient/AboutDialog.xaml
index da82547..68c72b7 100644
--- a/src/BreCalClient/AboutDialog.xaml
+++ b/src/BreCalClient/AboutDialog.xaml
@@ -54,7 +54,7 @@
-
+
diff --git a/src/BreCalClient/BreCalClient.csproj b/src/BreCalClient/BreCalClient.csproj
index a5d44cc..3ecd3f3 100644
--- a/src/BreCalClient/BreCalClient.csproj
+++ b/src/BreCalClient/BreCalClient.csproj
@@ -8,8 +8,8 @@
True
BreCalClient.App
..\..\misc\brecal.snk
- 0.9.4.0
- 0.9.4.0
+ 0.9.6.0
+ 0.9.6.0
Bremen calling client
A Windows WPF client for the Bremen calling API.
containership.ico
diff --git a/src/BreCalClient/EditShipcallControl.xaml b/src/BreCalClient/EditShipcallControl.xaml
index ffd82ee..8f20e1b 100644
--- a/src/BreCalClient/EditShipcallControl.xaml
+++ b/src/BreCalClient/EditShipcallControl.xaml
@@ -65,21 +65,20 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/src/BreCalClient/EditShipcallControl.xaml.cs b/src/BreCalClient/EditShipcallControl.xaml.cs
index 365a918..54706dc 100644
--- a/src/BreCalClient/EditShipcallControl.xaml.cs
+++ b/src/BreCalClient/EditShipcallControl.xaml.cs
@@ -159,9 +159,17 @@ namespace BreCalClient
this.ShipcallModel.Shipcall.ShipId = ((Ship)this.comboBoxShip.SelectedItem).Id;
this.ShipcallModel.Ship = (Ship)this.comboBoxShip.SelectedItem;
- this.ShipcallModel.Shipcall.ArrivalBerthId = (this.comboBoxArrivalBerth.SelectedItem != null) ? ((Berth)this.comboBoxArrivalBerth.SelectedItem).Id : null;
- this.ShipcallModel.Shipcall.DepartureBerthId = (this.comboBoxDepartureBerth.SelectedItem != null) ? ((Berth)this.comboBoxDepartureBerth.SelectedItem).Id : null;
-
+
+ if (this.ShipcallModel.Shipcall.Type != 3) // incoming, outgoing
+ {
+ this.ShipcallModel.Shipcall.ArrivalBerthId = (this.comboBoxArrivalBerth.SelectedItem != null) ? ((Berth)this.comboBoxArrivalBerth.SelectedItem).Id : null;
+ this.ShipcallModel.Shipcall.DepartureBerthId = (this.comboBoxDepartureBerth.SelectedItem != null) ? ((Berth)this.comboBoxDepartureBerth.SelectedItem).Id : null;
+ }
+ else // shifting
+ {
+ 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;
+ }
Participant? participant;
participant = (Participant?)this.comboBoxAgency.SelectedItem;
@@ -225,8 +233,17 @@ namespace BreCalClient
// this.textBoxVoyage.Text = this.ShipcallModel.Shipcall.Voyage;
this.datePickerETD.Value = this.ShipcallModel.Shipcall.Etd;
this.comboBoxShip.SelectedValue = this.ShipcallModel.Shipcall.ShipId;
- this.comboBoxArrivalBerth.SelectedValue = this.ShipcallModel.Shipcall.ArrivalBerthId;
- this.comboBoxDepartureBerth.SelectedValue = this.ShipcallModel.Shipcall.DepartureBerthId;
+
+ if (this.ShipcallModel.Shipcall.Type != 3) // incoming, outgoing
+ {
+ this.comboBoxArrivalBerth.SelectedValue = this.ShipcallModel.Shipcall.ArrivalBerthId;
+ this.comboBoxDepartureBerth.SelectedValue = this.ShipcallModel.Shipcall.DepartureBerthId;
+ }
+ else // shifting
+ {
+ this.comboBoxArrivalBerth.SelectedValue = this.ShipcallModel.Shipcall.DepartureBerthId;
+ this.comboBoxDepartureBerth.SelectedValue = this.ShipcallModel.Shipcall.ArrivalBerthId;
+ }
if (this.ShipcallModel.Shipcall.Participants == null) this.ShipcallModel.Shipcall.Participants = new();
diff --git a/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs b/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs
index 3945882..093e20f 100644
--- a/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs
+++ b/src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs
@@ -46,7 +46,8 @@ namespace BreCalClient
this.comboBoxArrivalBerth.ItemsSource = BreCalLists.Berths;
this.CopyToControls();
-
+
+ this.Title = this.ShipcallModel.Title;
Participant? p = null;
if(this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
diff --git a/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs b/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs
index 652346e..f0369b0 100644
--- a/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs
+++ b/src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs
@@ -46,6 +46,8 @@ namespace BreCalClient
this.comboBoxDepartureBerth.ItemsSource = BreCalLists.Berths;
this.CopyToControls();
+ this.Title = this.ShipcallModel.Title;
+
Participant? p = null;
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId);
diff --git a/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs b/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs
index 86b5dfb..adea08e 100644
--- a/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs
+++ b/src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs
@@ -47,6 +47,8 @@ namespace BreCalClient
this.comboBoxArrivalBerth.ItemsSource = BreCalLists.Berths;
this.CopyToControls();
+ this.Title = this.ShipcallModel.Title;
+
Participant? p = null;
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId);
diff --git a/src/BreCalClient/EditTimesControl.xaml b/src/BreCalClient/EditTimesControl.xaml
index 01a78c9..41fb072 100644
--- a/src/BreCalClient/EditTimesControl.xaml
+++ b/src/BreCalClient/EditTimesControl.xaml
@@ -13,7 +13,7 @@
-
+
@@ -24,7 +24,7 @@
-
+
@@ -43,7 +43,7 @@
-
+
@@ -55,7 +55,7 @@
-
+
@@ -67,7 +67,7 @@
-
+
@@ -79,7 +79,7 @@
-
+
diff --git a/src/BreCalClient/EditTimesControl.xaml.cs b/src/BreCalClient/EditTimesControl.xaml.cs
index a157705..20845f1 100644
--- a/src/BreCalClient/EditTimesControl.xaml.cs
+++ b/src/BreCalClient/EditTimesControl.xaml.cs
@@ -25,7 +25,7 @@ namespace BreCalClient
#region Properties
- public Times Times { get; set; } = new();
+ public Times Times { get; set; } = new();
public Extensions.TypeEnum CallType { get; set; }
@@ -34,7 +34,7 @@ namespace BreCalClient
#region event handler
private void Window_Loaded(object sender, RoutedEventArgs e)
- {
+ {
this.CopyToControls();
this.EnableControls();
}
@@ -64,10 +64,10 @@ namespace BreCalClient
this.Times.LockTime = this.datePickerLockTime.Value;
this.Times.ZoneEntry = this.datePickerZoneEntry.Value;
- this.Times.EtaBerthFixed = this.checkBoxEtaBerthFixed.IsChecked;
- this.Times.EtdBerthFixed = this.checkBoxEtDBerthFixed.IsChecked;
- this.Times.LockTimeFixed = this.checkBoxLockTimeFixed.IsChecked;
- this.Times.ZoneEntryFixed = this.checkBoxZoneEntryFixed.IsChecked;
+ //this.Times.EtaBerthFixed = this.checkBoxEtaBerthFixed.IsChecked;
+ //this.Times.EtdBerthFixed = this.checkBoxEtDBerthFixed.IsChecked;
+ //this.Times.LockTimeFixed = this.checkBoxLockTimeFixed.IsChecked;
+ //this.Times.ZoneEntryFixed = this.checkBoxZoneEntryFixed.IsChecked;
}
private void CopyToControls()
@@ -78,10 +78,10 @@ namespace BreCalClient
this.datePickerLockTime.Value = this.Times.LockTime;
this.datePickerZoneEntry.Value = this.Times.ZoneEntry;
- this.checkBoxEtaBerthFixed.IsChecked = this.Times.EtaBerthFixed;
- this.checkBoxEtDBerthFixed.IsChecked = this.Times.EtdBerthFixed;
- this.checkBoxLockTimeFixed.IsChecked = this.Times.LockTimeFixed;
- this.checkBoxZoneEntryFixed.IsChecked = this.Times.ZoneEntryFixed;
+ //this.checkBoxEtaBerthFixed.IsChecked = this.Times.EtaBerthFixed;
+ //this.checkBoxEtDBerthFixed.IsChecked = this.Times.EtdBerthFixed;
+ //this.checkBoxLockTimeFixed.IsChecked = this.Times.LockTimeFixed;
+ //this.checkBoxZoneEntryFixed.IsChecked = this.Times.ZoneEntryFixed;
}
private void EnableControls()
@@ -94,25 +94,25 @@ namespace BreCalClient
case Extensions.ParticipantType.MOORING:
case Extensions.ParticipantType.PORT_ADMINISTRATION:
case Extensions.ParticipantType.TUG:
- this.datePickerETABerth.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
- this.checkBoxEtaBerthFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
+ this.datePickerETABerth.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
+ //this.checkBoxEtaBerthFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
this.datePickerETDBerth.IsEnabled = (CallType == Extensions.TypeEnum.Outgoing || CallType == Extensions.TypeEnum.Shifting);
- this.checkBoxEtDBerthFixed.IsEnabled = (CallType == Extensions.TypeEnum.Outgoing || CallType == Extensions.TypeEnum.Shifting);
+ //this.checkBoxEtDBerthFixed.IsEnabled = (CallType == Extensions.TypeEnum.Outgoing || CallType == Extensions.TypeEnum.Shifting);
this.datePickerLockTime.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
- this.checkBoxLockTimeFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
+ //this.checkBoxLockTimeFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
this.datePickerZoneEntry.IsEnabled = false;
- this.checkBoxZoneEntryFixed.IsEnabled = false;
+ //this.checkBoxZoneEntryFixed.IsEnabled = false;
this.textBoxRemarks.IsEnabled = true;
break;
case Extensions.ParticipantType.PILOT:
- this.datePickerETABerth.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
- this.checkBoxEtaBerthFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
+ this.datePickerETABerth.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
+ //this.checkBoxEtaBerthFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
this.datePickerETDBerth.IsEnabled = (CallType == Extensions.TypeEnum.Outgoing || CallType == Extensions.TypeEnum.Shifting);
- this.checkBoxEtDBerthFixed.IsEnabled = (CallType == Extensions.TypeEnum.Outgoing || CallType == Extensions.TypeEnum.Shifting);
+ //this.checkBoxEtDBerthFixed.IsEnabled = (CallType == Extensions.TypeEnum.Outgoing || CallType == Extensions.TypeEnum.Shifting);
this.datePickerLockTime.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
- this.checkBoxLockTimeFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
+ //this.checkBoxLockTimeFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
this.datePickerZoneEntry.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
- this.checkBoxZoneEntryFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
+ //this.checkBoxZoneEntryFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
this.textBoxRemarks.IsEnabled = true;
break;
}
diff --git a/src/BreCalClient/EditTimesTerminalControl.xaml.cs b/src/BreCalClient/EditTimesTerminalControl.xaml.cs
index ea9f257..4d9549f 100644
--- a/src/BreCalClient/EditTimesTerminalControl.xaml.cs
+++ b/src/BreCalClient/EditTimesTerminalControl.xaml.cs
@@ -96,11 +96,11 @@ namespace BreCalClient
{
if (this.Times.ParticipantId != App.Participant.Id) return;
- this.datePickerOperationStart.IsEnabled = (CallType == Extensions.TypeEnum.Incoming) || (CallType == Extensions.TypeEnum.Shifting);
+ this.datePickerOperationStart.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
this.datePickerOperationEnd.IsEnabled = (CallType == Extensions.TypeEnum.Outgoing) || (CallType == Extensions.TypeEnum.Shifting);
- this.comboBoxBerth.IsEnabled = (CallType == Extensions.TypeEnum.Incoming) || (CallType == Extensions.TypeEnum.Shifting);
- this.comboBoxPierside.IsEnabled = (CallType == Extensions.TypeEnum.Incoming) || (CallType == Extensions.TypeEnum.Shifting);
- this.textBoxBerthRemarks.IsEnabled = (CallType == Extensions.TypeEnum.Incoming) || (CallType == Extensions.TypeEnum.Shifting);
+ this.comboBoxBerth.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
+ this.comboBoxPierside.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
+ this.textBoxBerthRemarks.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
this.textBoxRemarks.IsEnabled = true;
this.buttonOK.IsEnabled = true;
}
diff --git a/src/BreCalClient/IEditTimesControl.cs b/src/BreCalClient/IEditTimesControl.cs
index aaaf669..8be49ff 100644
--- a/src/BreCalClient/IEditTimesControl.cs
+++ b/src/BreCalClient/IEditTimesControl.cs
@@ -11,6 +11,8 @@ namespace BreCalClient
{
Times Times { get; set; }
+ string Title { get; set; }
+
Extensions.TypeEnum CallType { get; set; }
bool? ShowDialog();
diff --git a/src/BreCalClient/MainWindow.xaml.cs b/src/BreCalClient/MainWindow.xaml.cs
index e5f21ea..f9e31c6 100644
--- a/src/BreCalClient/MainWindow.xaml.cs
+++ b/src/BreCalClient/MainWindow.xaml.cs
@@ -46,7 +46,7 @@ namespace BreCalClient
private bool _refreshImmediately = false;
private bool? _showCanceled = null;
- private SortOrder? _sortOrder;
+ private SortOrder _sortOrder = SortOrder.ETA_ETD;
private int searchPastDays = 0;
// private bool _filterChanged = false;
@@ -90,6 +90,7 @@ namespace BreCalClient
Process.Start("explorer", Properties.Settings.Default.LOGO_IMAGE_URL);
};
this.comboBoxSortOrder.ItemsSource = Enum.GetValues(typeof(Extensions.SortOrder));
+ this.comboBoxSortOrder.SelectedIndex = (int)_sortOrder;
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
@@ -415,8 +416,16 @@ namespace BreCalClient
Shipcall shipcall = scm.Shipcall;
if (BreCalLists.ShipLookupDict.ContainsKey(shipcall.ShipId))
scm.Ship = BreCalLists.ShipLookupDict[shipcall.ShipId];
- if (BreCalLists.BerthLookupDict.ContainsKey(shipcall.ArrivalBerthId ?? 0))
- scm.Berth = BreCalLists.BerthLookupDict[shipcall.ArrivalBerthId ?? 0].Name;
+ if (shipcall.Type == 1)
+ {
+ if (BreCalLists.BerthLookupDict.ContainsKey(shipcall.ArrivalBerthId ?? 0))
+ scm.Berth = BreCalLists.BerthLookupDict[shipcall.ArrivalBerthId ?? 0].Name;
+ }
+ else
+ {
+ if (BreCalLists.BerthLookupDict.ContainsKey(shipcall.DepartureBerthId ?? 0))
+ scm.Berth = BreCalLists.BerthLookupDict[shipcall.DepartureBerthId ?? 0].Name;
+ }
scm.AssignParticipants();
}
@@ -511,31 +520,28 @@ namespace BreCalClient
{
_ = this._visibleControlModels.RemoveAll(x => x.Shipcall?.Canceled ?? false);
}
-
- if (this._sortOrder != null)
- {
- switch(this._sortOrder)
- {
- 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); });
- break;
- case Extensions.SortOrder.MODIFIED:
- this._visibleControlModels.Sort((x, y) => { if (x.Shipcall == null) return 0; if (y.Shipcall == null) return 0; return DateTime.Compare(x.Shipcall.Modified ?? x.Shipcall.Created, y.Shipcall.Modified ?? x.Shipcall.Created); });
- break;
- case Extensions.SortOrder.ETA_ETD:
- this._visibleControlModels.Sort((x, y) =>
- {
- if (x.Shipcall == null) return 0;
- if (y.Shipcall == null) return 0;
- DateTime xDate = (x.Shipcall.Type == (int) Extensions.TypeEnum.Incoming) ? x.Shipcall.Eta ?? DateTime.Now : x.Shipcall.Etd ?? DateTime.Now;
- DateTime yDate = (y.Shipcall.Type == (int) Extensions.TypeEnum.Incoming) ? y.Shipcall.Eta ?? DateTime.Now : y.Shipcall.Etd ?? DateTime.Now;
- return DateTime.Compare(xDate, yDate);
- });
- break;
- default:
- break;
- }
- }
+
+ switch(this._sortOrder)
+ {
+ 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); });
+ break;
+ case Extensions.SortOrder.MODIFIED:
+ this._visibleControlModels.Sort((x, y) => { if (x.Shipcall == null) return 0; if (y.Shipcall == null) return 0; return DateTime.Compare(x.Shipcall.Modified ?? x.Shipcall.Created, y.Shipcall.Modified ?? x.Shipcall.Created); });
+ break;
+ case Extensions.SortOrder.ETA_ETD:
+ this._visibleControlModels.Sort((x, y) =>
+ {
+ if (x.Shipcall == null) return 0;
+ if (y.Shipcall == null) return 0;
+ DateTime xDate = (x.Shipcall.Type == (int) Extensions.TypeEnum.Incoming) ? x.Eta ?? DateTime.Now : x.Etd ?? DateTime.Now;
+ DateTime yDate = (y.Shipcall.Type == (int) Extensions.TypeEnum.Incoming) ? y.Eta ?? DateTime.Now : y.Etd ?? DateTime.Now;
+ return DateTime.Compare(xDate, yDate);
+ });
+ break;
+ default:
+ break;
+ }
}
@@ -617,6 +623,7 @@ namespace BreCalClient
// show a dialog that lets the user create / update times for the given shipcall
IEditTimesControl etc = (participantType == ParticipantType.TERMINAL) ? new EditTimesTerminalControl() : new EditTimesControl();
+ etc.Title = obj.ShipcallControlModel.Title;
if(obj.ShipcallControlModel.Shipcall != null)
etc.CallType = (TypeEnum) obj.ShipcallControlModel.Shipcall.Type;
diff --git a/src/BreCalClient/Properties/PublishProfiles/ClickOnceDevelProfile.pubxml b/src/BreCalClient/Properties/PublishProfiles/ClickOnceDevelProfile.pubxml
new file mode 100644
index 0000000..bd31a28
--- /dev/null
+++ b/src/BreCalClient/Properties/PublishProfiles/ClickOnceDevelProfile.pubxml
@@ -0,0 +1,53 @@
+
+
+
+
+ 0
+ 0.9.6.0
+ True
+ Debug
+ True
+ True
+ https://www.textbausteine.net/
+ true
+ True
+ Web
+ https://www.bsmd-emswe.eu/develclient/
+ False
+ True
+ True
+ False
+ Any CPU
+ Bremen calling development client
+ bin\Debug\net6.0-windows\win-x64\app.publish\
+ bin\publish.devel\
+ Informatikbüro Daniel Schick
+ ClickOnce
+ True
+ True
+ win-x64
+ True
+ (none)
+ False
+ Bremen calling
+ https://www.textbausteine.net/
+ net6.0-windows
+ True
+ Foreground
+ False
+ Publish.html
+
+
+
+
+
+
+
+ Include
+ true
+ File
+
+
+
\ No newline at end of file
diff --git a/src/BreCalClient/Properties/PublishProfiles/ClickOnceProfile.pubxml b/src/BreCalClient/Properties/PublishProfiles/ClickOnceProfile.pubxml
index fdb647f..a95fcf9 100644
--- a/src/BreCalClient/Properties/PublishProfiles/ClickOnceProfile.pubxml
+++ b/src/BreCalClient/Properties/PublishProfiles/ClickOnceProfile.pubxml
@@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
0
- 0.9.4.0
+ 0.9.6.0
False
Release
True
@@ -19,12 +19,12 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
True
False
Any CPU
- bin\Release\net6.0-windows\app.publish\
+ bin\Release\net6.0-windows\win-x64\app.publish\
bin\publish\
ClickOnce
False
- False
- False
+ True
+ True
(none)
False
net6.0-windows
@@ -38,11 +38,17 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
Informatikbüro Daniel Schick
Bremen calling client
https://www.bsmd-emswe.eu/
+ win-x64
-
- true
- .NET Desktop Runtime 6.0.16 (x64)
-
+
+
+
+
+
+ Include
+ true
+ File
+
\ No newline at end of file
diff --git a/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml b/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml
index f423bad..3fcf4f2 100644
--- a/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml
+++ b/src/BreCalClient/Properties/PublishProfiles/ClickOnceTestProfile.pubxml
@@ -5,11 +5,12 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
0
- 0.9.4.0
- False
+ 0.9.6.*
+ True
Debug
+ True
True
- http://www.textbausteine.net
+ https://www.textbausteine.net/
true
True
Web
diff --git a/src/BreCalClient/Properties/Settings.Designer.cs b/src/BreCalClient/Properties/Settings.Designer.cs
index 6f08fee..0c67b24 100644
--- a/src/BreCalClient/Properties/Settings.Designer.cs
+++ b/src/BreCalClient/Properties/Settings.Designer.cs
@@ -9,20 +9,20 @@
//------------------------------------------------------------------------------
namespace BreCalClient.Properties {
-
-
+
+
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
-
+
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
-
+
public static Settings Default {
get {
return defaultInstance;
}
}
-
+
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("https://brecal.bsmd-emswe.eu")]
@@ -31,25 +31,25 @@ namespace BreCalClient.Properties {
return ((string)(this["API_URL"]));
}
}
-
+
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("#751D1F")]
+ [global::System.Configuration.DefaultSettingValueAttribute("#203864")]
public string BG_COLOR {
get {
return ((string)(this["BG_COLOR"]));
}
}
-
+
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Configuration.DefaultSettingValueAttribute("!!Bremen calling Testversion!!")]
+ [global::System.Configuration.DefaultSettingValueAttribute("Bremen calling")]
public string APP_TITLE {
get {
return ((string)(this["APP_TITLE"]));
}
}
-
+
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("https://www.textbausteine.net/")]
@@ -58,7 +58,7 @@ namespace BreCalClient.Properties {
return ((string)(this["LOGO_IMAGE_URL"]));
}
}
-
+
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
diff --git a/src/BreCalClient/Properties/Settings.settings b/src/BreCalClient/Properties/Settings.settings
index 4833674..8bb93c9 100644
--- a/src/BreCalClient/Properties/Settings.settings
+++ b/src/BreCalClient/Properties/Settings.settings
@@ -6,10 +6,10 @@
https://brecal.bsmd-emswe.eu
- #751D1F
+ #203864
- !!Bremen calling Testversion!!
+ Bremen calling
https://www.textbausteine.net/
diff --git a/src/BreCalClient/ShipcallControl.xaml b/src/BreCalClient/ShipcallControl.xaml
index 63a7bcb..7e6159d 100644
--- a/src/BreCalClient/ShipcallControl.xaml
+++ b/src/BreCalClient/ShipcallControl.xaml
@@ -1,12 +1,12 @@
@@ -45,7 +45,7 @@
-
@@ -81,23 +81,23 @@
-
+
-
-
-
-
-
-
-
+
@@ -111,7 +111,7 @@
-
+
diff --git a/src/BreCalClient/ShipcallControl.xaml.cs b/src/BreCalClient/ShipcallControl.xaml.cs
index 1fcd904..7bb61dc 100644
--- a/src/BreCalClient/ShipcallControl.xaml.cs
+++ b/src/BreCalClient/ShipcallControl.xaml.cs
@@ -1,6 +1,6 @@
// Copyright (c) 2023 schick Informatik
// Description: Show general shipcall info
-//
+//
using BreCalClient.misc.Model;
using log4net;
@@ -53,7 +53,7 @@ namespace BreCalClient
///
/// this is our datasource
///
- public ShipcallControlModel? ShipcallControlModel { get; set; }
+ public ShipcallControlModel? ShipcallControlModel { get; set; }
#endregion
@@ -71,26 +71,61 @@ namespace BreCalClient
name = _agency?.Name;
if (name != null) agentName = name;
this.labelAgent.Content = name ?? "- / -";
+ if(_agency == null)
+ {
+ // clear agency display controls
+ this.labelAgencyBerth.Content = "";
+ this.labelAgencyETAETDValue.Content = "";
+ this.textBlockAgencyRemarks.Text = "";
+ this.textBlockAgencyBerthRemarks.Text = "";
+ }
_mooring = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.MOORING);
name = _mooring?.Name;
this.labelMooring.Content = name ?? "- / -";
+ if(_mooring == null)
+ {
+ this.labelMooringETAETDValue.Content = "";
+ this.textBlockMooringRemarks.Text = "";
+ }
_pilot = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.PILOT);
name = _pilot?.Name;
this.labelPilot.Content = name ?? "- / - ";
+ if(_pilot == null)
+ {
+ this.labelPilotETAETDValue.Content = "";
+ this.textBlockPilotRemarks.Text = "";
+ }
_tug = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.TUG);
name = _tug?.Name;
this.labelTug.Content = name ?? "- / - ";
+ if(_tug == null)
+ {
+ this.labelTugETAETDValue.Content = "";
+ this.textBlockTugRemarks.Text = "";
+ }
_port_administration = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.PORT_ADMINISTRATION);
name = _port_administration?.Name;
this.labelPortAuthority.Content = name ?? "- / - ";
+ if(_port_administration == null)
+ {
+ this.labelPortAuthorityETAETDValue.Content = "";
+ this.textBlockPortAuthorityRemarks.Text = "";
+ }
_terminal = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.TERMINAL);
name = _terminal?.Name;
this.labelTerminal.Content = name ?? "- / - ";
+ if(_terminal == null)
+ {
+ this.textBlockTerminalRemarks.Text = "";
+ this.textBlockTerminalBerthRemarks.Text = "";
+ this.labelTerminalBerth.Content = "";
+ this.labelOperationsStart.Content = "";
+ }
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.TERMINAL) && (App.Participant.Id == _terminal?.Id))
{
@@ -171,9 +206,9 @@ namespace BreCalClient
ShipcallControlModel.TrafficLightMode resultColor = (ShipcallControlModel.TrafficLightMode) (this.ShipcallControlModel?.Shipcall?.Evaluation ?? 0); // der nullable Operator hier ist so doof, die VS validation blickts einfach nicht
switch (resultColor)
{
- case ShipcallControlModel.TrafficLightMode.GREEN:
- this.Background = Brushes.LightGreen;
- break;
+ //case ShipcallControlModel.TrafficLightMode.GREEN:
+ // this.Background = Brushes.LightGreen;
+ // break;
case ShipcallControlModel.TrafficLightMode.YELLOW:
this.Background= Brushes.LightYellow;
break;
@@ -191,7 +226,7 @@ namespace BreCalClient
this.textBlockBerth.Text = this.ShipcallControlModel?.Berth;
this.textBlockCallsign.Text = this.ShipcallControlModel?.Ship?.Callsign;
- if (this.ShipcallControlModel?.Shipcall?.Type == 1)
+ if (this.ShipcallControlModel?.Shipcall?.Type == 1)
{
this.textBlockETA.Text = this.ShipcallControlModel?.Shipcall?.Eta?.ToString("dd.MM. HH:mm");
}
@@ -204,7 +239,7 @@ namespace BreCalClient
this.textBlockIMO.Text = this.ShipcallControlModel?.Ship?.Imo.ToString();
this.textBlockLengthWidth.Text = $"{this.ShipcallControlModel?.Ship?.Length} / {this.ShipcallControlModel?.Ship?.Width}";
- // rename labels if this is not an incoming
+ // rename labels if this is not an incoming
// must be here because there may not be a times record for each participant (yet)
if (this.ShipcallControlModel?.Shipcall?.Type != 1)
@@ -222,12 +257,13 @@ namespace BreCalClient
foreach (Times times in this.ShipcallControlModel.Times)
{
string? berthText = null;
- if ((BreCalLists.Berths != null) && times.BerthId.HasValue)
+ if ((BreCalLists.Berths != null) && times.BerthId.HasValue && (this.ShipcallControlModel?.Shipcall?.Type != (int) Extensions.TypeEnum.Shifting))
{
Berth? berth = BreCalLists.Berths.Find((x) => x.Id == times.BerthId);
berthText = berth?.Name;
}
- if (berthText == null)
+
+ if ((berthText == null) && (times.ParticipantType != (int) Extensions.ParticipantType.TERMINAL))
{
if (this.ShipcallControlModel?.Shipcall?.Type == (int)Extensions.TypeEnum.Incoming)
{
@@ -328,7 +364,7 @@ namespace BreCalClient
{
if(App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD))
this.EditRequested?.Invoke(this);
- }
+ }
private void Image_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
@@ -336,10 +372,10 @@ namespace BreCalClient
}
private void labelAgent_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
- {
+ {
Times? times = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.AGENCY);
- this.EditAgencyRequested?.Invoke(this, times);
- }
+ this.EditAgencyRequested?.Invoke(this, times);
+ }
private void labelMooring_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
@@ -349,7 +385,7 @@ namespace BreCalClient
private void labelPortAuthority_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
- Times? times = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.PORT_ADMINISTRATION);
+ Times? times = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.PORT_ADMINISTRATION);
this.EditTimesRequested?.Invoke(this, times, Extensions.ParticipantType.PORT_ADMINISTRATION);
}
diff --git a/src/BreCalClient/ShipcallControlModel.cs b/src/BreCalClient/ShipcallControlModel.cs
index a5e8e73..81df824 100644
--- a/src/BreCalClient/ShipcallControlModel.cs
+++ b/src/BreCalClient/ShipcallControlModel.cs
@@ -52,6 +52,28 @@ namespace BreCalClient
public List Times { get; set; } = new();
+ public DateTime? Eta
+ {
+ get
+ {
+ Times? agencyTimes = this.GetTimesForParticipantType(Extensions.ParticipantType.AGENCY);
+ if((agencyTimes != null) && (agencyTimes.EtaBerth != null))
+ return agencyTimes.EtaBerth;
+ return Shipcall?.Eta;
+ }
+ }
+
+ public DateTime? Etd
+ {
+ get
+ {
+ Times? agencyTimes = this.GetTimesForParticipantType(Extensions.ParticipantType.AGENCY);
+ if ((agencyTimes != null) && (agencyTimes.EtdBerth != null))
+ return agencyTimes.EtdBerth;
+ return Shipcall?.Etd;
+ }
+ }
+
public TrafficLightMode LightMode
{
get
@@ -69,6 +91,16 @@ namespace BreCalClient
}
}
+ public string Title
+ {
+ get
+ {
+ if (this.Shipcall == null) return "";
+ Extensions.TypeEnum callType = (Extensions.TypeEnum) this.Shipcall.Type;
+ return string.Format("{0} {1}", callType, this.Ship?.Name);
+ }
+ }
+
#endregion
#region public methods
@@ -142,6 +174,33 @@ namespace BreCalClient
await _api.TimesPutAsync(times);
}
}
+
+ // if somebody just removed an assignment it is gone fom AssignedParticipants, but there is still a
+ // times record left which must be removed
+
+ List deleteTimes = new();
+ foreach (Times times in this.Times)
+ {
+ bool foundTimes = false;
+ foreach (ParticipantAssignment pa in this.AssignedParticipants.Values)
+ {
+ if((pa.ParticipantId == times.ParticipantId) && (pa.Type == times.ParticipantType))
+ {
+ foundTimes = true;
+ break;
+ }
+ }
+ if (!foundTimes)
+ {
+ deleteTimes.Add(times);
+ }
+ }
+
+ foreach(Times times in deleteTimes)
+ {
+ _api.TimesDelete(times.Id);
+ this.Times.Remove(times);
+ }
}
#endregion
diff --git a/src/server/BreCal/__init__.py b/src/server/BreCal/__init__.py
index 3e1839d..68edc12 100644
--- a/src/server/BreCal/__init__.py
+++ b/src/server/BreCal/__init__.py
@@ -13,6 +13,25 @@ from .api import ships
from .api import login
from .api import user
+from BreCal.brecal_utils.file_handling import get_project_root, ensure_path
+from BreCal.brecal_utils.test_handling import execute_test_with_pytest, execute_coverage_test
+from BreCal.brecal_utils.time_handling import difference_to_then
+
+from BreCal.validators.time_logic import TimeLogic
+from BreCal.validators.validation_rules import ValidationRules
+from BreCal.validators.schema_validation import validation_state_and_validation_name
+
+from BreCal.stubs.times_agency import get_times_agency
+from BreCal.stubs.times_bsmd import get_times_bsmd
+from BreCal.stubs.times_mooring import get_times_mooring
+from BreCal.stubs.times_pilot import get_times_pilot
+from BreCal.stubs.times_portauthority import get_times_port_authority
+from BreCal.stubs.times_terminal import get_times_terminal
+from BreCal.stubs.times_tug import get_times_tug
+from BreCal.stubs.times_full import get_times_full_simple
+from BreCal.stubs.df_times import get_df_times
+
+
def create_app(test_config=None):
app = Flask(__name__, instance_relative_config=True)
@@ -47,22 +66,24 @@ def create_app(test_config=None):
return app
-from BreCal.brecal_utils.file_handling import get_project_root, ensure_path
-from BreCal.brecal_utils.test_handling import execute_test_with_pytest, execute_coverage_test
-from BreCal.brecal_utils.time_handling import difference_to_then
-
-from BreCal.validators.time_logic import TimeLogic
-from BreCal.validators.validation_rules import ValidationRules
-from BreCal.validators.schema_validation import validation_state_and_validation_name
-
__all__ = [
- "get_project_root",
- "ensure_path",
- "execute_test_with_pytest",
- "execute_coverage_test",
+ "get_project_root",
+ "ensure_path",
+ "execute_test_with_pytest",
+ "execute_coverage_test",
"difference_to_then",
"TimeLogic",
"ValidationRules",
"validation_state_and_validation_name",
+
+ "get_times_agency",
+ "get_times_bsmd",
+ "get_times_mooring",
+ "get_times_pilot",
+ "get_times_port_authority",
+ "get_times_terminal",
+ "get_times_tug",
+ "get_times_full_simple",
+ "get_df_times",
]
diff --git a/src/server/BreCal/database/sql_handler.py b/src/server/BreCal/database/sql_handler.py
index 3ae0e27..6c1acd2 100644
--- a/src/server/BreCal/database/sql_handler.py
+++ b/src/server/BreCal/database/sql_handler.py
@@ -26,7 +26,7 @@ class SQLHandler():
with self.sql_connection.cursor(buffered=True) as cursor:
cursor.execute("SHOW TABLES")
schema = cursor.fetchall()
- all_schemas = [schem[0] for schem in schema]
+ all_schemas = [schem[0] for schem in schema]
return all_schemas
def build_str_to_model_dict(self):
@@ -42,13 +42,51 @@ class SQLHandler():
def read_mysql_table_to_df(self, table_name:str):
"""determine a {table_name}, which will be read from a mysql server. returns a pandas DataFrame with the respective data"""
- df = pd.read_sql(sql=f"SELECT * FROM {table_name}", con=self.sql_connection)
+ with self.sql_connection.cursor(buffered=True) as cursor: #df = pd.read_sql(sql=f"SELECT * FROM {table_name}", con=self.sql_connection)
+ # 1.) get the column names
+ cursor.execute(f"DESCRIBE {table_name}")
+ cols = cursor.fetchall()
+ column_names = [col_name[0] for col_name in cols]
+
+ # 2.) get the data tuples
+ cursor.execute(f"SELECT * FROM {table_name}")
+ data = cursor.fetchall()
+
+ # 3.) map the data tuples to the correct column names
+ data = [{k:v for k,v in zip(column_names, dat)} for dat in data]
+
+ # 4.) build a dataframe from the respective data models (which ensures the correct data type)
+ data_model = self.str_to_model_dict.get(table_name)
+ if data_model is not None:
+ df = pd.DataFrame([data_model(**dat) for dat in data])
+ else:
+ df = pd.DataFrame([dat for dat in data])
return df
- def mysql_to_df(self, query):
+ def mysql_to_df(self, query, table_name):
"""provide an arbitrary sql query that should be read from a mysql server {sql_connection}. returns a pandas DataFrame with the obtained data"""
- df = pd.read_sql(query, self.sql_connection).convert_dtypes()
- df = df.set_index('id', inplace=False) # avoid inplace updates, so the raw sql remains unchanged
+ with self.sql_connection.cursor(buffered=True) as cursor: # df = pd.read_sql(query, self.sql_connection).convert_dtypes()
+ # 1.) get the column names
+ cursor.execute(f"DESCRIBE {table_name}")
+ cols = cursor.fetchall()
+ column_names = [col_name[0] for col_name in cols]
+
+ # 2.) get the data tuples
+ cursor.execute(query)
+ data = cursor.fetchall()
+
+ # 3.) map the data tuples to the correct column names
+ data = [{k:v for k,v in zip(column_names, dat)} for dat in data]
+
+ # 4.) build a dataframe from the respective data models (which ensures the correct data type)
+ data_model = self.str_to_model_dict.get(table_name)
+ if data_model is not None:
+ df = pd.DataFrame([data_model(**dat) for dat in data])
+ else:
+ df = pd.DataFrame([dat for dat in data])
+
+ if 'id' in df.columns:
+ df = df.set_index('id', inplace=False) # avoid inplace updates, so the raw sql remains unchanged
return df
def read_all(self, all_schemas):
@@ -64,7 +102,7 @@ class SQLHandler():
mysql_df_dict = {}
for schem in all_schemas:
query = f"SELECT * FROM {schem}"
- mysql_df_dict[schem] = self.mysql_to_df(query)
+ mysql_df_dict[schem] = self.mysql_to_df(query, table_name=schem)
return mysql_df_dict
def initialize_shipcall_participant_list(self):
@@ -183,6 +221,9 @@ class SQLHandler():
# filter out all NaN and NaT entries
if non_null_column is not None:
+ # in the Pandas documentation, it says for .isnull():
+ # "This function takes a scalar or array-like object and indicates whether values are missing
+ # (NaN in numeric arrays, None or NaN in object arrays, NaT in datetimelike)."
df_times = df_times.loc[~df_times[non_null_column].isnull()] # NOT null filter
# filter by the agency participant_type
@@ -192,13 +233,20 @@ class SQLHandler():
def filter_df_by_key_value(self, df, key, value)->pd.DataFrame:
return df.loc[df[key]==value]
- def get_unique_ship_counts(self, all_df_times:pd.DataFrame, query:str, rounding:str="min", maximum_threshold=3):
+ def get_unique_ship_counts(self, all_df_times:pd.DataFrame, times_agency:pd.DataFrame, query:str, rounding:str="min", maximum_threshold=3):
"""given a dataframe of all agency times, get all unique ship counts, their values (datetime) and the string tags. returns a tuple (values,unique,counts)"""
- # get values and optional: rounding
- values = all_df_times.loc[:, query]
+ # optional: rounding
if rounding is not None:
- values = values.dt.round(rounding) # e.g., 'min'
+ all_df_times.loc[:, query] = all_df_times.loc[:, query].dt.round(rounding) # e.g., 'min'
+ query_time_agency = times_agency[query].iloc[0].round(rounding)# e.g., 'min'
- unique, counts = np.unique(values, return_counts=True)
- violation_state = np.any(np.greater(counts, maximum_threshold))
- return (values, unique, counts)
+ # after rounding, filter {all_df_times}, so only those, which match the current query are of interest
+ # takes 'times_agency' to sample, which value should match
+ all_df_times = all_df_times.loc[all_df_times[query]==query_time_agency]
+
+ # finally, count all remaining entries
+ values = all_df_times.loc[:, query]
+
+ # get unique entries and counts
+ counts = len(values) # unique, counts = np.unique(values, return_counts=True)
+ return counts # (values, unique, counts)
diff --git a/src/server/BreCal/impl/berths.py b/src/server/BreCal/impl/berths.py
index 7581d32..d41025c 100644
--- a/src/server/BreCal/impl/berths.py
+++ b/src/server/BreCal/impl/berths.py
@@ -15,7 +15,7 @@ def GetBerths(token):
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
- data = commands.query("SELECT id, name, participant_id, `lock`, owner_id, authority_id, created, modified, deleted FROM berth WHERE deleted = 0 ORDER BY name", model=model.Berth)
+ data = commands.query("SELECT id, name, `lock`, owner_id, authority_id, created, modified, deleted FROM berth WHERE deleted = 0 ORDER BY name", model=model.Berth)
pooledConnection.close()
except Exception as ex:
diff --git a/src/server/BreCal/impl/shipcalls.py b/src/server/BreCal/impl/shipcalls.py
index 1e29dec..dd9cc3f 100644
--- a/src/server/BreCal/impl/shipcalls.py
+++ b/src/server/BreCal/impl/shipcalls.py
@@ -21,8 +21,8 @@ def GetShipcalls(options):
commands = pydapper.using(pooledConnection)
query = ("SELECT id, ship_id, type, eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, "
"flags, pier_side, bunkering, replenishing_terminal, replenishing_lock, draft, tidal_window_from, tidal_window_to, rain_sensitive_cargo, recommended_tugs, "
- "anchored, moored_lock, canceled, evaluation, evaluation_message, created, modified FROM shipcall WHERE ((type = 1 OR type = 2) AND eta >= DATE(NOW() - INTERVAL %d DAY)"
- "OR (type = 3 AND etd >= DATE(NOW() - INTERVAL %d DAY))) "
+ "anchored, moored_lock, canceled, evaluation, evaluation_message, created, modified FROM shipcall WHERE ((type = 1 OR type = 3) AND eta >= DATE(NOW() - INTERVAL %d DAY)"
+ "OR (type = 2 AND etd >= DATE(NOW() - INTERVAL %d DAY))) "
"ORDER BY eta") % (options["past_days"], options["past_days"])
data = commands.query(query, model=model.Shipcall)
diff --git a/src/server/BreCal/local_db.py b/src/server/BreCal/local_db.py
index 4afd51b..33e03e3 100644
--- a/src/server/BreCal/local_db.py
+++ b/src/server/BreCal/local_db.py
@@ -10,7 +10,7 @@ def initPool(instancePath):
try:
global config_path
if(config_path == None):
- config_path = os.path.join(instancePath,'../../../secure/connection_data_test.json');
+ config_path = os.path.join(instancePath,'../../../secure/connection_data_prod.json');
print (config_path)
diff --git a/src/server/BreCal/schemas/model.py b/src/server/BreCal/schemas/model.py
index 905d412..193c3f4 100644
--- a/src/server/BreCal/schemas/model.py
+++ b/src/server/BreCal/schemas/model.py
@@ -16,7 +16,6 @@ def obj_dict(obj):
class Berth(Schema):
id: int
name: str
- participant_id: int
lock: bool
owner_id: int
authority_id: int
diff --git a/src/server/BreCal/stubs/__init__.py b/src/server/BreCal/stubs/__init__.py
index 83dd2ed..95578de 100644
--- a/src/server/BreCal/stubs/__init__.py
+++ b/src/server/BreCal/stubs/__init__.py
@@ -3,3 +3,4 @@ def generate_uuid1_int():
"""# TODO: clarify, what kind of integer ID is used in mysql. Generates a proxy ID, which is used in the stubs"""
from uuid import uuid1
return uuid1().int>>64
+
diff --git a/src/server/BreCal/stubs/berth.py b/src/server/BreCal/stubs/berth.py
index 240bcbe..2a782d7 100644
--- a/src/server/BreCal/stubs/berth.py
+++ b/src/server/BreCal/stubs/berth.py
@@ -7,7 +7,6 @@ def get_berth_simple():
# Note: #TODO: name, participant_id & lock state are arbitrary
name = "Avangard Dalben"
- participant_id = 1# e.g., Avangard
lock = False
owner_id = 1 # e.g., Avangard
authority_id = 1 # e.g., Avangard
@@ -19,7 +18,6 @@ def get_berth_simple():
berth = Berth(
berth_id,
name,
- participant_id,
lock,
owner_id,
authority_id,
diff --git a/src/server/BreCal/stubs/df_times.py b/src/server/BreCal/stubs/df_times.py
new file mode 100644
index 0000000..08d8fd7
--- /dev/null
+++ b/src/server/BreCal/stubs/df_times.py
@@ -0,0 +1,78 @@
+import pandas as pd
+import random
+import datetime
+
+from BreCal.stubs.times_agency import get_times_agency
+from BreCal.stubs.times_bsmd import get_times_bsmd
+from BreCal.stubs.times_mooring import get_times_mooring
+from BreCal.stubs.times_pilot import get_times_pilot
+from BreCal.stubs.times_portauthority import get_times_port_authority
+from BreCal.stubs.times_terminal import get_times_terminal
+from BreCal.stubs.times_tug import get_times_tug
+from BreCal.database.enums import ParticipantType
+
+def get_df_times(shipcall=None):
+ """in case of providing a shipcall, one can read the id to set each times entry in the dataframe towards that shipcall id"""
+ df_times = pd.DataFrame([
+ fct()
+
+ for fct in [
+ get_times_agency,
+ get_times_bsmd,
+ get_times_mooring,
+ get_times_pilot,
+ get_times_port_authority,
+ get_times_terminal,
+ get_times_tug
+ ]
+ ])
+ if shipcall is not None:
+ df_times.loc[:,"shipcall_id"] = shipcall.id
+ return df_times
+
+def random_time_perturbation(df_times, query):
+ # random perturbations
+ population = [datetime.datetime.now(), None, pd.NaT]
+
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, query] = random.sample(population,k=1)[0]
+ df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, query] = random.sample(population,k=1)[0]
+ df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, query] = random.sample(population,k=1)[0]
+ df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, query] = random.sample(population,k=1)[0]
+ df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, query] = random.sample(population,k=1)[0]
+ return df_times
+
+def get_df_times_participants_disagree(query, shipcall=None, df_times = None):
+ if df_times is None:
+ df_times = get_df_times(shipcall)
+ df_times = random_time_perturbation(df_times=df_times, query=query)
+
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, query] = datetime.datetime.now()+datetime.timedelta(hours=2, minutes=14)
+ df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, query] = datetime.datetime.now()+datetime.timedelta(hours=1, minutes=7)
+
+ return df_times
+
+def build_stub_df_times(shipcall, query, reference_time):
+ """creates an artificial dataset, which simulates having too many shipcalls with too many identical times"""
+ df_times_a = get_df_times(shipcall)
+ df_times_a = df_times_a.loc[df_times_a["participant_type"]==ParticipantType.AGENCY.value]
+ df_times_a.loc[:,query] = reference_time + datetime.timedelta(seconds=17)
+
+ df_times_b = get_df_times(shipcall)
+ df_times_b = df_times_b.loc[df_times_b["participant_type"]==ParticipantType.AGENCY.value]
+ df_times_b.loc[:,query] = reference_time + datetime.timedelta(seconds=21)
+
+ df_times_c = get_df_times(shipcall)
+ df_times_c = df_times_c.loc[df_times_c["participant_type"]==ParticipantType.AGENCY.value]
+ df_times_c.loc[:,query] = reference_time + datetime.timedelta(seconds=26)
+
+ df_times_d = get_df_times(shipcall)
+ df_times_d = df_times_d.loc[df_times_d["participant_type"]==ParticipantType.AGENCY.value]
+ df_times_d.loc[:,query] = reference_time + datetime.timedelta(seconds=28)
+
+ df_times_e = get_df_times(shipcall)
+ df_times_e = df_times_e.loc[df_times_e["participant_type"]==ParticipantType.AGENCY.value]
+ df_times_e.loc[:,query] = reference_time + datetime.timedelta(seconds=29)
+
+ return pd.concat([df_times_a, df_times_b, df_times_c, df_times_d, df_times_e],axis=0)
+
+
diff --git a/src/server/BreCal/stubs/times_agency.py b/src/server/BreCal/stubs/times_agency.py
index e69de29..31fc0e7 100644
--- a/src/server/BreCal/stubs/times_agency.py
+++ b/src/server/BreCal/stubs/times_agency.py
@@ -0,0 +1,8 @@
+from BreCal.stubs.times_full import get_times_full_simple
+from BreCal.database.enums import ParticipantType
+
+def get_times_agency():
+ times_agency = get_times_full_simple()
+ times_agency.participant_type = ParticipantType.AGENCY.value
+ return times_agency
+
diff --git a/src/server/BreCal/stubs/times_bsmd.py b/src/server/BreCal/stubs/times_bsmd.py
new file mode 100644
index 0000000..3f36e24
--- /dev/null
+++ b/src/server/BreCal/stubs/times_bsmd.py
@@ -0,0 +1,8 @@
+from BreCal.stubs.times_full import get_times_full_simple
+from BreCal.database.enums import ParticipantType
+
+def get_times_bsmd():
+ times_bsmd = get_times_full_simple()
+ times_bsmd.participant_type = ParticipantType.BSMD.value
+ return times_bsmd
+
diff --git a/src/server/BreCal/stubs/times_full.py b/src/server/BreCal/stubs/times_full.py
index 984f3db..f0176a1 100644
--- a/src/server/BreCal/stubs/times_full.py
+++ b/src/server/BreCal/stubs/times_full.py
@@ -3,9 +3,11 @@ this stub creates an example time object, where the times of every role are pres
users will thereby be able to modify these values
"""
import datetime
+
from BreCal.stubs import generate_uuid1_int
from BreCal.schemas.model import Times
+
def get_times_full_simple():
# only used for the stub
base_time = datetime.datetime.now()
@@ -65,3 +67,5 @@ def get_times_full_simple():
modified=modified,
)
return times
+
+
diff --git a/src/server/BreCal/stubs/times_mooring.py b/src/server/BreCal/stubs/times_mooring.py
index e69de29..5e6570a 100644
--- a/src/server/BreCal/stubs/times_mooring.py
+++ b/src/server/BreCal/stubs/times_mooring.py
@@ -0,0 +1,8 @@
+from BreCal.stubs.times_full import get_times_full_simple
+from BreCal.database.enums import ParticipantType
+
+def get_times_mooring():
+ times_mooring = get_times_full_simple()
+ times_mooring.participant_type = ParticipantType.MOORING.value
+ return times_mooring
+
diff --git a/src/server/BreCal/stubs/times_pilot.py b/src/server/BreCal/stubs/times_pilot.py
index e69de29..1282133 100644
--- a/src/server/BreCal/stubs/times_pilot.py
+++ b/src/server/BreCal/stubs/times_pilot.py
@@ -0,0 +1,8 @@
+from BreCal.stubs.times_full import get_times_full_simple
+from BreCal.database.enums import ParticipantType
+
+def get_times_pilot():
+ times_pilot = get_times_full_simple()
+ times_pilot.participant_type = ParticipantType.PILOT.value
+ return times_pilot
+
diff --git a/src/server/BreCal/stubs/times_portauthority.py b/src/server/BreCal/stubs/times_portauthority.py
index e69de29..61ad4a5 100644
--- a/src/server/BreCal/stubs/times_portauthority.py
+++ b/src/server/BreCal/stubs/times_portauthority.py
@@ -0,0 +1,9 @@
+from BreCal.stubs.times_full import get_times_full_simple
+from BreCal.database.enums import ParticipantType
+
+def get_times_port_authority():
+ times_port_authority = get_times_full_simple()
+ times_port_authority.participant_type = ParticipantType.PORT_ADMINISTRATION.value
+ return times_port_authority
+
+
diff --git a/src/server/BreCal/stubs/times_terminal.py b/src/server/BreCal/stubs/times_terminal.py
index e69de29..549ddb5 100644
--- a/src/server/BreCal/stubs/times_terminal.py
+++ b/src/server/BreCal/stubs/times_terminal.py
@@ -0,0 +1,8 @@
+from BreCal.stubs.times_full import get_times_full_simple
+from BreCal.database.enums import ParticipantType
+
+def get_times_terminal():
+ times_terminal = get_times_full_simple()
+ times_terminal.participant_type = ParticipantType.TERMINAL.value
+ return times_terminal
+
diff --git a/src/server/BreCal/stubs/times_tug.py b/src/server/BreCal/stubs/times_tug.py
new file mode 100644
index 0000000..0e8cdd1
--- /dev/null
+++ b/src/server/BreCal/stubs/times_tug.py
@@ -0,0 +1,8 @@
+from BreCal.stubs.times_full import get_times_full_simple
+from BreCal.database.enums import ParticipantType
+
+def get_times_tug():
+ times_tug = get_times_full_simple()
+ times_tug.participant_type = ParticipantType.TUG.value
+ return times_tug
+
diff --git a/src/server/BreCal/validators/validation_rule_functions.py b/src/server/BreCal/validators/validation_rule_functions.py
index 81f53f3..591dfb8 100644
--- a/src/server/BreCal/validators/validation_rule_functions.py
+++ b/src/server/BreCal/validators/validation_rule_functions.py
@@ -7,6 +7,43 @@ from BreCal.validators.time_logic import TimeLogic
from BreCal.database.enums import StatusFlags
#from BreCal.validators.schema_validation import validation_state_and_validation_name
+# a human interpretable dictionary for error messages. In this case, the English language is preferred
+error_message_dict = {
+ # 0001 A-M
+ "validation_rule_fct_missing_time_agency_berth_eta":"The shipcall arrives in less than 20 hours, but there are still missing times by the agency. Please add the estimated time of arrival (ETA) {Rule #0001A}", # A
+ "validation_rule_fct_missing_time_agency_berth_etd":"The shipcall departs in less than 20 hours, but there are still missing times by the agency. Please add the estimated time of departure (ETD) {Rule #0001B}", # B
+ "validation_rule_fct_missing_time_mooring_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the mooring. Please add the estimated time of arrival (ETA) {Rule #0001C}", # C
+ "validation_rule_fct_missing_time_mooring_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the mooring. Please add the estimated time of departure (ETD) {Rule #0001D}", # D
+ "validation_rule_fct_missing_time_portadministration_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the port administration. Please add the estimated time of arrival (ETA) {Rule #0001F}", # F
+ "validation_rule_fct_missing_time_portadministration_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the port administration. Please add the estimated time of departure (ETD) {Rule #0001G}", # G
+ "validation_rule_fct_missing_time_pilot_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the pilot. Please add the estimated time of arrival (ETA) {Rule #0001H}", # H
+ "validation_rule_fct_missing_time_pilot_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the pilot. Please add the estimated time of departure (ETD) {Rule #0001I}", # I
+ "validation_rule_fct_missing_time_tug_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the tugs. Please add the estimated time of arrival (ETA) {Rule #0001J}", # J
+ "validation_rule_fct_missing_time_tug_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the tugs. Please add the estimated time of departure (ETD) {Rule #0001K}", # K
+ "validation_rule_fct_missing_time_terminal_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the terminal. Please add the estimated time of arrival (ETA) {Rule #0001L}", # L
+ "validation_rule_fct_missing_time_terminal_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the terminal. Please add the estimated time of departure (ETD) {Rule #0001M}", # M
+
+ # 0002 A+B+C
+ "validation_rule_fct_shipcall_incoming_participants_disagree_on_eta":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of arrival (ETA) {Rule #0002A}",
+ "validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of departure (ETD) {Rule #0002B}",
+ "validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd":"There are deviating times between agency, mooring, port authority, pilot and tug for ETA and ETD {Rule #0002C}",
+
+ # 0003 A+B
+ "validation_rule_fct_eta_time_not_in_operation_window":"The estimated time of arrival will be AFTER the planned start of operations. {Rule #0003A}",
+ "validation_rule_fct_etd_time_not_in_operation_window":"The estimated time of departure is supposed to be AFTER the planned end of operations. {Rule #0003D}",
+
+ # 0004 A+B
+ "validation_rule_fct_eta_time_not_in_tidal_window":"The tidal window does not fit to the agency's estimated time of arrival (ETA) {Rule #0004A}",
+ "validation_rule_fct_etd_time_not_in_tidal_window":"The tidal window does not fit to the agency's estimated time of departure (ETD) {Rule #0004B}",
+
+ # 0005 A+B
+ "validation_rule_fct_too_many_identical_eta_times":"There are more than three ships with the same planned time of arrival (ETA) {Rule #0005A}",
+ "validation_rule_fct_too_many_identical_etd_times":"There are more than three ships with the same planned time of departure (ETD) {Rule #0005B}",
+
+ # 0006 A+B
+ "validation_rule_fct_agency_and_terminal_berth_id_disagreement":"Agency and Terminal are planning with different berths (the berth_id deviates). {Rule #0006A}",
+ "validation_rule_fct_agency_and_terminal_pier_side_disagreement":"Agency and Terminal are planning with different pier sides (the pier_side deviates). {Rule #0006B}",
+}
class ValidationRuleBaseFunctions():
"""
@@ -16,6 +53,16 @@ class ValidationRuleBaseFunctions():
def __init__(self, sql_handler):
self.sql_handler = sql_handler
self.time_logic = TimeLogic()
+ self.error_message_dict = error_message_dict
+
+ def describe_error_message(self, key)->str:
+ """
+ Takes any error message, which typically is the validation rule's function name and returns a description of the error.
+ In case that the error code is not defined in self.error_message_dict, return the cryptic error code instead
+
+ returns: string
+ """
+ return self.error_message_dict.get(key,key)
def check_time_delta_violation_query_time_to_now(self, query_time:pd.Timestamp, key_time:pd.Timestamp, threshold:float)->bool:
"""
@@ -26,17 +73,22 @@ class ValidationRuleBaseFunctions():
when the query_time lays in the future, the delta is positive
returns a violation state depending on whether the delta is
- Violation, if: 0 >= delta > threshold
+ Violation, if: 0 >= delta <= threshold
When the key time is defined (not None), there is no violation. Returns False
options:
query_time: will be used to measure the time difference of 'now' until the query time
key_time: will be used to check, whether the respective key already has a value
- threshold: threshold where a time difference becomes crucial. When the delta is below the threshold, a violation might occur
+ threshold: threshold where a time difference becomes crucial. When the delta is below the threshold, a violation might occur (minutes)
"""
# rule is not applicable -> return 'GREEN'
- if key_time is not None:
+ # rule is only applicable, when 'key_time' is not defined (neither None, nor pd.NaT)
+ if (key_time is not None) and (key_time is not pd.NaT):
+ return False
+
+ # when query_time is not valid, the rule cannot be applied
+ if self.check_is_not_a_time_or_is_none(query_time):
return False
# otherwise, this rule applies and the difference between 'now' and the query time is measured
@@ -44,7 +96,7 @@ class ValidationRuleBaseFunctions():
# a violation occurs, when the delta (in minutes) exceeds the specified threshold of a participant
# to prevent past-events from triggering violations, negative values are ignored
- # Violation, if 0 >= delta >= threshold
+ # Violation, if 0 <= delta <= threshold
violation_state = (delta >= 0) and (delta<=threshold)
return violation_state
@@ -76,7 +128,7 @@ class ValidationRuleBaseFunctions():
df_times = df_times.loc[df_times["participant_type"].isin(participant_types),:]
# exclude missing entries and consider only pd.Timestamp entries (which ignores pd.NaT/null entries)
- estimated_times = [type(time_) for time_ in df_times.loc[:,query].tolist() if isinstance(time_, pd.Timestamp)] # df_times = df_times.loc[~df_times[query].isnull(),:]
+ estimated_times = [time_ for time_ in df_times.loc[:,query].tolist() if isinstance(time_, pd.Timestamp)] # df_times = df_times.loc[~df_times[query].isnull(),:]
# apply rounding. For example, the agreement of different participants may be required to match minute-wise
# '15min' rounds to 'every 15 minutes'. E.g., '2023-09-22 08:18:49' becomes '2023-09-22 08:15:00'
@@ -94,7 +146,7 @@ class ValidationRuleBaseFunctions():
violation_state = n_unique_times!=1
return violation_state
- def check_unique_shipcall_counts(self, query:str, rounding="min", maximum_threshold=3)->bool:
+ def check_unique_shipcall_counts(self, query:str, times_agency:pd.DataFrame, rounding="min", maximum_threshold=3, all_times_agency=None)->bool:
"""
# base function for all validation rules in the group {0005} A&B
@@ -103,15 +155,21 @@ class ValidationRuleBaseFunctions():
"""
# filter the df: keep only times_agents
# filter out all NaN and NaT entries
- times_agency = self.sql_handler.get_times_for_agency(non_null_column=query)
+ if all_times_agency is None:
+ all_times_agency = self.sql_handler.get_times_for_agency(non_null_column=query)
- # get values and optionally round the values
- (values, unique, counts) = self.sql_handler.get_unique_ship_counts(all_df_times=times_agency, query=query, rounding=rounding, maximum_threshold=maximum_threshold)
+ # get values and optionally round the values (internally)
+ counts = self.sql_handler.get_unique_ship_counts(all_df_times=all_times_agency, times_agency=times_agency, query=query, rounding=rounding, maximum_threshold=maximum_threshold)
# when ANY of the unique values exceeds the threshold, a violation is observed
violation_state = np.any(np.greater(counts, maximum_threshold))
return violation_state
+ def check_is_not_a_time_or_is_none(self, value)->bool:
+ """checks, if a provided value is either None or NaT"""
+ return (value is None) or (value is pd.NaT)
+
+
class ValidationRuleFunctions(ValidationRuleBaseFunctions):
"""
@@ -466,12 +524,12 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
def validation_rule_fct_missing_time_terminal_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
- Code: #0001-K
+ Code: #0001-M
Type: Local Rule
Description: this validation checks, whether there is a missing time. When the difference between an event (e.g., the shipcall eta) is below
a certain threshold (e.g., 20 hours), a violation occurs
- 0001-K:
+ 0001-M:
- Checks, if times_terminal.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
@@ -595,11 +653,11 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value)
- if (times_terminal.operations_end is pd.NaT) or (times_agency.etd_berth is pd.NaT):
+ if self.check_is_not_a_time_or_is_none(times_terminal.operations_start) or self.check_is_not_a_time_or_is_none(times_agency.eta_berth):
return (StatusFlags.GREEN, None)
# check, whether the start of operations is AFTER the estimated arrival time
- violation_state = times_terminal.operations_start StatusFlags.GREEN.value, f"a violation must be identified"
- assert description is not None, f"a violation description must be identified"
+ threshold = 60*5
+ violation_state = vr.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
+ assert not violation_state, f"the key time is filled in, so there should not be a violation"
return
+def test_check_time_delta_violation_query_time_to_now_no_key_time_but_event_in_distant_future(build_sql_proxy_connection):
+ vr = build_sql_proxy_connection["vr"]
+
+ # ship arrives in three hours, while the threshold for an alert is (e.g.) 5 hours
+ # key time is given, so the function should always return False (no violation)
+ # query time (-> delta) & threshold have the same time -> no violation
+ query_time = datetime.datetime.now() + datetime.timedelta(hours=5, seconds=10) # when the delta & threshold are identical, microseconds between checking the time and defining it here, raise the violation
+ key_time = None
+
+ threshold = 60*5
+ violation_state = vr.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
+ assert not violation_state, f"the event is still far enough away, so there should not be a violation."
+ return
+
+def test_check_time_delta_violation_query_time_to_now_key_time_is_none(build_sql_proxy_connection):
+ vr = build_sql_proxy_connection["vr"]
+
+ # ship arrives in three hours, while the threshold for an alert is (e.g.) 5 hours
+ # key time is given, so the function should always return False (no violation)
+ query_time = datetime.datetime.now() + datetime.timedelta(hours=3)
+ key_time = None
+
+ threshold = 60*5 # minutes
+ violation_state = vr.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
+ assert violation_state, f"when the key time is not filled in and the query time is 'dangerously close', there should be a violation to indicate the traffic state"
+ return
+
+def test_check_time_delta_violation_query_time_to_now_key_time_is_pd_nat(build_sql_proxy_connection):
+ vr = build_sql_proxy_connection["vr"]
+
+ # ship arrives in three hours, while the threshold for an alert is (e.g.) 5 hours
+ # key time is given, so the function should always return False (no violation)
+ query_time = datetime.datetime.now() + datetime.timedelta(hours=3)
+ key_time = pd.NaT
+
+ threshold = 60*5 # minutes
+ violation_state = vr.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
+ assert violation_state, f"when the key time is not filled in and the query time is 'dangerously close', there should be a violation to indicate the traffic state"
+ return
+
+
+
+def test_validation_rule_fct_missing_time_agency_berth_eta__missing_time_agency_no_violation(build_sql_proxy_connection):
+ """0001-A validation_rule_fct_missing_time_agency_berth_eta"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # artificially remove the agency, so the function is properly checked
+ df_times = df_times.loc[df_times["participant_type"]!=ParticipantType.AGENCY.value]
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.GREEN, f"function should return 'green', because the agency's entry is not present"
+ assert msg is None, f"with a 'green' state, there should be no message returned"
+ return
+
+def test_validation_rule_fct_missing_time_agency_berth_eta__shipcall_eta_dangerously_close_no_times_agency(build_sql_proxy_connection):
+ """0001-A validation_rule_fct_missing_time_agency_berth_eta"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # the shipcall happens 'soon'
+ shipcall.eta = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.AGENCY-10)
+
+ # set times agency to be undetermined
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the agency's entry is not present and the shipcall takes place soon"
+ return
+
+def test_validation_rule_fct_missing_time_agency_berth_eta__shipcall_eta_distant_enough_no_times_agency(build_sql_proxy_connection):
+ """0001-A validation_rule_fct_missing_time_agency_berth_eta"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # the shipcall happens 'soon'
+ shipcall.eta = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.AGENCY+10)
+
+ # set times agency to be undetermined
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.GREEN, f"function should return 'yellow', because the agency's entry is not present and the shipcall takes place soon"
+ return
+
+def test_validation_rule_fct_missing_time_agency_berth_eta__shipcall_eta_is_undefined_agency_eta_is_defined(build_sql_proxy_connection):
+ """0001-A validation_rule_fct_missing_time_agency_berth_eta"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # the shipcall is undefined
+ shipcall.eta = None
+
+ # set times agency to be undetermined
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.GREEN, f"function should return 'yellow', because the agency's entry is not present and the shipcall takes place soon"
+ return
+
+def test_validation_rule_fct_missing_time_agency_berth_etd__shipcall_etd_is_undefined_agency_etd_is_defined(build_sql_proxy_connection):
+ """0001-B validation_rule_fct_missing_time_agency_berth_etd"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # the shipcall etd is 'soon'
+ shipcall.etd = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.AGENCY-10)
+
+ # set times agency to be undetermined
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = None
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_agency_berth_etd(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the agency's entry is not present and the shipcall takes place soon"
+ return
+
+def test_validation_rule_fct_missing_time_mooring_berth_eta__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
+ """0001-C validation_rule_fct_missing_time_mooring_berth_eta"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # according to the agency, a shipcall takes place soon (ETA/ETD)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.MOORING-10)
+
+ # set times agency to be undetermined
+ df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, "eta_berth"] = None
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_mooring_berth_eta(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
+ return
+
+
+
+def test_validation_rule_fct_missing_time_mooring_berth_etd__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
+ """0001-D validation_rule_fct_missing_time_mooring_berth_etd"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # according to the agency, a shipcall takes place soon (ETA/ETD)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.MOORING-10)
+
+ # set times agency to be undetermined
+ df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, "etd_berth"] = None
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_mooring_berth_etd(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
+ return
+
+
+
+def test_validation_rule_fct_missing_time_portadministration_berth_eta__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
+ """0001-F validation_rule_fct_missing_time_portadministration_berth_eta"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # according to the agency, a shipcall takes place soon (ETA/ETD)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.PORT_ADMINISTRATION-10)
+
+ # set times agency to be undetermined
+ df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, "eta_berth"] = None
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_portadministration_berth_eta(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
+ return
+
+
+
+def test_validation_rule_fct_missing_time_portadministration_berth_etd__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
+ """0001-G validation_rule_fct_missing_time_portadministration_berth_etd"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # according to the agency, a shipcall takes place soon (ETA/ETD)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.PORT_ADMINISTRATION-10)
+
+ # set times agency to be undetermined
+ df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, "etd_berth"] = None
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_portadministration_berth_etd(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
+ return
+
+def test_validation_rule_fct_missing_time_pilot_berth_eta__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
+ """0001-H validation_rule_fct_missing_time_pilot_berth_eta"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # according to the agency, a shipcall takes place soon (ETA/ETD)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.PILOT-10)
+
+ # set times agency to be undetermined
+ df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "eta_berth"] = None
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_pilot_berth_eta(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
+ return
+
+def test_validation_rule_fct_missing_time_pilot_berth_etd__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
+ """0001-I validation_rule_fct_missing_time_pilot_berth_etd"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # according to the agency, a shipcall takes place soon (ETA/ETD)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.PILOT-10)
+
+ # set times agency to be undetermined
+ df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "etd_berth"] = None
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_pilot_berth_etd(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
+ return
+
+
+
+def test_validation_rule_fct_missing_time_tug_berth_eta__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
+ """0001-J validation_rule_fct_missing_time_tug_berth_eta"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # according to the agency, a shipcall takes place soon (ETA/ETD)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.TUG-10)
+
+ # set times agency to be undetermined
+ df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, "eta_berth"] = None
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_tug_berth_eta(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
+ return
+
+
+
+def test_validation_rule_fct_missing_time_tug_berth_etd__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
+ """0001-K validation_rule_fct_missing_time_tug_berth_etd"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # according to the agency, a shipcall takes place soon (ETA/ETD)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.TUG-10)
+
+ # set times agency to be undetermined
+ df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, "etd_berth"] = None
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_tug_berth_etd(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
+ return
+
+
+
+def test_validation_rule_fct_missing_time_terminal_berth_eta__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
+ """0001-L validation_rule_fct_missing_time_terminal_berth_eta"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # according to the agency, a shipcall takes place soon (ETA/ETD)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.TERMINAL-10)
+
+ # set times agency to be undetermined
+ df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "eta_berth"] = None
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_terminal_berth_eta(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
+ return
+
+
+
+def test_validation_rule_fct_missing_time_terminal_berth_etd__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
+ """0001-M validation_rule_fct_missing_time_terminal_berth_etd"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ # according to the agency, a shipcall takes place soon (ETA/ETD)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.TERMINAL-10)
+
+ # set times agency to be undetermined
+ df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "etd_berth"] = None
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_missing_time_terminal_berth_etd(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
+ return
+
+
+def test_validation_rule_fct_shipcall_incoming_participants_disagree_on_eta__participants_disagree_on_time_but_different_shipcall_type(build_sql_proxy_connection):
+ """0002-A validation_rule_fct_shipcall_incoming_participants_disagree_on_eta"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+
+ # set shipcall type to NOT match the function -> returns 'green'
+ query = "eta_berth"
+ shipcall.type = ShipcallType.SHIFTING.value
+ df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall)
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_shipcall_incoming_participants_disagree_on_eta(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.GREEN, f"function should return 'green', because the shipcall type does not match the function"
+ return
+
+
+def test_validation_rule_fct_shipcall_incoming_participants_disagree_on_eta__participants_disagree_on_time(build_sql_proxy_connection):
+ """0002-A validation_rule_fct_shipcall_incoming_participants_disagree_on_eta"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+
+ # set shipcall type to match the function
+ query = "eta_berth"
+ shipcall.type = ShipcallType.INCOMING.value
+ df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall) # makes sure that there is disagreement among participants
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_shipcall_incoming_participants_disagree_on_eta(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.RED, f"function should return 'red', because agency and pilot disagree on the query"
+ return
+
+def test_validation_rule_fct_shipcall_incoming_participants_disagree_on_eta__participants_agree_on_time(build_sql_proxy_connection):
+ """0002-A validation_rule_fct_shipcall_incoming_participants_disagree_on_eta"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+
+ # set shipcall type to NOT match the function -> returns 'green'
+ query = "eta_berth"
+ shipcall.type = ShipcallType.INCOMING.value
+ df_times = get_df_times(shipcall)
+ df_times.loc[:,query] = datetime.datetime.now()
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_shipcall_incoming_participants_disagree_on_eta(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.GREEN, f"function should return 'green', because the participants fully agree on the time"
+ return
+
+def test_validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd__participants_disagree_on_time_but_different_shipcall_type(build_sql_proxy_connection):
+ """0002-B validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+
+ # set shipcall type to NOT match the function -> returns 'green'
+ query = "etd_berth"
+ shipcall.type = ShipcallType.SHIFTING.value
+ df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall)
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.GREEN, f"function should return 'green', because the shipcall type does not match the function"
+ return
+
+
+def test_validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd__participants_disagree_on_time(build_sql_proxy_connection):
+ """0002-B validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+
+ # set shipcall type to match the function
+ query = "etd_berth"
+ shipcall.type = ShipcallType.OUTGOING.value
+ df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall) # makes sure that there is disagreement among participants
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.RED, f"function should return 'red', because agency and pilot disagree on the query"
+ return
+
+def test_validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd__participants_agree_on_time(build_sql_proxy_connection):
+ """0002-B validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+
+ # set shipcall type to NOT match the function -> returns 'green'
+ query = "etd_berth"
+ shipcall.type = ShipcallType.OUTGOING.value
+ df_times = get_df_times(shipcall)
+ df_times.loc[:,query] = datetime.datetime.now()
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.GREEN, f"function should return 'green', because the participants fully agree on the time"
+ return
+
+
+
+def test_validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd__participants_disagree_on_time_but_different_shipcall_type(build_sql_proxy_connection):
+ """0002-C validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ shipcall.type = ShipcallType.INCOMING.value
+ df_times = get_df_times(shipcall)
+
+ # set shipcall type to match the function
+ query = "eta_berth"
+ df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall, df_times=df_times) # makes sure that there is disagreement among participants
+
+ # set shipcall type to match the function
+ query = "etd_berth"
+ df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall, df_times=df_times) # makes sure that there is disagreement among participants
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.GREEN, f"function should return 'green', because the shipcall type does not match the function"
+ return
+
+def test_validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd__participants_disagree_on_time(build_sql_proxy_connection):
+ """0002-C validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ shipcall.type = ShipcallType.SHIFTING.value
+ df_times = get_df_times(shipcall)
+
+ # set shipcall type to match the function
+ query = "eta_berth"
+ df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall, df_times=df_times) # makes sure that there is disagreement among participants
+
+ # set shipcall type to match the function
+ query = "etd_berth"
+ df_times = get_df_times_participants_disagree(query=query, shipcall=shipcall, df_times=df_times) # makes sure that there is disagreement among participants
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.RED, f"function should return 'red', because agency and pilot disagree on the query"
+ return
+
+def test_validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd__participants_agree_on_time(build_sql_proxy_connection):
+ """0002-C validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd"""
+ vr = build_sql_proxy_connection['vr']
+
+ shipcall = get_shipcall_simple()
+ shipcall.type = ShipcallType.SHIFTING.value
+ df_times = get_df_times(shipcall)
+
+ # set shipcall type to match the function
+ query = "eta_berth"
+ df_times.loc[:,query] = datetime.datetime.now()
+
+ # set shipcall type to match the function
+ query = "etd_berth"
+ df_times.loc[:,query] = datetime.datetime.now()
+
+ # apply the validation rule
+ (state, msg) = vr.validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd(shipcall=shipcall, df_times=df_times)
+
+ # expectation: green state, no msg
+ assert state==StatusFlags.GREEN, f"function should return 'green', because the participants fully agree on the time"
+ return
+
+def test_validation_rule_fct_eta_time_not_in_operation_window__times_dont_match(build_sql_proxy_connection):
+ """0003-A validation_rule_fct_eta_time_not_in_operation_window"""
+ vr = build_sql_proxy_connection['vr']
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ t0_time = datetime.datetime.now() # reference time for easier readability
+
+ # the planned operations_start is before eta_berth (by one minute in this case)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = t0_time + datetime.timedelta(minutes=1)
+ df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "operations_start"] = t0_time + datetime.timedelta(minutes=0)
+
+ (code, msg) = vr.validation_rule_fct_eta_time_not_in_operation_window(shipcall, df_times)
+ assert code==StatusFlags.RED, f"status flag should be 'red', because the planned operations start is BEFORE the estimated time of arrival for the shipcall"
+ return
+
+def test_validation_rule_fct_etd_time_not_in_operation_window__times_dont_match(build_sql_proxy_connection):
+ """0003-B validation_rule_fct_etd_time_not_in_operation_window"""
+ vr = build_sql_proxy_connection['vr']
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ t0_time = datetime.datetime.now() # reference time for easier readability
+
+ # the planned operations_end is after etd_berth (by one minute in this case)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = t0_time + datetime.timedelta(hours=1)
+ df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "operations_end"] = t0_time+datetime.timedelta(hours=1, minutes=1)
+
+
+ (code, msg) = vr.validation_rule_fct_etd_time_not_in_operation_window(shipcall, df_times)
+ assert code==StatusFlags.RED, f"status flag should be 'red', because the planned operations end is AFTER the estimated time of departure for the shipcall"
+ return
+
+def test_validation_rule_fct_eta_time_not_in_operation_window_and_validation_rule_fct_etd_time_not_in_operation_window__always_okay(build_sql_proxy_connection):
+ """
+ 0003-A validation_rule_fct_eta_time_not_in_operation_window
+ 0003-B validation_rule_fct_etd_time_not_in_operation_window
+ """
+ vr = build_sql_proxy_connection['vr']
+ import random
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ t0_time = datetime.datetime.now()
+
+ # 10 random permutations of None/pd.NaT/suitable values
+ # each of these combinations is okay and should return a 'green' state
+ for _i in range(10):
+ # eta_berth & operations start
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = random.sample([None, pd.NaT, t0_time + datetime.timedelta(minutes=0)],k=1)[0]
+ df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "operations_start"] = random.sample([None, pd.NaT, t0_time + datetime.timedelta(minutes=0)], k=1)[0]
+
+ # etd_berth & operations start
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = random.sample([None, pd.NaT, t0_time + datetime.timedelta(hours=1)],k=1)[0]
+ df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "operations_end"] = random.sample([None, pd.NaT, t0_time+datetime.timedelta(hours=1)],k=1)[0]
+
+ (code, msg) = vr.validation_rule_fct_eta_time_not_in_operation_window(shipcall=shipcall, df_times=df_times)
+ assert code==StatusFlags.GREEN, f"status flag should be 'green', as any of these perturbations sets operation & estimated time to be on par ot one the values missed"
+ (code, msg) = vr.validation_rule_fct_etd_time_not_in_operation_window(shipcall=shipcall, df_times=df_times)
+ assert code==StatusFlags.GREEN, f"status flag should be 'green', as any of these perturbations sets operation & estimated time to be on par ot one the values missed"
+ return
+
+def test_validation_rule_fct_eta_time_not_in_tidal_window__is_okay(build_sql_proxy_connection):
+ """0004-A validation_rule_fct_eta_time_not_in_tidal_window"""
+ vr = build_sql_proxy_connection['vr']
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ t0_time = datetime.datetime.now()
+
+ # tidal window: [t0 +1min, t0 +1hr)
+ # eta berth:
+ shipcall.tidal_window_from = t0_time + datetime.timedelta(minutes=1)
+ shipcall.tidal_window_to = t0_time + datetime.timedelta(hours=1)
+
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = t0_time + datetime.timedelta(minutes=1)
+ (code, msg) = vr.validation_rule_fct_eta_time_not_in_tidal_window(shipcall, df_times)
+ assert code==StatusFlags.GREEN, f"state should be 'green', because eta_berth matches precisely the tidal_window_from"
+
+
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = t0_time + datetime.timedelta(hours=1)
+ (code, msg) = vr.validation_rule_fct_eta_time_not_in_tidal_window(shipcall, df_times)
+ assert code==StatusFlags.GREEN, f"state should be 'green', because eta_berth matches precisely the tidal_window_to (in this case, the etd_berth would likely cause an issue)"
+ return
+
+def test_validation_rule_fct_eta_time_not_in_tidal_window__eta_outside_tidal_window(build_sql_proxy_connection):
+ """0004-A validation_rule_fct_eta_time_not_in_tidal_window"""
+ vr = build_sql_proxy_connection['vr']
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ t0_time = datetime.datetime.now()
+
+ # tidal window: [t0 +1min, t0 +1hr)
+ # eta berth: t0+0min
+ shipcall.tidal_window_from = t0_time + datetime.timedelta(minutes=1)
+ shipcall.tidal_window_to = t0_time + datetime.timedelta(hours=1)
+
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = t0_time + datetime.timedelta(minutes=0)
+ (code, msg) = vr.validation_rule_fct_eta_time_not_in_tidal_window(shipcall, df_times)
+ assert code==StatusFlags.RED, f"state should be 'red', eta_berth takes place before the tidal window"
+
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = t0_time + datetime.timedelta(hours=1, minutes=1)
+ (code, msg) = vr.validation_rule_fct_eta_time_not_in_tidal_window(shipcall, df_times)
+ assert code==StatusFlags.RED, f"state should be 'red', eta_berth takes place after the tidal window"
+ return
+
+
+
+def test_validation_rule_fct_etd_time_not_in_tidal_window__is_okay(build_sql_proxy_connection):
+ """0004-B validation_rule_fct_etd_time_not_in_tidal_window"""
+ vr = build_sql_proxy_connection['vr']
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ t0_time = datetime.datetime.now()
+
+ # tidal window: [t0 +1min, t0 +1hr)
+ # etd berth:
+ shipcall.tidal_window_from = t0_time + datetime.timedelta(minutes=1)
+ shipcall.tidal_window_to = t0_time + datetime.timedelta(hours=1)
+
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = t0_time + datetime.timedelta(minutes=1)
+ (code, msg) = vr.validation_rule_fct_etd_time_not_in_tidal_window(shipcall, df_times)
+ assert code==StatusFlags.GREEN, f"state should be 'green', because etd_berth matches precisely the tidal_window_from"
+
+
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = t0_time + datetime.timedelta(hours=1)
+ (code, msg) = vr.validation_rule_fct_etd_time_not_in_tidal_window(shipcall, df_times)
+ assert code==StatusFlags.GREEN, f"state should be 'green', because etd_berth matches precisely the tidal_window_to (in this case, the etd_berth would likely cause an issue)"
+ return
+
+def test_validation_rule_fct_etd_time_not_in_tidal_window__etd_outside_tidal_window(build_sql_proxy_connection):
+ """0004-B validation_rule_fct_etd_time_not_in_tidal_window"""
+ vr = build_sql_proxy_connection['vr']
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+
+ t0_time = datetime.datetime.now()
+
+ # tidal window: [t0 +1min, t0 +1hr)
+ # etd berth:
+ shipcall.tidal_window_from = t0_time + datetime.timedelta(minutes=1)
+ shipcall.tidal_window_to = t0_time + datetime.timedelta(hours=1)
+
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = t0_time + datetime.timedelta(minutes=0)
+ (code, msg) = vr.validation_rule_fct_etd_time_not_in_tidal_window(shipcall, df_times)
+ assert code==StatusFlags.RED, f"state should be 'red', etd_berth takes place before the tidal window"
+
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = t0_time + datetime.timedelta(hours=1, minutes=1)
+ (code, msg) = vr.validation_rule_fct_etd_time_not_in_tidal_window(shipcall, df_times)
+ assert code==StatusFlags.RED, f"state should be 'red', etd_berth takes place after the tidal window"
+ return
+
+def test_validation_rule_fct_too_many_identical_eta_times__is_violated_by_too_many_identical_times(build_sql_proxy_connection):
+ """0005-A validation_rule_fct_too_many_identical_eta_times"""
+ vr = build_sql_proxy_connection['vr']
+ query = "eta_berth"
+
+ reference_time = pd.Timestamp(datetime.datetime.now())
+ reference_time = reference_time.round("min")
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+ df_times = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
+ df_times.loc[:,query] = reference_time + datetime.timedelta(seconds=12)
+
+ all_times_df = build_stub_df_times(shipcall, query, reference_time)
+ (code, msg) = vr.validation_rule_fct_too_many_identical_eta_times(shipcall=shipcall, df_times=df_times, all_times_agency=all_times_df)
+ assert code == StatusFlags.YELLOW, f"status should be 'yellow', because the artificial 'all_times_df' contains five shipcalls with identical times, which exceeds the threshold"
+ return
+
+def test_validation_rule_fct_too_many_identical_etd_times__is_violated_by_too_many_identical_times(build_sql_proxy_connection):
+ """0005-B validation_rule_fct_too_many_identical_etd_times"""
+ vr = build_sql_proxy_connection['vr']
+ query = "etd_berth"
+
+ reference_time = pd.Timestamp(datetime.datetime.now())
+ reference_time = reference_time.round("min")
+
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+ df_times = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
+ df_times.loc[:,query] = reference_time + datetime.timedelta(seconds=12)
+
+ all_times_df = build_stub_df_times(shipcall, query, reference_time)
+ (code, msg) = vr.validation_rule_fct_too_many_identical_etd_times(shipcall=shipcall, df_times=df_times, all_times_agency=all_times_df)
+ assert code == StatusFlags.YELLOW, f"status should be 'yellow', because the artificial 'all_times_df' contains five shipcalls with identical times, which exceeds the threshold"
+ return
+
+def test_validation_rule_fct_agency_and_terminal_berth_id_disagreement__agency_and_terminal_agree(build_sql_proxy_connection):
+ """0006-A validation_rule_fct_agency_and_terminal_berth_id_disagreement"""
+ vr = build_sql_proxy_connection['vr']
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "berth_id"] = 143
+ df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "berth_id"] = 143
+
+ (code, msg) = vr.validation_rule_fct_agency_and_terminal_berth_id_disagreement(shipcall=shipcall, df_times=df_times)
+ assert code==StatusFlags.GREEN, f"status should be 'green', because agency and terminal agree on the selected berth id"
+ return
+
+def test_validation_rule_fct_agency_and_terminal_berth_id_disagreement__agency_and_terminal_disagree(build_sql_proxy_connection):
+ """0006-A validation_rule_fct_agency_and_terminal_berth_id_disagreement"""
+ vr = build_sql_proxy_connection['vr']
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "berth_id"] = 143
+ df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "berth_id"] = 145
+
+ (code, msg) = vr.validation_rule_fct_agency_and_terminal_berth_id_disagreement(shipcall=shipcall, df_times=df_times)
+ assert code==StatusFlags.YELLOW, f"status should be 'yellow', because agency and terminal do not agree on the selected berth id"
+ return
+
+def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement__agency_and_terminal_agree(build_sql_proxy_connection):
+ """0006-B validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
+ vr = build_sql_proxy_connection['vr']
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "pier_side"] = True
+ df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "pier_side"] = True
+
+ (code, msg) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall=shipcall, df_times=df_times)
+ assert code==StatusFlags.GREEN, f"status should be 'green', because agency and terminal agree on the selected pier side"
+ return
+
+def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement__agency_and_terminal_disagree(build_sql_proxy_connection):
+ """0006-B validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
+ vr = build_sql_proxy_connection['vr']
+ shipcall = get_shipcall_simple()
+ df_times = get_df_times(shipcall)
+ df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "pier_side"] = True
+ df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "pier_side"] = False
+
+ (code, msg) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall=shipcall, df_times=df_times)
+ assert code==StatusFlags.YELLOW, f"status should be 'yellow', because agency and terminal do not agree on the selected pier side"
+ return
+
+
def test_validation_rule_fct_agency_and_terminal_pier_side_agreement(build_sql_proxy_connection):
"""#0006-A validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
@@ -89,8 +853,36 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_agreement(build_sql_p
assert description is None, f"no violation should be observed"
return
+def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement(build_sql_proxy_connection):
+ """#0006-A validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
+ import pandas as pd
+ from BreCal.stubs.times_full import get_times_full_simple
+ from BreCal.stubs.shipcall import get_shipcall_simple
+ from BreCal.database.enums import ParticipantType
+ from BreCal.database.enums import StatusFlags
+ vr = build_sql_proxy_connection["vr"]
+ shipcall = get_shipcall_simple()
+ t1 = get_times_full_simple()
+ t2 = get_times_full_simple()
+
+ # roles: agency & terminal
+ t1.participant_type = ParticipantType.AGENCY.value
+ t2.participant_type = ParticipantType.TERMINAL.value
+
+ # disagreement
+ t1.pier_side = True
+ t2.pier_side = False
+
+ time_objects = [t1, t2]
+ df_times = pd.DataFrame.from_records([to_.__dict__ for to_ in time_objects])
+ df_times.set_index('id',inplace=True)
+
+ (state, description) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall, df_times)
+ assert state.value > StatusFlags.GREEN.value, f"a violation must be identified"
+ assert description is not None, f"a violation description must be identified"
+ return
def test_validation_rule_fct_agency_and_terminal_berth_id_disagreement(build_sql_proxy_connection):
"""#0006-B validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
@@ -156,4 +948,12 @@ def test_validation_rule_fct_agency_and_terminal_berth_id_agreement(build_sql_pr
+def test_all_validation_rule_fcts_have_a_description():
+ from BreCal.validators.validation_rule_functions import error_message_dict, ValidationRuleFunctions
+ import types
+ vr = ValidationRuleFunctions(sql_handler=None)
+ assert all(
+ [mthd_ in list(error_message_dict.keys()) for mthd_ in dir(vr) if ('validation_rule_fct' in mthd_)]
+ ), f"one of the validation_rule_fcts is currently not defined in the error_message_dict and will create cryptic descriptions! Please add it to the error_message_dict BreCal.validators.validation_rule_functions"
+ return