Merge branch 'release/pub_0.9.4'

This commit is contained in:
Daniel Schick 2023-10-27 09:05:08 +02:00
commit e77c9264b9
107 changed files with 6848 additions and 1307 deletions

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@ info:
servers:
# tutorial: https://idratherbewriting.com/learnapidoc/pubapis_openapi_step3_servers_object.html
- url: "https://brecaltest.bsmd-emswe.eu/"
- url: "https://brecal.bsmd-emswe.eu/"
description: "Test server hosted on vcup"
paths:
@ -68,13 +68,13 @@ paths:
/shipcalls:
get:
summary: Gets a list of ship calls
#parameters:
# - name: participant_id
# in: query
# required: true
# description: "**Id of participant**. *Example: 2*. Id of participant entity requesting ship calls"
# schema:
# type: integer
parameters:
- name: past_days
in: query
required: false
description: "number of days in the past to include in the result. *Example: 7*."
schema:
type: integer
responses:
200:
description: ship call list
@ -100,6 +100,8 @@ paths:
schema:
$ref: "#/components/schemas/shipcall"
responses:
201:
$ref: "#/components/responses/201"
400:
$ref: "#/components/responses/400"
401:
@ -119,6 +121,8 @@ paths:
schema:
$ref: "#/components/schemas/shipcall"
responses:
200:
$ref: "#/components/responses/200"
400:
$ref: "#/components/responses/400"
401:
@ -210,6 +214,8 @@ paths:
schema:
$ref: "#/components/schemas/times"
responses:
201:
$ref: "#/components/responses/201"
400:
$ref: "#/components/responses/400"
401:
@ -229,6 +235,8 @@ paths:
schema:
$ref: "#/components/schemas/times"
responses:
200:
$ref: "#/components/responses/200"
400:
$ref: "#/components/responses/400"
401:
@ -324,13 +332,23 @@ components:
shipcallId:
description: The unique identifier of a ship call
type: integer
participant_assignment:
description: Participant assigned to a shipcall with a given role (type)
type: object
required:
- participant_id
- type
properties:
participant_id:
type: integer
type:
type: integer
shipcall:
type: object
required:
- id
- ship_id
- type
- eta
properties:
id:
$ref: "#/components/schemas/shipcallId"
@ -342,6 +360,7 @@ components:
eta:
type: string
format: date-time
nullable: true
voyage:
type: string
maxLength: 16
@ -404,11 +423,16 @@ components:
canceled:
type: boolean
nullable: true
evaluation:
type: integer
nullable: true
evaluation_message:
type: string
nullable: true
participants:
type: array
items:
type: integer
example: [1, 5, 7]
$ref: "#/components/schemas/participant_assignment"
created:
type: string
format: date-time
@ -509,7 +533,10 @@ components:
name:
type: string
maxLength: 128
participant_id:
owner_id:
type: integer
nullable: true
authority_id:
type: integer
nullable: true
lock:
@ -669,6 +696,8 @@ components:
type: string
user_phone:
type: string
user_email:
type: string
exp:
type: number
format: float
@ -696,7 +725,15 @@ components:
user_phone:
type: string
nullable: true
user_email:
type: string
nullable: true
Id:
type: object
description: A unique identifier for an entity
properties:
id:
type: integer
Error:
type: object
required:
@ -712,6 +749,18 @@ components:
in: header
name: Authorization
responses:
200:
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Id"
201:
description: Created
content:
application/json:
schema:
$ref: "#/components/schemas/Id"
400:
description: Invalid input
content:

110
misc/Deployment.md Normal file
View File

@ -0,0 +1,110 @@
# System deployment
___
## Prerequisites
## Client
## Database
## Backend / Flask app
In order to not have complicated and error-prone copying manoevers a direct deployment from the repo is used using git.
### File structure
### 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.
3) In the shell, activate ssh-agent and add the key. For example:
```bash
eval ($ssh-agent)
ssh-add ~/.ssh/od_ed25519
```
4) Change to deployment folder
```bash
cd /var/www/brecal_test
```
5) Perform sparse checkout on the Flask server subtree
```bash
git clone -n git@github.com:puls200/brecal.git <target_folder>
cd <target_folder>
git sparse-checkout set --no-cone src/server
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.
### Installing Requirements
Python 3.11 & Pip3.11 installation (linux), virtualenv package
Optional, Step 0: Python source installation
Windows (untested):
Python 3.11: https://www.geeksforgeeks.org/download-and-install-python-3-latest-version/
PIP package manager (in command window): py -m ensurepip --upgrade
Virtual Environment 'virtualenv': https://mothergeo-py.readthedocs.io/en/latest/development/how-to/venv-win.html
Linux:
Python 3.11 & the PIP package manager: https://tecadmin.net/how-to-install-python-3-11-on-ubuntu-22-04/
Virtual Environment 'virtualenv'
### Building the Virtual Environment
Create a virtualenv called '.venv' with a specified python version, such as python3.11
All python packages will be installed in the virtual environment. Upon running the application,
make sure to activate the virtual environment before starting it.
Windows: # on windows, provide the full path, as obtained by "where python"
1.) Relocate to the 'brecal' directory
cd brecal
2.) create a virtual environment with the name '.venv'
where python
virtualenv --python {python_path} .venv
3.) activate the virtual environment and install
.venv\Scripts\activate
cd ./src/server
pip install -r requirements.txt
Linux:
1.) Relocate to the 'brecal' directory
cd brecal
2.) create a virtual environment with the name '.venv'
virtualenv --python python311 .venv
3.) activate the virtual environment and install
source .venv/bin/activate
cd ./src/server
pip install -r requirements.txt
### Testing (using 'pytest')
All tests in the directory brecal/src/server/tests can be run automatically with the 'pytest' module It recognizes any function within these
scripts, as these use 'test_'-prefixes. The module can be used to run the tests or even to create coverage reports, which show, what portions
of the directory are still untested. When the pytest module is installed, one can use a single line of code to run all tests.
1.) Relocate to the application's directory
cd brecal/src/server
2.) activate the virtual environment
Windows:
source .venv\Scripts\activate
Linux:
source .venv\bin\activate
3.) run the tests
option1: run pytest as a standalone
pytest tests/
option2: run pytest & coverage report (inspects coverage for the folder 'BreCal' by running each test in 'tests/'
this creates a folder with the name 'coverage_reports', where .html files are created and can be observed by a developer.
the coverage files are typically ignored by .gitignore
pytest --cov BreCal tests/

View File

@ -1,8 +1,6 @@
CREATE DATABASE IF NOT EXISTS `bremen_calling` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `bremen_calling`;
-- MySQL dump 10.13 Distrib 8.0.33, for Win64 (x86_64)
--
-- Host: localhost Database: bremen_calling
-- Host: localhost Database: bremen_calling_test
-- ------------------------------------------------------
-- Server version 8.0.34-0ubuntu0.22.04.1
@ -27,14 +25,17 @@ DROP TABLE IF EXISTS `berth`;
CREATE TABLE `berth` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) DEFAULT NULL COMMENT 'Descriptive name',
`participant_id` int unsigned DEFAULT NULL COMMENT 'If berth belongs to a participant, reference it here',
`lock` bit(1) DEFAULT NULL COMMENT 'The lock must be used',
`owner_id` int unsigned DEFAULT NULL,
`authority_id` int unsigned DEFAULT NULL,
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`deleted` bit(1) DEFAULT b'0',
PRIMARY KEY (`id`),
KEY `FK_BERTH_PART` (`participant_id`),
CONSTRAINT `FK_BERTH_PART` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`)
KEY `FK_OWNER_PART_idx` (`owner_id`),
KEY `FK_AUTHORITY_PART_idx` (`authority_id`),
CONSTRAINT `FK_AUTHORITY_PART` FOREIGN KEY (`authority_id`) REFERENCES `participant` (`id`),
CONSTRAINT `FK_OWNER_PART` FOREIGN KEY (`owner_id`) REFERENCES `participant` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=195 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Berth of ship for a ship call';
/*!40101 SET character_set_client = @saved_cs_client */;
@ -82,7 +83,7 @@ CREATE TABLE `participant` (
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`deleted` bit(1) DEFAULT b'0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=136 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='An organization taking part';
) ENGINE=InnoDB AUTO_INCREMENT=137 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='An organization taking part';
/*!40101 SET character_set_client = @saved_cs_client */;
--
@ -165,7 +166,7 @@ CREATE TABLE `ship` (
PRIMARY KEY (`id`),
KEY `FK_SHIP_PARTICIPANT` (`participant_id`),
CONSTRAINT `FK_SHIP_PARTICIPANT` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@ -199,6 +200,8 @@ CREATE TABLE `shipcall` (
`anchored` bit(1) DEFAULT NULL,
`moored_lock` bit(1) DEFAULT NULL,
`canceled` bit(1) DEFAULT NULL,
`evaluation` int unsigned DEFAULT NULL,
`evaluation_message` varchar(512) DEFAULT NULL,
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
@ -208,7 +211,7 @@ CREATE TABLE `shipcall` (
CONSTRAINT `FK_SHIPCALL_BERTH_ARRIVAL` FOREIGN KEY (`arrival_berth_id`) REFERENCES `berth` (`id`),
CONSTRAINT `FK_SHIPCALL_BERTH_DEPARTURE` FOREIGN KEY (`departure_berth_id`) REFERENCES `berth` (`id`),
CONSTRAINT `FK_SHIPCALL_SHIP` FOREIGN KEY (`ship_id`) REFERENCES `ship` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Incoming, outgoing or moving to another berth';
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Incoming, outgoing or moving to another berth';
/*!40101 SET character_set_client = @saved_cs_client */;
--
@ -222,6 +225,7 @@ CREATE TABLE `shipcall_participant_map` (
`id` int NOT NULL AUTO_INCREMENT,
`shipcall_id` int unsigned DEFAULT NULL,
`participant_id` int unsigned DEFAULT NULL,
`type` int unsigned DEFAULT NULL COMMENT 'Type of participant role',
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
@ -229,7 +233,7 @@ CREATE TABLE `shipcall_participant_map` (
KEY `FK_MAP_SHIPCALL_PARTICIPANT` (`participant_id`),
CONSTRAINT `FK_MAP_PARTICIPANT_SHIPCALL` FOREIGN KEY (`shipcall_id`) REFERENCES `shipcall` (`id`),
CONSTRAINT `FK_MAP_SHIPCALL_PARTICIPANT` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=82 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Associates a participant with a shipcall';
) ENGINE=InnoDB AUTO_INCREMENT=128 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Associates a participant with a shipcall';
/*!40101 SET character_set_client = @saved_cs_client */;
--
@ -287,7 +291,7 @@ CREATE TABLE `times` (
KEY `FK_TIME_BERTH` (`berth_id`) /*!80000 INVISIBLE */,
CONSTRAINT `FK_TIME_BERTH` FOREIGN KEY (`berth_id`) REFERENCES `berth` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FK_TIME_PART` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='the planned time for the participants work';
) ENGINE=InnoDB AUTO_INCREMENT=44 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='the planned time for the participants work';
/*!40101 SET character_set_client = @saved_cs_client */;
--
@ -312,7 +316,7 @@ CREATE TABLE `user` (
PRIMARY KEY (`id`),
KEY `FK_USER_PART` (`participant_id`),
CONSTRAINT `FK_USER_PART` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='member of a participant';
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='member of a participant';
/*!40101 SET character_set_client = @saved_cs_client */;
--
@ -345,4 +349,4 @@ CREATE TABLE `user_role_map` (
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2023-09-05 10:36:12
-- Dump completed on 2023-10-06 14:52:04

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,7 @@
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:p = "clr-namespace:BreCalClient.Resources"
mc:Ignorable="d"
Title="Help" Height="280" Width="500">
Title="Help" Height="374" Width="500" Loaded="Window_Loaded">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
@ -20,6 +20,10 @@
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="10" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
@ -39,14 +43,20 @@
</Hyperlink>
</TextBlock>
<Label FontWeight="DemiBold" Grid.Row="3" Grid.Column="0" Content="{x:Static p:Resources.textChangePassword}" HorizontalContentAlignment="Right"/>
<xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textOldPassword}" Grid.Column="1" Grid.Row="3" Margin="2" x:Name="wpBoxOldPassword" TextChanged="wpBoxOldPassword_TextChanged"/>
<xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textNewPassword}" Grid.Column="1" Grid.Row="4" Margin="2" x:Name="wpBoxNewPassword" TextChanged="wpBoxOldPassword_TextChanged"/>
<xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textRepeatNewPassword}" Grid.Column="1" Grid.Row="5" Margin="2" x:Name="wpBoxNewPasswordRepeat" TextChanged="wpBoxOldPassword_TextChanged"/>
<Button x:Name="buttonChangePassword" Click="buttonChangePassword_Click" Grid.Column="1" Grid.Row="6" Margin="2" Content="{x:Static p:Resources.textChange}" Width="80" HorizontalAlignment="Left" IsEnabled="False" />
<Label FontWeight="DemiBold" Grid.Row="3" Grid.Column="0" Content="{x:Static p:Resources.textChangeContactInfo}" HorizontalContentAlignment="Right"/>
<Label Grid.Row="4" Grid.Column="0" Content="{x:Static p:Resources.textEmail}" HorizontalContentAlignment="Right" />
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static p:Resources.textPhone}" HorizontalContentAlignment="Right" />
<TextBox Name="textBoxUserEmail" Grid.Column="1" Grid.Row="4" Margin="2" VerticalContentAlignment="Center" />
<TextBox Name="textBoxUserPhone" Grid.Column="1" Grid.Row="5" Margin="2" VerticalContentAlignment="Center" />
<Button x:Name="buttonClose" Click="buttonClose_Click" Content="{x:Static p:Resources.textClose}" Width="80" Margin="2" Grid.Column="1" Grid.Row="8" HorizontalAlignment="Right" />
<Label FontWeight="DemiBold" Grid.Row="7" Grid.Column="0" Content="{x:Static p:Resources.textChangePassword}" HorizontalContentAlignment="Right"/>
<xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textOldPassword}" Grid.Column="1" Grid.Row="7" Margin="2" x:Name="wpBoxOldPassword" TextChanged="wpBoxOldPassword_TextChanged"/>
<xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textNewPassword}" Grid.Column="1" Grid.Row="8" Margin="2" x:Name="wpBoxNewPassword" TextChanged="wpBoxOldPassword_TextChanged"/>
<xctk:WatermarkPasswordBox Watermark="{x:Static p:Resources.textRepeatNewPassword}" Grid.Column="1" Grid.Row="9" Margin="2" x:Name="wpBoxNewPasswordRepeat" TextChanged="wpBoxOldPassword_TextChanged"/>
<Button x:Name="buttonChangePassword" Click="buttonChangePassword_Click" Grid.Column="1" Grid.Row="10" Margin="2" Content="{x:Static p:Resources.textChange}" Width="80" HorizontalAlignment="Left" IsEnabled="False" />
<Button x:Name="buttonClose" Click="buttonClose_Click" Content="{x:Static p:Resources.textClose}" Width="80" Margin="2" Grid.Column="1" Grid.Row="12" HorizontalAlignment="Right" />
</Grid>
</Window>

View File

@ -2,6 +2,7 @@
// Description: Show about info and allow user detail editing
//
using BreCalClient.misc.Model;
using System;
using System.Diagnostics;
using System.Windows;
@ -24,6 +25,12 @@ namespace BreCalClient
#endregion
#region Properties
public LoginResult? LoginResult { get; set; }
#endregion
#region events
public event Action<string, string>? ChangePasswordRequested;
@ -39,6 +46,12 @@ namespace BreCalClient
private void buttonChangePassword_Click(object sender, RoutedEventArgs e)
{
if (this.LoginResult != null)
{
this.LoginResult.UserPhone = this.textBoxUserPhone.Text.Trim();
this.LoginResult.UserEmail = this.textBoxUserEmail.Text.Trim();
}
this.ChangePasswordRequested?.Invoke(this.wpBoxOldPassword.Password, this.wpBoxNewPassword.Password);
}
@ -56,9 +69,17 @@ namespace BreCalClient
(this.wpBoxNewPasswordRepeat.Password.Length > 0) &&
this.wpBoxNewPassword.Password.Equals(this.wpBoxNewPasswordRepeat.Password) &&
(!this.wpBoxNewPassword.Password.Equals(this.wpBoxOldPassword.Password));
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
if(LoginResult != null)
{
this.textBoxUserEmail.Text = LoginResult.UserEmail;
this.textBoxUserPhone.Text = LoginResult.UserPhone;
}
}
#endregion
}
}

View File

@ -14,10 +14,10 @@
<value>https://brecal.bsmd-emswe.eu</value>
</setting>
<setting name="BG_COLOR" serializeAs="String">
<value>#751D1F</value>
<value>#203864</value>
</setting>
<setting name="APP_TITLE" serializeAs="String">
<value>!!Bremen calling Testversion!!</value>
<value>Bremen calling</value>
</setting>
<setting name="LOGO_IMAGE_URL" serializeAs="String">
<value>https://www.textbausteine.net/</value>

View File

@ -8,12 +8,12 @@
<SignAssembly>True</SignAssembly>
<StartupObject>BreCalClient.App</StartupObject>
<AssemblyOriginatorKeyFile>..\..\misc\brecal.snk</AssemblyOriginatorKeyFile>
<AssemblyVersion>0.6.0.0</AssemblyVersion>
<FileVersion>0.6.0.0</FileVersion>
<AssemblyVersion>0.9.4.0</AssemblyVersion>
<FileVersion>0.9.4.0</FileVersion>
<Title>Bremen calling client</Title>
<Description>A Windows WPF client for the Bremen calling API.</Description>
<ApplicationIcon>containership.ico</ApplicationIcon>
<AssemblyName>BreCalTestClient</AssemblyName>
<AssemblyName>BreCalClient</AssemblyName>
</PropertyGroup>
<ItemGroup>
@ -24,14 +24,17 @@
<None Remove="Resources\arrow_up_blue.png" />
<None Remove="Resources\arrow_up_green.png" />
<None Remove="Resources\arrow_up_red.png" />
<None Remove="Resources\check.png" />
<None Remove="Resources\clipboard.png" />
<None Remove="Resources\clock.png" />
<None Remove="Resources\containership.ico" />
<None Remove="Resources\containership.png" />
<None Remove="Resources\delete.png" />
<None Remove="Resources\delete2.png" />
<None Remove="Resources\emergency_stop_button.png" />
<None Remove="Resources\logo_bremen_calling.png" />
<None Remove="Resources\ship2.png" />
<None Remove="Resources\sign_warning.png" />
<None Remove="Resources\trafficlight_green.png" />
<None Remove="Resources\trafficlight_off.png" />
<None Remove="Resources\trafficlight_on.png" />
@ -72,14 +75,17 @@
<Resource Include="Resources\arrow_up_blue.png" />
<Resource Include="Resources\arrow_up_green.png" />
<Resource Include="Resources\arrow_up_red.png" />
<Resource Include="Resources\check.png" />
<Resource Include="Resources\clipboard.png" />
<Resource Include="Resources\clock.png" />
<Resource Include="Resources\containership.ico" />
<Resource Include="Resources\containership.png" />
<Resource Include="Resources\delete.png" />
<Resource Include="Resources\delete2.png" />
<Resource Include="Resources\emergency_stop_button.png" />
<Resource Include="Resources\logo_bremen_calling.png" />
<Resource Include="Resources\ship2.png" />
<Resource Include="Resources\sign_warning.png" />
<Resource Include="Resources\StringResources.de.xaml">
<Generator>MSBuild:Compile</Generator>
</Resource>

View File

@ -0,0 +1,123 @@
// Copyright (c) 2023 schick Informatik
// Description: Static lists used everywhere
//
using BreCalClient.misc.Model;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace BreCalClient
{
public static class BreCalLists
{
#region Fields
private static readonly List<Participant> aList = new();
private static readonly List<Participant> mList = new();
private static readonly List<Participant> pList = new();
private static readonly List<Participant> tList = new();
private static readonly List<Participant> terList = new();
private static List<Berth> _berths = new();
private static List<Participant> _participants = new();
private static List<Ship> _ships = new();
private readonly static ConcurrentDictionary<int, Ship> _shipLookupDict = new();
private readonly static ConcurrentDictionary<int, Berth> _berthLookupDict = new();
private readonly static Dictionary<int, Participant> _participantLookupDict = new();
#endregion
#region Properties
public static ConcurrentDictionary<int, Ship> ShipLookupDict { get { return _shipLookupDict; } }
public static ConcurrentDictionary<int, Berth> BerthLookupDict { get { return _berthLookupDict; } }
public static Dictionary<int, Participant> ParticipantLookupDict { get { return _participantLookupDict; } }
/// <summary>
/// Participants that are agents
/// </summary>
public static List<Participant> Participants_Agent { get { return aList; } }
/// <summary>
/// Participants that are mooring companies
/// </summary>
public static List<Participant> Participants_Mooring { get { return mList; } }
/// <summary>
/// Participants that are pilots
/// </summary>
public static List<Participant> Participants_Pilot { get { return pList; } }
/// <summary>
/// Participants that are tug shipping companies
/// </summary>
public static List<Participant> Participants_Tug { get { return tList; } }
/// <summary>
/// Participants that are terminals
/// </summary>
public static List<Participant> Participants_Terminal { get { return terList; } }
/// <summary>
/// All participants
/// </summary>
public static List<Participant> Participants { get { return _participants; } }
/// <summary>
/// All berths
/// </summary>
public static List<Berth> Berths { get { return _berths; } }
/// <summary>
/// All ships
/// </summary>
public static List<Ship> Ships { get { return _ships; } }
#endregion
#region methods
internal static void InitializeParticipants(List<Participant> participants)
{
_participants = participants;
aList.Clear();
mList.Clear();
pList.Clear();
tList.Clear();
terList.Clear();
foreach (Participant p in participants)
{
_participantLookupDict[p.Id] = p;
if (p.IsTypeFlagSet(Extensions.ParticipantType.AGENCY)) aList.Add(p);
if (p.IsTypeFlagSet(Extensions.ParticipantType.MOORING)) mList.Add(p);
if (p.IsTypeFlagSet(Extensions.ParticipantType.PILOT)) pList.Add(p);
if (p.IsTypeFlagSet(Extensions.ParticipantType.TUG)) tList.Add(p);
if (p.IsTypeFlagSet(Extensions.ParticipantType.TERMINAL)) terList.Add(p);
}
}
internal static void InitializeBerths(List<Berth> berths)
{
foreach (var berth in berths)
_berthLookupDict[berth.Id] = berth;
_berths = berths;
}
internal static void InitializeShips(List<Ship> ships)
{
foreach (var ship in ships)
_shipLookupDict[ship.Id] = ship;
_ships = ships;
}
#endregion
}
}

View File

@ -8,7 +8,7 @@
xmlns:db="clr-namespace:BreCalClient;assembly=BreCalClient"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d"
Title="{x:Static p:Resources.textEditShipcall}" Height="466" Width="800" Loaded="Window_Loaded" ResizeMode="NoResize" Icon="Resources/containership.ico">
Title="{x:Static p:Resources.textEditShipcall}" Height="214" Width="800" Loaded="Window_Loaded" ResizeMode="NoResize" Icon="Resources/containership.ico">
<Window.Resources>
<local:BoolToIndexConverter x:Key="boolToIndexConverter" />
</Window.Resources>
@ -26,135 +26,73 @@
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<Label Content="{x:Static p:Resources.textShip}" Grid.Column="0" Grid.Row="0" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textType}" Grid.Column="0" Grid.Row="1" HorizontalContentAlignment="Right" />
<Label Content="ETA" Grid.Column="0" Grid.Row="2" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textVoyage}" Grid.Column="0" Grid.Row="3" HorizontalContentAlignment="Right"/>
<Label Content="ETD" Grid.Column="0" Grid.Row="4" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textArrivalTerminal}" Grid.Column="0" Grid.Row="5" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textDepartureTerminal}" Grid.Column="0" Grid.Row="6" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textTugRequired}" Grid.Column="1" Grid.Row="7" />
<Label Content="{x:Static p:Resources.textPilotRequired}" Grid.Column="1" Grid.Row="8" />
<Label Content="{x:Static p:Resources.textPierside}" Grid.Column="0" Grid.Row="9" HorizontalContentAlignment="Right" />
<Label Content="{x:Static p:Resources.textBunkering}" Grid.Column="1" Grid.Row="10" />
<Label Content="{x:Static p:Resources.textReplenishingTerminal}" Grid.Column="1" Grid.Row="11" />
<Label Content="{x:Static p:Resources.textReplenishingLock}" Grid.Column="1" Grid.Row="12" />
<Label Content="{x:Static p:Resources.textDraft}" Grid.Column="0" Grid.Row="13" HorizontalContentAlignment="Right" />
<ComboBox x:Name="comboBoxShip" Margin="2" Grid.Column="1" Grid.Row="0" SelectionChanged="comboBoxShip_SelectionChanged" SelectedValuePath="Id">
<ComboBox.ItemTemplate>
<DataTemplate>
<!--Border-->
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="Name" />
<Binding Path="Imo" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="Name" />
<Binding Path="Imo" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!--/Border-->
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox x:Name="comboBoxCategories" Grid.Column="1" Margin="2" Grid.Row="1" SelectedValuePath="Key"/>
<xctk:DateTimePicker x:Name="datePickerETA" Grid.Column="1" Grid.Row="2" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<TextBox x:Name="textBoxVoyage" Grid.Column="1" Grid.Row="3" Margin="2" VerticalContentAlignment="Center" />
<xctk:DateTimePicker x:Name="datePickerETD" Grid.Column="1" Grid.Row="4" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<ComboBox Name="comboBoxArrivalBerth" Grid.Column="1" Grid.Row="5" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemArrivalBerth" Click="contextMenuItemArrivalBerth_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<ComboBox Name="comboBoxDepartureBerth" Grid.Column="1" Grid.Row="6" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemDepartureBerth" Click="contextMenuItemDepartureBerth_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<CheckBox x:Name="checkBoxTugRequired" Grid.Column="0" Grid.Row="7" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,4,0" />
<CheckBox x:Name="checkBoxPilotRequired" Grid.Column="0" Grid.Row="8" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,4,0" />
<ComboBox x:Name="comboBoxPierside" Grid.Column="1" Grid.Row="9" Margin="2" >
<ComboBoxItem Content="{x:Static p:Resources.textNotRotated}" />
<ComboBoxItem Content="{x:Static p:Resources.textRotated}" />
</ComboBox>
<CheckBox x:Name="checkBoxBunkering" Grid.Column="0" Grid.Row="10" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,4,0" />
<CheckBox x:Name="checkBoxReplenishingTerminal" Grid.Column="0" Grid.Row="11" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,4,0" />
<CheckBox x:Name="checkBoxReplenishingLock" Grid.Column="0" Grid.Row="12" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,4,0" />
<xctk:DoubleUpDown x:Name="doubleUpDownDraft" Grid.Column="1" Grid.Row="13" Margin="2" FormatString="N2" Minimum="0" />
<Label Content="IMO" Grid.Column="0" Grid.Row="1" HorizontalContentAlignment="Right"/>
<xctk:IntegerUpDown x:Name="integerUpDownIMO" IsReadOnly="True" Margin="2" Grid.Column="1" Grid.Row="1" />
<Label Content="{x:Static p:Resources.textCallsign}" Grid.Column="0" Grid.Row="2" HorizontalContentAlignment="Right"/>
<TextBox x:Name="textBoxCallsign" IsReadOnly="True" Grid.Column="1" Grid.Row="2" Margin="2" />
<Label Content="{x:Static p:Resources.textLength}" Grid.Column="0" Grid.Row="3" HorizontalContentAlignment="Right"/>
<xctk:DoubleUpDown x:Name="doubleUpDownLength" Margin="2" Grid.Column="1" Grid.Row="3" FormatString="N2" IsReadOnly="True" />
<Label Content="{x:Static p:Resources.textWidth}" Grid.Column="0" Grid.Row="4" HorizontalContentAlignment="Right"/>
<xctk:DoubleUpDown x:Name="doubleUpDownWidth" Margin="2" Grid.Column="1" Grid.Row="4" FormatString="N2" IsReadOnly="True" />
<Label Content="{x:Static p:Resources.textType}" Grid.Column="2" Grid.Row="0" HorizontalContentAlignment="Right" />
<Label Content="ETA" Grid.Column="2" Grid.Row="2" HorizontalContentAlignment="Right"/>
<Label Content="ETD" Grid.Column="2" Grid.Row="3" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textParticipants}" FontWeight="DemiBold" Grid.Column="3" Grid.Row="0" />
<Label Content="{x:Static p:Resources.textAgency}" Grid.Column="2" Grid.Row="1" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textMooring}" Grid.Column="2" Grid.Row="2" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textPilot}" Grid.Column="2" Grid.Row="3" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textTug}" Grid.Column="2" Grid.Row="4" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textTerminal}" Grid.Column="2" Grid.Row="5" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textTidalWindow}" FontWeight="DemiBold" Grid.Column="3" Grid.Row="6" />
<Label Content="{x:Static p:Resources.textFrom}" Grid.Column="2" Grid.Row="7" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textTo}" Grid.Column="2" Grid.Row="8" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textRainSensitiveCargo}" Grid.Column="3" Grid.Row="9" />
<Label Content="{x:Static p:Resources.textRecommendedTugs}" Grid.Column="2" Grid.Row="10" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textAnchored}" Grid.Column="3" Grid.Row="11" />
<Label Content="{x:Static p:Resources.textMooredLock}" Grid.Column="3" Grid.Row="12" />
<Label Content="{x:Static p:Resources.textCancelled}" Grid.Column="3" Grid.Row="13" />
<ComboBox x:Name="comboBoxCategories" Grid.Column="3" Margin="2" Grid.Row="0" SelectedValuePath="Key" SelectionChanged="comboBoxCategories_SelectionChanged"/>
<ComboBox Name="comboBoxAgency" Grid.Column="3" Grid.Row="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxAgency_SelectionChanged">
<Label Content="{x:Static p:Resources.textBerth}" Grid.Column="2" Grid.Row="1" HorizontalContentAlignment="Right"/>
<Grid Grid.Row="1" Grid.Column="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*" />
<ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions>
<ComboBox Name="comboBoxArrivalBerth" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemArrivalBerth" Click="contextMenuItemArrivalBerth_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<ComboBox Name="comboBoxDepartureBerth" Grid.Column="0" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemDepartureBerth" Click="contextMenuItemDepartureBerth_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<xctk:DateTimePicker x:Name="datePickerETA" Grid.Column="3" Grid.Row="2" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False"/>
<xctk:DateTimePicker x:Name="datePickerETD" Grid.Column="3" Grid.Row="3" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False"/>
<Label Content="{x:Static p:Resources.textAgency}" Grid.Column="2" Grid.Row="4" HorizontalContentAlignment="Right"/>
<ComboBox Name="comboBoxAgency" Grid.Column="3" Grid.Row="4" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxAgency_SelectionChanged">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearAgency" Click="contextMenuItemClearAgency_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<ComboBox Name="comboBoxMooring" Grid.Column="3" Grid.Row="2" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearMooring" Click="contextMenuItemClearMooring_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<ComboBox Name="comboBoxPilot" Grid.Column="3" Grid.Row="3" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<ComboBox Name="comboBoxTug" Grid.Column="3" Grid.Row="4" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<ComboBox Name="comboBoxTerminal" Grid.Column="3" Grid.Row="5" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTerminal" Click="contextMenuItemClearTerminal_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<xctk:DateTimePicker Name="datePickerTidalWindowFrom" Grid.Column="3" Grid.Row="7" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<xctk:DateTimePicker Name="datePickerTidalWindowTo" Grid.Column="3" Grid.Row="8" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<CheckBox x:Name="checkBoxRainsensitiveCargo" Grid.Column="2" Grid.Row="9" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,4,0" />
<xctk:IntegerUpDown x:Name="integerUpDownRecommendedTugs" Grid.Column="3" Grid.Row="10" Minimum="0" Margin="2" />
<CheckBox x:Name="checkBoxAnchored" Grid.Column="2" Grid.Row="11" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,4,0" />
<CheckBox x:Name="checkBoxMooredLock" Grid.Column="2" Grid.Row="12" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,4,0" />
<CheckBox x:Name="checkBoxCanceled" Grid.Column="2" Grid.Row="13" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,4,0" />
<Label x:Name="labelBSMDGranted" Grid.Row="14" Grid.Column="1" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textBSMDGranted}" Visibility="Hidden" FontWeight="DemiBold" />
<StackPanel Grid.Row="14" Grid.Column="3" Orientation="Horizontal" HorizontalAlignment="Right">

View File

@ -1,6 +1,6 @@
// Copyright (c) 2023 schick Informatik
// Description: Windows dialog to create / edit ship calls
//
//
using BreCalClient.misc.Model;
using System;
@ -27,26 +27,11 @@ namespace BreCalClient
public ShipcallControlModel ShipcallModel { get; set; } = new ();
/// <summary>
/// All participants
/// </summary>
public List<Participant> Participants { get; set; } = new();
/// <summary>
/// All berths
/// </summary>
public List<Berth> Berths { get; set; } = new();
/// <summary>
/// All ships
/// </summary>
public List<Ship> Ships { get; set; } = new();
public Ship? SelectedShip {
public Ship? SelectedShip {
get
{
return this.comboBoxShip.SelectedItem as Ship;
}
}
}
#endregion
@ -55,32 +40,14 @@ namespace BreCalClient
private void Window_Loaded(object sender, RoutedEventArgs e)
{
List<Participant> aList = new();
List<Participant> mList = new();
List<Participant> pList = new();
List<Participant> tList = new();
List<Participant> terList = new();
this.comboBoxAgency.ItemsSource = BreCalLists.Participants_Agent;
foreach(Participant p in Participants)
{
if (p.IsTypeFlagSet(Extensions.ParticipantType.AGENCY)) aList.Add(p);
if (p.IsTypeFlagSet(Extensions.ParticipantType.MOORING)) mList.Add(p);
if (p.IsTypeFlagSet(Extensions.ParticipantType.PILOT)) pList.Add(p);
if (p.IsTypeFlagSet(Extensions.ParticipantType.TUG)) tList.Add(p);
if (p.IsTypeFlagSet(Extensions.ParticipantType.TERMINAL)) terList.Add(p);
}
this.comboBoxAgency.ItemsSource = aList;
this.comboBoxMooring.ItemsSource = mList;
this.comboBoxPilot.ItemsSource = pList;
this.comboBoxTug.ItemsSource = tList;
this.comboBoxTerminal.ItemsSource = terList;
this.comboBoxShip.ItemsSource = Ships;
this.comboBoxCategories.ItemsSource = Enum.GetValues(typeof(Extensions.TypeEnum));
this.comboBoxArrivalBerth.ItemsSource = this.Berths;
this.comboBoxDepartureBerth.ItemsSource = this.Berths;
this.comboBoxShip.ItemsSource = BreCalLists.Ships;
this.comboBoxCategories.ItemsSource = Enum.GetValues(typeof(TypeEnum));
this.comboBoxArrivalBerth.ItemsSource = BreCalLists.Berths;
this.comboBoxDepartureBerth.ItemsSource = BreCalLists.Berths;
if (this.ShipcallModel.Shipcall == null) this.ShipcallModel.Shipcall = new();
this.CopyToControls();
this.EnableControls();
@ -89,19 +56,34 @@ namespace BreCalClient
private void buttonOK_Click(object sender, RoutedEventArgs e)
{
this.CopyToModel();
this.DialogResult = true;
this.DialogResult = true;
this.Close();
}
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
this.DialogResult= false;
this.DialogResult= false;
this.Close();
}
private void comboBoxShip_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.buttonOK.IsEnabled = this.comboBoxShip.SelectedItem != null;
if (this.comboBoxShip.SelectedItem != null)
{
Ship? ship = this.comboBoxShip.SelectedItem as Ship;
this.integerUpDownIMO.Value = ship?.Imo;
this.textBoxCallsign.Text = ship?.Callsign;
this.doubleUpDownLength.Value = ship?.Length;
this.doubleUpDownWidth.Value = ship?.Width;
}
else
{
this.integerUpDownIMO.Value = null;
this.textBoxCallsign.Text = string.Empty;
this.doubleUpDownLength.Value = null;
this.doubleUpDownWidth.Value = null;
}
}
private void comboBoxAgency_SelectionChanged(object sender, SelectionChangedEventArgs e)
@ -109,6 +91,39 @@ namespace BreCalClient
this.EnableControls();
}
private void comboBoxCategories_SelectionChanged(object? sender, SelectionChangedEventArgs? e)
{
TypeEnum? type = this.comboBoxCategories.SelectedItem as TypeEnum?;
if (type != null)
{
switch (type)
{
case TypeEnum.Incoming:
this.datePickerETA.IsEnabled = true;
this.datePickerETD.IsEnabled = false;
this.datePickerETD.Value = null;
this.comboBoxDepartureBerth.SelectedIndex = -1;
this.comboBoxDepartureBerth.IsEnabled = false;
this.comboBoxArrivalBerth.IsEnabled = true;
break;
case TypeEnum.Outgoing:
this.datePickerETA.IsEnabled = false;
this.datePickerETD.IsEnabled = true;
this.datePickerETA.Value = null;
this.comboBoxArrivalBerth.SelectedIndex = -1;
this.comboBoxArrivalBerth.IsEnabled = false;
this.comboBoxDepartureBerth.IsEnabled = true;
break;
case TypeEnum.Shifting:
this.datePickerETA.IsEnabled = true;
this.datePickerETD.IsEnabled = true;
this.comboBoxArrivalBerth.IsEnabled = true;
this.comboBoxDepartureBerth.IsEnabled = true;
break;
}
}
}
#endregion
#region Context menu handlers
@ -119,30 +134,6 @@ namespace BreCalClient
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.AGENCY);
}
private void contextMenuItemClearMooring_Click(object sender, RoutedEventArgs e)
{
this.comboBoxMooring.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.MOORING);
}
private void contextMenuItemClearPilot_Click(object sender, RoutedEventArgs e)
{
this.comboBoxPilot.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.PILOT);
}
private void contextMenuItemClearTug_Click(object sender, RoutedEventArgs e)
{
this.comboBoxTug.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.TUG);
}
private void contextMenuItemClearTerminal_Click(object sender, RoutedEventArgs e)
{
this.comboBoxTerminal.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.TERMINAL);
}
private void contextMenuItemArrivalBerth_Click(object sender, RoutedEventArgs e)
{
this.comboBoxArrivalBerth.SelectedIndex = -1;
@ -152,7 +143,7 @@ namespace BreCalClient
private void contextMenuItemDepartureBerth_Click(object sender, RoutedEventArgs e)
{
this.comboBoxDepartureBerth.SelectedIndex -= 1;
}
}
#endregion
@ -163,77 +154,62 @@ namespace BreCalClient
if (this.ShipcallModel.Shipcall != null)
{
this.ShipcallModel.Shipcall.Type = (int)this.comboBoxCategories.SelectedItem;
this.ShipcallModel.Shipcall.Eta = this.datePickerETA.Value ?? DateTime.Now;
this.ShipcallModel.Shipcall.Voyage = this.textBoxVoyage.Text.Trim();
this.ShipcallModel.Shipcall.Etd = this.datePickerETD.Value ?? DateTime.Now.AddDays(1);
this.ShipcallModel.Shipcall.Anchored = this.checkBoxAnchored.IsChecked;
this.ShipcallModel.Shipcall.Eta = this.datePickerETA.Value;
this.ShipcallModel.Shipcall.Etd = this.datePickerETD.Value;
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;
this.ShipcallModel.Shipcall.Bunkering = this.checkBoxBunkering.IsChecked;
this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked;
this.ShipcallModel.Shipcall.Draft = (float?)this.doubleUpDownDraft.Value;
this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked;
this.ShipcallModel.Shipcall.RainSensitiveCargo = this.checkBoxRainsensitiveCargo.IsChecked;
this.ShipcallModel.Shipcall.PilotRequired = this.checkBoxPilotRequired.IsChecked;
this.ShipcallModel.Shipcall.ReplenishingLock = this.checkBoxReplenishingLock.IsChecked;
this.ShipcallModel.Shipcall.ReplenishingTerminal = this.checkBoxReplenishingTerminal.IsChecked;
this.ShipcallModel.Shipcall.RecommendedTugs = this.integerUpDownRecommendedTugs.Value;
this.ShipcallModel.Shipcall.TidalWindowFrom = this.datePickerTidalWindowFrom.Value;
this.ShipcallModel.Shipcall.TidalWindowTo = this.datePickerTidalWindowTo.Value;
this.ShipcallModel.Shipcall.TugRequired = this.checkBoxTugRequired.IsChecked;
if (this.comboBoxPierside.SelectedIndex >= 0)
{
this.ShipcallModel.Shipcall.PierSide = (this.comboBoxPierside.SelectedIndex == 0) ? true : false;
}
// remove all and add selected participants
this.ShipcallModel.Shipcall.Participants.Clear();
this.ShipcallModel.AssignedParticipants.Clear();
Participant? participant;
participant = (Participant?)this.comboBoxAgency.SelectedItem;
if (participant != null)
{
this.ShipcallModel.Shipcall.Participants.Add(participant.Id);
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.AGENCY] = participant;
}
participant = (Participant?)this.comboBoxMooring.SelectedItem;
if (participant != null)
ParticipantAssignment pa = new()
{
ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.AGENCY
};
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.AGENCY] = pa;
}
else
{
this.ShipcallModel.Shipcall.Participants.Add(participant.Id);
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.MOORING] = participant;
}
participant = (Participant?)this.comboBoxPilot.SelectedItem;
if (participant != null)
{
this.ShipcallModel.Shipcall.Participants.Add(participant.Id);
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.PILOT] = participant;
}
participant = (Participant?)this.comboBoxTerminal.SelectedItem;
if (participant != null) {
this.ShipcallModel.Shipcall.Participants.Add(participant.Id);
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.TERMINAL] = participant;
}
participant = (Participant?)this.comboBoxTug.SelectedItem;
if (participant != null) {
this.ShipcallModel.Shipcall.Participants.Add(participant.Id);
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.TUG] = participant;
// AGENCY was set before and now is set to nothing
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
this.ShipcallModel.AssignedParticipants.Remove(ParticipantType.AGENCY);
}
// BSMD and port authority are always added
foreach (Participant p in Participants)
// get port authority from berth
int? berthId = this.ShipcallModel.Shipcall.ArrivalBerthId;
berthId ??= this.ShipcallModel.Shipcall.DepartureBerthId;
if (berthId != null)
{
if (p.Type == (int)Extensions.ParticipantType.PORT_ADMINISTRATION)
Berth? selectedBerth = BreCalLists.Berths.Find((x) => x.Id == berthId);
if (selectedBerth?.AuthorityId != null)
{
this.ShipcallModel.Shipcall.Participants.Add(p.Id);
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.PORT_ADMINISTRATION] = p;
if (BreCalLists.ParticipantLookupDict.ContainsKey(selectedBerth.AuthorityId.Value))
{
ParticipantAssignment pab = new()
{
ParticipantId = selectedBerth.AuthorityId.Value,
Type = (int)ParticipantType.PORT_ADMINISTRATION
};
this.ShipcallModel.AssignedParticipants[ParticipantType.PORT_ADMINISTRATION] = pab;
}
}
if (p.Type == (int)Extensions.ParticipantType.BSMD)
ParticipantAssignment pa = new()
{
this.ShipcallModel.Shipcall.Participants.Add(p.Id);
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.BSMD] = p;
}
ParticipantId = App.Participant.Id,
Type = (int)ParticipantType.BSMD
};
this.ShipcallModel.AssignedParticipants[ParticipantType.BSMD] = pa;
}
}
}
@ -243,54 +219,31 @@ namespace BreCalClient
if (this.ShipcallModel == null) return;
if (this.ShipcallModel.Shipcall != null)
{
this.comboBoxCategories.SelectedItem = (Extensions.TypeEnum)this.ShipcallModel.Shipcall.Type;
this.comboBoxCategories.SelectedItem = (TypeEnum)this.ShipcallModel.Shipcall.Type;
if (this.ShipcallModel.Shipcall.Eta != DateTime.MinValue)
this.datePickerETA.Value = this.ShipcallModel.Shipcall.Eta;
this.textBoxVoyage.Text = this.ShipcallModel.Shipcall.Voyage;
// this.textBoxVoyage.Text = this.ShipcallModel.Shipcall.Voyage;
this.datePickerETD.Value = this.ShipcallModel.Shipcall.Etd;
this.checkBoxAnchored.IsChecked = this.ShipcallModel.Shipcall.Anchored;
this.comboBoxShip.SelectedValue = this.ShipcallModel.Shipcall.ShipId;
this.comboBoxArrivalBerth.SelectedValue = this.ShipcallModel.Shipcall.ArrivalBerthId;
this.comboBoxDepartureBerth.SelectedValue = this.ShipcallModel.Shipcall.DepartureBerthId;
this.checkBoxBunkering.IsChecked = this.ShipcallModel.Shipcall.Bunkering;
this.checkBoxCanceled.IsChecked = this.ShipcallModel.Shipcall.Canceled;
this.doubleUpDownDraft.Value = this.ShipcallModel.Shipcall.Draft;
this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock;
this.checkBoxRainsensitiveCargo.IsChecked = this.ShipcallModel.Shipcall.RainSensitiveCargo;
this.checkBoxPilotRequired.IsChecked = this.ShipcallModel.Shipcall.PilotRequired;
this.checkBoxReplenishingLock.IsChecked = this.ShipcallModel.Shipcall.ReplenishingLock;
this.checkBoxReplenishingTerminal.IsChecked = this.ShipcallModel.Shipcall.ReplenishingTerminal;
this.integerUpDownRecommendedTugs.Value = this.ShipcallModel.Shipcall.RecommendedTugs;
this.datePickerTidalWindowFrom.Value = this.ShipcallModel.Shipcall.TidalWindowFrom;
this.datePickerTidalWindowTo.Value = this.ShipcallModel.Shipcall.TidalWindowTo;
this.checkBoxTugRequired.IsChecked = this.ShipcallModel.Shipcall.TugRequired;
if (this.ShipcallModel.Shipcall.PierSide.HasValue)
{
if (this.ShipcallModel.Shipcall.PierSide.Value) this.comboBoxPierside.SelectedIndex = 0;
else this.comboBoxPierside.SelectedIndex = 1;
}
if (this.ShipcallModel.Shipcall.Participants == null) this.ShipcallModel.Shipcall.Participants = new();
// Hier wird noch ein Problem vergessen: Wenn ein Participant mehrere Types gleichzeitig ist und es einen weitere Participant mit diesem Type gibt
// müsste der "einzelne" Participant für die Rolle ausgewählt werden. (Angenommen ein Test-Teilnehmer hat "alle" Rollen..)
foreach (int participant_id in this.ShipcallModel.Shipcall.Participants)
if(this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
{
if (((List<Participant>)this.comboBoxAgency.ItemsSource).Any(x => x.Id == participant_id)) this.comboBoxAgency.SelectedValue = participant_id;
if (((List<Participant>)this.comboBoxMooring.ItemsSource).Any(x => x.Id == participant_id)) this.comboBoxMooring.SelectedValue = participant_id;
if (((List<Participant>)this.comboBoxPilot.ItemsSource).Any(x => x.Id == participant_id)) this.comboBoxPilot.SelectedValue = participant_id;
if (((List<Participant>)this.comboBoxTerminal.ItemsSource).Any(x => x.Id == participant_id)) this.comboBoxTerminal.SelectedValue = participant_id;
if (((List<Participant>)this.comboBoxTug.ItemsSource).Any(x => x.Id == participant_id)) this.comboBoxTug.SelectedValue = participant_id;
}
if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId))
{
this.comboBoxAgency.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId;
}
}
}
}
private void EnableControls()
{
bool isBsmd = App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD);
bool isAgency = App.Participant.IsTypeFlagSet(Extensions.ParticipantType.AGENCY);
bool isBsmd = App.Participant.IsTypeFlagSet(ParticipantType.BSMD);
bool isAgency = App.Participant.IsTypeFlagSet(ParticipantType.AGENCY);
bool editRightGrantedForBSMD = false;
@ -298,12 +251,12 @@ namespace BreCalClient
if (this.comboBoxAgency.SelectedIndex >= 0)
{
int agencyParticipantId = (int)this.comboBoxAgency.SelectedValue;
Participant? p = this.Participants.Find(x => x.Id == agencyParticipantId);
Participant? p = BreCalLists.Participants.Find(x => x.Id == agencyParticipantId);
if (p != null)
{
if(p.IsFlagSet(ParticipantFlag.ALLOW_BSMD) && isBsmd)
isAgency = true;
if(p.IsFlagSet(ParticipantFlag.ALLOW_BSMD))
if(p.IsFlagSet(ParticipantFlag.ALLOW_BSMD))
editRightGrantedForBSMD = true;
}
}
@ -312,34 +265,16 @@ namespace BreCalClient
this.comboBoxArrivalBerth.IsEnabled = isBsmd || isAgency;
this.comboBoxCategories.IsEnabled = isBsmd;
this.comboBoxDepartureBerth.IsEnabled = isBsmd || isAgency;
this.checkBoxAnchored.IsEnabled = isAgency;
this.checkBoxBunkering.IsEnabled = isAgency;
this.checkBoxCanceled.IsEnabled = isBsmd || isAgency;
this.checkBoxMooredLock.IsEnabled = isAgency;
this.checkBoxPilotRequired.IsEnabled = isAgency;
this.checkBoxRainsensitiveCargo.IsEnabled = isAgency;
this.checkBoxReplenishingLock.IsEnabled = isAgency;
this.checkBoxReplenishingTerminal.IsEnabled = isAgency;
this.checkBoxTugRequired.IsEnabled = isAgency;
this.comboBoxMooring.IsEnabled = isBsmd;
this.comboBoxPierside.IsEnabled = isAgency;
this.comboBoxPilot.IsEnabled = isAgency;
this.comboBoxShip.IsEnabled = isBsmd;
this.comboBoxMooring.IsEnabled = isAgency;
this.comboBoxTerminal.IsEnabled = isAgency;
this.comboBoxTug.IsEnabled = isAgency;
this.datePickerETA.IsEnabled = isAgency || isBsmd;
this.datePickerETD.IsEnabled = isAgency;
this.textBoxVoyage.IsEnabled = isAgency;
this.datePickerTidalWindowFrom.IsEnabled = isAgency;
this.datePickerTidalWindowTo.IsEnabled = isAgency;
this.integerUpDownRecommendedTugs.IsEnabled = isAgency;
this.doubleUpDownDraft.IsEnabled = isAgency || isBsmd;
this.labelBSMDGranted.Visibility = editRightGrantedForBSMD ? Visibility.Visible : Visibility.Hidden;
this.comboBoxCategories_SelectionChanged(null, null);
}
#endregion
}
}

View File

@ -0,0 +1,139 @@
<Window x:Class="BreCalClient.EditTimesAgencyIncomingControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BreCalClient"
xmlns:p = "clr-namespace:BreCalClient.Resources"
xmlns:db="clr-namespace:BreCalClient;assembly=BreCalClient"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d"
Title="{x:Static p:Resources.textEditShipcall}" Height="403" Width="800" Loaded="Window_Loaded" ResizeMode="NoResize" Icon="Resources/containership.ico">
<Window.Resources>
<local:BoolToIndexConverter x:Key="boolToIndexConverter" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width=".3*" />
<ColumnDefinition Width="0.15*"/>
<ColumnDefinition Width=".35*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="{x:Static p:Resources.textIncoming}" FontWeight="DemiBold"/>
<Image Margin="2" Grid.Column="1" Source="Resources/arrow_down_red.png" />
</Grid>
<Label Content="ETA" Grid.Column="0" Grid.Row="1" HorizontalContentAlignment="Right"/>
<xctk:DateTimePicker x:Name="datePickerETA" Grid.Column="1" Grid.Row="1" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<Label Content="{x:Static p:Resources.textBerth}" Grid.Column="0" Grid.Row="2" HorizontalContentAlignment="Right"/>
<ComboBox Name="comboBoxArrivalBerth" Grid.Column="1" Grid.Row="2" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemArrivalBerth" Click="contextMenuItemArrivalBerth_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textPierside}" Grid.Column="0" Grid.Row="3" HorizontalContentAlignment="Right" />
<ComboBox x:Name="comboBoxPierside" Grid.Column="1" Grid.Row="3" Margin="2" >
<ComboBoxItem Content="{x:Static p:Resources.textNotRotated}" />
<ComboBoxItem Content="{x:Static p:Resources.textRotated}" />
</ComboBox>
<Label Content="{x:Static p:Resources.textBerthRemarks}" Grid.Column="0" Grid.Row="4" HorizontalContentAlignment="Right" />
<TextBox x:Name="textBoxBerthRemarks" Grid.Column="1" Grid.Row="4" Margin="2" Grid.RowSpan="2" VerticalContentAlignment="Top" AcceptsReturn="True"/>
<Label Content="{x:Static p:Resources.textDraft}" Grid.Column="0" Grid.Row="6" HorizontalContentAlignment="Right" />
<xctk:DoubleUpDown x:Name="doubleUpDownDraft" Grid.Column="1" Grid.Row="6" Margin="2" FormatString="N2" Minimum="0" />
<Label Content="{x:Static p:Resources.textTidalWindow}" FontWeight="DemiBold" Grid.Column="0" Grid.Row="7" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textFrom}" Grid.Column="0" Grid.Row="8" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textTo}" Grid.Column="0" Grid.Row="9" HorizontalContentAlignment="Right"/>
<xctk:DateTimePicker Name="datePickerTidalWindowFrom" Grid.Column="1" Grid.Row="8" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<xctk:DateTimePicker Name="datePickerTidalWindowTo" Grid.Column="1" Grid.Row="9" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<Label Content="{x:Static p:Resources.textCancelled}" Grid.Column="0" Grid.Row="10" HorizontalContentAlignment="Right" />
<CheckBox x:Name="checkBoxCanceled" Grid.Column="1" Grid.Row="10" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,4,0" />
<Label Content="{x:Static p:Resources.textAnchored}" Grid.Column="2" Grid.Row="0" HorizontalContentAlignment="Right"/>
<CheckBox x:Name="checkBoxAnchored" Grid.Column="3" Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<Label Content="{x:Static p:Resources.textTugRequired}" Grid.Column="2" Grid.Row="1" HorizontalContentAlignment="Right"/>
<Grid Grid.Column="3" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="28" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBoxTugRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<ComboBox Name="comboBoxTug" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<Label Content="{x:Static p:Resources.textRecommendedTugs}" Grid.Column="2" Grid.Row="2" HorizontalContentAlignment="Right"/>
<xctk:IntegerUpDown x:Name="integerUpDownRecommendedTugs" Grid.Column="3" Grid.Row="2" Minimum="0" Margin="2" />
<Label Content="{x:Static p:Resources.textPilotRequired}" Grid.Column="2" Grid.Row="3" HorizontalContentAlignment="Right" />
<Grid Grid.Column="3" Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="28" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBoxPilotRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<ComboBox Name="comboBoxPilot" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<Label Content="{x:Static p:Resources.textMooring}" Grid.Column="2" Grid.Row="4" HorizontalContentAlignment="Right"/>
<ComboBox Name="comboBoxMooring" Grid.Column="3" Grid.Row="4" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearMooring" Click="contextMenuItemClearMooring_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textMooredLock}" Grid.Column="2" Grid.Row="5" HorizontalContentAlignment="Right" />
<CheckBox x:Name="checkBoxMooredLock" Grid.Column="3" Grid.Row="5" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,4,0" />
<Label Content="{x:Static p:Resources.textTerminal}" Grid.Column="2" Grid.Row="6" HorizontalContentAlignment="Right"/>
<ComboBox Name="comboBoxTerminal" Grid.Column="3" Grid.Row="6" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTerminal" Click="contextMenuItemClearTerminal_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textBunkering}" Grid.Column="3" Grid.Row="7" />
<Label Content="{x:Static p:Resources.textReplenishingTerminal}" Grid.Column="3" Grid.Row="8" />
<Label Content="{x:Static p:Resources.textReplenishingLock}" Grid.Column="3" Grid.Row="9" />
<CheckBox x:Name="checkBoxBunkering" Grid.Column="2" Grid.Row="7" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,4,0" />
<CheckBox x:Name="checkBoxReplenishingTerminal" Grid.Column="2" Grid.Row="8" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,4,0" />
<CheckBox x:Name="checkBoxReplenishingLock" Grid.Column="2" Grid.Row="9" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,4,0" />
<Label Content="{x:Static p:Resources.textRemarks}" Grid.Row="10" Grid.Column="2" HorizontalAlignment="Right"/>
<TextBox x:Name="textBoxRemarks" Grid.Column="3" Grid.Row="10" Margin="2" Grid.RowSpan="2" VerticalContentAlignment="Top" AcceptsReturn="True" />
<StackPanel Grid.Row="14" Grid.Column="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" IsEnabled="False" />
<Button Width="80" Margin="2" Content="{x:Static p:Resources.textCancel}" x:Name="buttonCancel" Click="buttonCancel_Click"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,305 @@
// Copyright (c) 2023 schick Informatik
// Description: Input control for incoming shipcalls
//
using BreCalClient.misc.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using static BreCalClient.Extensions;
namespace BreCalClient
{
/// <summary>
/// Interaction logic for EditTimesAgencyIncomingControl.xaml
/// </summary>
public partial class EditTimesAgencyIncomingControl : Window, IEditShipcallTimesControl
{
#region Construction
public EditTimesAgencyIncomingControl()
{
InitializeComponent();
}
#endregion
#region Properties
public ShipcallControlModel ShipcallModel { get; set; } = new();
public Times Times { get; set; } = new();
public Extensions.TypeEnum CallType { get; set; }
#endregion
#region event handler
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.comboBoxMooring.ItemsSource = BreCalLists.Participants_Mooring;
this.comboBoxPilot.ItemsSource = BreCalLists.Participants_Pilot;
this.comboBoxTug.ItemsSource = BreCalLists.Participants_Tug;
this.comboBoxTerminal.ItemsSource = BreCalLists.Participants_Terminal;
this.comboBoxArrivalBerth.ItemsSource = BreCalLists.Berths;
this.CopyToControls();
Participant? p = null;
if(this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId);
bool allowBSMD = false;
if (p != null)
{
allowBSMD = p.IsFlagSet(ParticipantFlag.ALLOW_BSMD);
}
bool enableControls = (this.Times.ParticipantId == App.Participant.Id) ||
(App.Participant.IsTypeFlagSet(ParticipantType.BSMD) && allowBSMD);
this.EnableControls(enableControls);
}
private void buttonOK_Click(object sender, RoutedEventArgs e)
{
this.CopyToModel();
this.DialogResult = true;
this.Close();
}
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
this.Close();
}
#endregion
#region private methods
private void CopyToModel()
{
if (this.ShipcallModel.Shipcall != null)
{
this.Times.EtaBerth = this.datePickerETA.Value;
if (this.comboBoxPierside.SelectedIndex >= 0)
{
this.ShipcallModel.Shipcall.PierSide = (this.comboBoxPierside.SelectedIndex == 0) ? true : false;
}
this.Times.BerthInfo = this.textBoxBerthRemarks.Text.Trim();
this.Times.BerthId = (int?)this.comboBoxArrivalBerth.SelectedValue;
this.ShipcallModel.Shipcall.Draft = (float?)this.doubleUpDownDraft.Value;
this.ShipcallModel.Shipcall.TidalWindowFrom = this.datePickerTidalWindowFrom.Value;
this.ShipcallModel.Shipcall.TidalWindowTo = this.datePickerTidalWindowTo.Value;
this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked;
this.ShipcallModel.Shipcall.Anchored = this.checkBoxAnchored.IsChecked;
this.ShipcallModel.Shipcall.TugRequired = this.checkBoxTugRequired.IsChecked;
this.ShipcallModel.Shipcall.RecommendedTugs = this.integerUpDownRecommendedTugs.Value;
this.ShipcallModel.Shipcall.PilotRequired = this.checkBoxPilotRequired.IsChecked;
this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked;
this.ShipcallModel.Shipcall.Bunkering = this.checkBoxBunkering.IsChecked;
this.ShipcallModel.Shipcall.ReplenishingTerminal = this.checkBoxReplenishingTerminal.IsChecked;
this.ShipcallModel.Shipcall.ReplenishingLock = this.checkBoxReplenishingLock.IsChecked;
if (!string.IsNullOrEmpty(this.textBoxRemarks.Text.Trim()))
this.Times.Remarks = this.textBoxRemarks.Text.Trim();
Participant? participant = (Participant?)this.comboBoxMooring.SelectedItem;
if (participant != null)
{
ParticipantAssignment participantAssignment = new() {
ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.MOORING
};
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.MOORING] = participantAssignment;
}
participant = (Participant?)this.comboBoxPilot.SelectedItem;
if (participant != null)
{
ParticipantAssignment participantAssignment = new()
{
ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.PILOT
};
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.PILOT] = participantAssignment;
}
participant = (Participant?)this.comboBoxTerminal.SelectedItem;
if (participant != null)
{
ParticipantAssignment participantAssignment = new()
{
ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.TERMINAL
};
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.TERMINAL] = participantAssignment;
}
participant = (Participant?)this.comboBoxTug.SelectedItem;
if (participant != null)
{
ParticipantAssignment participantAssignment = new()
{
ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.TUG
};
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.TUG] = participantAssignment;
}
}
}
private void CopyToControls()
{
if (this.ShipcallModel == null) return;
if (this.ShipcallModel.Shipcall != null)
{
if(this.Times.EtaBerth.HasValue)
{
this.datePickerETA.Value = this.Times.EtaBerth.Value;
}
else
{
// if not set through times use value of BSMD entry
if (this.ShipcallModel.Shipcall.Eta != DateTime.MinValue)
this.datePickerETA.Value = this.ShipcallModel.Shipcall.Eta;
}
if (Times.BerthId.HasValue)
this.comboBoxArrivalBerth.SelectedValue = Times.BerthId;
else if (this.ShipcallModel.Shipcall.ArrivalBerthId.HasValue)
this.comboBoxArrivalBerth.SelectedValue = this.ShipcallModel.Shipcall.ArrivalBerthId;
if (this.ShipcallModel.Shipcall.PierSide.HasValue)
{
if (this.ShipcallModel.Shipcall.PierSide.Value) this.comboBoxPierside.SelectedIndex = 0;
else this.comboBoxPierside.SelectedIndex = 1;
}
this.textBoxBerthRemarks.Text = this.Times.BerthInfo;
this.doubleUpDownDraft.Value = this.ShipcallModel.Shipcall.Draft;
this.datePickerTidalWindowFrom.Value = this.ShipcallModel.Shipcall.TidalWindowFrom;
this.datePickerTidalWindowTo.Value = this.ShipcallModel.Shipcall.TidalWindowTo;
this.checkBoxCanceled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false;
this.checkBoxAnchored.IsChecked = this.ShipcallModel.Shipcall.Anchored ?? false;
this.checkBoxTugRequired.IsChecked = this.ShipcallModel.Shipcall.TugRequired ?? false;
this.integerUpDownRecommendedTugs.Value = this.ShipcallModel.Shipcall.RecommendedTugs;
this.checkBoxPilotRequired.IsChecked = this.ShipcallModel.Shipcall.PilotRequired ?? false;
this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false;
this.checkBoxBunkering.IsChecked = this.ShipcallModel.Shipcall.Bunkering ?? false;
this.checkBoxReplenishingLock.IsChecked = this.ShipcallModel.Shipcall.ReplenishingLock ?? false;
this.checkBoxReplenishingTerminal.IsChecked = this.ShipcallModel.Shipcall.ReplenishingTerminal ?? false;
if(!string.IsNullOrEmpty(this.Times.Remarks))
this.textBoxRemarks.Text = this.Times.Remarks;
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.MOORING))
{
if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.MOORING].ParticipantId))
{
this.comboBoxMooring.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.MOORING].ParticipantId;
}
}
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.PILOT))
{
if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.PILOT].ParticipantId))
{
this.comboBoxPilot.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.PILOT].ParticipantId;
}
}
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.TERMINAL))
{
if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.TERMINAL].ParticipantId))
{
this.comboBoxTerminal.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.TERMINAL].ParticipantId;
}
}
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.TUG))
{
if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.TUG].ParticipantId))
{
this.comboBoxTug.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.TUG].ParticipantId;
}
}
}
}
private void EnableControls(bool isEnabled)
{
this.datePickerETA.IsEnabled = isEnabled;
this.comboBoxArrivalBerth.IsEnabled = isEnabled;
this.comboBoxPierside.IsEnabled = isEnabled;
this.textBoxBerthRemarks.IsEnabled = isEnabled;
this.doubleUpDownDraft.IsEnabled = isEnabled;
this.datePickerTidalWindowFrom.IsEnabled = isEnabled;
this.datePickerTidalWindowTo.IsEnabled = isEnabled;
this.checkBoxCanceled.IsEnabled = isEnabled;
this.checkBoxAnchored.IsEnabled = isEnabled;
this.checkBoxTugRequired.IsEnabled = isEnabled;
this.comboBoxTug.IsEnabled = isEnabled;
this.integerUpDownRecommendedTugs.IsEnabled = isEnabled;
this.checkBoxPilotRequired.IsEnabled = isEnabled;
this.comboBoxPilot.IsEnabled = isEnabled;
this.comboBoxMooring.IsEnabled = isEnabled;
this.checkBoxMooredLock.IsEnabled = isEnabled;
this.comboBoxTerminal.IsEnabled = isEnabled;
this.checkBoxBunkering.IsEnabled = isEnabled;
this.checkBoxReplenishingTerminal.IsEnabled = isEnabled;
this.checkBoxReplenishingLock.IsEnabled = isEnabled;
this.textBoxRemarks.IsEnabled = isEnabled;
this.buttonOK.IsEnabled = isEnabled;
}
#endregion
#region context menu handlers
private void contextMenuItemArrivalBerth_Click(object sender, RoutedEventArgs e)
{
this.comboBoxArrivalBerth.SelectedIndex = -1;
this.ShipcallModel.Berth = "";
}
private void contextMenuItemClearTug_Click(object sender, RoutedEventArgs e)
{
this.comboBoxTug.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.TUG);
}
private void contextMenuItemClearPilot_Click(object sender, RoutedEventArgs e)
{
this.comboBoxPilot.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.PILOT);
}
private void contextMenuItemClearMooring_Click(object sender, RoutedEventArgs e)
{
this.comboBoxMooring.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.MOORING);
}
private void contextMenuItemClearTerminal_Click(object sender, RoutedEventArgs e)
{
this.comboBoxTerminal.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.TERMINAL);
}
#endregion
}
}

View File

@ -0,0 +1,129 @@
<Window x:Class="BreCalClient.EditTimesAgencyOutgoingControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BreCalClient"
xmlns:p = "clr-namespace:BreCalClient.Resources"
xmlns:db="clr-namespace:BreCalClient;assembly=BreCalClient"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d"
Title="{x:Static p:Resources.textEditShipcall}" Height="375" Width="800" Loaded="Window_Loaded" ResizeMode="NoResize" Icon="Resources/containership.ico">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width=".3*" />
<ColumnDefinition Width="0.15*"/>
<ColumnDefinition Width=".35*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="{x:Static p:Resources.textOutgoing}" FontWeight="DemiBold"/>
<Image Margin="2" Grid.Column="1" Source="Resources/arrow_up_blue.png" />
</Grid>
<Label Content="ETD" Grid.Column="0" Grid.Row="1" HorizontalContentAlignment="Right"/>
<xctk:DateTimePicker x:Name="datePickerETD" Grid.Column="1" Grid.Row="1" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<Label Content="{x:Static p:Resources.textBerth}" Grid.Column="0" Grid.Row="2" HorizontalContentAlignment="Right" />
<ComboBox Name="comboBoxDepartureBerth" Grid.Column="1" Grid.Row="2" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemDepartureBerth" Click="contextMenuItemDepartureBerth_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textPierside}" Grid.Column="0" Grid.Row="3" HorizontalContentAlignment="Right" />
<ComboBox x:Name="comboBoxPierside" Grid.Column="1" Grid.Row="3" Margin="2" >
<ComboBoxItem Content="{x:Static p:Resources.textNotRotated}" />
<ComboBoxItem Content="{x:Static p:Resources.textRotated}" />
</ComboBox>
<Label Content="{x:Static p:Resources.textBerthRemarks}" Grid.Column="0" Grid.Row="4" HorizontalContentAlignment="Right" />
<TextBox x:Name="textBoxBerthRemarks" Grid.Column="1" Grid.Row="4" Margin="2" Grid.RowSpan="2" VerticalContentAlignment="Top" AcceptsReturn="True"/>
<Label Content="{x:Static p:Resources.textDraft}" Grid.Column="0" Grid.Row="6" HorizontalContentAlignment="Right" />
<xctk:DoubleUpDown x:Name="doubleUpDownDraft" Grid.Column="1" Grid.Row="6" Margin="2" FormatString="N2" Minimum="0" />
<Label Content="{x:Static p:Resources.textTidalWindow}" FontWeight="DemiBold" Grid.Column="0" Grid.Row="7" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textFrom}" Grid.Column="0" Grid.Row="8" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textTo}" Grid.Column="0" Grid.Row="9" HorizontalContentAlignment="Right"/>
<xctk:DateTimePicker Name="datePickerTidalWindowFrom" Grid.Column="1" Grid.Row="8" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<xctk:DateTimePicker Name="datePickerTidalWindowTo" Grid.Column="1" Grid.Row="9" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<Label Content="{x:Static p:Resources.textCancelled}" Grid.Column="0" Grid.Row="10" HorizontalContentAlignment="Right" />
<CheckBox x:Name="checkBoxCanceled" Grid.Column="1" Grid.Row="10" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,4,0" />
<Label Content="{x:Static p:Resources.textTugRequired}" Grid.Column="2" Grid.Row="1" HorizontalContentAlignment="Right"/>
<Grid Grid.Column="3" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="28" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBoxTugRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<ComboBox Name="comboBoxTug" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<Label Content="{x:Static p:Resources.textRecommendedTugs}" Grid.Column="2" Grid.Row="2" HorizontalContentAlignment="Right"/>
<xctk:IntegerUpDown x:Name="integerUpDownRecommendedTugs" Grid.Column="3" Grid.Row="2" Minimum="0" Margin="2" />
<Label Content="{x:Static p:Resources.textPilotRequired}" Grid.Column="2" Grid.Row="3" HorizontalContentAlignment="Right" />
<Grid Grid.Column="3" Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="28" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBoxPilotRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<ComboBox Name="comboBoxPilot" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<Label Content="{x:Static p:Resources.textMooring}" Grid.Column="2" Grid.Row="4" HorizontalContentAlignment="Right"/>
<ComboBox Name="comboBoxMooring" Grid.Column="3" Grid.Row="4" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearMooring" Click="contextMenuItemClearMooring_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textMooredLock}" Grid.Column="2" Grid.Row="5" HorizontalContentAlignment="Right" />
<CheckBox x:Name="checkBoxMooredLock" Grid.Column="3" Grid.Row="5" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,4,0" />
<Label Content="{x:Static p:Resources.textTerminal}" Grid.Column="2" Grid.Row="6" HorizontalContentAlignment="Right"/>
<ComboBox Name="comboBoxTerminal" Grid.Column="3" Grid.Row="6" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTerminal" Click="contextMenuItemClearTerminal_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textRainSensitiveCargo}" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="7" HorizontalAlignment="Right" />
<CheckBox x:Name="checkBoxRainsensitiveCargo" Grid.Column="3" Grid.Row="7" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,4,0" />
<Label Content="{x:Static p:Resources.textRemarks}" Grid.Column="2" Grid.Row="8" HorizontalContentAlignment="Right" />
<TextBox x:Name="textBoxRemarks" Grid.Column="3" Grid.Row="8" Margin="2" Grid.RowSpan="3" VerticalContentAlignment="Top" AcceptsReturn="True" />
<StackPanel Grid.Row="11" Grid.Column="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" IsEnabled="False" />
<Button Width="80" Margin="2" Content="{x:Static p:Resources.textCancel}" x:Name="buttonCancel" Click="buttonCancel_Click"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,290 @@
// Copyright (c) 2023 schick Informatik
// Description: Input control for outgoing shipcalls
//
using BreCalClient.misc.Model;
using System;
using System.Windows;
using static BreCalClient.Extensions;
namespace BreCalClient
{
/// <summary>
/// Interaction logic for EditTimesAgencyOutgoingControl.xaml
/// </summary>
public partial class EditTimesAgencyOutgoingControl : Window, IEditShipcallTimesControl
{
#region Construction
public EditTimesAgencyOutgoingControl()
{
InitializeComponent();
}
#endregion
#region Properties
public ShipcallControlModel ShipcallModel { get; set; } = new();
public Times Times { get; set; } = new();
public Extensions.TypeEnum CallType { get; set; }
#endregion
#region event handler
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.comboBoxMooring.ItemsSource = BreCalLists.Participants_Mooring;
this.comboBoxPilot.ItemsSource = BreCalLists.Participants_Pilot;
this.comboBoxTug.ItemsSource = BreCalLists.Participants_Tug;
this.comboBoxTerminal.ItemsSource = BreCalLists.Participants_Terminal;
this.comboBoxDepartureBerth.ItemsSource = BreCalLists.Berths;
this.CopyToControls();
Participant? p = null;
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId);
bool allowBSMD = false;
if (p != null)
{
allowBSMD = p.IsFlagSet(ParticipantFlag.ALLOW_BSMD);
}
bool enableControls = (this.Times.ParticipantId == App.Participant.Id) ||
(App.Participant.IsTypeFlagSet(ParticipantType.BSMD) && allowBSMD);
this.EnableControls(enableControls);
}
private void buttonOK_Click(object sender, RoutedEventArgs e)
{
this.CopyToModel();
this.DialogResult = true;
this.Close();
}
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
this.Close();
}
#endregion
#region private methods
private void CopyToModel()
{
if (this.ShipcallModel.Shipcall != null)
{
this.Times.EtdBerth = this.datePickerETD.Value;
if (this.comboBoxPierside.SelectedIndex >= 0)
{
this.ShipcallModel.Shipcall.PierSide = (this.comboBoxPierside.SelectedIndex == 0) ? true : false;
}
this.Times.BerthId = (int?)this.comboBoxDepartureBerth.SelectedValue;
this.Times.BerthInfo = this.textBoxBerthRemarks.Text.Trim();
this.ShipcallModel.Shipcall.Draft = (float?)this.doubleUpDownDraft.Value;
this.ShipcallModel.Shipcall.TidalWindowFrom = this.datePickerTidalWindowFrom.Value;
this.ShipcallModel.Shipcall.TidalWindowTo = this.datePickerTidalWindowTo.Value;
this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked;
this.ShipcallModel.Shipcall.TugRequired = this.checkBoxTugRequired.IsChecked;
this.ShipcallModel.Shipcall.RecommendedTugs = this.integerUpDownRecommendedTugs.Value;
this.ShipcallModel.Shipcall.PilotRequired = this.checkBoxPilotRequired.IsChecked;
this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked;
this.ShipcallModel.Shipcall.RainSensitiveCargo = this.checkBoxRainsensitiveCargo.IsChecked;
if(!string.IsNullOrEmpty(this.textBoxRemarks.Text.Trim()))
this.Times.Remarks = this.textBoxRemarks.Text.Trim();
Participant? participant = (Participant?)this.comboBoxMooring.SelectedItem;
if (participant != null)
{
ParticipantAssignment participantAssignment = new()
{
ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.MOORING
};
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.MOORING] = participantAssignment;
}
participant = (Participant?)this.comboBoxPilot.SelectedItem;
if (participant != null)
{
ParticipantAssignment participantAssignment = new()
{
ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.PILOT
};
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.PILOT] = participantAssignment;
}
participant = (Participant?)this.comboBoxTerminal.SelectedItem;
if (participant != null)
{
ParticipantAssignment participantAssignment = new()
{
ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.TERMINAL
};
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.TERMINAL] = participantAssignment;
}
participant = (Participant?)this.comboBoxTug.SelectedItem;
if (participant != null)
{
ParticipantAssignment participantAssignment = new()
{
ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.TUG
};
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.TUG] = participantAssignment;
}
}
}
private void CopyToControls()
{
if (this.ShipcallModel == null) return;
if (this.ShipcallModel.Shipcall != null)
{
if (this.Times.EtdBerth.HasValue)
{
this.datePickerETD.Value = this.Times.EtdBerth.Value;
}
else
{
// if not set through times use value of BSMD entry
if (this.ShipcallModel.Shipcall.Etd != DateTime.MinValue)
this.datePickerETD.Value = this.ShipcallModel.Shipcall.Etd;
}
if (this.Times.BerthId.HasValue)
this.comboBoxDepartureBerth.SelectedValue = this.Times.BerthId;
else if (this.ShipcallModel.Shipcall.DepartureBerthId.HasValue)
this.comboBoxDepartureBerth.SelectedValue = this.ShipcallModel.Shipcall.DepartureBerthId;
if (this.ShipcallModel.Shipcall.PierSide.HasValue)
{
if (this.ShipcallModel.Shipcall.PierSide.Value) this.comboBoxPierside.SelectedIndex = 0;
else this.comboBoxPierside.SelectedIndex = 1;
}
this.textBoxBerthRemarks.Text = this.Times.BerthInfo;
this.doubleUpDownDraft.Value = this.ShipcallModel.Shipcall.Draft;
this.datePickerTidalWindowFrom.Value = this.ShipcallModel.Shipcall.TidalWindowFrom;
this.datePickerTidalWindowTo.Value = this.ShipcallModel.Shipcall.TidalWindowTo;
this.checkBoxCanceled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false;
this.checkBoxTugRequired.IsChecked = this.ShipcallModel.Shipcall.TugRequired ?? false;
this.integerUpDownRecommendedTugs.Value = this.ShipcallModel.Shipcall.RecommendedTugs;
this.checkBoxPilotRequired.IsChecked = this.ShipcallModel.Shipcall.PilotRequired ?? false;
this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false;
this.checkBoxRainsensitiveCargo.IsChecked = this.ShipcallModel.Shipcall.RainSensitiveCargo ?? false;
if(!string.IsNullOrEmpty(this.Times.Remarks))
this.textBoxRemarks.Text = this.Times.Remarks;
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.MOORING))
{
if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.MOORING].ParticipantId))
{
this.comboBoxMooring.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.MOORING].ParticipantId;
}
}
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.PILOT))
{
if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.PILOT].ParticipantId))
{
this.comboBoxPilot.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.PILOT].ParticipantId;
}
}
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.TERMINAL))
{
if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.TERMINAL].ParticipantId))
{
this.comboBoxTerminal.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.TERMINAL].ParticipantId;
}
}
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.TUG))
{
if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.TUG].ParticipantId))
{
this.comboBoxTug.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.TUG].ParticipantId;
}
}
}
}
private void EnableControls(bool isEnabled)
{
this.datePickerETD.IsEnabled = isEnabled;
this.comboBoxDepartureBerth.IsEnabled = isEnabled;
this.comboBoxPierside.IsEnabled = isEnabled;
this.textBoxBerthRemarks.IsEnabled = isEnabled;
this.doubleUpDownDraft.IsEnabled = isEnabled;
this.datePickerTidalWindowFrom.IsEnabled = isEnabled;
this.datePickerTidalWindowTo.IsEnabled = isEnabled;
this.checkBoxCanceled.IsEnabled = isEnabled;
this.checkBoxTugRequired.IsEnabled = isEnabled;
this.comboBoxTug.IsEnabled = isEnabled;
this.integerUpDownRecommendedTugs.IsEnabled = isEnabled;
this.checkBoxPilotRequired.IsEnabled = isEnabled;
this.comboBoxPilot.IsEnabled = isEnabled;
this.comboBoxMooring.IsEnabled = isEnabled;
this.checkBoxMooredLock.IsEnabled = isEnabled;
this.comboBoxTerminal.IsEnabled = isEnabled;
this.checkBoxRainsensitiveCargo.IsEnabled = isEnabled;
this.textBoxRemarks.IsEnabled = isEnabled;
this.buttonOK.IsEnabled = isEnabled;
}
#endregion
#region context menu handlers
private void contextMenuItemClearTug_Click(object sender, RoutedEventArgs e)
{
this.comboBoxTug.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.TUG);
}
private void contextMenuItemClearPilot_Click(object sender, RoutedEventArgs e)
{
this.comboBoxPilot.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.PILOT);
}
private void contextMenuItemClearMooring_Click(object sender, RoutedEventArgs e)
{
this.comboBoxMooring.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.MOORING);
}
private void contextMenuItemDepartureBerth_Click(object sender, RoutedEventArgs e)
{
this.comboBoxDepartureBerth.SelectedIndex = -1;
this.ShipcallModel.Berth = "";
}
private void contextMenuItemClearTerminal_Click(object sender, RoutedEventArgs e)
{
this.comboBoxTerminal.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.TERMINAL);
}
#endregion
}
}

View File

@ -0,0 +1,158 @@
<Window x:Class="BreCalClient.EditTimesAgencyShiftingControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BreCalClient"
xmlns:p = "clr-namespace:BreCalClient.Resources"
xmlns:db="clr-namespace:BreCalClient;assembly=BreCalClient"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d"
Title="{x:Static p:Resources.textEditShipcall}" Height="490" Width="800" Loaded="Window_Loaded" ResizeMode="NoResize" Icon="Resources/containership.ico">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width=".3*" />
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width=".3*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="{x:Static p:Resources.textShiftingFrom}" FontWeight="DemiBold"/>
<Image Margin="2" Grid.Column="1" Source="Resources/arrow_right_green.png" />
</Grid>
<Label Content="ETD" Grid.Column="0" Grid.Row="1" HorizontalContentAlignment="Right"/>
<xctk:DateTimePicker x:Name="datePickerETD" Grid.Column="1" Grid.Row="1" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<Label Content="{x:Static p:Resources.textTerminal}" Grid.Column="0" Grid.Row="2" HorizontalContentAlignment="Right"/>
<ComboBox Name="comboBoxTerminal" Grid.Column="1" Grid.Row="2" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTerminal" Click="contextMenuItemClearTerminal_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textBerth}" Grid.Column="0" Grid.Row="3" HorizontalContentAlignment="Right" />
<ComboBox Name="comboBoxDepartureBerth" Grid.Column="1" Grid.Row="3" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemDepartureBerth" Click="contextMenuItemDepartureBerth_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textDraft}" Grid.Column="0" Grid.Row="4" HorizontalContentAlignment="Right" />
<xctk:DoubleUpDown x:Name="doubleUpDownDraft" Grid.Column="1" Grid.Row="4" Margin="2" FormatString="N2" Minimum="0" />
<Label Content="{x:Static p:Resources.textTidalWindow}" FontWeight="DemiBold" Grid.Column="0" Grid.Row="5" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textFrom}" Grid.Column="0" Grid.Row="6" HorizontalContentAlignment="Right"/>
<Label Content="{x:Static p:Resources.textTo}" Grid.Column="0" Grid.Row="7" HorizontalContentAlignment="Right"/>
<xctk:DateTimePicker Name="datePickerTidalWindowFrom" Grid.Column="1" Grid.Row="6" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<xctk:DateTimePicker Name="datePickerTidalWindowTo" Grid.Column="1" Grid.Row="7" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<Grid Grid.Row="8" Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="{x:Static p:Resources.textShiftingTo}" FontWeight="DemiBold"/>
<Image Margin="2" Grid.Column="1" Source="Resources/arrow_right_green.png" />
</Grid>
<Label Content="ETA" Grid.Column="0" Grid.Row="9" HorizontalContentAlignment="Right"/>
<xctk:DateTimePicker x:Name="datePickerETA" Grid.Column="1" Grid.Row="9" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm"/>
<Label Content="{x:Static p:Resources.textBerth}" Grid.Column="0" Grid.Row="10" HorizontalContentAlignment="Right"/>
<ComboBox Name="comboBoxArrivalBerth" Grid.Column="1" Grid.Row="10" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemArrivalBerth" Click="contextMenuItemArrivalBerth_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textPierside}" Grid.Column="0" Grid.Row="11" HorizontalContentAlignment="Right" />
<ComboBox x:Name="comboBoxPiersideArrival" Grid.Column="1" Grid.Row="11" Margin="2" >
<ComboBoxItem Content="{x:Static p:Resources.textNotRotated}" />
<ComboBoxItem Content="{x:Static p:Resources.textRotated}" />
</ComboBox>
<Label Content="{x:Static p:Resources.textBerthRemarks}" Grid.Column="0" Grid.Row="12" HorizontalContentAlignment="Right" />
<TextBox x:Name="textBoxBerthRemarksArrival" Grid.Column="1" Grid.Row="12" Margin="2,1,2,3" Grid.RowSpan="2" VerticalContentAlignment="Top" AcceptsReturn="True" />
<Label Content="{x:Static p:Resources.textCancelled}" Grid.Column="0" Grid.Row="14" HorizontalContentAlignment="Right" />
<CheckBox x:Name="checkBoxCanceled" Grid.Column="1" Grid.Row="14" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,4,0" />
<Label Content="{x:Static p:Resources.textTugRequired}" Grid.Column="2" Grid.Row="1" HorizontalContentAlignment="Right"/>
<Grid Grid.Column="3" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="28" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBoxTugRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<ComboBox Name="comboBoxTug" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<Label Content="{x:Static p:Resources.textRecommendedTugs}" Grid.Column="2" Grid.Row="2" HorizontalContentAlignment="Right"/>
<xctk:IntegerUpDown x:Name="integerUpDownRecommendedTugs" Grid.Column="3" Grid.Row="2" Minimum="0" Margin="2" />
<Label Content="{x:Static p:Resources.textPilotRequired}" Grid.Column="2" Grid.Row="3" HorizontalContentAlignment="Right" />
<Grid Grid.Column="3" Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="28" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBoxPilotRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<ComboBox Name="comboBoxPilot" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<Label Content="{x:Static p:Resources.textMooring}" Grid.Column="2" Grid.Row="4" HorizontalContentAlignment="Right"/>
<ComboBox Name="comboBoxMooring" Grid.Column="3" Grid.Row="4" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearMooring" Click="contextMenuItemClearMooring_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textMooredLock}" Grid.Column="2" Grid.Row="5" HorizontalContentAlignment="Right" />
<CheckBox x:Name="checkBoxMooredLock" Grid.Column="3" Grid.Row="5" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,4,0" />
<Label Content="{x:Static p:Resources.textRainSensitiveCargo}" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="6" HorizontalAlignment="Right" />
<CheckBox x:Name="checkBoxRainsensitiveCargo" Grid.Column="3" Grid.Row="6" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,4,0" />
<Label Content="{x:Static p:Resources.textRemarks}" Grid.Column="2" Grid.Row="7" HorizontalContentAlignment="Right" />
<TextBox x:Name="textBoxRemarks" Grid.Column="3" Grid.Row="7" Margin="2,1,2,3" Grid.RowSpan="7" VerticalContentAlignment="Top" AcceptsReturn="True" />
<StackPanel Grid.Row="15" Grid.Column="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" IsEnabled="False"/>
<Button Width="80" Margin="2" Content="{x:Static p:Resources.textCancel}" x:Name="buttonCancel" Click="buttonCancel_Click"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,313 @@
// Copyright (c) 2023 schick Informatik
// Description: Input control for shifting operations
//
using BreCalClient.misc.Model;
using System;
using System.Windows;
using static BreCalClient.Extensions;
namespace BreCalClient
{
/// <summary>
/// Interaction logic for EditTimesAgencyShiftingControl.xaml
/// </summary>
public partial class EditTimesAgencyShiftingControl : Window, IEditShipcallTimesControl
{
#region Construction
public EditTimesAgencyShiftingControl()
{
InitializeComponent();
}
#endregion
#region Properties
public ShipcallControlModel ShipcallModel { get; set; } = new();
public Times Times { get; set; } = new();
public Extensions.TypeEnum CallType { get; set; }
#endregion
#region event handler
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.comboBoxMooring.ItemsSource = BreCalLists.Participants_Mooring;
this.comboBoxPilot.ItemsSource = BreCalLists.Participants_Pilot;
this.comboBoxTug.ItemsSource = BreCalLists.Participants_Tug;
this.comboBoxTerminal.ItemsSource = BreCalLists.Participants_Terminal;
this.comboBoxDepartureBerth.ItemsSource = BreCalLists.Berths;
this.comboBoxArrivalBerth.ItemsSource = BreCalLists.Berths;
this.CopyToControls();
Participant? p = null;
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId);
bool allowBSMD = false;
if (p != null)
{
allowBSMD = p.IsFlagSet(ParticipantFlag.ALLOW_BSMD);
}
bool enableControls = (this.Times.ParticipantId == App.Participant.Id) ||
(App.Participant.IsTypeFlagSet(ParticipantType.BSMD) && allowBSMD);
this.EnableControls(enableControls);
}
private void buttonOK_Click(object sender, RoutedEventArgs e)
{
this.CopyToModel();
this.DialogResult = true;
this.Close();
}
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
this.Close();
}
#endregion
#region private methods
private void CopyToModel()
{
if (this.ShipcallModel.Shipcall != null)
{
this.Times.EtdBerth = this.datePickerETD.Value;
this.Times.EtaBerth = this.datePickerETA.Value;
this.ShipcallModel.Shipcall.DepartureBerthId = (int)this.comboBoxDepartureBerth.SelectedValue;
if (this.comboBoxPiersideArrival.SelectedIndex >= 0)
{
this.ShipcallModel.Shipcall.PierSide = (this.comboBoxPiersideArrival.SelectedIndex == 0) ? true : false;
}
this.Times.BerthInfo = this.textBoxBerthRemarksArrival.Text.Trim();
this.Times.BerthId = (int?)this.comboBoxArrivalBerth.SelectedValue;
this.ShipcallModel.Shipcall.Draft = (float?)this.doubleUpDownDraft.Value;
this.ShipcallModel.Shipcall.TidalWindowFrom = this.datePickerTidalWindowFrom.Value;
this.ShipcallModel.Shipcall.TidalWindowTo = this.datePickerTidalWindowTo.Value;
this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked;
this.ShipcallModel.Shipcall.TugRequired = this.checkBoxTugRequired.IsChecked;
this.ShipcallModel.Shipcall.RecommendedTugs = this.integerUpDownRecommendedTugs.Value;
this.ShipcallModel.Shipcall.PilotRequired = this.checkBoxPilotRequired.IsChecked;
this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked;
this.ShipcallModel.Shipcall.RainSensitiveCargo = this.checkBoxRainsensitiveCargo.IsChecked;
if(!string.IsNullOrEmpty(this.textBoxRemarks.Text.Trim()))
this.Times.Remarks = this.textBoxRemarks.Text.Trim();
Participant? participant = (Participant?)this.comboBoxMooring.SelectedItem;
if (participant != null)
{
ParticipantAssignment pa = new()
{
ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.MOORING
};
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.MOORING] = pa;
}
participant = (Participant?)this.comboBoxPilot.SelectedItem;
if (participant != null)
{
ParticipantAssignment pa = new()
{
ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.PILOT
};
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.PILOT] = pa;
}
participant = (Participant?)this.comboBoxTerminal.SelectedItem;
if (participant != null)
{
ParticipantAssignment pa = new()
{
ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.TERMINAL
};
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.TERMINAL] = pa;
}
participant = (Participant?)this.comboBoxTug.SelectedItem;
if (participant != null)
{
ParticipantAssignment pa = new()
{
ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.TUG
};
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.TUG] = pa;
}
}
}
private void CopyToControls()
{
if (this.ShipcallModel == null) return;
if (this.ShipcallModel.Shipcall != null)
{
if (this.Times.EtaBerth.HasValue)
{
this.datePickerETA.Value = this.Times.EtaBerth.Value;
}
else
{
// if not set through times use value of BSMD entry
if (this.ShipcallModel.Shipcall.Eta != DateTime.MinValue)
this.datePickerETA.Value = this.ShipcallModel.Shipcall.Eta;
}
if(this.Times.EtdBerth.HasValue)
{
this.datePickerETD.Value = this.Times.EtdBerth.Value;
}
else
{
if (this.ShipcallModel.Shipcall.Etd != DateTime.MinValue)
this.datePickerETD.Value = this.ShipcallModel.Shipcall.Etd;
}
if (this.Times.BerthId.HasValue)
this.comboBoxArrivalBerth.SelectedValue = this.Times.BerthId;
else if (this.ShipcallModel.Shipcall.ArrivalBerthId.HasValue)
this.comboBoxArrivalBerth.SelectedValue = this.ShipcallModel.Shipcall.ArrivalBerthId;
if (this.ShipcallModel.Shipcall.DepartureBerthId.HasValue)
this.comboBoxDepartureBerth.SelectedValue = this.ShipcallModel.Shipcall.DepartureBerthId;
if (this.ShipcallModel.Shipcall.PierSide.HasValue)
{
if (this.ShipcallModel.Shipcall.PierSide.Value) this.comboBoxPiersideArrival.SelectedIndex = 0;
else this.comboBoxPiersideArrival.SelectedIndex = 1;
}
this.textBoxBerthRemarksArrival.Text = this.Times.BerthInfo;
this.doubleUpDownDraft.Value = this.ShipcallModel.Shipcall.Draft;
this.datePickerTidalWindowFrom.Value = this.ShipcallModel.Shipcall.TidalWindowFrom;
this.datePickerTidalWindowTo.Value = this.ShipcallModel.Shipcall.TidalWindowTo;
this.checkBoxCanceled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false;
this.checkBoxTugRequired.IsChecked = this.ShipcallModel.Shipcall.TugRequired ?? false;
this.integerUpDownRecommendedTugs.Value = this.ShipcallModel.Shipcall.RecommendedTugs;
this.checkBoxPilotRequired.IsChecked = this.ShipcallModel.Shipcall.PilotRequired ?? false;
this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false;
this.checkBoxRainsensitiveCargo.IsChecked = this.ShipcallModel.Shipcall.RainSensitiveCargo ?? false;
if(!string.IsNullOrEmpty(this.Times.Remarks))
this.textBoxRemarks.Text = this.Times.Remarks;
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.MOORING))
{
if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.MOORING].ParticipantId))
{
this.comboBoxMooring.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.MOORING].ParticipantId;
}
}
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.PILOT))
{
if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.PILOT].ParticipantId))
{
this.comboBoxPilot.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.PILOT].ParticipantId;
}
}
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.TERMINAL))
{
if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.TERMINAL].ParticipantId))
{
this.comboBoxTerminal.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.TERMINAL].ParticipantId;
}
}
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.TUG))
{
if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.TUG].ParticipantId))
{
this.comboBoxTug.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.TUG].ParticipantId;
}
}
}
}
private void EnableControls(bool isEnabled)
{
this.datePickerETD.IsEnabled = isEnabled;
this.comboBoxArrivalBerth.IsEnabled = isEnabled;
this.doubleUpDownDraft.IsEnabled = isEnabled;
this.datePickerTidalWindowFrom.IsEnabled = isEnabled;
this.datePickerTidalWindowTo.IsEnabled = isEnabled;
this.datePickerETA.IsEnabled = isEnabled;
this.comboBoxDepartureBerth.IsEnabled = isEnabled;
this.comboBoxPiersideArrival.IsEnabled = isEnabled;
this.textBoxBerthRemarksArrival.IsEnabled = isEnabled;
this.checkBoxCanceled.IsEnabled = isEnabled;
this.checkBoxTugRequired.IsEnabled = isEnabled;
this.comboBoxTug.IsEnabled = isEnabled;
this.integerUpDownRecommendedTugs.IsEnabled = isEnabled;
this.checkBoxPilotRequired.IsEnabled = isEnabled;
this.comboBoxPilot.IsEnabled = isEnabled;
this.comboBoxMooring.IsEnabled = isEnabled;
this.checkBoxMooredLock.IsEnabled = isEnabled;
this.comboBoxTerminal.IsEnabled = isEnabled;
this.checkBoxRainsensitiveCargo.IsEnabled = isEnabled;
this.textBoxRemarks.IsEnabled = isEnabled;
this.buttonOK.IsEnabled = isEnabled;
}
#endregion
#region context menu handlers
private void contextMenuItemDepartureBerth_Click(object sender, RoutedEventArgs e)
{
this.comboBoxDepartureBerth.SelectedIndex = -1;
this.ShipcallModel.Berth = "";
}
private void contextMenuItemArrivalBerth_Click(object sender, RoutedEventArgs e)
{
this.comboBoxArrivalBerth.SelectedIndex = -1;
this.ShipcallModel.Berth = "";
}
private void contextMenuItemClearTug_Click(object sender, RoutedEventArgs e)
{
this.comboBoxTug.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.TUG);
}
private void contextMenuItemClearPilot_Click(object sender, RoutedEventArgs e)
{
this.comboBoxPilot.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.PILOT);
}
private void contextMenuItemClearMooring_Click(object sender, RoutedEventArgs e)
{
this.comboBoxMooring.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.MOORING);
}
private void contextMenuItemClearTerminal_Click(object sender, RoutedEventArgs e)
{
this.comboBoxTerminal.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.TERMINAL);
}
#endregion
}
}

View File

@ -81,9 +81,9 @@
</xctk:DateTimePicker>
<CheckBox IsEnabled="False" Grid.Row="4" Grid.Column="2" Margin="4,0,0,0" Name="checkBoxZoneEntryFixed" VerticalAlignment="Center" />
<TextBox Grid.Row="5" Grid.Column="1" Margin="2" Name="textBoxRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="True" />
<TextBox Grid.Row="5" Grid.Column="1" Margin="2" Name="textBoxRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="True" IsEnabled="False"/>
<StackPanel Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" />
<Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" IsEnabled="False" />
<Button Width="80" Margin="2" Content="{x:Static p:Resources.textCancel}" x:Name="buttonCancel" Click="buttonCancel_Click"/>
</StackPanel>

View File

@ -27,7 +27,7 @@ namespace BreCalClient
public Times Times { get; set; } = new();
internal Extensions.ParticipantType ParticipantType { get; set; } = Extensions.ParticipantType.NONE;
public Extensions.TypeEnum CallType { get; set; }
#endregion
@ -36,26 +36,7 @@ namespace BreCalClient
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.CopyToControls();
// enable controls according to participant type
this.datePickerETABerth.IsEnabled = App.Participant.IsTypeFlagSet(Extensions.ParticipantType.AGENCY) ||
App.Participant.IsTypeFlagSet(Extensions.ParticipantType.MOORING) ||
App.Participant.IsTypeFlagSet(Extensions.ParticipantType.PILOT) ||
App.Participant.IsTypeFlagSet(Extensions.ParticipantType.PORT_ADMINISTRATION) ||
App.Participant.IsTypeFlagSet(Extensions.ParticipantType.TUG);
this.checkBoxEtaBerthFixed.IsEnabled = this.datePickerETABerth.IsEnabled;
this.datePickerETDBerth.IsEnabled = this.datePickerETABerth.IsEnabled;
this.checkBoxEtDBerthFixed.IsEnabled = this.datePickerETABerth.IsEnabled;
this.datePickerLockTime.IsEnabled = App.Participant.IsTypeFlagSet(Extensions.ParticipantType.AGENCY) ||
App.Participant.IsTypeFlagSet(Extensions.ParticipantType.MOORING) ||
App.Participant.IsTypeFlagSet(Extensions.ParticipantType.PORT_ADMINISTRATION);
this.checkBoxLockTimeFixed.IsEnabled = this.datePickerLockTime.IsEnabled;
this.datePickerZoneEntry.IsEnabled = App.Participant.IsTypeFlagSet(Extensions.ParticipantType.AGENCY) ||
App.Participant.IsTypeFlagSet(Extensions.ParticipantType.PILOT);
this.checkBoxZoneEntryFixed.IsEnabled = this.datePickerZoneEntry.IsEnabled;
this.EnableControls();
}
private void buttonOK_Click(object sender, RoutedEventArgs e)
@ -103,6 +84,41 @@ namespace BreCalClient
this.checkBoxZoneEntryFixed.IsChecked = this.Times.ZoneEntryFixed;
}
private void EnableControls()
{
Extensions.ParticipantType pType = (Extensions.ParticipantType) (this.Times.ParticipantType ?? 0);
if (this.Times.ParticipantId != App.Participant.Id) return; // if this is not "my" entry, there is no editing!
switch (pType)
{
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.datePickerETDBerth.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.datePickerZoneEntry.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.datePickerETDBerth.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.datePickerZoneEntry.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
this.checkBoxZoneEntryFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
this.textBoxRemarks.IsEnabled = true;
break;
}
this.buttonOK.IsEnabled = true;
}
#endregion
#region clear value event handler

View File

@ -32,7 +32,7 @@
<Label Grid.Row="5" Grid.Column="0" Content="{x:Static p:Resources.textRemarks}" HorizontalContentAlignment="Right" />
<xctk:DateTimePicker Grid.Row="0" Grid.Column="1" Margin="2" Name="datePickerOperationStart" Format="Custom" FormatString="dd.MM. yyyy HH:mm">
<xctk:DateTimePicker Grid.Row="0" Grid.Column="1" Margin="2" Name="datePickerOperationStart" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False">
<xctk:DateTimePicker.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearOperationStart" Click="contextMenuItemClearOperationStart_Click" >
@ -43,7 +43,7 @@
</ContextMenu>
</xctk:DateTimePicker.ContextMenu>
</xctk:DateTimePicker>
<xctk:DateTimePicker Grid.Row="1" Grid.Column="1" Margin="2" Name="datePickerOperationEnd" Format="Custom" FormatString="dd.MM. yyyy HH:mm">
<xctk:DateTimePicker Grid.Row="1" Grid.Column="1" Margin="2" Name="datePickerOperationEnd" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False">
<xctk:DateTimePicker.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemClearOperationEnd" Click="contextMenuItemClearOperationEnd_Click" >
@ -54,21 +54,21 @@
</ContextMenu>
</xctk:DateTimePicker.ContextMenu>
</xctk:DateTimePicker>
<ComboBox Name="comboBoxBerth" Grid.Column="1" Grid.Row="2" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox Name="comboBoxBerth" Grid.Column="1" Grid.Row="2" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" IsEnabled="False">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemBerth" Click="contextMenuItemBerth_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<ComboBox x:Name="comboBoxPierside" Grid.Column="1" Grid.Row="3" Margin="2" >
<ComboBox x:Name="comboBoxPierside" Grid.Column="1" Grid.Row="3" Margin="2" IsEnabled="False">
<ComboBoxItem Content="{x:Static p:Resources.textNotRotated}" />
<ComboBoxItem Content="{x:Static p:Resources.textRotated}" />
</ComboBox>
<TextBox Grid.Row="4" Grid.Column="1" Margin="2" Name="textBoxBerthRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="True" />
<TextBox Grid.Row="5" Grid.Column="1" Margin="2" Name="textBoxRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="True" />
<TextBox Grid.Row="4" Grid.Column="1" Margin="2" Name="textBoxBerthRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="True" IsEnabled="False" />
<TextBox Grid.Row="5" Grid.Column="1" Margin="2" Name="textBoxRemarks" TextWrapping="Wrap" AcceptsReturn="True" SpellCheck.IsEnabled="True" AcceptsTab="True" IsEnabled="False" />
<StackPanel Grid.Row="6" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" />
<Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" IsEnabled="False"/>
<Button Width="80" Margin="2" Content="{x:Static p:Resources.textCancel}" x:Name="buttonCancel" Click="buttonCancel_Click"/>
</StackPanel>

View File

@ -3,19 +3,7 @@
//
using BreCalClient.misc.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace BreCalClient
{
@ -31,9 +19,9 @@ namespace BreCalClient
#region Properties
public Times Times { get; set; } = new();
public List<Berth> Berths { get; set; }
public Times Times { get; set; } = new();
public Extensions.TypeEnum CallType { get; set; }
#endregion
@ -41,8 +29,9 @@ namespace BreCalClient
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.comboBoxBerth.ItemsSource = this.Berths;
this.comboBoxBerth.ItemsSource = BreCalLists.Berths;
this.CopyToControls();
this.EnableControls();
}
private void contextMenuItemClearOperationStart_Click(object sender, RoutedEventArgs e)
@ -103,8 +92,20 @@ namespace BreCalClient
this.textBoxBerthRemarks.Text = this.Times.BerthInfo;
}
#endregion
private void EnableControls()
{
if (this.Times.ParticipantId != App.Participant.Id) return;
this.datePickerOperationStart.IsEnabled = (CallType == Extensions.TypeEnum.Incoming) || (CallType == Extensions.TypeEnum.Shifting);
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.textBoxRemarks.IsEnabled = true;
this.buttonOK.IsEnabled = true;
}
#endregion
}
}

View File

@ -4,15 +4,11 @@
using BreCalClient.misc.Model;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BreCalClient
{
internal static class Extensions
public static class Extensions
{
#region Enum

View File

@ -1,9 +1,9 @@
using BreCalClient.misc.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// Copyright (c) 2023 schick Informatik
// Description: Interfaces to simplify dialog handling
//
using BreCalClient.misc.Model;
namespace BreCalClient
{
@ -11,7 +11,15 @@ namespace BreCalClient
{
Times Times { get; set; }
Extensions.TypeEnum CallType { get; set; }
bool? ShowDialog();
}
internal interface IEditShipcallTimesControl : IEditTimesControl
{
ShipcallControlModel ShipcallModel { get; set; }
}
}

View File

@ -72,7 +72,7 @@
</Grid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".1*" />
<ColumnDefinition Width=".13*" />
<ColumnDefinition Width=".15*" />
<ColumnDefinition Width=".15*" />
<ColumnDefinition Width=".15*" />

View File

@ -2,19 +2,21 @@
// Description: Bremen calling main window
//
using BreCalClient.misc.Api;
using BreCalClient.misc.Client;
using BreCalClient.misc.Model;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using log4net;
using BreCalClient.misc.Api;
using BreCalClient.misc.Client;
using BreCalClient.misc.Model;
using static BreCalClient.Extensions;
using System.Collections.Concurrent;
namespace BreCalClient
{
@ -23,33 +25,30 @@ namespace BreCalClient
/// </summary>
public partial class MainWindow : Window
{
private readonly ILog _log = LogManager.GetLogger(typeof(MainWindow));
private const int SHIPCALL_UPDATE_INTERVAL_SECONDS = 30;
#region Fields
private static Int32 _uiUpdateRunning = 0;
private readonly Dictionary<int, ShipcallControlModel> _allShipcallsDict = new();
private readonly Dictionary<int, ShipcallControl> _allShipCallsControlDict = new();
private Timer? _timer;
private Credentials? _credentials;
private readonly List<ShipcallControlModel> _visibleControlModels = new();
private List<Ship> _ships = new();
private readonly ConcurrentDictionary<int, Ship> _shipLookupDict = new();
private List<Berth> _berths = new();
private readonly ConcurrentDictionary<int, Berth> _berthLookupDict = new();
private List<Participant> _participants = new();
private readonly Dictionary<int, Participant> _participantLookupDict = new();
private readonly ConcurrentDictionary<int, ShipcallControlModel> _allShipcallsDict = new();
private readonly ConcurrentDictionary<int, ShipcallControl> _allShipCallsControlDict = new();
private readonly List<ShipcallControlModel> _visibleControlModels = new();
private readonly DefaultApi _api;
private readonly CancellationTokenSource _tokenSource = new();
private CancellationTokenSource _tokenSource = new();
private LoginResult? _loginResult;
private bool _refreshImmediately = false;
private bool? _showCanceled = null;
private Extensions.SortOrder? _sortOrder;
private SortOrder? _sortOrder;
private int searchPastDays = 0;
// private bool _filterChanged = false;
// private bool _sequenceChanged = false;
@ -71,8 +70,8 @@ namespace BreCalClient
public MainWindow()
{
InitializeComponent();
_api = new DefaultApi();
_api.Configuration.ApiKeyPrefix["Authorization"] = "Bearer";
_api = new DefaultApi(Properties.Settings.Default.API_URL);
_api.Configuration.ApiKeyPrefix["Authorization"] = "Bearer";
}
#endregion
@ -109,12 +108,11 @@ namespace BreCalClient
return;
}
Credentials credentials = new(username: textUsername.Text.Trim(),
password: textPassword.Password.Trim());
_credentials = new(username: textUsername.Text.Trim(), password: textPassword.Password.Trim());
try
{
_loginResult = await _api.LoginPostAsync(credentials);
_loginResult = await _api.LoginPostAsync(_credentials);
if (_loginResult != null)
{
if (_loginResult.Id > 0)
@ -122,7 +120,8 @@ namespace BreCalClient
this.busyIndicator.IsBusy = false;
this._api.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this.LoadStaticLists();
this.labelUsername.Text = $"{_loginResult.FirstName} {_loginResult.LastName}";
this.labelUsername.Text = $"{_loginResult.FirstName} {_loginResult.LastName}";
_timer = new Timer(RefreshToken, null, 4000000, Timeout.Infinite);
}
}
labelGeneralStatus.Text = $"Connection {ConnectionStatus.SUCCESSFUL}";
@ -139,6 +138,29 @@ namespace BreCalClient
}
}
private void RefreshToken(object? state)
{
try
{
_loginResult = _api.LoginPost(_credentials);
if (_loginResult != null)
{
if (_loginResult.Id > 0)
{
this._api.Configuration.ApiKey["Authorization"] = _loginResult.Token;
}
}
else
{
_log.Error("Token refresh: Renewed login returned empty login result");
}
}
catch (Exception ex)
{
_log.ErrorFormat("Error refreshing token: {0}", ex.Message);
}
}
private void buttonExit_Click(object sender, RoutedEventArgs e)
{
this.Close();
@ -146,12 +168,7 @@ namespace BreCalClient
private void buttonNew_Click(object sender, RoutedEventArgs e)
{
EditShipcallControl esc = new()
{
Participants = this._participants,
Ships = this._ships,
Berths = this._berths
};
EditShipcallControl esc = new();
if (esc.ShowDialog() ?? false)
{
@ -160,6 +177,9 @@ namespace BreCalClient
{
this.UpdateUI();
esc.ShipcallModel.Shipcall?.Participants.Clear();
foreach (ParticipantAssignment pa in esc.ShipcallModel.AssignedParticipants.Values)
esc.ShipcallModel.Shipcall?.Participants.Add(pa);
this._api.ShipcallsPost(esc.ShipcallModel.Shipcall); // save new ship call
this.AddShipcall(esc.ShipcallModel);
@ -172,6 +192,7 @@ namespace BreCalClient
private void buttonInfo_Click(object sender, RoutedEventArgs e)
{
AboutDialog ad = new();
ad.LoginResult = this._loginResult;
ad.ChangePasswordRequested += async (oldPw, newPw) =>
{
if (_loginResult != null)
@ -182,6 +203,7 @@ namespace BreCalClient
FirstName = _loginResult.FirstName,
LastName = _loginResult.LastName,
UserPhone = _loginResult.UserPhone,
UserEmail = _loginResult.UserEmail,
OldPassword = oldPw,
NewPassword = newPw
};
@ -234,28 +256,22 @@ namespace BreCalClient
private async void LoadStaticLists()
{
this._berths = await _api.BerthsGetAsync();
foreach(var berth in this._berths)
_berthLookupDict[berth.Id] = berth;
this.searchFilterControl.SetBerths(this._berths);
this._ships = await _api.ShipsGetAsync();
foreach(var ship in this._ships)
_shipLookupDict[ship.Id] = ship;
this._participants = await _api.ParticipantsGetAsync();
BreCalLists.InitializeBerths(await _api.BerthsGetAsync());
BreCalLists.InitializeShips(await _api.ShipsGetAsync());
BreCalLists.InitializeParticipants(await _api.ParticipantsGetAsync());
List<Participant> agencies = new();
foreach (Participant participant in this._participants)
{
this._participantLookupDict[participant.Id] = participant;
this.searchFilterControl.SetBerths(BreCalLists.Berths);
foreach (Participant participant in BreCalLists.Participants)
{
if (_loginResult?.ParticipantId == participant.Id)
{
App.Participant = participant;
EnableControlsForParticipant();
}
if(participant.IsTypeFlagSet(Extensions.ParticipantType.AGENCY))
agencies.Add(participant);
}
}
this.searchFilterControl.SetAgencies(agencies);
this.searchFilterControl.SetAgencies(BreCalLists.Participants_Agent);
if (!string.IsNullOrEmpty(Properties.Settings.Default.FilterCriteria))
{
@ -271,11 +287,21 @@ namespace BreCalClient
{
while (!_tokenSource.Token.IsCancellationRequested || _refreshImmediately)
{
_refreshImmediately = false;
if (_refreshImmediately)
{
_refreshImmediately = false;
_tokenSource = new CancellationTokenSource();
}
List<Shipcall>? shipcalls = null;
try
{
shipcalls = await _api.ShipcallsGetAsync();
if(this.searchPastDays != 0)
shipcalls = await _api.ShipcallsGetAsync(this.searchPastDays);
else
shipcalls = await _api.ShipcallsGetAsync();
this.Dispatcher.Invoke(new Action(() =>
{
labelGeneralStatus.Text = $"Connection {ConnectionStatus.SUCCESSFUL}";
@ -283,12 +309,17 @@ namespace BreCalClient
}));
}
catch (Exception ex)
{
{
this.Dispatcher.Invoke(new Action(() =>
{
labelGeneralStatus.Text = $"Connection {ConnectionStatus.FAILED}";
labelStatusBar.Text = ex.Message;
}));
if (ex.Message.Contains("access", StringComparison.OrdinalIgnoreCase))
{
this.RefreshToken(null);
}
}
if (shipcalls != null)
@ -313,7 +344,7 @@ namespace BreCalClient
// update entry
_allShipcallsDict[shipcall.Id].Shipcall = shipcall;
_allShipcallsDict[shipcall.Id].Times = currentTimes;
this.UpdateShipcall(_allShipcallsDict[shipcall.Id]);
UpdateShipcall(_allShipcallsDict[shipcall.Id]);
}
}
@ -349,37 +380,44 @@ namespace BreCalClient
_allShipcallsDict[scm.Shipcall.Id] = scm;
Shipcall shipcall = scm.Shipcall;
if (this._shipLookupDict.ContainsKey(shipcall.ShipId))
scm.Ship = this._shipLookupDict[shipcall.ShipId];
if (this._berthLookupDict.ContainsKey(shipcall.ArrivalBerthId ?? 0))
scm.Berth = this._berthLookupDict[shipcall.ArrivalBerthId ?? 0].Name;
scm.AssignParticipants(this._participants);
if (BreCalLists.ShipLookupDict.ContainsKey(shipcall.ShipId))
scm.Ship = BreCalLists.ShipLookupDict[shipcall.ShipId];
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();
this.Dispatcher.Invoke(() =>
{
ShipcallControl sc = new()
{
Height = 120,
ShipcallControlModel = scm,
ParticipantDict = _participantLookupDict,
Berths = _berths
ShipcallControlModel = scm
};
sc.EditTimesRequested += Sc_EditTimesRequested;
sc.EditRequested += Sc_EditRequested;
sc.EditAgencyRequested += Sc_EditAgencyRequested;
sc.RefreshData();
this._allShipCallsControlDict[scm.Shipcall.Id] = sc;
});
}
}
private void UpdateShipcall(ShipcallControlModel scm)
private static void UpdateShipcall(ShipcallControlModel scm)
{
if(scm.Shipcall == null) return;
Shipcall shipcall = scm.Shipcall;
if (this._shipLookupDict.ContainsKey(shipcall.ShipId))
scm.Ship = this._shipLookupDict[shipcall.ShipId];
if (this._berthLookupDict.ContainsKey(shipcall.ArrivalBerthId ?? 0))
scm.Berth = this._berthLookupDict[shipcall.ArrivalBerthId ?? 0].Name;
scm.AssignParticipants(this._participants);
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;
scm.AssignParticipants();
}
private void RemoveShipcall(int shipcallId)
@ -392,14 +430,29 @@ namespace BreCalClient
ShipcallControlModel removeModel = this._allShipcallsDict[shipcallId];
_visibleControlModels.Remove(removeModel);
this._allShipCallsControlDict.Remove(shipcallId);
this._allShipcallsDict.Remove(shipcallId);
this._allShipCallsControlDict.Remove(shipcallId, out _);
this._allShipcallsDict.Remove(shipcallId, out _);
}
private void FilterShipcalls()
{
SearchFilterModel sfm = this.searchFilterControl.SearchFilter;
if( sfm.EtaFrom.HasValue && sfm.EtaFrom < DateTime.Now.AddDays(-2))
{
int daysInThePast = (int)Math.Ceiling((DateTime.Now - sfm.EtaFrom.Value).TotalDays);
if (this.searchPastDays != daysInThePast)
{
this.searchPastDays = daysInThePast;
_refreshImmediately = true; // set flag to avoid timer loop termination
_tokenSource.Cancel(); // force timer loop end
}
}
else
{
searchPastDays = 0;
}
this._visibleControlModels.Clear();
// first add everything
this._visibleControlModels.AddRange(_allShipcallsDict.Values);
@ -413,42 +466,50 @@ namespace BreCalClient
if(sfm.Agencies.Count > 0 )
{
this._visibleControlModels.RemoveAll(x => !sfm.Agencies.Contains((x.GetParticipantIdForType(Extensions.ParticipantType.AGENCY)) ?? -1));
_ = this._visibleControlModels.RemoveAll((x) =>
{
Participant? agency = x.GetParticipantForType(ParticipantType.AGENCY);
if(agency != null)
{
return !sfm.Agencies.Contains(agency.Id);
}
return true;
});
}
if(sfm.Categories.Count > 0 )
{
this._visibleControlModels.RemoveAll(x => !sfm.Categories.Contains((x.Shipcall?.Type) ?? -1));
_ = this._visibleControlModels.RemoveAll(x => !sfm.Categories.Contains((x.Shipcall?.Type) ?? -1));
}
if(!string.IsNullOrEmpty(sfm.SearchString))
{
this._visibleControlModels.RemoveAll(x => !x.ContainsRemarkText(sfm.SearchString));
_ = this._visibleControlModels.RemoveAll(x => !(x.ContainsRemarkText(sfm.SearchString) || (x.Ship?.Name.Contains(sfm.SearchString, StringComparison.InvariantCultureIgnoreCase) ?? false)));
}
if(sfm.ShipLengthTo != null)
{
this._visibleControlModels.RemoveAll(x => x.Ship?.Length > sfm.ShipLengthTo);
_ = this._visibleControlModels.RemoveAll(x => x.Ship?.Length > sfm.ShipLengthTo);
}
if(sfm.ShipLengthFrom != null)
{
this._visibleControlModels.RemoveAll(x => x.Ship?.Length < sfm.ShipLengthFrom);
_ = this._visibleControlModels.RemoveAll(x => x.Ship?.Length < sfm.ShipLengthFrom);
}
if(sfm.EtaFrom != null)
{
this._visibleControlModels.RemoveAll(x => x.Shipcall?.Eta < sfm.EtaFrom);
_ = this._visibleControlModels.RemoveAll(x => x.Shipcall?.Eta < sfm.EtaFrom);
}
if(sfm.EtaTo != null)
{
this._visibleControlModels.RemoveAll(x => x.Shipcall?.Eta > sfm.EtaTo);
_ = this._visibleControlModels.RemoveAll(x => x.Shipcall?.Eta > sfm.EtaTo);
}
if(!_showCanceled ?? true) // canceled calls are filtered by default
{
this._visibleControlModels.RemoveAll(x => x.Shipcall?.Canceled ?? true);
_ = this._visibleControlModels.RemoveAll(x => x.Shipcall?.Canceled ?? false);
}
if (this._sortOrder != null)
@ -466,8 +527,8 @@ namespace BreCalClient
{
if (x.Shipcall == null) return 0;
if (y.Shipcall == null) return 0;
DateTime xDate = (x.Shipcall.Type == (int) Extensions.TypeEnum.Incoming) ? x.Shipcall.Eta : x.Shipcall.Etd ?? x.Shipcall.Eta;
DateTime yDate = (y.Shipcall.Type == (int) Extensions.TypeEnum.Incoming) ? y.Shipcall.Eta : y.Shipcall.Etd ?? y.Shipcall.Eta;
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;
@ -477,28 +538,45 @@ namespace BreCalClient
}
}
#endregion
#region UpdateUI func
private void UpdateUI()
{
this.Dispatcher.Invoke(new Action(() =>
{
this.stackPanel.Children.Clear();
foreach(ShipcallControlModel visibleModel in this._visibleControlModels)
if (Interlocked.CompareExchange(ref _uiUpdateRunning, 1, 0) == 1) return;
try
{
if (visibleModel.Shipcall == null) continue; // should not happen
if(this._allShipCallsControlDict.ContainsKey(visibleModel.Shipcall.Id))
this.stackPanel.Children.Clear();
foreach (ShipcallControlModel visibleModel in this._visibleControlModels)
{
this._allShipCallsControlDict[visibleModel.Shipcall.Id].RefreshData();
this.stackPanel.Children.Add(this._allShipCallsControlDict[visibleModel.Shipcall.Id]);
if (visibleModel.Shipcall == null) continue; // should not happen
if (this._allShipCallsControlDict.ContainsKey(visibleModel.Shipcall.Id))
{
this._allShipCallsControlDict[visibleModel.Shipcall.Id].RefreshData();
this.stackPanel.Children.Add(this._allShipCallsControlDict[visibleModel.Shipcall.Id]);
}
}
}
catch(Exception e) {
_log.ErrorFormat("Exception running ui update: {0}", e.ToString());
}
finally
{
_uiUpdateRunning = 0;
}
}));
}
#endregion
#region control event handler
private async void Sc_EditRequested(ShipcallControl obj)
@ -507,16 +585,17 @@ namespace BreCalClient
{
EditShipcallControl esc = new()
{
ShipcallModel = obj.ShipcallControlModel,
Ships = _ships,
Participants = _participants,
Berths = _berths
ShipcallModel = obj.ShipcallControlModel
};
if(esc.ShowDialog() ?? false)
{
try
{
obj.ShipcallControlModel.Shipcall?.Participants.Clear();
obj.ShipcallControlModel.UpdateTimesAssignments(this._api);
foreach(ParticipantAssignment pa in obj.ShipcallControlModel.AssignedParticipants.Values)
obj.ShipcallControlModel.Shipcall?.Participants.Add(pa);
await _api.ShipcallsPutAsync(obj.ShipcallControlModel.Shipcall);
obj.RefreshData();
_refreshImmediately = true;
@ -532,10 +611,15 @@ namespace BreCalClient
private async void Sc_EditTimesRequested(ShipcallControl obj, Times? times, Extensions.ParticipantType participantType)
{
if( obj.ShipcallControlModel == null) { return; }
if (!obj.ShipcallControlModel.AssignedParticipants.ContainsKey(participantType)) return; // no assigment means no dialog my friend
// show a dialog that lets the user create / update times for the given shipcall
IEditTimesControl etc = (participantType == ParticipantType.TERMINAL) ? new EditTimesTerminalControl() : new EditTimesControl();
if (etc is EditTimesTerminalControl ettc)
ettc.Berths = this._berths;
if(obj.ShipcallControlModel.Shipcall != null)
etc.CallType = (TypeEnum) obj.ShipcallControlModel.Shipcall.Type;
bool wasEdit = false;
if (times != null)
@ -543,6 +627,13 @@ namespace BreCalClient
etc.Times = times;
wasEdit = true;
}
else
{
if(obj.ShipcallControlModel.AssignedParticipants[participantType].ParticipantId == App.Participant.Id)
{
etc.Times.ParticipantId = App.Participant.Id; // this is my record, so the Participant Id is set that allows editing
}
}
// actually we should only do this on create but we have existing data
etc.Times.ParticipantType = (int) participantType;
@ -562,7 +653,8 @@ namespace BreCalClient
{
etc.Times.ShipcallId = obj.ShipcallControlModel.Shipcall.Id;
}
await _api.TimesPostAsync(etc.Times);
Id apiResultId = await _api.TimesPostAsync(etc.Times);
etc.Times.Id = apiResultId.VarId;
obj.ShipcallControlModel?.Times.Add(etc.Times);
}
_refreshImmediately = true;
@ -575,6 +667,84 @@ namespace BreCalClient
}
}
private async void Sc_EditAgencyRequested(ShipcallControl sc, Times? times)
{
IEditShipcallTimesControl? editControl = null;
switch(sc.ShipcallControlModel?.Shipcall?.Type)
{
case (int)TypeEnum.Incoming:
editControl = new EditTimesAgencyIncomingControl();
break;
case (int)TypeEnum.Outgoing:
editControl = new EditTimesAgencyOutgoingControl();
break;
case (int)TypeEnum.Shifting:
editControl = new EditTimesAgencyShiftingControl();
break;
}
if (editControl != null)
{
editControl.ShipcallModel = sc.ShipcallControlModel ?? new ShipcallControlModel();
bool wasEdit = false;
if (times != null)
{
editControl.Times = times;
wasEdit = true;
}
else
{
if(editControl.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
editControl.Times.ParticipantId = editControl.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId;
}
editControl.Times.ParticipantType = (int)ParticipantType.AGENCY;
if(editControl.ShowDialog() ?? false)
{
try
{
sc.ShipcallControlModel?.UpdateTimesAssignments(_api); // if the agent changed the assignment of the participant to another
// always try to be the agent, even if we are BSMD
if (editControl.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
{
editControl.Times.ParticipantId = editControl.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId;
}
else
{
editControl.Times.ParticipantId = App.Participant.Id;
}
if (wasEdit)
{
await _api.TimesPutAsync(editControl.Times);
}
else
{
if ((sc.ShipcallControlModel != null) && (sc.ShipcallControlModel.Shipcall != null))
{
editControl.Times.ShipcallId = sc.ShipcallControlModel.Shipcall.Id;
}
Id resultAPI_Id = await _api.TimesPostAsync(editControl.Times);
editControl.Times.Id = resultAPI_Id.VarId;
sc.ShipcallControlModel?.Times.Add(editControl.Times);
}
editControl.ShipcallModel.Shipcall?.Participants.Clear();
foreach (ParticipantAssignment pa in editControl.ShipcallModel.AssignedParticipants.Values)
editControl.ShipcallModel.Shipcall?.Participants.Add(pa);
await _api.ShipcallsPutAsync(editControl.ShipcallModel.Shipcall);
_refreshImmediately = true;
_tokenSource.Cancel();
}
catch(Exception ex)
{
ShowErrorDialog(ex.Message, "Error saving agency information");
}
}
}
}
#endregion
#region helper

View File

@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project>
<PropertyGroup>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>0.6.0.0</ApplicationVersion>
<ApplicationVersion>0.9.4.0</ApplicationVersion>
<BootstrapperEnabled>False</BootstrapperEnabled>
<Configuration>Release</Configuration>
<CreateWebPageOnPublish>True</CreateWebPageOnPublish>

View File

@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project>
<PropertyGroup>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>0.6.2.0</ApplicationVersion>
<ApplicationVersion>0.9.4.0</ApplicationVersion>
<BootstrapperEnabled>False</BootstrapperEnabled>
<Configuration>Debug</Configuration>
<CreateWebPageOnPublish>True</CreateWebPageOnPublish>

View File

@ -110,6 +110,16 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
public static byte[] check {
get {
object obj = ResourceManager.GetObject("check", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
@ -160,6 +170,16 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
public static byte[] delete2 {
get {
object obj = ResourceManager.GetObject("delete2", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
@ -190,6 +210,16 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
public static byte[] sign_warning {
get {
object obj = ResourceManager.GetObject("sign_warning", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized string similar to Agencies.
/// </summary>
@ -316,6 +346,15 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Update contact info.
/// </summary>
public static string textChangeContactInfo {
get {
return ResourceManager.GetString("textChangeContactInfo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Change password.
/// </summary>
@ -406,6 +445,15 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to E-mail.
/// </summary>
public static string textEmail {
get {
return ResourceManager.GetString("textEmail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enter keyword.
/// </summary>
@ -460,6 +508,15 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Incoming.
/// </summary>
public static string textIncoming {
get {
return ResourceManager.GetString("textIncoming", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Interval.
/// </summary>
@ -469,6 +526,15 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Length.
/// </summary>
public static string textLength {
get {
return ResourceManager.GetString("textLength", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to L/W.
/// </summary>
@ -577,6 +643,15 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Outgoing.
/// </summary>
public static string textOutgoing {
get {
return ResourceManager.GetString("textOutgoing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Participants.
/// </summary>
@ -604,6 +679,15 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Phone.
/// </summary>
public static string textPhone {
get {
return ResourceManager.GetString("textPhone", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Pier side.
/// </summary>
@ -703,6 +787,33 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Shifting.
/// </summary>
public static string textShifting {
get {
return ResourceManager.GetString("textShifting", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shifting from.
/// </summary>
public static string textShiftingFrom {
get {
return ResourceManager.GetString("textShiftingFrom", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shifting to.
/// </summary>
public static string textShiftingTo {
get {
return ResourceManager.GetString("textShiftingTo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Ship.
/// </summary>
@ -820,6 +931,15 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Width.
/// </summary>
public static string textWidth {
get {
return ResourceManager.GetString("textWidth", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Zone entry.
/// </summary>

View File

@ -236,7 +236,7 @@
<value>Operation Start</value>
</data>
<data name="textMooredLock" xml:space="preserve">
<value>Festmacher in Schleuse</value>
<value>auch in Schleuse</value>
</data>
<data name="textMooring" xml:space="preserve">
<value>Festmacher</value>
@ -385,4 +385,34 @@
<data name="textRemarks" xml:space="preserve">
<value>Info</value>
</data>
<data name="textIncoming" xml:space="preserve">
<value>Einkommend</value>
</data>
<data name="textOutgoing" xml:space="preserve">
<value>Ausgehend</value>
</data>
<data name="textShifting" xml:space="preserve">
<value>Verholung</value>
</data>
<data name="textLength" xml:space="preserve">
<value>Länge</value>
</data>
<data name="textShiftingFrom" xml:space="preserve">
<value>Verholung von</value>
</data>
<data name="textShiftingTo" xml:space="preserve">
<value>Verholung nach</value>
</data>
<data name="textWidth" xml:space="preserve">
<value>Breite</value>
</data>
<data name="textChangeContactInfo" xml:space="preserve">
<value>Kontaktdaten bearbeiten</value>
</data>
<data name="textEmail" xml:space="preserve">
<value>E-Mail</value>
</data>
<data name="textPhone" xml:space="preserve">
<value>Telefon</value>
</data>
</root>

View File

@ -133,6 +133,9 @@
<data name="arrow_up_red" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>arrow_up_red.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="check" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>check.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="clipboard" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>clipboard.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
@ -148,6 +151,9 @@
<data name="delete" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>delete.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="delete2" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>delete2.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="emergency_stop_button" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>emergency_stop_button.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
@ -157,6 +163,9 @@
<data name="ship2" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>ship2.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="sign_warning" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>sign_warning.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="textAgencies" xml:space="preserve">
<value>Agencies</value>
</data>
@ -199,6 +208,9 @@
<data name="textChange" xml:space="preserve">
<value>Change</value>
</data>
<data name="textChangeContactInfo" xml:space="preserve">
<value>Update contact info</value>
</data>
<data name="textChangePassword" xml:space="preserve">
<value>Change password</value>
</data>
@ -229,6 +241,9 @@
<data name="textEditTimes" xml:space="preserve">
<value>Edit times</value>
</data>
<data name="textEmail" xml:space="preserve">
<value>E-mail</value>
</data>
<data name="textEnterKeyword" xml:space="preserve">
<value>Enter keyword</value>
</data>
@ -247,9 +262,15 @@
<data name="textFrom" xml:space="preserve">
<value>from</value>
</data>
<data name="textIncoming" xml:space="preserve">
<value>Incoming</value>
</data>
<data name="textInterval" xml:space="preserve">
<value>Interval</value>
</data>
<data name="textLength" xml:space="preserve">
<value>Length</value>
</data>
<data name="textLengthWidth" xml:space="preserve">
<value>L/W</value>
</data>
@ -286,6 +307,9 @@
<data name="textOperationsStart" xml:space="preserve">
<value>Operations start</value>
</data>
<data name="textOutgoing" xml:space="preserve">
<value>Outgoing</value>
</data>
<data name="textParticipants" xml:space="preserve">
<value>Participants</value>
</data>
@ -295,6 +319,9 @@
<data name="textPasswordChanged" xml:space="preserve">
<value>Password changed.</value>
</data>
<data name="textPhone" xml:space="preserve">
<value>Phone</value>
</data>
<data name="textPierside" xml:space="preserve">
<value>Pier side</value>
</data>
@ -328,6 +355,15 @@
<data name="textSearch" xml:space="preserve">
<value>Search</value>
</data>
<data name="textShifting" xml:space="preserve">
<value>Shifting</value>
</data>
<data name="textShiftingFrom" xml:space="preserve">
<value>Shifting from</value>
</data>
<data name="textShiftingTo" xml:space="preserve">
<value>Shifting to</value>
</data>
<data name="textShip" xml:space="preserve">
<value>Ship</value>
</data>
@ -367,6 +403,9 @@
<data name="textVoyage" xml:space="preserve">
<value>Voyage</value>
</data>
<data name="textWidth" xml:space="preserve">
<value>Width</value>
</data>
<data name="textZoneEntryTime" xml:space="preserve">
<value>Zone entry</value>
</data>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -2,18 +2,16 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BreCalClient"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:p = "clr-namespace:BreCalClient.Resources"
xmlns:sets="clr-namespace:BreCalClient.Properties"
xmlns:db="clr-namespace:BreCalClient;assembly=BreCalClient"
xmlns:db2="clr-namespace:BreCalClient.misc.Model;assembly=BreCalClient"
xmlns:db="clr-namespace:BreCalClient;assembly=BreCalClient"
mc:Ignorable="d"
d:DesignHeight="120" d:DesignWidth="800" Loaded="UserControl_Loaded">
<Border BorderBrush="LightGray" Margin="1" BorderThickness="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".1*" />
<ColumnDefinition Width=".13*" />
<ColumnDefinition Width=".15*" />
<ColumnDefinition Width=".15*" />
<ColumnDefinition Width=".15*" />
@ -35,20 +33,23 @@
<RowDefinition Height=".05*"/>
<RowDefinition Height=".125*"/>
<RowDefinition Height=".125*"/>
<RowDefinition Height=".125*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".4*" />
<ColumnDefinition Width=".6*" />
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" Grid.ColumnSpan="2">
<Grid Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" Grid.ColumnSpan="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="32" />
</Grid.ColumnDefinitions>
<Image Margin="2" Grid.Column="0" PreviewMouseUp="Image_PreviewMouseUp" x:Name="imageShipcallType" />
<Label Grid.Column="1" FontSize="12" x:Name="labelShipName" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" PreviewMouseUp="Image_PreviewMouseUp"/>
<Grid Grid.Column="2" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}">
<Image Grid.Column="3" Margin="2" x:Name="imageEvaluation" />
</Grid>
</Grid>
<Viewbox Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left">
<TextBlock Text="IMO" />
@ -72,20 +73,15 @@
<TextBlock Text="ETA" x:Name="labelETA"/>
</Viewbox>
<Viewbox Grid.Row="5" Grid.Column="1" HorizontalAlignment="Left">
<TextBlock x:Name="textBlockETA" />
<TextBlock x:Name="textBlockETA" Padding="0" FontWeight="DemiBold"/>
</Viewbox>
<Viewbox Grid.Row="6" Grid.Column="0" HorizontalAlignment="Left">
<TextBlock Text="{x:Static p:Resources.textBerth}" />
<TextBlock Text="{x:Static p:Resources.textBerth}"/>
</Viewbox>
<Viewbox Grid.Row="6" Grid.Column="1" HorizontalAlignment="Left">
<TextBlock x:Name="textBlockBerth" />
</Viewbox>
<Viewbox Grid.Row="7" Grid.Column="0" HorizontalAlignment="Left">
<TextBlock Text="{x:Static p:Resources.textAgency}" />
</Viewbox>
<Viewbox Grid.Row="7" Grid.Column="1" HorizontalAlignment="Left">
<TextBlock x:Name="textBlockAgency" />
<TextBlock x:Name="textBlockBerth" Padding="0" FontWeight="DemiBold" />
</Viewbox>
</Grid>
@ -101,160 +97,123 @@
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelTug" PreviewMouseUp="labelTug_PreviewMouseUp"/>
<Label Grid.Row="0" Grid.Column="6" Grid.RowSpan="1" FontSize="12" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelTerminal" PreviewMouseUp="labelTerminal_PreviewMouseUp" />
<!-- AGENCY -->
<Border Grid.Row="2" Grid.Column="1" BorderThickness="1, 0, 0, 0" BorderBrush="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Padding="3,0,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.7*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height=".5*" />
<RowDefinition Height="14" />
<RowDefinition Height=".5*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content = "ETA" x:Name="labelETAETDAgent" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static p:Resources.textRemarks}" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textBerth}" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="0" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelAgencyETAETDValue" FontWeight="DemiBold"/>
<TextBlock Grid.Row="1" Grid.Column="1" Grid.RowSpan="1" Padding="0" TextWrapping="Wrap" VerticalAlignment="Top" x:Name="textBlockAgencyRemarks" FontSize="10"/>
<Label Grid.Row="2" Grid.Column="1" HorizontalContentAlignment="Left" x:Name="labelAgencyBerth" Padding="0" VerticalContentAlignment="Center" FontSize="11" FontWeight="SemiBold" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="{x:Static p:Resources.textBerthRemarks}" Padding="0" VerticalAlignment="Top" TextWrapping="Wrap" FontSize="9"/>
<TextBlock Grid.Row="3" Grid.Column="1" Grid.RowSpan="1" Padding="0" TextWrapping="Wrap" VerticalAlignment="Top" x:Name="textBlockAgencyBerthRemarks" FontSize="10"/>
</Grid>
</Border>
<!-- MOORING -->
<Border Grid.Row="2" Grid.Column="2" BorderThickness="1, 0, 0, 0" BorderBrush="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Padding="3,0,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.7*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" x:Name="labelETAETDMooring" Grid.Column="0" Content="ETA" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textRemarks}" Padding="0" VerticalContentAlignment="Top" FontSize="9"/>
<Label Grid.Row="0" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelMooringETAETDValue" FontWeight="DemiBold"/>
<TextBlock Grid.Row="1" Grid.Column="1" Padding="0" TextWrapping="Wrap" VerticalAlignment="Top" x:Name="textBlockMooringRemarks"/>
</Grid>
</Border>
<Grid Grid.Row="2" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.7*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="14" />
<RowDefinition Height="20" />
<RowDefinition Height="20" />
<RowDefinition Height="14" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textBerth}" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="1" Grid.Column="0" Content="ETA" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="2" Grid.Column="0" Content="ETD" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textRemarks}" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="1" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelAgencyETA" FontWeight="DemiBold"/>
<Label Grid.Row="2" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelAgencyETD" FontWeight="DemiBold"/>
<TextBlock Grid.Row="3" Grid.Column="1" Grid.RowSpan="2" Padding="0" TextWrapping="Wrap" VerticalAlignment="Top" x:Name="textBlockAgencyRemarks" />
</Grid>
<Grid Grid.Row="2" Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.7*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="14" />
<RowDefinition Height="20" />
<RowDefinition Height="20" />
<RowDefinition Height="14" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textBerth}" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="1" Grid.Column="0" Content="ETA" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="2" Grid.Column="0" Content="ETD" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textRemarks}" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="1" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelMooringETA" FontWeight="DemiBold"/>
<Label Grid.Row="2" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelMooringETD" FontWeight="DemiBold"/>
<TextBlock Grid.Row="3" Grid.Column="1" Grid.RowSpan="2" Padding="0" TextWrapping="Wrap" VerticalAlignment="Top" x:Name="textBlockMooringRemarks"/>
</Grid>
<Grid Grid.Row="2" Grid.Column="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.7*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="14" />
<RowDefinition Height="20" />
<RowDefinition Height="20" />
<RowDefinition Height="14" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textBerth}" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="1" Grid.Column="0" Content="ETA" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="2" Grid.Column="0" Content="ETD" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textRemarks}" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="1" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelPortAuthorityETA" FontWeight="DemiBold"/>
<Label Grid.Row="2" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelPortAuthorityETD" FontWeight="DemiBold"/>
<TextBlock Grid.Row="3" Grid.Column="1" Grid.RowSpan="2" Padding="0" TextWrapping="Wrap" VerticalAlignment="Top" x:Name="textBlockPortAuthorityRemarks"/>
</Grid>
<Grid Grid.Row="2" Grid.Column="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.7*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="14" />
<RowDefinition Height="20" />
<RowDefinition Height="20" />
<RowDefinition Height="14" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textBerth}" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="1" Grid.Column="0" Content="ETA" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="2" Grid.Column="0" Content="ETD" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textRemarks}" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="1" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelPilotETA" FontWeight="DemiBold"/>
<Label Grid.Row="2" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelPilotETD" FontWeight="DemiBold"/>
<TextBlock Grid.Row="3" Grid.Column="1" Grid.RowSpan="2" Padding="0" TextWrapping="Wrap" VerticalAlignment="Top" x:Name="textBlockPilotRemarks"/>
</Grid>
<Grid Grid.Row="2" Grid.Column="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.7*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="14" />
<RowDefinition Height="20" />
<RowDefinition Height="20" />
<RowDefinition Height="14" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textBerth}" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="1" Grid.Column="0" Content="ETA" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="2" Grid.Column="0" Content="ETD" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textRemarks}" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="1" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelTugETA" FontWeight="DemiBold"/>
<Label Grid.Row="2" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelTugETD" FontWeight="DemiBold"/>
<TextBlock Grid.Row="3" Grid.Column="1" Grid.RowSpan="2" Padding="0" TextWrapping="Wrap" VerticalAlignment="Top" x:Name="textBlockTugRemarks" />
</Grid>
<Grid Grid.Row="2" Grid.Column="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.7*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="14" />
<RowDefinition Height="20" />
<RowDefinition Height="20" />
<RowDefinition Height="14" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" x:Name="labelTerminalBerth" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="1" Grid.Column="0" Content="Start" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="2" Grid.Column="0" Content="End" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textRemarks}" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="1" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelOperationsStart" FontWeight="DemiBold"/>
<Label Grid.Row="2" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelOperationsEnd" FontWeight="DemiBold"/>
<TextBlock Grid.Row="3" Grid.Column="1" Grid.RowSpan="2" Padding="0" TextWrapping="Wrap" VerticalAlignment="Top" x:Name="textBlockTerminalRemarks" />
</Grid>
<!-- Image Margin="2" Grid.Column="3" Grid.Row="0" Grid.RowSpan="3">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="{Binding NotFolderImage}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding LightMode}" Value="{x:Static db:ShipcallControlModel+TrafficLightMode.OFF}">
<Setter Property="Source" Value="./Resources/trafficlight_off.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding LightMode}" Value="{x:Static db:ShipcallControlModel+TrafficLightMode.RED}">
<Setter Property="Source" Value="./Resources/trafficlight_red.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding LightMode}" Value="{x:Static db:ShipcallControlModel+TrafficLightMode.RED_YELLOW}">
<Setter Property="Source" Value="./Resources/trafficlight_red_yellow.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding LightMode}" Value="{x:Static db:ShipcallControlModel+TrafficLightMode.GREEN}">
<Setter Property="Source" Value="./Resources/trafficlight_green.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding LightMode}" Value="{x:Static db:ShipcallControlModel+TrafficLightMode.ALL}">
<Setter Property="Source" Value="./Resources/trafficlight_on.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding LightMode}" Value="{x:Static db:ShipcallControlModel+TrafficLightMode.YELLOW}">
<Setter Property="Source" Value="./Resources/trafficlight_yellow.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image-->
<!-- PORT AUTHORITY -->
<Border Grid.Row="2" Grid.Column="3" BorderThickness="1, 0, 0, 0" BorderBrush="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Padding="3,0,0,0">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.7*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" x:Name="labelETAETDPortAuthority" Content="ETA" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textRemarks}" Padding="0" VerticalContentAlignment="Top" FontSize="9"/>
<Label Grid.Row="0" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelPortAuthorityETAETDValue" FontWeight="DemiBold"/>
<TextBlock Grid.Row="1" Grid.Column="1" Padding="0" TextWrapping="Wrap" VerticalAlignment="Top" x:Name="textBlockPortAuthorityRemarks"/>
</Grid>
</Border>
<!-- PILOT -->
<Border Grid.Row="2" Grid.Column="4" BorderThickness="1, 0, 0, 0" BorderBrush="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Padding="3,0,0,0">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.7*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" x:Name="labelETAETDPilot" Content="ETA" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textRemarks}" Padding="0" VerticalContentAlignment="Top" FontSize="9"/>
<Label Grid.Row="0" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelPilotETAETDValue" FontWeight="DemiBold"/>
<TextBlock Grid.Row="1" Grid.Column="1" Padding="0" TextWrapping="Wrap" VerticalAlignment="Top" x:Name="textBlockPilotRemarks"/>
</Grid>
</Border>
<!-- TUG -->
<Border Grid.Row="2" Grid.Column="5" BorderThickness="1, 0, 0, 0" BorderBrush="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Padding="3,0,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.7*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" x:Name="labelETAETDTug" Content="ETA" Padding="0" VerticalContentAlignment="Center" />
<Label Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textRemarks}" Padding="0" VerticalContentAlignment="Top" FontSize="9"/>
<Label Grid.Row="0" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelTugETAETDValue" FontWeight="DemiBold"/>
<TextBlock Grid.Row="1" Grid.Column="1" Padding="0" TextWrapping="Wrap" VerticalAlignment="Top" x:Name="textBlockTugRemarks"/>
</Grid>
</Border>
<!-- TERMINAL -->
<Border Grid.Row="2" Grid.Column="6" BorderThickness="1, 0, 0, 0" BorderBrush="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Padding="3,0,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*" />
<ColumnDefinition Width="0.7*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height=".5*" />
<RowDefinition Height="14" />
<RowDefinition Height=".5*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="{x:Static p:Resources.textOperationsStart}" x:Name="labelETAETDTerminal" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="0" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" x:Name="labelOperationsStart" FontWeight="DemiBold"/>
<Label Grid.Row="1" Grid.Column="0" Content="{x:Static p:Resources.textRemarks}" Padding="0" VerticalContentAlignment="Top" FontSize="9"/>
<TextBlock Grid.Row="1" Grid.Column="1" Padding="0" TextWrapping="Wrap" VerticalAlignment="Top" x:Name="textBlockTerminalRemarks" FontSize="10"/>
<Label Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textBerth}" Padding="0" VerticalContentAlignment="Center" FontSize="9"/>
<Label Grid.Row="2" Grid.Column="1" HorizontalContentAlignment="Left" x:Name="labelTerminalBerth" Padding="0" VerticalContentAlignment="Center" FontSize="11" FontWeight="SemiBold" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="{x:Static p:Resources.textBerthRemarks}" TextWrapping="Wrap" Padding="0" VerticalAlignment="Top" FontSize="9"/>
<TextBlock Grid.Row="3" Grid.Column="1" Padding="0" TextWrapping="Wrap" VerticalAlignment="Top" x:Name="textBlockTerminalBerthRemarks" FontSize="10"/>
</Grid>
</Border>
</Grid>
</Border>
</UserControl>

View File

@ -3,15 +3,13 @@
//
using BreCalClient.misc.Model;
using log4net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace BreCalClient
{
/// <summary>
@ -19,6 +17,17 @@ namespace BreCalClient
/// </summary>
public partial class ShipcallControl : UserControl
{
#region Fields
Participant? _agency;
Participant? _pilot;
Participant? _mooring;
Participant? _terminal;
Participant? _tug;
Participant? _port_administration;
private static readonly ILog _log = LogManager.GetLogger(typeof(ShipcallControl));
#endregion
#region Construction
@ -33,6 +42,8 @@ namespace BreCalClient
public event Action<ShipcallControl>? EditRequested;
public event Action<ShipcallControl, Times?>? EditAgencyRequested;
internal event Action<ShipcallControl, Times?, Extensions.ParticipantType>? EditTimesRequested;
#endregion
@ -42,165 +53,266 @@ namespace BreCalClient
/// <summary>
/// this is our datasource
/// </summary>
public ShipcallControlModel? ShipcallControlModel { get; set; }
/// <summary>
/// these are all participants (currently loaded)
/// </summary>
public Dictionary<int, Participant>? ParticipantDict { get; set; }
/// <summary>
/// For berth name lookup
/// </summary>
public List<Berth>? Berths {get; set;}
public ShipcallControlModel? ShipcallControlModel { get; set; }
#endregion
#region public methods
public void RefreshData()
{
if (this.ShipcallControlModel == null) return;
string agentName = "";
string? name;
name = this.ShipcallControlModel.GetParticipantNameForType(Extensions.ParticipantType.AGENCY);
if (name != null) agentName = name;
this.labelAgent.Content = name ?? "- / -";
name = this.ShipcallControlModel.GetParticipantNameForType(Extensions.ParticipantType.MOORING);
this.labelMooring.Content = name ?? "- / -";
name = this.ShipcallControlModel.GetParticipantNameForType(Extensions.ParticipantType.PILOT);
this.labelPilot.Content = name ?? "- / - ";
name = this.ShipcallControlModel.GetParticipantNameForType(Extensions.ParticipantType.TUG);
this.labelTug.Content = name ?? "- / - ";
name = this.ShipcallControlModel.GetParticipantNameForType(Extensions.ParticipantType.PORT_ADMINISTRATION);
this.labelPortAuthority.Content = name ?? "- / - ";
name = this.ShipcallControlModel.GetParticipantNameForType(Extensions.ParticipantType.TERMINAL);
this.labelTerminal.Content = name ?? "- / - ";
if(App.Participant.IsTypeFlagSet(Extensions.ParticipantType.TERMINAL) && (App.Participant.Id == this.ShipcallControlModel.GetParticipantIdForType(Extensions.ParticipantType.TERMINAL)))
{
this.labelTerminal.FontWeight = FontWeights.Bold;
this.labelTerminal.Foreground = Brushes.LightYellow;
}
if(App.Participant.IsTypeFlagSet(Extensions.ParticipantType.PILOT) && (App.Participant.Id == this.ShipcallControlModel.GetParticipantIdForType(Extensions.ParticipantType.PILOT)))
{
this.labelPilot.FontWeight = FontWeights.Bold;
this.labelPilot.Foreground = Brushes.LightYellow;
}
if(App.Participant.IsTypeFlagSet(Extensions.ParticipantType.AGENCY) && (App.Participant.Id == this.ShipcallControlModel.GetParticipantIdForType(Extensions.ParticipantType.AGENCY)))
{
this.labelAgent.FontWeight = FontWeights.Bold;
this.labelAgent.Foreground = Brushes.LightYellow;
}
if(App.Participant.IsTypeFlagSet(Extensions.ParticipantType.MOORING) && (App.Participant.Id == this.ShipcallControlModel.GetParticipantIdForType(Extensions.ParticipantType.MOORING)))
{
this.labelMooring.FontWeight = FontWeights.Bold;
this.labelMooring.Foreground = Brushes.LightYellow;
}
if(App.Participant.IsTypeFlagSet(Extensions.ParticipantType.PORT_ADMINISTRATION) && (App.Participant.Id == this.ShipcallControlModel.GetParticipantIdForType(Extensions.ParticipantType.PORT_ADMINISTRATION)))
{
this.labelPortAuthority.FontWeight = FontWeights.Bold;
this.labelPortAuthority.Foreground = Brushes.LightYellow;
}
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.TUG) && (App.Participant.Id == this.ShipcallControlModel.GetParticipantIdForType(Extensions.ParticipantType.TUG)))
{
this.labelTug.FontWeight = FontWeights.Bold;
this.labelTug.Foreground = Brushes.LightYellow;
}
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD) ||
(App.Participant.IsTypeFlagSet(Extensions.ParticipantType.AGENCY) && (App.Participant.Id == this.ShipcallControlModel.GetParticipantIdForType(Extensions.ParticipantType.AGENCY))))
{
this.labelShipName.FontWeight = FontWeights.Bold;
this.labelShipName.Foreground = Brushes.LightYellow;
}
this.labelShipName.Content = this.ShipcallControlModel?.Ship?.Name;
switch(this.ShipcallControlModel?.Shipcall?.Type)
{
case 1: // incoming
this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalTestClient;component/Resources/arrow_down_red.png"));
break;
case 2: // outgoing
this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalTestClient;component/Resources/arrow_up_blue.png"));
break;
case 3: // shifting
this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalTestClient;component/Resources/arrow_right_green.png"));
break;
default:
break;
}
this.textBlockBerth.Text = this.ShipcallControlModel?.Berth;
this.textBlockCallsign.Text = this.ShipcallControlModel?.Ship?.Callsign;
if ((this.ShipcallControlModel?.Shipcall?.Type == 1) || (this.ShipcallControlModel?.Shipcall?.Type == 3))
{
this.textBlockETA.Text = this.ShipcallControlModel?.Shipcall?.Eta.ToString("dd.MM. HH:mm");
}
if(this.ShipcallControlModel?.Shipcall?.Type == 2)
{
this.labelETA.Text = "ETD";
this.textBlockETA.Text = this.ShipcallControlModel?.Shipcall?.Etd?.ToString("dd.MM. HH:mm");
}
this.textBlockIMO.Text = this.ShipcallControlModel?.Ship?.Imo.ToString();
this.textBlockLengthWidth.Text = $"{this.ShipcallControlModel?.Ship?.Length} / {this.ShipcallControlModel?.Ship?.Width}";
this.textBlockAgency.Text = agentName.TruncateDots(10);
if (this.ParticipantDict != null)
{
try
{
if (this.ShipcallControlModel != null)
{
foreach (Times times in this.ShipcallControlModel.Times)
{
if (times.ParticipantType == (int)Extensions.ParticipantType.AGENCY)
{
this.labelAgencyETA.Content = times.EtaBerth.HasValue ? times.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.labelAgencyETD.Content = times.EtdBerth.HasValue ? times.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockAgencyRemarks.Text = times.Remarks;
}
if (times.ParticipantType == (int) Extensions.ParticipantType.MOORING)
{
this.labelMooringETA.Content = times.EtaBerth.HasValue ? times.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.labelMooringETD.Content = times.EtdBerth.HasValue ? times.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockMooringRemarks.Text = times.Remarks;
}
if (times.ParticipantType == (int)Extensions.ParticipantType.PORT_ADMINISTRATION)
{
this.labelPortAuthorityETA.Content = times.EtaBerth.HasValue ? times.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.labelPortAuthorityETD.Content = times.EtdBerth.HasValue ? times.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockPortAuthorityRemarks.Text = times.Remarks;
}
if (times.ParticipantType == (int)Extensions.ParticipantType.PILOT)
{
this.labelPilotETA.Content = times.EtaBerth.HasValue ? times.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.labelPilotETD.Content = times.EtdBerth.HasValue ? times.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockPilotRemarks.Text = times.Remarks;
}
if (times.ParticipantType == (int)Extensions.ParticipantType.TUG)
{
this.labelTugETA.Content = times.EtaBerth.HasValue ? times.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.labelTugETD.Content = times.EtdBerth.HasValue ? times.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockTugRemarks.Text = times.Remarks;
}
if (times.ParticipantType == (int)Extensions.ParticipantType.TERMINAL)
{
if (this.Berths != null)
{
Berth? berth = this.Berths.Find((x) => x.Id == times.BerthId);
this.labelTerminalBerth.Content = (berth != null) ? berth.Name : "";
}
this.labelOperationsStart.Content = times.OperationsStart.HasValue ? times.OperationsStart.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.labelOperationsEnd.Content = times.OperationsEnd.HasValue ? times.OperationsEnd.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockTerminalRemarks.Text = times.Remarks;
}
string agentName = "";
string? name;
_agency = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.AGENCY);
name = _agency?.Name;
if (name != null) agentName = name;
this.labelAgent.Content = name ?? "- / -";
_mooring = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.MOORING);
name = _mooring?.Name;
this.labelMooring.Content = name ?? "- / -";
_pilot = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.PILOT);
name = _pilot?.Name;
this.labelPilot.Content = name ?? "- / - ";
_tug = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.TUG);
name = _tug?.Name;
this.labelTug.Content = name ?? "- / - ";
_port_administration = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.PORT_ADMINISTRATION);
name = _port_administration?.Name;
this.labelPortAuthority.Content = name ?? "- / - ";
_terminal = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.TERMINAL);
name = _terminal?.Name;
this.labelTerminal.Content = name ?? "- / - ";
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.TERMINAL) && (App.Participant.Id == _terminal?.Id))
{
this.labelTerminal.FontWeight = FontWeights.Bold;
this.labelTerminal.Foreground = Brushes.LightYellow;
}
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.PILOT) && (App.Participant.Id == _pilot?.Id))
{
this.labelPilot.FontWeight = FontWeights.Bold;
this.labelPilot.Foreground = Brushes.LightYellow;
}
if ((App.Participant.IsTypeFlagSet(Extensions.ParticipantType.AGENCY) && (App.Participant.Id == _agency?.Id)) ||
(App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD) && (_agency != null) && _agency.IsFlagSet(Extensions.ParticipantFlag.ALLOW_BSMD)))
{
this.labelAgent.FontWeight = FontWeights.Bold;
this.labelAgent.Foreground = Brushes.LightYellow;
}
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.MOORING) && (App.Participant.Id == _mooring?.Id))
{
this.labelMooring.FontWeight = FontWeights.Bold;
this.labelMooring.Foreground = Brushes.LightYellow;
}
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.PORT_ADMINISTRATION) && (App.Participant.Id == _port_administration?.Id))
{
this.labelPortAuthority.FontWeight = FontWeights.Bold;
this.labelPortAuthority.Foreground = Brushes.LightYellow;
}
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.TUG) && (App.Participant.Id == _tug?.Id))
{
this.labelTug.FontWeight = FontWeights.Bold;
this.labelTug.Foreground = Brushes.LightYellow;
}
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD))
{
this.labelShipName.FontWeight = FontWeights.Bold;
this.labelShipName.Foreground = Brushes.LightYellow;
}
this.labelShipName.Content = this.ShipcallControlModel?.Ship?.Name;
switch (this.ShipcallControlModel?.Shipcall?.Type)
{
case 1: // incoming
this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalClient;component/Resources/arrow_down_red.png"));
break;
case 2: // outgoing
this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalClient;component/Resources/arrow_up_blue.png"));
break;
case 3: // shifting
this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalClient;component/Resources/arrow_right_green.png"));
break;
default:
break;
}
switch(this.ShipcallControlModel?.LightMode)
{
case ShipcallControlModel.TrafficLightMode.GREEN:
this.imageEvaluation.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalClient;component/Resources/check.png"));
break;
case ShipcallControlModel.TrafficLightMode.YELLOW:
this.imageEvaluation.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalClient;component/Resources/sign_warning.png"));
break;
case ShipcallControlModel.TrafficLightMode.RED:
this.imageEvaluation.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalClient;component/Resources/delete2.png"));
break;
default:
break;
}
if (this.ShipcallControlModel?.Shipcall?.Evaluation != null)
{
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.YELLOW:
this.Background= Brushes.LightYellow;
break;
case ShipcallControlModel.TrafficLightMode.RED:
this.Background = new SolidColorBrush(Color.FromArgb(200, 255, 100, 100));
break;
default:
this.Background = Brushes.Transparent;
break;
}
}
if (!string.IsNullOrEmpty(this.ShipcallControlModel?.Shipcall?.EvaluationMessage))
this.imageEvaluation.ToolTip = this.ShipcallControlModel?.Shipcall?.EvaluationMessage;
this.textBlockBerth.Text = this.ShipcallControlModel?.Berth;
this.textBlockCallsign.Text = this.ShipcallControlModel?.Ship?.Callsign;
if (this.ShipcallControlModel?.Shipcall?.Type == 1)
{
this.textBlockETA.Text = this.ShipcallControlModel?.Shipcall?.Eta?.ToString("dd.MM. HH:mm");
}
if ((this.ShipcallControlModel?.Shipcall?.Type == 2) || (this.ShipcallControlModel?.Shipcall?.Type == 3))
{
this.labelETA.Text = "ETD";
this.textBlockETA.Text = this.ShipcallControlModel?.Shipcall?.Etd?.ToString("dd.MM. HH:mm");
}
this.textBlockIMO.Text = this.ShipcallControlModel?.Ship?.Imo.ToString();
this.textBlockLengthWidth.Text = $"{this.ShipcallControlModel?.Ship?.Length} / {this.ShipcallControlModel?.Ship?.Width}";
// 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)
{
this.labelETAETDAgent.Content = "ETD";
this.labelETAETDMooring.Content = "ETD";
this.labelETAETDPilot.Content = "ETD";
this.labelETAETDPortAuthority.Content = "ETD";
this.labelETAETDTug.Content = "ETD";
this.labelETAETDTerminal.Content = BreCalClient.Resources.Resources.textOperationsEnd;
}
if (this.ShipcallControlModel != null)
{
foreach (Times times in this.ShipcallControlModel.Times)
{
string? berthText = null;
if ((BreCalLists.Berths != null) && times.BerthId.HasValue)
{
Berth? berth = BreCalLists.Berths.Find((x) => x.Id == times.BerthId);
berthText = berth?.Name;
}
if (berthText == null)
{
if (this.ShipcallControlModel?.Shipcall?.Type == (int)Extensions.TypeEnum.Incoming)
{
Berth? berth = BreCalLists.Berths?.Find((x) => x.Id == this.ShipcallControlModel.Shipcall?.ArrivalBerthId);
berthText = berth?.Name;
}
else
{
Berth? berth = BreCalLists.Berths?.Find((x) => x.Id == this.ShipcallControlModel?.Shipcall?.DepartureBerthId);
berthText = berth?.Name;
}
}
if (times.ParticipantType == (int)Extensions.ParticipantType.AGENCY)
{
this.labelAgencyBerth.Content = berthText;
this.labelAgencyETAETDValue.Content = times.EtaBerth.HasValue ? times.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockAgencyRemarks.Text = times.Remarks;
this.textBlockAgencyBerthRemarks.Text = times.BerthInfo;
if (this.ShipcallControlModel?.Shipcall?.Type != 1)
{
this.labelAgencyETAETDValue.Content = times.EtdBerth.HasValue ? times.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
}
}
if (times.ParticipantType == (int)Extensions.ParticipantType.MOORING)
{
this.labelMooringETAETDValue.Content = times.EtaBerth.HasValue ? times.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockMooringRemarks.Text = times.Remarks;
if (this.ShipcallControlModel?.Shipcall?.Type != 1)
{
this.labelMooringETAETDValue.Content = times.EtdBerth.HasValue ? times.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
}
}
if (times.ParticipantType == (int)Extensions.ParticipantType.PORT_ADMINISTRATION)
{
this.labelPortAuthorityETAETDValue.Content = times.EtaBerth.HasValue ? times.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockPortAuthorityRemarks.Text = times.Remarks;
if (this.ShipcallControlModel?.Shipcall?.Type != 1)
{
this.labelPortAuthorityETAETDValue.Content = times.EtdBerth.HasValue ? times.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
}
}
if (times.ParticipantType == (int)Extensions.ParticipantType.PILOT)
{
this.labelPilotETAETDValue.Content = times.EtaBerth.HasValue ? times.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockPilotRemarks.Text = times.Remarks;
if (this.ShipcallControlModel?.Shipcall?.Type != 1)
{
this.labelPilotETAETDValue.Content = times.EtdBerth.HasValue ? times.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
}
}
if (times.ParticipantType == (int)Extensions.ParticipantType.TUG)
{
this.labelTugETAETDValue.Content = times.EtaBerth.HasValue ? times.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockTugRemarks.Text = times.Remarks;
if (this.ShipcallControlModel?.Shipcall?.Type != 1)
{
this.labelTugETAETDValue.Content = times.EtdBerth.HasValue ? times.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
}
}
if (times.ParticipantType == (int)Extensions.ParticipantType.TERMINAL)
{
this.labelTerminalBerth.Content = berthText;
this.labelOperationsStart.Content = times.OperationsStart.HasValue ? times.OperationsStart.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockTerminalRemarks.Text = times.Remarks;
if (this.ShipcallControlModel?.Shipcall?.Type != 1)
{
this.labelOperationsStart.Content = times.OperationsEnd.HasValue ? times.OperationsEnd.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
}
this.textBlockTerminalBerthRemarks.Text = times.BerthInfo;
}
}
}
this.DataContext = this.ShipcallControlModel;
}
}
catch (Exception ex)
{
_log.ErrorFormat("Something went wrong during data refresh: {0}", ex.ToString());
}
}
#endregion
@ -209,14 +321,13 @@ namespace BreCalClient
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// TBD
}
private void buttonEditShipcall_Click(object? sender, RoutedEventArgs? e)
{
if(App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD) ||
(App.Participant.IsTypeFlagSet(Extensions.ParticipantType.AGENCY) && (App.Participant.Id == this.ShipcallControlModel?.GetParticipantIdForType(Extensions.ParticipantType.AGENCY))))
this.EditRequested?.Invoke(this);
if(App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD))
this.EditRequested?.Invoke(this);
}
private void Image_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
@ -225,56 +336,39 @@ namespace BreCalClient
}
private void labelAgent_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.AGENCY) && (App.Participant.Id == this.ShipcallControlModel?.GetParticipantIdForType(Extensions.ParticipantType.AGENCY)))
{
this.EditRequested?.Invoke(this);
}
{
Times? times = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.AGENCY);
this.EditAgencyRequested?.Invoke(this, times);
}
private void labelMooring_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.MOORING) && (App.Participant.Id == this.ShipcallControlModel?.GetParticipantIdForType(Extensions.ParticipantType.MOORING)))
{
Times? times = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.MOORING);
this.EditTimesRequested?.Invoke(this, times, Extensions.ParticipantType.MOORING);
}
Times? times = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.MOORING);
this.EditTimesRequested?.Invoke(this, times, Extensions.ParticipantType.MOORING);
}
private void labelPortAuthority_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.PORT_ADMINISTRATION) && (App.Participant.Id == this.ShipcallControlModel?.GetParticipantIdForType(Extensions.ParticipantType.PORT_ADMINISTRATION)))
{
Times? times = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.PORT_ADMINISTRATION);
this.EditTimesRequested?.Invoke(this, times, Extensions.ParticipantType.PORT_ADMINISTRATION);
}
Times? times = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.PORT_ADMINISTRATION);
this.EditTimesRequested?.Invoke(this, times, Extensions.ParticipantType.PORT_ADMINISTRATION);
}
private void labelPilot_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.PILOT) && (App.Participant.Id == this.ShipcallControlModel?.GetParticipantIdForType(Extensions.ParticipantType.PILOT)))
{
Times? times = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.PILOT);
this.EditTimesRequested?.Invoke(this, times, Extensions.ParticipantType.PILOT);
}
Times? times = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.PILOT);
this.EditTimesRequested?.Invoke(this, times, Extensions.ParticipantType.PILOT);
}
private void labelTug_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.TUG) && (App.Participant.Id == this.ShipcallControlModel?.GetParticipantIdForType(Extensions.ParticipantType.TUG)))
{
Times? times = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.TUG);
this.EditTimesRequested?.Invoke(this, times, Extensions.ParticipantType.TUG);
}
Times? times = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.TUG);
this.EditTimesRequested?.Invoke(this, times, Extensions.ParticipantType.TUG);
}
private void labelTerminal_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.TERMINAL) && (App.Participant.Id == this.ShipcallControlModel?.GetParticipantIdForType(Extensions.ParticipantType.TERMINAL)))
{
Times? times = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.TERMINAL);
this.EditTimesRequested?.Invoke(this, times, Extensions.ParticipantType.TERMINAL);
}
Times? times = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.TERMINAL);
this.EditTimesRequested?.Invoke(this, times, Extensions.ParticipantType.TERMINAL);
}
#endregion

View File

@ -2,6 +2,7 @@
// Description: Container model for shipcall related info
//
using BreCalClient.misc.Api;
using BreCalClient.misc.Model;
using System;
using System.Collections.Generic;
@ -47,7 +48,7 @@ namespace BreCalClient
public string? Berth { get; set; }
internal Dictionary<Extensions.ParticipantType, Participant> AssignedParticipants { get; } = new();
internal Dictionary<Extensions.ParticipantType, ParticipantAssignment> AssignedParticipants { get; } = new();
public List<Times> Times { get; set; } = new();
@ -55,46 +56,31 @@ namespace BreCalClient
{
get
{
if (IsFlagSet(StatusFlags.RED))
TrafficLightMode tlm = TrafficLightMode.OFF;
if (this.Shipcall != null)
{
if (IsFlagSet((StatusFlags)StatusFlags.YELLOW))
if(this.Shipcall.Evaluation.HasValue)
{
if (IsFlagSet(StatusFlags.GREEN))
{
return TrafficLightMode.ALL;
}
return TrafficLightMode.RED_YELLOW;
tlm = (TrafficLightMode)this.Shipcall.Evaluation;
}
return TrafficLightMode.RED;
}
if (IsFlagSet(StatusFlags.YELLOW))
return TrafficLightMode.YELLOW;
if (IsFlagSet(StatusFlags.GREEN))
return TrafficLightMode.GREEN;
return TrafficLightMode.OFF;
}
return tlm;
}
}
#endregion
#region public methods
public void AssignParticipants(List<Participant> participants)
public void AssignParticipants()
{
this.AssignedParticipants.Clear();
if (Shipcall != null)
{
foreach (int participantId in Shipcall.Participants)
foreach (ParticipantAssignment participantAssignment in Shipcall.Participants)
{
Participant? participant = participants.Find((x) => x.Id == participantId);
if (participant != null)
{
foreach(Extensions.ParticipantType participantType in Enum.GetValues(typeof(Extensions.ParticipantType)))
{
if(participant.IsTypeFlagSet(participantType))
AssignedParticipants[participantType] = participant;
}
}
AssignedParticipants[(Extensions.ParticipantType)participantAssignment.Type] = participantAssignment;
}
}
}
@ -103,12 +89,24 @@ namespace BreCalClient
{
if (AssignedParticipants.ContainsKey(type)) {
int participantId = AssignedParticipants[type].Id;
int participantId = AssignedParticipants[type].ParticipantId;
foreach (Times times in this.Times)
{
if ((times.ParticipantId == participantId) && (times.ParticipantType == (int) type))
return times;
}
if(type == Extensions.ParticipantType.AGENCY)
{
// if I am BSMD and no agency entry was found this means we are editing the agency entry
if(App.Participant.Type == (int) Extensions.ParticipantType.BSMD)
{
foreach(Times times in this.Times)
{
if ((times.ParticipantId == App.Participant.Id) && (times.ParticipantType == (int) Extensions.ParticipantType.AGENCY))
return times;
}
}
}
}
return null;
}
@ -127,29 +125,36 @@ namespace BreCalClient
return false;
}
/// <summary>
/// After closing the edit shipcall or edit agency dialogs, the times assignments may have changed.
/// This function updates the assignments for existing times records accordingly and saves them.
/// </summary>
/// <param name="_api">API reference to PUT eidted times</param>
internal async void UpdateTimesAssignments(DefaultApi _api)
{
foreach (Extensions.ParticipantType participantType in this.AssignedParticipants.Keys)
{
Times? times = this.GetTimesForParticipantType(participantType);
if(times == null) continue;
if(times.ParticipantId != this.AssignedParticipants[participantType].ParticipantId)
{
times.ParticipantId = this.AssignedParticipants[participantType].ParticipantId;
await _api.TimesPutAsync(times);
}
}
}
#endregion
#region helper
internal string? GetParticipantNameForType(Extensions.ParticipantType participantType)
internal Participant? GetParticipantForType(Extensions.ParticipantType participantType)
{
foreach(Participant p in AssignedParticipants.Values)
{
if (p.IsTypeFlagSet(participantType))
return p.Name;
}
if(AssignedParticipants.ContainsKey(participantType) && BreCalLists.ParticipantLookupDict.ContainsKey(AssignedParticipants[participantType].ParticipantId))
return BreCalLists.ParticipantLookupDict[AssignedParticipants[participantType].ParticipantId];
return null;
}
internal int? GetParticipantIdForType(Extensions.ParticipantType participantType)
{
foreach(Participant p in AssignedParticipants.Values)
{
if(p.IsTypeFlagSet(participantType))
return p.Id;
}
return null;
}
}
private bool IsFlagSet(StatusFlags flag)
{

View File

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

View File

@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RoleEditor"
mc:Ignorable="d"
Title="Edit berth" Height="160" Width="450" Loaded="Window_Loaded">
Title="Edit berth" Height="188" Width="450" Loaded="Window_Loaded">
<Grid x:Name="berthGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".3*" />
@ -15,25 +15,37 @@
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="*" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<Label Content="Name" HorizontalAlignment="Right" />
<TextBox x:Name="textBoxName" Grid.Column="1" Margin="2" VerticalContentAlignment="Center" Text="{Binding Name, Mode=OneWay}" />
<Label Content="Participant / Terminal" HorizontalAlignment="Right" Grid.Row="1" />
<Label Content="Owner / Terminal" HorizontalAlignment="Right" Grid.Row="1" />
<Grid Grid.Row="1" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="28" />
</Grid.ColumnDefinitions>
<ComboBox x:Name="comboBoxParticipants" Margin="2" SelectedItem="{Binding Participant, Mode=OneWay}" />
<ComboBox x:Name="comboBoxParticipants" Margin="2" SelectedItem="{Binding Owner, Mode=OneWay}" />
<Button x:Name="buttonResetParticipant" Grid.Column="1" Margin="2" Click="buttonResetParticipant_Click">
<Image Source="./Resources/delete2.png"/>
</Button>
</Grid>
<Label Content="Uses lock" HorizontalAlignment="Right" Grid.Row="2" />
<CheckBox x:Name="checkBoxLock" Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" Margin="2" IsChecked="{Binding Path=Lock, Mode=OneWay}"/>
<StackPanel Grid.Column="1" Grid.Row="4" Orientation="Horizontal" FlowDirection="RightToLeft">
<Label Content="Authority" HorizontalAlignment="Right" Grid.Row="2" />
<Grid Grid.Row="2" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="28" />
</Grid.ColumnDefinitions>
<ComboBox x:Name="comboBoxAuthorities" Margin="2" SelectedItem="{Binding Authority, Mode=OneWay}" />
<Button x:Name="buttonResetAuthority" Grid.Column="1" Margin="2" Click="buttonResetAuthority_Click">
<Image Source="./Resources/delete2.png"/>
</Button>
</Grid>
<Label Content="Uses lock" HorizontalAlignment="Right" Grid.Row="3" />
<CheckBox x:Name="checkBoxLock" Grid.Row="3" Grid.Column="1" VerticalAlignment="Center" Margin="2" IsChecked="{Binding Path=Lock, Mode=OneWay}"/>
<StackPanel Grid.Column="1" Grid.Row="5" Orientation="Horizontal" FlowDirection="RightToLeft">
<Button x:Name="buttonCancel" Width="80" Content="Cancel" Margin="2" Click="buttonCancel_Click" />
<Button x:Name="buttonOK" Width="80" Content="OK" Margin="2" Click="buttonOK_Click"/>
</StackPanel>

View File

@ -16,7 +16,9 @@ namespace RoleEditor
public Berth Berth { get; set; } = new Berth();
public List<Participant> Participants { get; } = new List<Participant>();
public List<Participant> Owners { get; } = new List<Participant>();
public List<Participant> Authorities { get; } = new List<Participant>();
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
@ -28,11 +30,19 @@ namespace RoleEditor
{
this.Berth.Name = this.textBoxName.Text.Trim();
this.Berth.Lock = this.checkBoxLock.IsChecked;
this.Berth.Participant = this.comboBoxParticipants.SelectedItem as Participant;
if (this.Berth.Participant != null)
this.Berth.Participant_Id = this.Berth.Participant.Id;
this.Berth.Owner = this.comboBoxParticipants.SelectedItem as Participant;
if (this.Berth.Owner != null)
this.Berth.Owner_Id = this.Berth.Owner.Id;
else
this.Berth.Participant_Id = null;
this.Berth.Owner_Id = null;
this.Berth.Authority = this.comboBoxAuthorities.SelectedItem as Participant;
if (this.Berth.Authority != null)
this.Berth.Authority_Id = this.Berth.Authority.Id;
else
this.Berth.Authority_Id = null;
this.DialogResult = true;
this.Close();
}
@ -40,12 +50,18 @@ namespace RoleEditor
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = this.Berth;
this.comboBoxParticipants.ItemsSource = this.Participants;
this.comboBoxParticipants.ItemsSource = this.Owners;
this.comboBoxAuthorities.ItemsSource = this.Authorities;
}
private void buttonResetParticipant_Click(object sender, RoutedEventArgs e)
{
this.comboBoxParticipants.SelectedItem = null;
}
private void buttonResetAuthority_Click(object sender, RoutedEventArgs e)
{
this.comboBoxAuthorities.SelectedItem = null;
}
}
}

View File

@ -6,7 +6,7 @@
xmlns:local="clr-namespace:RoleEditor"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d"
Title="Bremen calling admin editor" Height="650" Width="800" Icon="Resources/lock_preferences.ico" Loaded="Window_Loaded">
Title="Bremen calling admin editor" Height="670" Width="800" Icon="Resources/lock_preferences.ico" Loaded="Window_Loaded">
<Grid>
<TabControl>
<TabItem Header="Participant, users and roles">
@ -121,19 +121,23 @@
<Label Grid.Row="1" Grid.Column="1" Content="Last name" HorizontalAlignment="Right"/>
<Label Grid.Row="2" Grid.Column="1" Content="User name" HorizontalAlignment="Right"/>
<Label Grid.Row="3" Grid.Column="1" Content="Password" HorizontalAlignment="Right"/>
<Label Grid.Row="4" Grid.Column="1" Content="API Key" HorizontalAlignment="Right"/>
<Label Content="Created" Grid.Row="5" Grid.Column="1" HorizontalAlignment="Right"/>
<Label Content="Modified" Grid.Row="6" Grid.Column="1" HorizontalAlignment="Right"/>
<Label Grid.Row="4" Grid.Column="1" Content="E-Mail" HorizontalAlignment="Right" />
<Label Grid.Row="5" Grid.Column="1" Content="Phone" HorizontalAlignment="Right" />
<Label Grid.Row="6" Grid.Column="1" Content="API Key" HorizontalAlignment="Right"/>
<Label Content="Created" Grid.Row="7" Grid.Column="1" HorizontalAlignment="Right"/>
<Label Content="Modified" Grid.Row="8" Grid.Column="1" HorizontalAlignment="Right"/>
<TextBox x:Name="textBoxUserFirstName" Grid.Row="0" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<TextBox x:Name="textBoxUserLastName" Grid.Row="1" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<TextBox x:Name="textBoxUserUserName" Grid.Row="2" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<TextBox x:Name="textBoxUserPassword" Grid.Row="3" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<TextBox x:Name="textBoxUserAPIKey" Grid.Row="4" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<TextBox x:Name="textBoxUserCreated" Grid.Row="5" IsReadOnly="True" IsEnabled="False" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<TextBox x:Name="textBoxUserModified" Grid.Row="6" IsReadOnly="True" IsEnabled="False" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<TextBox x:Name="textBoxUserEMail" Grid.Row="4" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<TextBox x:Name="textBoxUserPhone" Grid.Row="5" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<TextBox x:Name="textBoxUserAPIKey" Grid.Row="6" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<TextBox x:Name="textBoxUserCreated" Grid.Row="7" IsReadOnly="True" IsEnabled="False" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<TextBox x:Name="textBoxUserModified" Grid.Row="8" IsReadOnly="True" IsEnabled="False" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" />
<Button x:Name="buttonUserSave" Grid.Row="7" Grid.Column="2" Click="buttonUserSave_Click" Margin="2">
<Button x:Name="buttonUserSave" Grid.Row="9" Grid.Column="2" Click="buttonUserSave_Click" Margin="2">
<DockPanel>
<Image Source="./Resources/disk_blue.png" Margin="0,0,5,0" Height="24" DockPanel.Dock="Left" Width="16"/>
<TextBlock Text="Save" VerticalAlignment="Center" DockPanel.Dock="Right"/>
@ -261,6 +265,7 @@
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" IsReadOnly="True"/>
<DataGridCheckBoxColumn Header="Lock" Binding="{Binding Path=Lock}" IsReadOnly="True"/>
<DataGridTextColumn Header="Terminal" Binding="{Binding Path=Terminal, Mode=OneWay}" IsReadOnly="True"/>
<DataGridTextColumn Header="Authority" Binding="{Binding Path=Authority_Text, Mode=OneWay}" IsReadOnly="True" />
</DataGrid.Columns>
</local:ENIDataGrid>
</Grid>

View File

@ -28,6 +28,9 @@ namespace RoleEditor
#region private fields
private readonly ObservableCollection<Participant> _participants = new ObservableCollection<Participant>();
private readonly ObservableCollection<Participant> _terminals = new ObservableCollection<Participant>();
private readonly ObservableCollection<Participant> _authorities = new ObservableCollection<Participant>();
private readonly ObservableCollection<Role> _roles = new ObservableCollection<Role>();
private readonly ObservableCollection<Securable> _securables = new ObservableCollection<Securable>();
private readonly ObservableCollection<User> _users = new ObservableCollection<User>();
@ -56,8 +59,12 @@ namespace RoleEditor
// load all participants
List<Participant> participants = await Participant.LoadAll(_dbManager);
foreach(Participant p in participants)
foreach (Participant p in participants)
{
_participants.Add(p);
if(p.IsTypeFlagSet(Participant.ParticipantType.TERMINAL)) { _terminals.Add(p); }
if(p.IsTypeFlagSet(Participant.ParticipantType.PORT_ADMINISTRATION)) { _authorities.Add(p); }
}
this.listBoxParticipant.ItemsSource = _participants;
// load all roles
@ -74,9 +81,13 @@ namespace RoleEditor
foreach (Berth b in await Berth.LoadAll(_dbManager))
{
_berths.Add(b);
if(b.Participant_Id != null)
if (b.Owner_Id != null)
{
b.Participant = participants.Where( p => p.Id== b.Participant_Id ).FirstOrDefault();
b.Owner = participants.Where(p => p.Id == b.Owner_Id).FirstOrDefault();
}
if (b.Authority_Id != null)
{
b.Authority = participants.Where(p => p.Id == b.Authority_Id).FirstOrDefault();
}
}
this.dataGridBerths.Initialize();
@ -173,7 +184,8 @@ namespace RoleEditor
{
EditBerthDialog ebd = new();
ebd.Berth = b;
ebd.Participants.AddRange(this._participants);
ebd.Owners.AddRange(this._terminals);
ebd.Authorities.AddRange(this._authorities);
if (ebd.ShowDialog() ?? false)
{
await b.Save(_dbManager);
@ -188,7 +200,8 @@ namespace RoleEditor
Berth b = new();
EditBerthDialog ebd = new();
ebd.Berth = b;
ebd.Participants.AddRange(this._participants);
ebd.Owners.AddRange(this._terminals);
ebd.Authorities.AddRange(this._authorities);
if (ebd.ShowDialog() ?? false)
{
_berths.Add(b);
@ -240,6 +253,8 @@ namespace RoleEditor
u.Firstname = this.textBoxUserFirstName.Text.Trim();
u.Lastname = this.textBoxUserLastName.Text.Trim();
u.Username = this.textBoxUserUserName.Text.Trim();
u.EMail = this.textBoxUserEMail.Text.Trim();
u.Phone = this.textBoxUserPhone.Text.Trim();
if(this.textBoxUserPassword.Text.Trim().Length > 0 )
{
string passwortText = this.textBoxUserPassword.Text.Trim();
@ -442,6 +457,8 @@ namespace RoleEditor
this.textBoxUserFirstName.Text = (u != null) ? u.Firstname : string.Empty;
this.textBoxUserLastName.Text = (u != null) ? u.Lastname : string.Empty;
this.textBoxUserUserName.Text = (u != null) ? u.Username : string.Empty;
this.textBoxUserEMail.Text = (u != null) ? u.EMail : string.Empty;
this.textBoxUserPhone.Text = (u != null) ? u.Phone : string.Empty;
this.textBoxUserAPIKey.Text = (u != null) ? u.APIKey : string.Empty;
this.textBoxUserCreated.Text = (u != null) ? u.Created.ToString() : string.Empty;
this.textBoxUserModified.Text = (u != null) ? u.Modified.ToString() : string.Empty;
@ -634,7 +651,7 @@ namespace RoleEditor
{
if ((p.Name != null) && p.Name.Contains(participant_name, StringComparison.OrdinalIgnoreCase))
{
b.Participant_Id = p.Id;
b.Owner_Id = p.Id;
found_participant = true;
break;
}
@ -649,7 +666,7 @@ namespace RoleEditor
await p.Save(_dbManager);
_participants.Add(p);
pCounter++;
b.Participant_Id = p.Id;
b.Owner_Id = p.Id;
}
await b.Save(_dbManager);

View File

@ -1,4 +1,7 @@
using System;
// Copyright (c) 2023- schick Informatik
// Description: Model class for berth entity
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
@ -16,11 +19,17 @@ namespace brecal.model
public bool? Lock { get; set; }
public uint? Participant_Id { get; set; }
public uint? Owner_Id { get; set; }
public Participant? Participant { get; set; }
public uint? Authority_Id { get; set; }
public string? Terminal { get { if (Participant != null) return Participant.Name; else return "n/a"; } }
public Participant? Owner { get; set; }
public Participant? Authority { get; set; }
public string? Terminal { get { if (Owner != null) return Owner.Name; else return "n/a"; } }
public string? Authority_Text { get { if (Authority != null) return Authority.Name; else return "n/a"; } }
#endregion
@ -37,7 +46,7 @@ namespace brecal.model
public static void SetLoadQuery(IDbCommand cmd, params object?[] list)
{
cmd.CommandText = "SELECT id, name, participant_id, `lock`, created, modified FROM berth";
cmd.CommandText = "SELECT id, name, owner_id, authority_id, `lock`, created, modified FROM berth";
}
public static List<DbEntity> LoadElems(IDataReader reader)
@ -48,10 +57,11 @@ namespace brecal.model
Berth b = new();
b.Id = (uint)reader.GetInt32(0);
if (!reader.IsDBNull(1)) b.Name = reader.GetString(1);
if (!reader.IsDBNull(2)) b.Participant_Id = (uint) reader.GetInt32(2);
if (!reader.IsDBNull(3)) b.Lock = reader.GetBoolean(3);
if (!reader.IsDBNull(4)) b.Created = reader.GetDateTime(4);
if (!reader.IsDBNull(5)) b.Modified = reader.GetDateTime(5);
if (!reader.IsDBNull(2)) b.Owner_Id = (uint) reader.GetInt32(2);
if (!reader.IsDBNull(3)) b.Authority_Id = (uint) reader.GetInt32(3);
if (!reader.IsDBNull(4)) b.Lock = reader.GetBoolean(4);
if (!reader.IsDBNull(5)) b.Created = reader.GetDateTime(5);
if (!reader.IsDBNull(6)) b.Modified = reader.GetDateTime(6);
result.Add(b);
}
return result;
@ -63,7 +73,7 @@ namespace brecal.model
public override void SetCreate(IDbCommand cmd)
{
cmd.CommandText = "INSERT INTO berth (participant_id, name, `lock`) VALUES ( @PID, @NAME, @LOCK)";
cmd.CommandText = "INSERT INTO berth (owner_id, authority_id, name, `lock`) VALUES ( @PID, @AID, @NAME, @LOCK)";
this.SetParameters(cmd);
}
@ -79,7 +89,7 @@ namespace brecal.model
public override void SetUpdate(IDbCommand cmd)
{
cmd.CommandText = "UPDATE berth SET name = @NAME, participant_id = @PID, `lock` = @LOCK WHERE id = @ID";
cmd.CommandText = "UPDATE berth SET name = @NAME, owner_id = @PID, authority_id = @AID, `lock` = @LOCK WHERE id = @ID";
this.SetParameters(cmd);
}
@ -96,9 +106,14 @@ namespace brecal.model
IDbDataParameter pid = cmd.CreateParameter();
pid.ParameterName = "PID";
pid.Value = this.Participant_Id;
pid.Value = this.Owner_Id;
cmd.Parameters.Add(pid);
IDbDataParameter aid = cmd.CreateParameter();
aid.ParameterName = "AID";
aid.Value = this.Authority_Id;
cmd.Parameters.Add(aid);
IDbDataParameter name = cmd.CreateParameter();
name.ParameterName = "NAME";
name.Value = this.Name;

View File

@ -12,6 +12,10 @@ namespace brecal.model
public string Username { get; set; } = "";
public string EMail { get; set; } = "";
public string Phone { get; set; } = "";
public string? PasswordHash { get; set; }
public string? APIKey { get; set; }
@ -33,7 +37,7 @@ namespace brecal.model
public static void SetLoadQuery(IDbCommand cmd, params object?[] args)
{
cmd.CommandText = "SELECT id, first_name, last_name, user_name, api_key, created, modified FROM user WHERE participant_id = @PID";
cmd.CommandText = "SELECT id, first_name, last_name, user_name, user_email, user_phone, api_key, created, modified FROM user WHERE participant_id = @PID";
if (args.Length != 1 || !(args[0] is Participant))
throw new ArgumentException("loader needs single partipant as argument");
IDataParameter pid = cmd.CreateParameter();
@ -53,9 +57,11 @@ namespace brecal.model
if (!reader.IsDBNull(1)) u.Firstname = reader.GetString(1);
if (!reader.IsDBNull(2)) u.Lastname = reader.GetString(2);
if (!reader.IsDBNull(3)) u.Username = reader.GetString(3);
if (!reader.IsDBNull(4)) u.APIKey = reader.GetString(4);
if (!reader.IsDBNull(5)) u.Created = reader.GetDateTime(5);
if (!reader.IsDBNull(6)) u.Modified = reader.GetDateTime(6);
if (!reader.IsDBNull(4)) u.EMail = reader.GetString(4);
if (!reader.IsDBNull(5)) u.Phone = reader.GetString(5);
if (!reader.IsDBNull(6)) u.APIKey = reader.GetString(6);
if (!reader.IsDBNull(7)) u.Created = reader.GetDateTime(7);
if (!reader.IsDBNull(8)) u.Modified = reader.GetDateTime(8);
result.Add(u);
}
return result;
@ -68,15 +74,15 @@ namespace brecal.model
public override void SetUpdate(IDbCommand cmd)
{
if(!string.IsNullOrEmpty(this.PasswordHash))
cmd.CommandText = "UPDATE user SET first_name = @FIRSTNAME, last_name = @LASTNAME, user_name = @USERNAME, password_hash = @PWHASH, api_key = @APIKEY WHERE id = @ID";
cmd.CommandText = "UPDATE user SET first_name = @FIRSTNAME, last_name = @LASTNAME, user_name = @USERNAME, password_hash = @PWHASH, api_key = @APIKEY, user_email = @USEREMAIL, user_phone = @USERPHONE WHERE id = @ID";
else
cmd.CommandText = "UPDATE user SET first_name = @FIRSTNAME, last_name = @LASTNAME, user_name = @USERNAME, api_key = @APIKEY WHERE id = @ID";
cmd.CommandText = "UPDATE user SET first_name = @FIRSTNAME, last_name = @LASTNAME, user_name = @USERNAME, api_key = @APIKEY, user_email = @USEREMAIL, user_phone = @USERPHONE WHERE id = @ID";
this.SetParameters(cmd);
}
public override void SetCreate(IDbCommand cmd)
{
cmd.CommandText = "INSERT INTO user (participant_id, first_name, last_name, user_name, password_hash, api_key) VALUES ( @PID, @FIRSTNAME, @LASTNAME, @USERNAME, @PWHASH, @APIKEY)";
cmd.CommandText = "INSERT INTO user (participant_id, first_name, last_name, user_name, password_hash, api_key, user_email, user_phone) VALUES ( @PID, @FIRSTNAME, @LASTNAME, @USERNAME, @PWHASH, @APIKEY, @USEREMAIL, @USERPHONE)";
this.SetParameters(cmd);
}
@ -126,6 +132,16 @@ namespace brecal.model
username.Value = this.Username;
cmd.Parameters.Add(username);
IDbDataParameter email = cmd.CreateParameter();
email.ParameterName = "USEREMAIL";
email.Value = this.EMail;
cmd.Parameters.Add(email);
IDbDataParameter phone = cmd.CreateParameter();
phone.ParameterName = "USERPHONE";
phone.Value = this.Phone;
cmd.Parameters.Add(phone);
IDbDataParameter pwhash = cmd.CreateParameter();
pwhash.ParameterName = "PWHASH";
pwhash.Value = this.PasswordHash;

View File

@ -13,8 +13,6 @@ from .api import ships
from .api import login
from .api import user
sessions = dict()
def create_app(test_config=None):
app = Flask(__name__, instance_relative_config=True)
@ -44,7 +42,27 @@ def create_app(test_config=None):
logging.basicConfig(filename='brecal.log', level=logging.DEBUG, format='%(asctime)s | %(name)s | %(levelname)s | %(message)s')
local_db.initPool()
local_db.initPool(os.path.dirname(app.instance_path))
logging.info('App started')
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",
"difference_to_then",
"TimeLogic",
"ValidationRules",
"validation_state_and_validation_name",
]

View File

View File

@ -17,6 +17,7 @@ def GetShipcalls():
token = request.headers.get('Authorization')
options = {}
options["participant_id"] = request.args.get("participant_id")
options["past_days"] = request.args.get("past_days", default=2, type=int)
return impl.shipcalls.GetShipcalls(options)
else:

View File

@ -0,0 +1,46 @@
import os
def get_project_root(root_base_name:str, root_dir:None=None):
"""
given a {root_base_name}, this function searches the parent folders of {root_dir} until
the basename matches.
Example:
root_base_name = "brecal"
root_dir = "/home/arbitrary_user/brecal/_template/tests"
returns: "/home/arbitrary_user/brecal"
arguments:
root_base_name:str, base directory name that should be searched for
root_dir: defaults to 'None', whereas then the current working directory is selected. Can be an arbitrary path.
returns: root_dir
"""
if root_dir is None:
root_dir = os.getcwd()
assert root_base_name in root_dir, f"the desired base name MUST be present within the root directory.\nRoot Directory: {root_dir}\nDesired Root Base Name: {root_base_name}"
assert root_dir.count(root_base_name)==1, f"found multiple matches for root_base_name" # do not change, as a pytest requires precise wording
while not os.path.basename(root_dir)==root_base_name:
root_dir = os.path.dirname(root_dir)
return root_dir
def ensure_path(path, print_info=0):
"""
Function ensures that a certain directory exists. If it does not exist, it will be created.
It further checks if the parent-directory of the file exists and also ensures that.
options:
print_info: print additional information (debugging)
"""
if not os.path.exists(path):
os.makedirs(path)
if print_info == 1:
print(f"Created directory and subdirectories: {path}")
return

View File

@ -0,0 +1,131 @@
import json
from abc import ABC, abstractmethod
from BreCal.schemas.model import obj_dict
"""implementation of default objects for http request codes. this enforces standardized outputs in the (response, code, headers)-style"""
def get_request_code(code_id):
"""convenience function, which returns the desired request code object"""
request_code_dict = {
200:RequestCode_HTTP_200_OK,
201:RequestCode_HTTP_201_CREATED,
400:RequestCode_HTTP_400_BAD_REQUEST,
403:RequestCode_HTTP_403_FORBIDDEN,
404:RequestCode_HTTP_404_NOT_FOUND,
500:RequestCode_HTTP_500_INTERNAL_SERVER_ERROR
}
assert code_id in list(request_code_dict.keys()), f"unsupported request code: {code_id}. \nAvailable codes: {request_code_dict}"
return request_code_dict.get(code_id)()
class RequestStatusCode(ABC):
def __init__(self):
return
@abstractmethod
def __call__(self, data):
raise NotImplementedError("any default status code object must be callable")
@abstractmethod
def status_code(self):
raise NotImplementedError("any default status code object should return an integer")
@abstractmethod
def response(self, data):
raise NotImplementedError("the response method should return a binary json object. typically, json.dumps is used")
def headers(self):
return {'Content-Type': 'application/json; charset=utf-8'}
class RequestCode_HTTP_200_OK(RequestStatusCode):
def __init__(self) -> None:
super().__init__()
def __call__(self, data):
return (self.response(data), self.status_code(), self.headers())
def status_code(self):
return 200
def response(self, data):
return json.dumps(data, default=obj_dict)
class RequestCode_HTTP_201_CREATED(RequestStatusCode):
def __init__(self) -> None:
super().__init__()
def __call__(self, data):
return (self.response(data), self.status_code(), self.headers())
def status_code(self):
return 201
def response(self, new_id):
return json.dumps({"id":new_id})
class RequestCode_HTTP_400_BAD_REQUEST(RequestStatusCode):
def __init__(self) -> None:
super().__init__()
def __call__(self, data):
return (self.response(data), self.status_code(), self.headers())
def status_code(self):
return 400
def response(self, data):
return json.dumps(data)
class RequestCode_HTTP_403_FORBIDDEN(RequestStatusCode):
def __init__(self) -> None:
super().__init__()
def __call__(self, data):
return (self.response(data), self.status_code(), self.headers())
def status_code(self):
return 403
def response(self, message="invalid credentials"):
result = {}
result["message"] = message
return json.dumps(result)
class RequestCode_HTTP_404_NOT_FOUND(RequestStatusCode):
def __init__(self) -> None:
super().__init__()
def __call__(self, data):
return (self.response(data), self.status_code(), self.headers())
def status_code(self):
return 404
def response(self, message="no such record"):
result = {}
result["message"] = message
return json.dumps(result)
class RequestCode_HTTP_500_INTERNAL_SERVER_ERROR(RequestStatusCode):
def __init__(self) -> None:
super().__init__()
def __call__(self, data):
return (self.response(data), self.status_code(), self.headers())
def status_code(self):
return 500
def response(self, message="credential lookup mismatch"):
result = {}
result["message"] = message
return json.dumps(result)

View File

@ -0,0 +1,84 @@
def execute_test_with_pytest(filepath):
"""
creates a subprocess to use 'pytest' on a script. Every function inside the filepath
will be tested individually. The function returns verbose information about the outcome.
filepath:
can either be an individual .py file or a root directory, which contains multiple files
"""
import os
import pytest
from subprocess import Popen, PIPE
assert os.path.exists(filepath), f"cannot find file {filepath}"
with Popen(['pytest',
'-v',
'-W ignore::DeprecationWarning',
'-vv',
'--durations=0',
'--tb=short', # shorter traceback format
str(filepath)], stdout=PIPE, bufsize=1,
universal_newlines=True) as p:
for line in p.stdout:
print(line, end='')
return
def execute_coverage_test(tests_path, coverage_path, cov_report_dst_dir=None, cov_fail_under_rate=80, is_test=0):
"""
creates a subprocess to use 'coverage' on a script. Every function inside the file
will be tested individually. The function returns verbose information about the outcome.
this function needs two inputs:
tests_path, a path that locates each test that should be executed
e.g.: "/home/scope_sorting/brecal/src/server/tests"
coverage_path, a path where the code is stored, which should be analyzed for coverage
e.g.: "/home/scope_sorting/brecal/src/server/BreCal"
optional:
cov_report_dst_dir, which determines, where the coverage report will be stored. This function then
creates & stores an .html and .xml report in that folder. default: None
cov_fail_under_rate, an integer which determines, when a coverage test should fail. Default: 80, meaning
that at least 80 % of the directory should be tested to pass the test.
"""
import os
import pytest
from subprocess import Popen, PIPE
assert os.path.exists(tests_path), f"cannot find root directory {tests_path}"
assert os.path.exists(coverage_path), f"cannot find root directory {coverage_path}"
if cov_report_dst_dir is not None:
p_open_list_arguments = [
'pytest',
f"{str(tests_path)}",
"-v",
"-vv",
"--durations=0",
"--cov-report=term",
f"--cov-report=html:{cov_report_dst_dir}",
f"--cov-report=xml:{cov_report_dst_dir}/coverage.xml",
f"--cov-fail-under={cov_fail_under_rate}",
f"--cov={str(coverage_path)}",
]
else:
p_open_list_arguments = [
'pytest',
f"{str(tests_path)}",
"-v",
"-vv",
"--durations=0",
"--cov-report=term",
f"--cov-fail-under={cov_fail_under_rate}",
f"--cov={str(coverage_path)}",
]
with Popen(p_open_list_arguments, stdout=PIPE, bufsize=1,
universal_newlines=True) as p:
for line in p.stdout:
print(line, end='')
if is_test:
raise KeyboardInterrupt("is_test_interrupt")

View File

@ -0,0 +1,21 @@
import datetime
def difference_to_then(event_time, tgt_time=None, make_absolute=False):
"""
measures the difference between {tgt_time} and {event_time}. this function automatically converts the datetime.timedelta object to seconds.
tgt_time defaults to {now}, if it is not specified.
Note: using divmod(time_diff, interval_duration) may be interesting to determine, how many units of {interval_duration} have passed.
e.g.,
divmod(time_diff, 3600) returns a float of hours. This will then return a tuple
options:
make_absolute: bool. Whether to return an absolute difference
Returns: time_diff (float)
"""
tgt_time = tgt_time or datetime.datetime.now()
time_diff = tgt_time - event_time
if make_absolute:
return abs(time_diff.total_seconds())
return time_diff.total_seconds()

View File

@ -1,10 +0,0 @@
{
"host" : "localhost",
"port" : 33306,
"user" : "ds",
"password" : "HalloWach_2323XXL!!",
"pool_name" : "brecal_pool",
"pool_size" : 20,
"database" : "bremen_calling",
"autocommit" : true
}

View File

View File

@ -0,0 +1,38 @@
from enum import Enum
class ParticipantType(Enum):
"""determines the type of a participant"""
NONE = 0
BSMD = 1
TERMINAL = 2
PILOT = 4
AGENCY = 8
MOORING = 16
PORT_ADMINISTRATION = 32
TUG = 64
class ShipcallType(Enum):
"""determines the type of a shipcall, as this changes the applicable validation rules"""
INCOMING = 1
OUTGOING = 2
SHIFTING = 3
class ParticipantwiseTimeDelta():
"""stores the time delta for every participant, which triggers the validation rules in the rule set '0001'"""
AGENCY = 1200.0 # 20 h * 60 min/h = 1200 min
MOORING = 960.0 # 16 h * 60 min/h = 960 min
PILOT = 960.0 # 16 h * 60 min/h = 960 min
PORT_ADMINISTRATION = 960.0 # 16 h * 60 min/h = 960 min
TUG = 960.0 # 16 h * 60 min/h = 960 min
TERMINAL = 960.0 # 16 h * 60 min/h = 960 min
class StatusFlags(Enum):
"""
these enumerators ensure that each traffic light validation rule state corresponds to a value, which will be used in the ValidationRules object to identify
the necessity of notifications.
"""
NONE = 0
GREEN = 1
YELLOW = 2
RED = 3

View File

@ -0,0 +1,204 @@
import numpy as np
import pandas as pd
import datetime
from BreCal.schemas.model import Shipcall, Ship, Participant, Berth, User, Times
from BreCal.database.enums import ParticipantType
def pandas_series_to_data_model():
return
class SQLHandler():
"""
An object that reads SQL queries from the sql_connection and stores it in pandas DataFrames. The object can read all available tables
at once into memory, when providing 'read_all=True'.
# #TODO_initialization: shipcall_tug_map, user_role_map & role_securable_map might be mapped to the respective dataframes
"""
def __init__(self, sql_connection, read_all=False):
self.sql_connection = sql_connection
self.all_schemas = self.get_all_schemas_from_mysql()
self.build_str_to_model_dict()
if read_all:
self.read_all(self.all_schemas)
def get_all_schemas_from_mysql(self):
with self.sql_connection.cursor(buffered=True) as cursor:
cursor.execute("SHOW TABLES")
schema = cursor.fetchall()
all_schemas = [schem[0] for schem in schema]
return all_schemas
def build_str_to_model_dict(self):
"""
creates a simple dictionary, which maps a string to a data object
e.g.,
'ship'->BreCal.schemas.model.Ship object
"""
self.str_to_model_dict = {
"shipcall":Shipcall, "ship":Ship, "participant":Participant, "berth":Berth, "user":User, "times":Times
}
return
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)
return df
def mysql_to_df(self, query):
"""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
return df
def read_all(self, all_schemas):
# create a dictionary, which maps every mysql schema to pandas DataFrames
self.df_dict = self.build_full_mysql_df_dict(all_schemas)
# update the 'participants' column in 'shipcall'
self.initialize_shipcall_participant_list()
return
def build_full_mysql_df_dict(self, all_schemas):
"""given a list of strings {all_schemas}, every schema will be read as individual pandas DataFrames to a dictionary with the respective keys. returns: dictionary {schema_name:pd.DataFrame}"""
mysql_df_dict = {}
for schem in all_schemas:
query = f"SELECT * FROM {schem}"
mysql_df_dict[schem] = self.mysql_to_df(query)
return mysql_df_dict
def initialize_shipcall_participant_list(self):
"""
iteratively applies the .get_participants method to each shipcall.
the function updates the 'participants' column.
"""
# 1.) get all shipcalls
df = self.df_dict.get('shipcall')
# 2.) iterate over each individual shipcall, obtain the id (pandas calls it 'name')
# and apply the 'get_participants' method, which returns a list
# if the shipcall_id exists, the list contains ids
# otherwise, return a blank list
df['participants'] = df.apply(
lambda x: self.get_participants(x.name),
axis=1)
return
def standardize_model_str(self, model_str:str)->str:
"""check if the 'model_str' is valid and apply lowercasing to the string"""
model_str = model_str.lower()
assert model_str in list(self.df_dict.keys()), f"cannot find the requested 'model_str' in mysql: {model_str}"
return model_str
def get_data(self, id:int, model_str:str):
"""
obtains {id} from the respective mysql database and builds a data model from that.
the id should match the 'id'-column in the mysql schema.
returns: data model, such as Ship, Shipcall, etc.
e.g.,
data = self.get_data(0,"shipcall")
returns a Shipcall object
"""
model_str = self.standardize_model_str(model_str)
df = self.df_dict.get(model_str)
data = self.df_loc_to_data_model(df, id, model_str)
return data
def get_all(self, model_str:str)->list:
"""
given a model string (e.g., 'shipcall'), return a list of all
data models of that type from the sql
"""
model_str = self.standardize_model_str(model_str)
all_ids = self.df_dict.get(model_str).index
all_data = [
self.get_data(_aid, model_str)
for _aid in all_ids
]
return all_data
def df_loc_to_data_model(self, df, id, model_str, loc_type:str="loc"):
assert len(df)>0, f"empty dataframe"
# get a pandas series from the dataframe
series = df.loc[id] if loc_type=="loc" else df.iloc[id]
# get the respective data model object
data_model = self.str_to_model_dict.get(model_str,None)
assert data_model is not None, f"could not find the requested model_str: {model_str}"
# build 'data' and fill the data model object
# convert the 'id' to an integer, so the np.uint64 (used by pandas) is convertible to mysql
data = {**{'id':int(id)}, **series.to_dict()} # 'id' must be added manually, as .to_dict does not contain the index, which was set with .set_index
data = data_model(**data)
return data
def get_times_for_participant_type(self, df_times, participant_type:int):
filtered_series = df_times.loc[df_times["participant_type"]==participant_type]
assert len(filtered_series)<=1, f"found multiple results"
times = self.df_loc_to_data_model(filtered_series, id=0, model_str='times', loc_type="iloc") # use iloc! to retrieve the first result
return times
def dataframe_to_data_model_list(self, df, model_str)->list:
model_str = self.standardize_model_str(model_str)
all_ids = df.index
all_data = [
self.df_loc_to_data_model(df, _aid, model_str)
for _aid in all_ids
]
return all_data
def get_participants(self, shipcall_id:id)->list:
"""
given a {shipcall_id}, obtain the respective list of participants.
when there are no participants, return a blank list
returns: participant_id_list, where every element is an int
"""
df = self.df_dict.get("shipcall_participant_map")
df = df.set_index('shipcall_id', inplace=False)
# the 'if' call is needed to ensure, that no Exception is raised, when the shipcall_id is not present in the df
participant_id_list = df.loc[shipcall_id, "participant_id"].to_list() if shipcall_id in list(df.index) else []
return participant_id_list
def get_times_of_shipcall(self, shipcall)->pd.DataFrame:
df_times = self.df_dict.get('times') # -> pd.DataFrame
df_times = df_times.loc[df_times["shipcall_id"]==shipcall.id]
return df_times
def get_times_for_agency(self, non_null_column=None)->pd.DataFrame:
"""
options:
non_null_column:
None or str. If provided, the 'non_null_column'-column of the dataframe will be filtered,
so only entries with provided values are returned (filters all NaN and NaT entries)
"""
# get all times
df_times = self.df_dict.get('times') # -> pd.DataFrame
# filter out all NaN and NaT entries
if non_null_column is not None:
df_times = df_times.loc[~df_times[non_null_column].isnull()] # NOT null filter
# filter by the agency participant_type
times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
return times_agency
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):
"""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]
if rounding is not None:
values = values.dt.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)

View File

@ -0,0 +1,102 @@
import json
import pydapper
import pandas as pd
import mysql.connector
from BreCal.database.sql_handler import SQLHandler
from BreCal.validators.validation_rules import ValidationRules
from BreCal.schemas.model import Shipcall
def update_shipcall_in_mysql_database(sql_connection, shipcall:Shipcall, relevant_keys:list = ["evaluation", "evaluation_message"]):
"""
given an individual schemaModel (e.g., Shipcall.__dict__), update the entry within the mysql database
options:
sql_connection: an instance of mysql.connector.connect
shipcall: a Shipcall data model
relevant_keys: a list of the keys to be updated. Should never contain 'id', which is immutable
returns: (query, affected_rows)
"""
assert not "id" in relevant_keys, f"the 'id' key should never be updated."
schemaModel = shipcall.__dict__
commands = pydapper.using(sql_connection)
sentinel = object()
theshipcall = commands.query_single_or_default("SELECT * FROM shipcall where id = ?id?", sentinel, param={"id" : schemaModel["id"]})
if theshipcall is sentinel:
return json.dumps("no such record"), 404, {'Content-Type': 'application/json; charset=utf-8'}
query = build_mysql_query_to_update_shipcall(shipcall=shipcall, relevant_keys=relevant_keys)
affected_rows = commands.execute(query, param=schemaModel)
return (query, affected_rows)
def build_mysql_query_to_update_shipcall(shipcall, relevant_keys:list):
"""builds a mysql query, which updates the shipcall table. In particular, the provided shipcall will be updated for each key in {relevant_keys}"""
schemaModel = shipcall.__dict__
# prepare prefix and suffix. Then build the body of the query
prefix = "UPDATE shipcall SET "
suffix = "where id = ?id?"
body = ", ".join([f"{key} = ?{key}? " for key in schemaModel.keys() if (key in relevant_keys)]) # .join ignores the first ', ', which equals the 'isNotFirst' boolean-loop
# build query
query = f"{prefix}{body}{suffix}"
return query
def update_all_shipcalls_in_mysql_database(sql_connection, sql_handler:SQLHandler, shipcall_df:pd.DataFrame)->None:
"""
iterates over each shipcall_id in a shipcall dataframe, builds Shipcall data models and updates those in the sql database, which
is located in {sql_connection}
options:
sql_connection: an instance of mysql.connector.connect
sql_handler: an SQLHandler instance
shipcall_df: dataframe, which stores the data that is used to retrieve the shipcall data models (that are then updated in the database)
"""
for shipcall_id in shipcall_df.index:
shipcall = sql_handler.df_loc_to_data_model(df=shipcall_df, id=shipcall_id, model_str="shipcall")
update_shipcall_in_mysql_database(sql_connection, shipcall=shipcall, relevant_keys = ["evaluation", "evaluation_message"])
return
def evaluate_shipcall_state(mysql_connector_instance, shipcall_id:int=None, debug=False)->pd.DataFrame:
"""
options:
mysql_connector_instance: an instance created by the mysql.connector.connect() call. It is advised to use Python's context manager to close the connection after finished.
e.g.,
with mysql.connector.connect(**mysql_connection_data) as mysql_connector_instance:
evaluate_shipcall_state(mysql_connector_instance)
returns None
"""
sql_handler = SQLHandler(sql_connection=mysql_connector_instance, read_all=True)
vr = ValidationRules(sql_handler)
shipcall_df = sql_handler.df_dict.get("shipcall")
if shipcall_id is not None:
shipcall_df = shipcall_df.loc[[shipcall_id]]
# placeholder: filter shipcalls. For example, exclude historic entries.
shipcall_df = vr.evaluate_shipcalls(shipcall_df)
if debug:
return shipcall_df
# iterate over each shipcall in shipcall_df and update the respective entry in the mysql database
update_all_shipcalls_in_mysql_database(sql_connection=mysql_connector_instance, sql_handler=sql_handler, shipcall_df=shipcall_df)
return shipcall_df
def update_shipcall_evaluation_state(mysql_connection_data:dict, shipcall_id:int=None)->pd.DataFrame:
"""
single line function to connect to a mysql database (using the {mysql_connection_data}), evaluate each shipcall (bei traffic state)
and finally, update those in the database.
options:
mysql_connection_data: connection data to the mysql database (e.g., port, host, password)
shipcall_id: int. ID of the shipcall to be updated. Defaults to 'None'. When providing 'None', all shipcalls are updated.
"""
with mysql.connector.connect(**mysql_connection_data) as mysql_connector_instance:
shipcall_df = evaluate_shipcall_state(mysql_connector_instance=mysql_connector_instance, shipcall_id=shipcall_id, debug=False)
return shipcall_df

View File

@ -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`, created, modified, deleted FROM berth ORDER BY name", model=model.Berth)
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)
pooledConnection.close()
except Exception as ex:

View File

@ -26,9 +26,10 @@ def GetUser(options):
"first_name": data[0].first_name,
"last_name": data[0].last_name,
"user_name": data[0].user_name,
"user_phone": data[0].user_phone
"user_phone": data[0].user_phone,
"user_email": data[0].user_email
}
token = jwt_handler.generate_jwt(payload=result, lifetime=60) # generate token valid 60 mins
token = jwt_handler.generate_jwt(payload=result, lifetime=120) # generate token valid 60 mins
result["token"] = token # add token to user data
return json.dumps(result), 200, {'Content-Type': 'application/json; charset=utf-8'}

View File

@ -6,6 +6,8 @@ import pydapper
from ..schemas import model
from .. import local_db
from BreCal.database.update_database import evaluate_shipcall_state
def GetShipcalls(options):
"""
No parameters, gets all entries
@ -17,14 +19,19 @@ def GetShipcalls(options):
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
data = commands.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, created, modified FROM shipcall WHERE eta IS NULL OR eta >= DATE(NOW() - INTERVAL 2 DAY) " +
"ORDER BY eta", model=model.Shipcall)
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))) "
"ORDER BY eta") % (options["past_days"], options["past_days"])
data = commands.query(query, model=model.Shipcall)
for shipcall in data:
participant_query = "SELECT participant_id FROM shipcall_participant_map WHERE shipcall_id=?shipcall_id?";
participant_query = "SELECT participant_id, type FROM shipcall_participant_map WHERE shipcall_id=?shipcall_id?";
for record in commands.query(participant_query, model=dict, param={"shipcall_id" : shipcall.id}, buffered=False):
shipcall.participants.append(record["participant_id"])
# model.Participant_Assignment = model.Participant_Assignment()
pa = model.Participant_Assignment(record["participant_id"], record["type"])
shipcall.participants.append(pa)
pooledConnection.close()
@ -63,6 +70,10 @@ def PostShipcalls(schemaModel):
continue
if key == "modified":
continue
if key == "evaluation":
continue
if key == "evaluation_message":
continue
if isNotFirst:
query += ","
isNotFirst = True
@ -78,6 +89,10 @@ def PostShipcalls(schemaModel):
continue
if key == "modified":
continue
if key == "evaluation":
continue
if key == "evaluation_message":
continue
if isNotFirst:
query += ","
isNotFirst = True
@ -88,11 +103,12 @@ def PostShipcalls(schemaModel):
new_id = commands.execute_scalar("select last_insert_id()")
# add participant assignments
# TODO: make sure there are not two participants of the same type
pquery = "INSERT INTO shipcall_participant_map (shipcall_id, participant_id, type) VALUES (?shipcall_id?, ?participant_id?, ?type?)"
for participant_assignment in schemaModel["participants"]:
commands.execute(pquery, param={"shipcall_id" : new_id, "participant_id" : participant_assignment["participant_id"], "type" : participant_assignment["type"]})
pquery = "INSERT INTO shipcall_participant_map (shipcall_id, participant_id) VALUES (?shipcall_id?, ?participant_id?)"
for participant_id in schemaModel["participants"]:
commands.execute(pquery, param={"shipcall_id" : new_id, "participant_id" : participant_id})
# apply 'Traffic Light' evaluation to obtain 'GREEN', 'YELLOW' or 'RED' evaluation state. The function internally updates the mysql database
evaluate_shipcall_state(mysql_connector_instance=pooledConnection, shipcall_id=new_id) # new_id (last insert id) refers to the shipcall id
pooledConnection.close()
@ -137,6 +153,10 @@ def PutShipcalls(schemaModel):
continue
if key == "modified":
continue
if key == "evaluation":
continue
if key == "evaluation_message":
continue
if isNotFirst:
query += ", "
isNotFirst = True
@ -145,33 +165,35 @@ def PutShipcalls(schemaModel):
query += "WHERE id = ?id?"
affected_rows = commands.execute(query, param=schemaModel)
pquery = "SELECT id, participant_id FROM shipcall_participant_map where shipcall_id = ?id?"
pquery = "SELECT id, participant_id, type FROM shipcall_participant_map where shipcall_id = ?id?"
pdata = commands.query(pquery,param={"id" : schemaModel["id"]}) # existing list of assignments
# loop across passed participant ids, creating entries for those not present in pdata
# TODO: make sure there are not two participants of the same type
for participant_id in schemaModel["participants"]:
for participant_assignment in schemaModel["participants"]:
found_participant = False
for elem in pdata:
if elem["participant_id"] == participant_id:
if elem["participant_id"] == participant_assignment["participant_id"] and elem["type"] == participant_assignment["type"]:
found_participant = True
break
if not found_participant:
nquery = "INSERT INTO shipcall_participant_map (shipcall_id, participant_id) VALUES (?shipcall_id?, ?participant_id?)"
commands.execute(nquery, param={"shipcall_id" : schemaModel["id"], "participant_id" : participant_id})
nquery = "INSERT INTO shipcall_participant_map (shipcall_id, participant_id, type) VALUES (?shipcall_id?, ?participant_id?, ?type?)"
commands.execute(nquery, param={"shipcall_id" : schemaModel["id"], "participant_id" : participant_assignment["participant_id"], "type" : participant_assignment["type"]})
# loop across existing pdata entries, deleting those not present in participant list
for elem in pdata:
found_participant = False
for participant_id in schemaModel["participants"]:
if(participant_id == elem["participant_id"]):
for participant_assignment in schemaModel["participants"]:
if(participant_assignment["participant_id"] == elem["participant_id"] and participant_assignment["type"] == elem["type"]):
found_participant = True
break;
if not found_participant:
dquery = "DELETE FROM shipcall_participant_map WHERE id = ?existing_id?"
commands.execute(dquery, param={"existing_id" : elem["id"]})
# apply 'Traffic Light' evaluation to obtain 'GREEN', 'YELLOW' or 'RED' evaluation state. The function internally updates the mysql database
evaluate_shipcall_state(mysql_connector_instance=pooledConnection, shipcall_id=schemaModel["id"]) # schemaModel["id"] refers to the shipcall id
pooledConnection.close()
return json.dumps({"id" : schemaModel["id"]}), 200

View File

@ -5,6 +5,8 @@ import pydapper
from ..schemas import model
from .. import local_db
from BreCal.database.update_database import evaluate_shipcall_state
def GetTimes(options):
"""
:param options: A dictionary containing all the paramters for the Operations
@ -80,6 +82,9 @@ def PostTimes(schemaModel):
commands.execute(query, schemaModel)
new_id = commands.execute_scalar("select last_insert_id()")
# apply 'Traffic Light' evaluation to obtain 'GREEN', 'YELLOW' or 'RED' evaluation state. The function internally updates the mysql database 'shipcall'
evaluate_shipcall_state(mysql_connector_instance=pooledConnection, shipcall_id=schemaModel["shipcall_id"]) # every times data object refers to the 'shipcall_id'
pooledConnection.close()
return json.dumps({"id" : new_id}), 201, {'Content-Type': 'application/json; charset=utf-8'}
@ -122,12 +127,16 @@ def PutTimes(schemaModel):
affected_rows = commands.execute(query, param=schemaModel)
# apply 'Traffic Light' evaluation to obtain 'GREEN', 'YELLOW' or 'RED' evaluation state. The function internally updates the mysql database 'shipcall'
evaluate_shipcall_state(mysql_connector_instance=pooledConnection, shipcall_id=schemaModel["shipcall_id"]) # every times data object refers to the 'shipcall_id'
pooledConnection.close()
if affected_rows == 1:
return json.dumps({"id" : schemaModel["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'}
# if affected_rows == 1: # this doesn't work as expected
return json.dumps("no such record"), 404, {'Content-Type': 'application/json; charset=utf-8'}
return json.dumps({"id" : schemaModel["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'}
# return json.dumps("no such record"), 404, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex:
logging.error(ex)

View File

@ -27,7 +27,7 @@ def PutUser(schemaModel):
return json.dumps("no such record"), 404, {'Content-Type': 'application/json; charset=utf-8'}
# see if we need to update public fields
if "first_name" in schemaModel or "last_name" in schemaModel or "user_phone" in schemaModel:
if "first_name" in schemaModel or "last_name" in schemaModel or "user_phone" in schemaModel or "user_email" in schemaModel:
query = "UPDATE user SET "
isNotFirst = False
for key in schemaModel.keys():

View File

@ -4,15 +4,19 @@ import logging
import json
import os
connection_pool = None
config_path = None
def initPool():
def initPool(instancePath):
try:
global config_path
if(config_path == None):
config_path = os.path.join(instancePath,'../../../secure/connection_data_test.json');
print (config_path)
config_path = './src/server/BreCal/connection_data.json'
print (os.getcwd())
if not os.path.exists(config_path):
print ('cannot find ' + config_path)
print("instance path", instancePath)
exit(1)
f = open(config_path);
@ -31,7 +35,7 @@ def initPool():
print(e)
def getPoolConnection():
config_path = './src/server/BreCal/connection_data.json'
global config_path
f = open(config_path);
connection_data = json.load(f)
return mysql.connector.connect(**connection_data)

View File

View File

@ -18,6 +18,8 @@ class Berth(Schema):
name: str
participant_id: int
lock: bool
owner_id: int
authority_id: int
created: datetime
modified: datetime
deleted: bool
@ -57,6 +59,10 @@ class Participant(Schema):
class ParticipantList(Participant):
pass
class ParticipantAssignmentSchema(Schema):
participant_id = fields.Int()
type = fields.Int()
class ShipcallSchema(Schema):
def __init__(self):
super().__init__(unknown=None)
@ -65,7 +71,7 @@ class ShipcallSchema(Schema):
id = fields.Int()
ship_id = fields.Int()
type = fields.Int()
eta = fields.DateTime()
eta = fields.DateTime(Required = False, allow_none=True)
voyage = fields.Str(Required = False, allow_none=True)
etd = fields.DateTime(Required = False, allow_none=True)
arrival_berth_id = fields.Int(Required = False, allow_none=True)
@ -85,10 +91,22 @@ class ShipcallSchema(Schema):
anchored = fields.Bool(Required = False, allow_none=True)
moored_lock = fields.Bool(Required = False, allow_none=True)
canceled = fields.Bool(Required = False, allow_none=True)
participants = fields.List(fields.Int)
evaluation = fields.Int(Required = False, allow_none=True)
evaluation_message = fields.Str(Required = False, allow_none=True)
participants = fields.List(fields.Nested(ParticipantAssignmentSchema))
created = fields.DateTime(Required = False, allow_none=True)
modified = fields.DateTime(Required = False, allow_none=True)
@dataclass
class Participant_Assignment:
def __init__(self, participant_id, type):
self.participant_id = participant_id
self.type = type
pass
participant_id: int
type: int
@dataclass
class Shipcall:
@ -115,9 +133,11 @@ class Shipcall:
anchored: bool
moored_lock: bool
canceled: bool
evaluation: int
evaluation_message: str
created: datetime
modified: datetime
participants: List[int] = field(default_factory=list)
participants: List[Participant_Assignment] = field(default_factory=list)
class ShipcallId(Schema):
pass
@ -157,11 +177,12 @@ class UserSchema(Schema):
super().__init__(unknown=None)
pass
id = fields.Int(required=True)
first_name = fields.Str(required=False)
last_name = fields.Str(required=False)
user_phone = fields.Str(required=False)
old_password = fields.Str(required=False)
new_password = fields.Str(required=False)
first_name = fields.Str(Required=False, allow_none=True)
last_name = fields.Str(required=False, allow_none=True)
user_phone = fields.Str(required=False, allow_none=True)
user_email = fields.Str(required=False, allow_none=True)
old_password = fields.Str(required=False, allow_none=True)
new_password = fields.Str(required=False, allow_none=True)
@dataclass
class Times:

View File

View File

@ -0,0 +1,5 @@
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

View File

@ -0,0 +1,31 @@
import datetime
from BreCal.stubs import generate_uuid1_int
from BreCal.schemas.model import Berth
def get_berth_simple():
berth_id = generate_uuid1_int() # uid?
# 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
created = datetime.datetime.now()
modified = created+datetime.timedelta(seconds=10)
deleted = modified+datetime.timedelta(seconds=3)
berth = Berth(
berth_id,
name,
participant_id,
lock,
owner_id,
authority_id,
created,
modified,
deleted,
)
return berth

View File

@ -0,0 +1,49 @@
import datetime
from BreCal.stubs import generate_uuid1_int
from BreCal.schemas.model import Notification
def get_notification_simple():
"""creates a default notification, where 'created' is now, and modified is now+10 seconds"""
notification_id = generate_uuid1_int() # uid?
times_id = generate_uuid1_int() # uid?
acknowledged = False
level = 10
type = 0
message = "hello world"
created = datetime.datetime.now()
modified = created+datetime.timedelta(seconds=10)
notification = Notification(
notification_id,
times_id,
acknowledged,
level,
type,
message,
created,
modified
)
return notification
def get_notification_in_the_past(created_delta_seconds, modified_delta_seconds, acknowledged=False):
"""
creates a notification of the past, where the
'created' date is {created_delta_seconds} seconds ago
'modified' date is {modified_delta_seconds} seconds ago
for example, if datetime.datetime.now() returns
now = datetime.datetime(2023, 9, 15, 7, 25, 50, 733644)), then calling this function
as get_notification_modified_in_the_past(2*60, 1*60) provides
'created':datetime.datetime(2023, 9, 15, 7, 23, 50, 733644) (two minutes ago)
'modified':datetime.datetime(2023, 9, 15, 7, 24, 50, 733644) (one minute ago)
optionally, one can also overwrite the 'acknowledged' attribute
returns notification
"""
notification = get_notification_simple()
notification.created = datetime.datetime.now()-datetime.timedelta(seconds=created_delta_seconds)
notification.modified = datetime.datetime.now()-datetime.timedelta(seconds=modified_delta_seconds)
notification.acknowledged = acknowledged
return notification

View File

@ -0,0 +1,32 @@
import datetime
from BreCal.stubs import generate_uuid1_int
from BreCal.schemas.model import Participant
def get_participant_simple():
participant_id = generate_uuid1_int()
# #TODO: role_type and flags are arbitrary
name = "Max Mustermann"
street = "Musterstrasse 1"
postal_code = "12345"
city = "Bremen"
role_type = 1 # integer
flags = 0 # integer. unclear
created = datetime.datetime.now()
modified = created+datetime.timedelta(seconds=10)
deleted = modified+datetime.timedelta(seconds=3)
participant = Participant(
participant_id,
name,
street,
postal_code,
city,
role_type,
flags,
created,
modified,
deleted
)
return participant

View File

View File

@ -0,0 +1,38 @@
import datetime
from BreCal.stubs import generate_uuid1_int
from BreCal.schemas.model import Ship
def get_ship_simple():
ship_id = generate_uuid1_int()
name = "african halcyon".upper() # 'Schiffe_sample_format.xlsx' uses .upper() for every ship
imo = 9343613 # assert str(len(imo))==7
callsign = 1234567 # up to 7 characters. assert str(len(callsign))<=7
participant_id = generate_uuid1_int()
length = 177.13 # assert 0>length<=500
width = 28.4 # assert 0>width<=500
is_tug = False
bollard_pull = None # only if is_tug
participant_id = None # only if is_tug
eni = "01234567" # Alternative to IMO. Dynamic assertion? assert len(str(eni))==8
created = datetime.datetime.now()
modified = created+datetime.timedelta(seconds=10)
deleted = modified+datetime.timedelta(seconds=3)
ship = Ship(
ship_id,
name,
imo,
callsign,
participant_id,
length,
width,
is_tug,
bollard_pull,
eni,
created,
modified,
deleted
)
return ship

View File

@ -0,0 +1,81 @@
import datetime
from BreCal.stubs import generate_uuid1_int
from BreCal.schemas.model import Shipcall
from dataclasses import field
def get_shipcall_simple():
# only used for the stub
base_time = datetime.datetime.now()
shipcall_id = generate_uuid1_int()
ship_id = generate_uuid1_int()
eta = base_time+datetime.timedelta(hours=3, minutes=12)
role_type = 1
voyage = "987654321"
etd = base_time+datetime.timedelta(hours=6, minutes=12) # should never be before eta
arrival_berth_id = generate_uuid1_int()
departure_berth_id = generate_uuid1_int()
tug_required = False
pilot_required = False
flags = 0 # #TODO_shipcall_flags. What is meant here? What should be tested?
pier_side = False # whether a ship will be fixated on the pier side. en: pier side, de: Anlegestelle. From 'BremenCalling_Datenmodell.xlsx': gedreht/ungedreht
bunkering = False # #TODO_bunkering_unclear
replenishing_terminal = False # en: replenishing terminal, de: Nachfüll-Liegeplatz
replenishing_lock = False # en: replenishing lock, de: Nachfüllschleuse
draft = 0.12 # #TODO_draft_value: clarify, what 'draft' means and what kind of values are to be expected
# tidal window: built in a way, where ETA and ETD are in-between the window
# #TODO_tidal_window_source: are these windows taken from a database or provided by the user? How do they know this?
tidal_window_from = base_time+datetime.timedelta(hours=2, minutes=12)
tidal_window_to = base_time+datetime.timedelta(hours=7, minutes=12)
rain_sensitive_cargo = False
recommended_tugs = 2 # assert 0<recommended_tugs<={threshold}. E.g., 20 should not be exceeded.
anchored = False
moored_lock = False # de: 'Festmacherschleuse', en: 'moored lock'
canceled = False
evaluation = None
evaluation_message = ""
created = datetime.datetime.now()
modified = created+datetime.timedelta(seconds=10)
participants = [generate_uuid1_int(), generate_uuid1_int(), generate_uuid1_int(), generate_uuid1_int()] # field(default_factory=[generate_uuid1_int(), generate_uuid1_int(), generate_uuid1_int(), generate_uuid1_int()]) # list
shipcall = Shipcall(
shipcall_id,
ship_id,
role_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,
participants,
)
return shipcall

View File

View File

@ -0,0 +1,67 @@
"""
this stub creates an example time object, where the times of every role are present.
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()
# note 1: eta and etd should never individually be in reverse order. assert etd>eta (for berth) & lock
# note 2: times are currently computed as a sequence of (eta_berth -> lock_time -> etd_berth -> zone_entry). The deltas are arbitrary
times_id = generate_uuid1_int()
eta_berth = base_time+datetime.timedelta(hours=1, minutes=12)
eta_berth_fixed = False
lock_time = eta_berth+datetime.timedelta(hours=0, minutes=50)
lock_time_fixed = False
etd_berth = lock_time+datetime.timedelta(hours=0, minutes=45)
etd_berth_fixed = False
zone_entry = etd_berth+datetime.timedelta(hours=0, minutes=15)
zone_entry_fixed = False
operations_start = zone_entry+datetime.timedelta(hours=1, minutes=30)
operations_end = operations_start+datetime.timedelta(hours=4, minutes=30)
remarks = "" # assert len(remarks)<{max_len_threshold}
participant_id = generate_uuid1_int()
shipcall_id = generate_uuid1_int()
berth_id = generate_uuid1_int()
berth_info = ""
pier_side = True
participant_type = None
created = datetime.datetime.now()
modified = created+datetime.timedelta(seconds=10)
times = Times(
id=times_id,
eta_berth=eta_berth,
eta_berth_fixed=eta_berth_fixed,
etd_berth=etd_berth,
etd_berth_fixed=etd_berth_fixed,
lock_time=lock_time,
lock_time_fixed=lock_time_fixed,
zone_entry=zone_entry,
zone_entry_fixed=zone_entry_fixed,
operations_start=operations_start,
operations_end=operations_end,
remarks=remarks,
participant_id=participant_id,
berth_id=berth_id,
berth_info=berth_info,
pier_side=pier_side,
participant_type=participant_type,
shipcall_id=shipcall_id,
created=created,
modified=modified,
)
return times

View File

View File

View File

@ -0,0 +1,35 @@
import bcrypt
import datetime
from BreCal.stubs import generate_uuid1_int
from BreCal.schemas.model import User
def get_user_simple():
user_id = generate_uuid1_int()
participant_id = generate_uuid1_int() # should be taken from the database
first_name = "Max"
last_name = "Mustermann"
user_name = "maxm123"
user_email = "max.mustermann@brecal.de"
user_phone = "0173123456" # formatting?
password_hash = bcrypt.hashpw("123456".encode('utf-8'), bcrypt.gensalt( 12 )).decode('utf8')
api_key = bcrypt.hashpw("apikey123".encode('utf-8'), bcrypt.gensalt( 12 )).decode('utf8')
created = datetime.datetime.now()
modified = created+datetime.timedelta(seconds=10)
user = User(
user_id,
participant_id,
first_name,
last_name,
user_name,
user_email,
user_phone,
password_hash,
api_key,
created,
modified
)
return user

View File

View File

@ -0,0 +1,165 @@
####################################### InputValidation #######################################
from abc import ABC, abstractmethod
from BreCal.schemas.model import Ship, Shipcall, Berth, User, Participant
class InputValidation():
def __init__(self):
self.build_supported_models_dictionary()
return
def build_supported_models_dictionary(self):
self.supported_models = {
Ship:ShipValidation(),
Shipcall:ShipcallValidation(),
Berth:BerthValidation(),
User:UserValidation(),
Participant:ParticipantValidation(),
}
return
def assert_if_not_supported(self, dataclass_object):
assert type(dataclass_object) in self.supported_models, f"unsupported model. Found: {type(dataclass_object)}"
return
def verify(self, dataclass_object):
self.assert_if_not_supported(dataclass_object)
# determine the type of the dataclass object. The internal dictionary 'supported_models' matches the dataclass object
# to the respective validation protocol
validator = self.supported_models.get(type(dataclass_object))
# check the object based on the rules within the matched validator
input_validation_state = validator.check(dataclass_object)
return input_validation_state
class DataclassValidation(ABC):
"""parent class of dataclas validators, which determines the outline of every object"""
def __init__(self):
return
def check(self, dataclass_object) -> (list, bool):
"""
the 'check' method provides a default style, how each dataclass object is validated. It returns a list of violations
and a boolean, which determines, whether the check is passed successfully
"""
all_rules = self.apply_all_rules(dataclass_object)
violations = self.filter_violations(all_rules)
input_validation_state = self.evaluate(violations)
return (violations, input_validation_state)
@abstractmethod
def apply_all_rules(self, dataclass_object) -> list:
"""
the 'apply_all_rules' method is mandatory for any dataclass validation object. It should execute all validation rules and
return a list of tuples, where each element is (output_boolean, validation_name)
"""
all_rules = [(True, 'blank_validation_rule')]
return all_rules
def filter_violations(self, all_rules):
"""input: all_rules, a list of tuples, where each element is (output, validation_name), which are (bool, str). """
# if output is False, a violation is observed
violations = [result[1] for result in all_rules if not result[0]]
return violations
def evaluate(self, violations) -> bool:
input_validation_state = len(violations)==0
return input_validation_state
class ShipcallValidation(DataclassValidation):
"""an object that validates a Shipcall dataclass object"""
def __init__(self):
super().__init__()
return
def apply_all_rules(self, dataclass_object) -> list:
"""apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)"""
raise NotImplementedError()
return all_rules
from BreCal.validators.schema_validation import ship_bollard_pull_is_defined_or_is_not_tug, ship_bollard_pull_is_none_or_in_range, ship_callsign_len_is_seven_at_maximum, ship_eni_len_is_eight, ship_imo_len_is_seven, ship_length_in_range, ship_participant_id_is_defined_or_is_not_tug, ship_participant_id_is_none_or_int, ship_width_in_range
# skip: ship_max_draft_is_defined_or_is_not_tug, ship_max_draft_is_none_or_in_range,
class ShipValidation(DataclassValidation):
"""an object that validates a Ship dataclass object"""
def __init__(self):
super().__init__()
return
def apply_all_rules(self, dataclass_object) -> list:
"""apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)"""
# skip: ship_max_draft_is_defined_or_is_not_tug, ship_max_draft_is_none_or_in_range,
"""
#TODO_ship_max_draft
with pytest.raises(AttributeError, match="'Ship' object has no attribute 'max_draft'"):
assert ship_max_draft_in_range(ship)[0], f"max draft of a ship must be between 0 and 20 meters"
assert ship_max_draft_is_none_or_in_range(ship)[0], f"the max_draft should either be undefined or between 0 and 20 meters"
"""
# list comprehension: every function becomes part of the loop and will be executed. Each function is wrapped and provides (output, validation_name)
all_rules = [
# tuple: (output, validation_name)
check_rule(dataclass_object)
for check_rule in [
ship_bollard_pull_is_defined_or_is_not_tug,
ship_bollard_pull_is_none_or_in_range,
ship_callsign_len_is_seven_at_maximum,
ship_eni_len_is_eight,
ship_imo_len_is_seven,
ship_length_in_range,
ship_participant_id_is_defined_or_is_not_tug,
ship_participant_id_is_none_or_int,
ship_width_in_range
]
]
return all_rules
class BerthValidation(DataclassValidation):
"""an object that validates a Berth dataclass object"""
def __init__(self):
super().__init__()
return
def apply_all_rules(self, dataclass_object) -> list:
"""apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)"""
raise NotImplementedError()
return all_rules
class UserValidation(DataclassValidation):
"""an object that validates a User dataclass object"""
def __init__(self):
super().__init__()
return
def apply_all_rules(self, dataclass_object) -> list:
"""apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)"""
raise NotImplementedError()
return all_rules
from BreCal.validators.schema_validation import participant_postal_code_len_is_five
class ParticipantValidation(DataclassValidation):
"""an object that validates a Participant dataclass object"""
def __init__(self):
super().__init__()
return
def apply_all_rules(self, dataclass_object) -> list:
"""apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)"""
# list comprehension: every function becomes part of the loop and will be executed. Each function is wrapped and provides (output, validation_name)
all_rules = [
# tuple: (output, validation_name)
check_rule(dataclass_object)
for check_rule in [
participant_postal_code_len_is_five,
]
]
return all_rules

View File

@ -0,0 +1,139 @@
# wrapper: every validation function returns a tuple of (validation_state, validation_name)
# example: validate_ship_eni_length might return the tuple (True, 'ship_eni_length')
# thereby, one could always know, which test causes an issue
####################################### general functions #######################################
def validation_state_and_validation_name(validation_name):
"""
can wrap arbitrary functions, so they return (output, validation_name)-tuples
usage example:
@validation_state_and_validation_name("ship_eni_length")
def validate_ship_eni_length(ship):
return length_matches_exactly(ship.eni,8)
"""
def wrapper(validation_fct):
def decorated_fct(*args, **kwargs):
return (validation_fct(*args, **kwargs), validation_name)
return decorated_fct
return wrapper
def value_in_range(query_value, start_range, end_range):
"""determines, whether the query_value is greater than start_range, but smaller than end_range. Returns bool"""
return start_range<query_value<end_range
def length_is_at_maximum(query_value, max_len):
"""determines, whether the query_value's length is l<={max_len}. Returns bool"""
return len(str(query_value))<=max_len
def length_matches_exactly(query_value, length_value):
"""determines, whether the query_value's length is exactly l=={length_value}. Returns bool"""
return len(str(query_value)) == length_value
####################################### dataclass specifics #######################################
### Ship dataclass (BreCal.schema.model.Ship) ###
@validation_state_and_validation_name("ship_bollard_pull_none_or_value_in_range")
def ship_bollard_pull_is_none_or_in_range(ship):
"""a ship should either have its bollard_pull between 0 and 500, or have an undefined bollard_pull (when not a tug)"""
return (ship.bollard_pull is None) or (value_in_range(ship.bollard_pull, 0, 500))
@validation_state_and_validation_name("ship_max_draft_none_or_value_in_range")
def ship_max_draft_is_none_or_in_range(ship):
"""a ship should either have its max_draft between 0 and 20, or have an undefined max_draft (when not a tug)"""
return (ship.max_draft is None) or (value_in_range(ship.max_draft, 0, 20))
@validation_state_and_validation_name("ship_participant_id_none_or_int")
def ship_participant_id_is_none_or_int(ship):
"""a ship should either have its participant_id defined (integer when ship is a tug), or have an undefined participant_id (when not a tug)"""
return isinstance(ship.participant_id, int) or (ship.participant_id is None)
@validation_state_and_validation_name("ship_length")
def ship_length_in_range(ship):
"""ship length-values should be valid. between 0 and 500 meters is plausible. returns bool"""
return value_in_range(ship.length, 0, 500)
@validation_state_and_validation_name("ship_width")
def ship_width_in_range(ship):
"""ship length-values should be valid. between 0 and 500 meters is plausible. returns bool"""
return value_in_range(ship.width, 0, 500)
@validation_state_and_validation_name("ship_eni_length")
def ship_eni_len_is_eight(ship):
"""eni-no. are standardized. They should have exactly eight characters. returns bool"""
return length_matches_exactly(ship.eni,8)
@validation_state_and_validation_name("ship_imo_length")
def ship_imo_len_is_seven(ship):
"""IMO-numbers are standardized. They should have exactly seven characters. returns bool"""
return length_matches_exactly(ship.imo,7)
@validation_state_and_validation_name("ship_callsign_length")
def ship_callsign_len_is_seven_at_maximum(ship):
"""the ship's callsign should have l<=7 characters. returns bool"""
return length_is_at_maximum(ship.callsign, 7)
def ship_is_not_tug_or_key_is_defined(is_tug, key_):
""" # base function
function that checks, if a Ship dataclass is either
a) not a tug
b) has a defined value of {key_}
can be used for max_draft, participant_id and bollard_pull
"""
return (not is_tug) or (key_ is not None)
@validation_state_and_validation_name("ship_bollard_pull_dynamically_mandatory")
def ship_bollard_pull_is_defined_or_is_not_tug(ship):
"""
there are two valid cases for the bollard_pull:
a) bollard_pull is undefined (None), if the ship is not a tug
b) bollard_pull is defined, if the ship is a tug
if the ship is a tug, a separate function validates in addition, if the value is in an accepted range
returns bool
"""
return ship_is_not_tug_or_key_is_defined(ship.is_tug, ship.bollard_pull)
@validation_state_and_validation_name("ship_max_draft_dynamically_mandatory")
def ship_max_draft_is_defined_or_is_not_tug(ship):
"""
there are two valid cases for the max_draft:
a) max_draft is undefined (None), if the ship is not a tug
b) max_draft is defined, if the ship is a tug
if the ship is a tug, a separate function validates in addition, if the value is in an accepted range
returns bool
"""
return ship_is_not_tug_or_key_is_defined(ship.is_tug, ship.max_draft)
# #TODO_ship_tug_participant_id: is this semantically correct? Will the participant_id be entered or automatically filled?
@validation_state_and_validation_name("ship_max_draft_dynamically_mandatory")
def ship_participant_id_is_defined_or_is_not_tug(ship):
"""
there are two valid cases for the max_draft:
a) participant_id is undefined (None), if the ship is not a tug
b) participant_id is defined, if the ship is a tug
returns bool
"""
return ship_is_not_tug_or_key_is_defined(ship.is_tug, ship.participant_id)
### Participant dataclass (BreCal.schema.model.Participant) ###
@validation_state_and_validation_name("participant_postal_code_length")
def participant_postal_code_len_is_five(participant):
"""
validates, that a postal code has 5 characters. returns bool
# #TODO_postal_code_length_validation: might make sense to request postal_code<=5 characters
# is the 5-character requirement true when international ships arive?
"""
return length_matches_exactly(participant.postal_code, 5)

View File

@ -0,0 +1,91 @@
import datetime
import numpy as np
import pandas as pd
class TimeLogic():
def __init__(self):
return
def time_delta(self, src_time, tgt_time, unit:str="m"):
"""
in brief, this function measures tgt_time - src_time
if the tgt_time is in the future, it is a positive value (tgt_time > src_time)
if the tgt_time is in the past, it is a negative value (tgt_time < src_time)
returns the delta between tgt_time and src_time as a float of minutes (or the optionally provided unit)
options:
unit: str, which defaults to 'm' (minutes). 'h' (hours) or 's' (seconds) are also common units. Determines the unit of the output time delta
"""
# convert np.datetime64
if isinstance(src_time, pd.Timestamp):
src_time = src_time.to_datetime64()
if isinstance(tgt_time, pd.Timestamp):
tgt_time = tgt_time.to_datetime64()
if isinstance(src_time, datetime.datetime):
src_time = np.datetime64(src_time)
if isinstance(tgt_time, datetime.datetime):
tgt_time = np.datetime64(tgt_time)
delta = tgt_time - src_time
minute_delta = delta / np.timedelta64(1, unit)
return minute_delta
def time_delta_from_now_to_tgt(self, tgt_time, unit="m"):
return self.time_delta(datetime.datetime.now(), tgt_time=tgt_time, unit=unit)
def time_inbetween(self, query_time:datetime.datetime, start_time:datetime.datetime, end_time:datetime.datetime) -> bool:
"""
checks, whether the query time is inbetween the start & end time. Returns a bool to indicate that.
Example:
a = datetime.datetime(2017, 5, 16, 8, 21, 10)
b = datetime.datetime(2017, 5, 17, 8, 21, 10)
c = datetime.datetime(2017, 5, 18, 8, 21, 10)
is b between a and c? -> yes. Returns True
is c between a and b? -> no. Returns False
returns bool
"""
assert isinstance(query_time, datetime.datetime)
assert isinstance(start_time, datetime.datetime)
assert isinstance(end_time, datetime.datetime)
return start_time <= query_time <= end_time
def time_inbetween_absolute_delta(self, query_time:datetime.datetime, start_time:datetime.datetime, end_time:datetime.datetime) -> tuple:
"""
similarly to self.time_inbetween, this function compares a query_time with the provided start and end time.
however, this function instead returns timedelta objects, which show the difference towards start and end
this function applies abs() to return only absolute deviations. Thereby, -23 becomes +23
returns: tuple(absolute_start_delta, absolute_end_delta)
"""
return (abs(query_time-start_time), abs(query_time-end_time))
def compare_query_is_inbetween_list(self, query_time, list_of_other_times) -> list:
list_of_bools = [
self.time_inbetween(query_time, time_elem_begin, time_elem_end)
for (time_elem_begin, time_elem_end) in list_of_other_times
]
return list_of_bools
def query_time_any_inbetween(self, query_time, list_of_other_times):
"""
given a query_time element, the element will be compared to every element in a list, where each
element is a tuple of (start_time, end_time)
"""
if len(list_of_other_times)==0:
# the time is not inbetween, if the provided list is empty
return False
list_of_bools = self.compare_query_is_inbetween_list(query_time, list_of_other_times)
return np.any(list_of_bools), list_of_bools

View File

@ -0,0 +1,769 @@
import inspect
import types
from BreCal.database.enums import ParticipantType, ShipcallType, ParticipantwiseTimeDelta
import numpy as np
import pandas as pd
from BreCal.validators.time_logic import TimeLogic
from BreCal.database.enums import StatusFlags
#from BreCal.validators.schema_validation import validation_state_and_validation_name
class ValidationRuleBaseFunctions():
"""
Base object with individual functions, which the {ValidationRuleFunctions}-child refers to.
This parent class provides base functions and helps to restructure the code in a more comprehensible way.
"""
def __init__(self, sql_handler):
self.sql_handler = sql_handler
self.time_logic = TimeLogic()
def check_time_delta_violation_query_time_to_now(self, query_time:pd.Timestamp, key_time:pd.Timestamp, threshold:float)->bool:
"""
# base function for all validation rules in the group {0001} A-L
measures the time between NOW and query_time.
When the query_time lays in the past, the delta is negative
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
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
"""
# rule is not applicable -> return 'GREEN'
if key_time is not None:
return False
# otherwise, this rule applies and the difference between 'now' and the query time is measured
delta = self.time_logic.time_delta_from_now_to_tgt(tgt_time=query_time, unit="m")
# 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_state = (delta >= 0) and (delta<=threshold)
return violation_state
def check_participants_agree_on_estimated_time(self, shipcall, query, df_times, applicable_shipcall_type)->bool:
"""
# base function for all validation rules in the group {0002} A-C
compares, whether the participants agree on the estimated time (of arrival or departure), depending on
whether the shipcall type is incoming, outgoing or shifting.
No violations are observed, when
- the shipcall belongs to a different type than the rule expects
- there are no matching times for the provided {query} (e.g., "eta_berth")
Instead of comparing each individual result, this function counts the amount of unique instances.
When there is not only one unique value, there are deviating time estimates, and a violation occurs
To reduce the potential of false violations, the agreement is rounded (e.g., by minute).
returns: violation_state (bool)
"""
# shipcall type filter: consider only shipcalls, where the type matches
if shipcall.type != applicable_shipcall_type.value:
violation_state = False
return violation_state
# filter by participant types of interest (agency, mooring, portauthority/administration, pilot, tug)
participant_types = [ParticipantType.AGENCY.value, ParticipantType.MOORING.value, ParticipantType.PORT_ADMINISTRATION.value, ParticipantType.PILOT.value, ParticipantType.TUG.value]
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(),:]
# 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'
estimated_times = [time_.round("15min") for time_ in estimated_times]
# when there are no entries left (no entries are provided), skip
if len(estimated_times)==0:
violation_state = False
return violation_state
# there should only be one eta_berth, when all participants have provided the same time
# this equates to the same criteria as checking, whether
# times_agency.eta_berth==times_mooring.eta_berth==times_portadministration.eta_berth==times_pilot.eta_berth==times_tug.eta_berth
n_unique_times = len(np.unique(estimated_times))
violation_state = n_unique_times!=1
return violation_state
def check_unique_shipcall_counts(self, query:str, rounding="min", maximum_threshold=3)->bool:
"""
# base function for all validation rules in the group {0005} A&B
compares how many unique times are found for the provided {query} (e.g., "eta_berth")
This function rounds the results, counts the unique values and returns a boolean state, whether the {maximum_threshold} is exceeded
"""
# 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)
# 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)
# 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
class ValidationRuleFunctions(ValidationRuleBaseFunctions):
"""
an accumulation object that makes sure, that any validation rule is translated to a function with default naming convention and
return types. Each function should return a ValidationRuleState enumeration object and a description string to which validation rule
the result belongs. These are returned as tuples (ValidationRuleState, validation_name)
Each rule should have the same input arguments (self, shipcall, df_times, *args, **kwargs)
The object makes heavy use of calls from an SQLHandler object, which provides functions for dataframe access and filtering.
each validation_name is generated by calling the function inside a method
validation_name = inspect.currentframe().f_code.co_name # validation_name then returns the name of the method from where 'currentframe()' was called.
# example:
#def validation_rule_fct_example(self, shipcall, df_times):
#validation_name = inspect.currentframe().f_code.co_name
#return (ValidationRuleState.NONE, validation_name)
"""
def __init__(self, sql_handler):
super().__init__(sql_handler)
return
def get_validation_rule_functions(self):
"""return a list of all methods in this object, which are all validation rule functions."""
return [self.__getattribute__(mthd_) for mthd_ in dir(self) if ('validation_rule_fct' in mthd_) and (isinstance(self.__getattribute__(mthd_), types.MethodType))]
def validation_rule_fct_missing_time_agency_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-A
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-A:
- Checks, if times_agency.eta_berth is filled in.
- Measures the difference between 'now' and 'shipcall.eta'.
"""
# check, if the header is filled in (agency)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
return (StatusFlags.GREEN, None)
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
query_time = shipcall.eta
key_time = times_agency.eta_berth
threshold = ParticipantwiseTimeDelta.AGENCY
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_agency_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-B
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-B:
- Checks, if times_agency.etd_berth is filled in.
- Measures the difference between 'now' and 'shipcall.etd'.
"""
# check, if the header is filled in (agency)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
return (StatusFlags.GREEN, None)
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
query_time = shipcall.etd
key_time = times_agency.etd_berth
threshold = ParticipantwiseTimeDelta.AGENCY
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_mooring_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-C
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-C:
- Checks, if times_mooring.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & MOORING)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.MOORING.value])]) != 2:
return (StatusFlags.GREEN, None)
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_mooring = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.MOORING.value)
query_time = times_agency.eta_berth
key_time = times_mooring.eta_berth
threshold = ParticipantwiseTimeDelta.MOORING
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_mooring_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-D
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-D:
- Checks, if times_mooring.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & MOORING)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.MOORING.value])]) != 2:
return (StatusFlags.GREEN, None)
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_mooring = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.MOORING.value)
query_time = times_agency.etd_berth
key_time = times_mooring.etd_berth
threshold = ParticipantwiseTimeDelta.MOORING
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_portadministration_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-F
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-F:
- Checks, if times_port_administration.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & PORT_ADMINISTRATION)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PORT_ADMINISTRATION.value])]) != 2:
return (StatusFlags.GREEN, None)
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_port_administration = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PORT_ADMINISTRATION.value)
query_time = times_agency.eta_berth
key_time = times_port_administration.eta_berth
threshold = ParticipantwiseTimeDelta.PORT_ADMINISTRATION
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_portadministration_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-G
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-G:
- Checks, if times_port_administration.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & PORT_ADMINISTRATION)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PORT_ADMINISTRATION.value])]) != 2:
return (StatusFlags.GREEN, None)
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_port_administration = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PORT_ADMINISTRATION.value)
query_time = times_agency.etd_berth
key_time = times_port_administration.etd_berth
threshold = ParticipantwiseTimeDelta.PORT_ADMINISTRATION
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_pilot_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-H
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-H:
- Checks, if times_pilot.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & PILOT)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PILOT.value])]) != 2:
return (StatusFlags.GREEN, None)
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_pilot = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PILOT.value)
query_time = times_agency.eta_berth
key_time = times_pilot.eta_berth
threshold = ParticipantwiseTimeDelta.PILOT
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_pilot_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-I
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-I:
- Checks, if times_pilot.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & PILOT)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PILOT.value])]) != 2:
return (StatusFlags.GREEN, None)
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_pilot = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PILOT.value)
query_time = times_agency.etd_berth
key_time = times_pilot.etd_berth
threshold = ParticipantwiseTimeDelta.PILOT
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_tug_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-J
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-J:
- Checks, if times_tug.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & TUG)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TUG.value])]) != 2:
return (StatusFlags.GREEN, None)
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_tug = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TUG.value)
query_time = times_agency.eta_berth
key_time = times_tug.eta_berth
threshold = ParticipantwiseTimeDelta.TUG
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_tug_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-K
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:
- Checks, if times_tug.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & TUG)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TUG.value])]) != 2:
return (StatusFlags.GREEN, None)
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_tug = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TUG.value)
query_time = times_agency.etd_berth
key_time = times_tug.etd_berth
threshold = ParticipantwiseTimeDelta.TUG
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_terminal_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-L
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-L:
- Checks, if times_terminal.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
# preparation: obtain the correct times of the participant, define the query time and the key time
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)
query_time = times_agency.eta_berth
key_time = times_terminal.eta_berth
threshold = ParticipantwiseTimeDelta.TERMINAL
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_missing_time_terminal_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0001-K
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:
- Checks, if times_terminal.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
# preparation: obtain the correct times of the participant, define the query time and the key time
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)
query_time = times_agency.etd_berth
key_time = times_terminal.etd_berth
threshold = ParticipantwiseTimeDelta.TERMINAL
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_shipcall_incoming_participants_disagree_on_eta(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0002-A
Type: Local Rule
Description: this validation checks, whether the participants expect different ETA times
Filter: only applies to incoming shipcalls
"""
query = "eta_berth"
violation_state = self.check_participants_agree_on_estimated_time(
shipcall = shipcall,
query=query,
df_times=df_times,
applicable_shipcall_type=ShipcallType.INCOMING
)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0002-B
Type: Local Rule
Description: this validation checks, whether the participants expect different ETA times
Filter: only applies to outgoing shipcalls
"""
query = "etd_berth"
violation_state = self.check_participants_agree_on_estimated_time(
shipcall = shipcall,
query=query,
df_times=df_times,
applicable_shipcall_type=ShipcallType.OUTGOING
)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0002-C
Type: Local Rule
Description: this validation checks, whether the participants expect different ETA or ETD times
Filter: only applies to shifting shipcalls
"""
violation_state_eta = self.check_participants_agree_on_estimated_time(
shipcall = shipcall,
query="eta_berth",
df_times=df_times,
applicable_shipcall_type=ShipcallType.SHIFTING
)
violation_state_etd = self.check_participants_agree_on_estimated_time(
shipcall = shipcall,
query="etd_berth",
df_times=df_times,
applicable_shipcall_type=ShipcallType.SHIFTING
)
# apply 'eta_berth' check
# apply 'etd_berth'
# violation: if either 'eta_berth' or 'etd_berth' is violated
# functionally, this is the same as individually comparing all times for the participants
# times_agency.eta_berth==times_mooring.eta_berth==times_portadministration.eta_berth==times_pilot.eta_berth==times_tug.eta_berth
# times_agency.etd_berth==times_mooring.etd_berth==times_portadministration.etd_berth==times_pilot.etd_berth==times_tug.etd_berth
violation_state = (violation_state_eta) or (violation_state_etd)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_eta_time_not_in_operation_window(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0003-A
Type: Local Rule
Description: this validation checks, whether the ETA time is between the provided operations window of the terminal
query time: eta_berth (times_agency)
start_time & end_time: operations_start & operations_end (times_terminal)
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
# get agency & terminal times
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):
return (StatusFlags.GREEN, None)
# check, whether the start of operations is AFTER the estimated arrival time
violation_state = times_terminal.operations_start<times_agency.eta_berth
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_eta_time_not_in_operation_window(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0003-B
Type: Local Rule
Description: this validation checks, whether the ETD time is between the provided operations window of the terminal
query time: eta_berth (times_agency)
start_time & end_time: operations_start & operations_end (times_terminal)
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
# get agency & terminal times
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):
return (StatusFlags.GREEN, None)
# check, whether the end of operations is AFTER the estimated departure time
violation_state = times_terminal.operations_end > times_agency.etd_berth
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_eta_time_not_in_tidal_window(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0004-A
Type: Local Rule
Description: this validation checks, whether the ETA time is between the provided tidal window
query time: eta_berth (times_agency)
start_time & end_time: tidal_window_from & tidal_window_to (shipcall)
"""
# check, if the header is filled in (agency)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
return (StatusFlags.GREEN, None)
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
# requirements: tidal window (from & to) is filled in
if (shipcall.tidal_window_from is pd.NaT) or (shipcall.tidal_window_to is pd.NaT) or (df_times.eta_berth is pd.NaT):
return (StatusFlags.GREEN, None)
# check, whether the query time is between start & end time
# a violation is observed, when the is NOT between start & end
violation_state = not self.time_logic.time_inbetween(query_time=times_agency.eta_berth, start_time=shipcall.tidal_window_from, end_time=shipcall.tidal_window_to)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_etd_time_not_in_tidal_window(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0004-B
Type: Local Rule
Description: this validation checks, whether the ETD time is between the provided tidal window
query time: eta_berth (times_agency)
start_time & end_time: tidal_window_from & tidal_window_to (shipcall)
"""
# check, if the header is filled in (agency)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
return (StatusFlags.GREEN, None)
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
# requirements: tidal window (from & to) is filled in
if (shipcall.tidal_window_from is pd.NaT) or (shipcall.tidal_window_to is pd.NaT) or (df_times.etd_berth is pd.NaT):
return (StatusFlags.GREEN, None)
# check, whether the query time is between start & end time
# a violation is observed, when the is NOT between start & end
violation_state = not self.time_logic.time_inbetween(query_time=times_agency.etd_berth, start_time=shipcall.tidal_window_from, end_time=shipcall.tidal_window_to)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_too_many_identical_eta_times(self, shipcall, df_times, rounding = "min", maximum_threshold = 3, *args, **kwargs):
"""
Code: #0005-A
Type: Global Rule
Description: this validation rule checks, whether there are too many shipcalls with identical ETA times.
"""
# when ANY of the unique values exceeds the threshold, a violation is observed
query = "eta_berth"
violation_state = self.check_unique_shipcall_counts(query, rounding=rounding, maximum_threshold=maximum_threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_too_many_identical_etd_times(self, shipcall, df_times, rounding = "min", maximum_threshold = 3, *args, **kwargs):
"""
Code: #0005-B
Type: Global Rule
Description: this validation rule checks, whether there are too many shipcalls with identical ETD times.
"""
# when ANY of the unique values exceeds the threshold, a violation is observed
query = "etd_berth"
violation_state = self.check_unique_shipcall_counts(query, rounding=rounding, maximum_threshold=maximum_threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_agency_and_terminal_berth_id_disagreement(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0006-A
Type: Local Rule
Description: This validation rule checks, whether agency and terminal agree with their designated berth place by checking berth_id.
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
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)
violation_state = times_agency.berth_id!=times_terminal.berth_id
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
def validation_rule_fct_agency_and_terminal_pier_side_disagreement(self, shipcall, df_times, *args, **kwargs):
"""
Code: #0006-B
Type: Local Rule
Description: This validation rule checks, whether agency and terminal agree with their designated pier side by checking pier_side.
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
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)
violation_state = times_agency.pier_side!=times_terminal.pier_side
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)

View File

@ -0,0 +1,128 @@
import copy
import numpy as np
import pandas as pd
from BreCal.database.enums import StatusFlags
from BreCal.validators.validation_rule_functions import ValidationRuleFunctions
from BreCal.schemas.model import Shipcall
class ValidationRules(ValidationRuleFunctions):
"""
An object that determines the traffic light state for validation and notification. The provided feedback ('green', 'yellow', 'red')
determines, whether the state is critical. It uses ValidationRuleState enumerations.
In case of a critical validation state, the user's input prompt may be interrupted and the user may be warned.
In case of a critical notification state, the respective users will be automatically notified after n seconds. (#TODO_n_seconds_delay)
"""
def __init__(self, sql_handler): # use the entire data that is provided for this query (e.g., json input)
super().__init__(sql_handler)
self.validation_state = self.determine_validation_state()
# currently flagged: notification_state initially was based on using one ValidationRules object for each query. This is deprecated.
# self.notification_state = self.determine_notification_state() # (state:str, should_notify:bool)
return
def evaluate(self, shipcall):
"""
1.) prepare df_times, which every validation rule tends to use
calling this only once saves a lot of computational overhead
2.) apply all validation rules
returns: (evaluation_state, violations)
"""
# prepare df_times, which every validation rule tends to use
df_times = self.sql_handler.df_dict.get('times') # -> pd.DataFrame
# filter by shipcall id
df_times = self.sql_handler.get_times_of_shipcall(shipcall)
# apply all validation rules
# list of tuples, where each element is (state, msg)
evaluation_results = [elem(shipcall, df_times) for elem in self.get_validation_rule_functions()]
# filter out all 'None' results, which indicate that no violation occured.
evaluation_results = [evaluation_result for evaluation_result in evaluation_results if evaluation_result[1] is not None]
""" # deprecated
# check, if ANY of the evaluation results (evaluation_state) is larger than the .GREEN state. This means, that .YELLOW and .RED
# would return 'True'. Numpy arrays and functions are used to accelerate the comparison.
# np.any returns a boolean.
#evaluation_state = not np.any(np.greater(np.array([result[0] for result in evaluation_results]), ValidationRuleState.GREEN))
"""
# check, what the maximum state flag is and return it
evaluation_state = np.max(np.array([result[0].value for result in evaluation_results])) if len(evaluation_results)>0 else 1
evaluation_verbosity = [result[1] for result in evaluation_results]
return (evaluation_state, evaluation_verbosity)
def evaluation_verbosity(self, evaluation_state, evaluation_results):
"""This function suggestions verbosity for the evaluation results. Based on 'True'/'False' evaluation outcome, the returned string is different."""
if evaluation_state:
return f"OK! The validation was successful. There are no rule violations."
else:
verbose_string = "These are:" + "\n\t".join(evaluation_results) # every element of the list will be displayed in a new line with a tab
return f"FAILED VALIDATION. There have been {len(evaluation_results)} violations. {verbose_string}"
def evaluate_shipcall_from_df(self, x):
shipcall = Shipcall(**{**{'id':x.name}, **x.to_dict()})
evaluation_state, violations = self.evaluate(shipcall)
return evaluation_state, violations
def evaluate_shipcalls(self, shipcall_df:pd.DataFrame)->pd.DataFrame:
"""apply 'evaluate_shipcall_from_df' to each individual shipcall in {shipcall_df}. Returns shipcall_df ('evaluation' and 'evaluation_message' are updated)"""
results = shipcall_df.apply(lambda x: self.evaluate_shipcall_from_df(x), axis=1).values
# unbundle individual results. evaluation_state becomes an integer, violation
evaluation_state = [StatusFlags(res[0]).value for res in results]
violations = [",".join(res[1]) if len(res[1])>0 else None for res in results]
shipcall_df.loc[:,"evaluation"] = evaluation_state
shipcall_df.loc[:,"evaluation_message"] = violations
return shipcall_df
def determine_validation_state(self) -> str:
"""
this method determines the validation state of a shipcall. The state is either ['green', 'yellow', 'red'] and signals,
whether an entry causes issues within the workflow of users.
returns: validation_state_new (str)
"""
(validation_state_new, description) = self.undefined_method()
# should there also be notifications for critical validation states? In principle, the traffic light itself provides that notification.
self.validation_state = validation_state_new
return validation_state_new
def determine_notification_state(self) -> (str, bool):
"""
this method determines state changes in the notification state. When the state is changed to yellow or red,
a user is notified about it. The only exception for this rule is when the state was yellow or red before,
as the user has then already been notified.
returns: notification_state_new (str), should_notify (bool)
"""
(state_new, description) = self.undefined_method() # determine the successor
should_notify = self.identify_notification_state_change(state_new)
self.notification_state = state_new # overwrite the predecessor
return state_new, should_notify
def identify_notification_state_change(self, state_new) -> bool:
"""
determines, whether the observed state change should trigger a notification.
internally, this function maps a color string to an integer and determines, if the successor state is more severe than the predecessor.
state changes trigger a notification in the following cases:
green -> yellow
green -> red
yellow -> red
(none -> yellow) or (none -> red)
due to the values in the enumeration objects, the states are mapped to provide this function.
green=1, yellow=2, red=3, none=1. Hence, critical changes can be observed by simply checking with "greater than".
returns bool, whether a notification should be triggered
"""
# state_old is always considered at least 'Green' (1)
state_old = max(copy.copy(self.notification_state) if "notification_state" in list(self.__dict__.keys()) else StatusFlags.NONE, StatusFlags.GREEN.value)
return state_new.value > state_old.value
def undefined_method(self) -> str:
"""this function should apply the ValidationRules to the respective .shipcall, in regards to .times"""
# #TODO_traffic_state
return (StatusFlags.GREEN, False) # (state:str, should_notify:bool)

View File

@ -1,8 +1,12 @@
import os
import sys
import logging
sys.path.insert(0, '/var/www/brecal/server')
sys.path.insert(0, '/var/www/brecal/venv/lib/python3.10/site-packages/')
sys.path.insert(0, '/var/www/brecal_test/src/server')
sys.path.insert(0, '/var/www/venv/lib/python3.10/site-packages/')
# set the key
os.environ['SECRET_KEY'] = 'zdiTz8P3jXOc7jztIQAoelK4zztyuCpJ'
# Set up logging
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

View File

@ -3,11 +3,21 @@ Flask==1.1.2
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
marshmallow==3.9.1
marshmallow>=3.9.1
webargs==6.1.1
Werkzeug==1.0.1
pydapper[mysql-connector-python]
marshmallow-dataclass
bcrypt
jwt
flask-jwt-extended
pyjwt
flask-jwt-extended
numpy
pandas
tqdm
schedule
pytest
pytest-cov
coverage
../server/.

View File

View File

View File

@ -0,0 +1,52 @@
import unittest
import pytest
def test_execute_coverage_test():
"""
executes {execute_coverage_test} to check, whether reporting works as expected
"""
import os
import BreCal.brecal_utils
from BreCal.brecal_utils.file_handling import get_project_root
from BreCal.brecal_utils.test_handling import execute_coverage_test
# find the root folder 'server'
root_dir = BreCal.brecal_utils.__file__
root_dir = get_project_root("server", root_dir=root_dir)
# find the test path, the library path and the coverage report path
tests_path = os.path.join(root_dir, "tests")
coverage_path = os.path.join(root_dir, "BreCal")
report_path = os.path.join(root_dir, "coverage_reports")
with pytest.raises(KeyboardInterrupt, match="is_test_interrupt"):
execute_coverage_test(tests_path=tests_path, coverage_path=coverage_path, cov_report_dst_dir=report_path, cov_fail_under_rate=0, is_test=1)
return
def test_execute_coverage_test_no_report():
"""
executes {execute_coverage_test} to check, whether the function also works without reporting
"""
import os
import BreCal.brecal_utils
from BreCal.brecal_utils.file_handling import get_project_root
from BreCal.brecal_utils.test_handling import execute_coverage_test
# find the root folder 'server'
root_dir = BreCal.brecal_utils.__file__
root_dir = get_project_root("server", root_dir=root_dir)
# find the test path, the library path and the coverage report path
tests_path = os.path.join(root_dir, "tests")
coverage_path = os.path.join(root_dir, "BreCal")
report_path = os.path.join(root_dir, "coverage_reports")
with pytest.raises(KeyboardInterrupt, match="is_test_interrupt"):
execute_coverage_test(tests_path=tests_path, coverage_path=coverage_path, cov_report_dst_dir=None, cov_fail_under_rate=0, is_test=1)
return
if __name__=="__main__":
pass

View File

@ -0,0 +1,52 @@
import pytest
def test_difference_to_then_tgt_time_none():
import math
import datetime
from BreCal import difference_to_then
difference_in_seconds = 42
event_time = datetime.datetime.now() - datetime.timedelta(seconds=difference_in_seconds)
event_time_diff = difference_to_then(event_time) # tgt_time = datetime.datetime.now()
# {difference_to_then} internally creates a .now() time, when the {then_time} is not defined
# hence, the difference will never be exactly 42 seconds due to slight latency
# math.isclose allows deviations up to 0.05 seconds
assert math.isclose(42, event_time_diff, abs_tol=0.05), f"both times are reasonably close"
return
def test_difference_to_then_tgt_time_not_none():
import math
import datetime
from BreCal import difference_to_then
difference_in_seconds = 42
event_time = datetime.datetime(2000, 1, 1, 0, 0, 0)
tgt_time = event_time - datetime.timedelta(seconds=difference_in_seconds)
event_time_diff = difference_to_then(event_time, tgt_time)
# tgt time is -42 seconds, as it is 42 seconds before event_time
assert event_time_diff==-42, f"event time difference is incorrect"
return
def test_difference_to_then_tgt_time_not_none_make_absolute():
import math
import datetime
from BreCal import difference_to_then
difference_in_seconds = 42
event_time = datetime.datetime(2000, 1, 1, 0, 0, 0)
tgt_time = event_time - datetime.timedelta(seconds=difference_in_seconds)
event_time_diff = difference_to_then(event_time, tgt_time, make_absolute=True) # difference: -42. make_absolute: +42
# tgt time is -42 seconds, as it is 42 seconds before event_time. However, we are interested in an absolute value
assert event_time_diff==42, f"event time difference is incorrect"
return
if __name__=="__main__":
test_difference_to_then_tgt_time_none()
test_difference_to_then_tgt_time_not_none()
test_difference_to_then_tgt_time_not_none_make_absolute()

View File

View File

View File

View File

View File

@ -0,0 +1,59 @@
import pytest
def test_build_stub_berth():
from BreCal.schemas.model import Berth
from BreCal.stubs.berth import get_berth_simple
berth = get_berth_simple()
assert isinstance(berth, Berth)
return
def test_build_stub_participant():
from BreCal.schemas.model import Participant
from BreCal.stubs.participant import get_participant_simple
participant = get_participant_simple()
assert isinstance(participant, Participant)
return
def test_build_stub_user():
from BreCal.schemas.model import User
from BreCal.stubs.user import get_user_simple
user = get_user_simple()
assert isinstance(user, User)
return
def test_build_stub_ship():
from BreCal.schemas.model import Ship
from BreCal.stubs.ship import get_ship_simple
ship = get_ship_simple()
assert isinstance(ship, Ship)
return
def test_build_stub_shipcall():
from BreCal.schemas.model import Shipcall
from BreCal.stubs.shipcall import get_shipcall_simple
shipcall = get_shipcall_simple()
assert isinstance(shipcall, Shipcall)
return
def test_build_stub_times():
from BreCal.schemas.model import Times
from BreCal.stubs.times_full import get_times_full_simple
times = get_times_full_simple()
assert isinstance(times, Times)
return
def test_build_stub_notification():
from BreCal.schemas.model import Notification
from BreCal.stubs.notification import get_notification_simple
notification = get_notification_simple()
assert isinstance(notification, Notification)
if __name__=="__main__":
test_build_stub_berth()
test_build_stub_participant()
test_build_stub_berth()
test_build_stub_user()
test_build_stub_ship()
test_build_stub_shipcall()
test_build_stub_times()
test_build_stub_notification()

View File

@ -0,0 +1,21 @@
import pytest
def test_create_app():
"""
"""
import os
import sys
from BreCal import get_project_root
project_root = get_project_root("brecal")
lib_location = os.path.join(project_root, "src", "server")
sys.path.append(lib_location)
from BreCal import create_app
os.chdir(os.path.join(lib_location,"BreCal")) # set the current directory to ~/brecal/src/server/BreCal, so the config is found
application = create_app()
return
if __name__=="__main__":
test_create_app()

View File

@ -0,0 +1,86 @@
import pytest
def test_import_tqdm_tqdm():
"""tqdm is a neat utility library for simple display of progress in loops"""
from tqdm import tqdm
return
def test_import_numpy():
"""numpy is useful to evaluate multiple entries simultaneously, as boolean operations (e.g., greater than) are efficiently handled"""
import numpy as np
return
def test_import_pandas():
"""pandas is useful to handle dataframes and read from .csv or .json files, which can be collected into joint DataFrame objects"""
import pandas as pd
return
def test_import_flask():
"""flask is a WSGI framework for quick and easy design of web-based applications"""
import flask
from flask import Flask, Blueprint, request
return
def test_import_flask_specific_objects():
"""common flask objects, such as the Flask api object, the Blueprint and requests"""
from flask import Flask, Blueprint, request
return
def test_import_mysql_connector():
"""the 'mysql.connector' Object is used for the BreCal server database"""
import mysql.connector
return
def test_import_pydapper():
"""is a library that provides convenient methods for database related work"""
import pydapper
return
def test_import_webargs():
"""currently used in ~/brecal/src/server/BreCal/api/berths.py"""
import webargs
from webargs.flaskparser import parser
return
def test_import_mashmallow():
"""currently used in ~/brecal/src/server/BreCal/api/shipcalls.py"""
import marshmallow
from marshmallow import Schema, fields
return
def test_import_flask_jwt_extended():
"""currently used in ~/brecal/src/server/BreCal/api/login.py"""
import flask_jwt_extended
from flask_jwt_extended import create_access_token
return
def test_import_pyjwt():
"""currently used in ~/brecal/src/server/BreCal/services/jwt_handler.py"""
import jwt
return
def test_import_bcrypt():
"""currently used in ~/brecal/src/server/BreCal/impl/login.py"""
import bcrypt
return
def test_import_math():
"""math.isclose can be interesting to measure differences between two times (e.g., to ignore milliseconds)"""
import math
math.isclose
return
def test_import_datetime():
"""datetime is the default library for times"""
import datetime
datetime.datetime.now()
return
if __name__=="__main__":
test_import_tqdm_tqdm()
test_import_pandas()
test_import_flask()

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