Merge branch 'release/pub_0.9.4'
This commit is contained in:
commit
e77c9264b9
File diff suppressed because it is too large
Load Diff
@ -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
110
misc/Deployment.md
Normal 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/
|
||||
|
||||
@ -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
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
123
src/BreCalClient/BreCalLists.cs
Normal file
123
src/BreCalClient/BreCalLists.cs
Normal 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
|
||||
|
||||
}
|
||||
}
|
||||
@ -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">
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
139
src/BreCalClient/EditTimesAgencyIncomingControl.xaml
Normal file
139
src/BreCalClient/EditTimesAgencyIncomingControl.xaml
Normal 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>
|
||||
305
src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs
Normal file
305
src/BreCalClient/EditTimesAgencyIncomingControl.xaml.cs
Normal 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
|
||||
|
||||
}
|
||||
}
|
||||
129
src/BreCalClient/EditTimesAgencyOutgoingControl.xaml
Normal file
129
src/BreCalClient/EditTimesAgencyOutgoingControl.xaml
Normal 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>
|
||||
290
src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs
Normal file
290
src/BreCalClient/EditTimesAgencyOutgoingControl.xaml.cs
Normal 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
|
||||
|
||||
}
|
||||
}
|
||||
158
src/BreCalClient/EditTimesAgencyShiftingControl.xaml
Normal file
158
src/BreCalClient/EditTimesAgencyShiftingControl.xaml
Normal 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>
|
||||
313
src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs
Normal file
313
src/BreCalClient/EditTimesAgencyShiftingControl.xaml.cs
Normal 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
|
||||
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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*" />
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
120
src/BreCalClient/Resources/Resources.Designer.cs
generated
120
src/BreCalClient/Resources/Resources.Designer.cs
generated
@ -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>
|
||||
|
||||
@ -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>
|
||||
@ -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>
|
||||
|
||||
BIN
src/BreCalClient/Resources/check.png
Normal file
BIN
src/BreCalClient/Resources/check.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/BreCalClient/Resources/delete2.png
Normal file
BIN
src/BreCalClient/Resources/delete2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/BreCalClient/Resources/sign_warning.png
Normal file
BIN
src/BreCalClient/Resources/sign_warning.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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",
|
||||
]
|
||||
|
||||
|
||||
0
src/server/BreCal/api/__init__.py
Normal file
0
src/server/BreCal/api/__init__.py
Normal 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:
|
||||
|
||||
0
src/server/BreCal/brecal_utils/__init__.py
Normal file
0
src/server/BreCal/brecal_utils/__init__.py
Normal file
46
src/server/BreCal/brecal_utils/file_handling.py
Normal file
46
src/server/BreCal/brecal_utils/file_handling.py
Normal 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
|
||||
131
src/server/BreCal/brecal_utils/request_status_code.py
Normal file
131
src/server/BreCal/brecal_utils/request_status_code.py
Normal 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)
|
||||
|
||||
84
src/server/BreCal/brecal_utils/test_handling.py
Normal file
84
src/server/BreCal/brecal_utils/test_handling.py
Normal 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")
|
||||
21
src/server/BreCal/brecal_utils/time_handling.py
Normal file
21
src/server/BreCal/brecal_utils/time_handling.py
Normal 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()
|
||||
@ -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
|
||||
}
|
||||
0
src/server/BreCal/database/__init__.py
Normal file
0
src/server/BreCal/database/__init__.py
Normal file
38
src/server/BreCal/database/enums.py
Normal file
38
src/server/BreCal/database/enums.py
Normal 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
|
||||
|
||||
204
src/server/BreCal/database/sql_handler.py
Normal file
204
src/server/BreCal/database/sql_handler.py
Normal 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)
|
||||
102
src/server/BreCal/database/update_database.py
Normal file
102
src/server/BreCal/database/update_database.py
Normal 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
|
||||
@ -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:
|
||||
|
||||
@ -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'}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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)
|
||||
0
src/server/BreCal/schemas/__init__.py
Normal file
0
src/server/BreCal/schemas/__init__.py
Normal 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:
|
||||
|
||||
0
src/server/BreCal/services/__init__.py
Normal file
0
src/server/BreCal/services/__init__.py
Normal file
5
src/server/BreCal/stubs/__init__.py
Normal file
5
src/server/BreCal/stubs/__init__.py
Normal 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
|
||||
31
src/server/BreCal/stubs/berth.py
Normal file
31
src/server/BreCal/stubs/berth.py
Normal 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
|
||||
|
||||
49
src/server/BreCal/stubs/notification.py
Normal file
49
src/server/BreCal/stubs/notification.py
Normal 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
|
||||
|
||||
32
src/server/BreCal/stubs/participant.py
Normal file
32
src/server/BreCal/stubs/participant.py
Normal 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
|
||||
0
src/server/BreCal/stubs/roles.py
Normal file
0
src/server/BreCal/stubs/roles.py
Normal file
38
src/server/BreCal/stubs/ship.py
Normal file
38
src/server/BreCal/stubs/ship.py
Normal 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
|
||||
|
||||
81
src/server/BreCal/stubs/shipcall.py
Normal file
81
src/server/BreCal/stubs/shipcall.py
Normal 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
|
||||
|
||||
|
||||
0
src/server/BreCal/stubs/times_agency.py
Normal file
0
src/server/BreCal/stubs/times_agency.py
Normal file
67
src/server/BreCal/stubs/times_full.py
Normal file
67
src/server/BreCal/stubs/times_full.py
Normal 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
|
||||
0
src/server/BreCal/stubs/times_mooring.py
Normal file
0
src/server/BreCal/stubs/times_mooring.py
Normal file
0
src/server/BreCal/stubs/times_pilot.py
Normal file
0
src/server/BreCal/stubs/times_pilot.py
Normal file
0
src/server/BreCal/stubs/times_portauthority.py
Normal file
0
src/server/BreCal/stubs/times_portauthority.py
Normal file
0
src/server/BreCal/stubs/times_terminal.py
Normal file
0
src/server/BreCal/stubs/times_terminal.py
Normal file
35
src/server/BreCal/stubs/user.py
Normal file
35
src/server/BreCal/stubs/user.py
Normal 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
|
||||
0
src/server/BreCal/validators/__init__.py
Normal file
0
src/server/BreCal/validators/__init__.py
Normal file
165
src/server/BreCal/validators/input_validation.py
Normal file
165
src/server/BreCal/validators/input_validation.py
Normal 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
|
||||
|
||||
139
src/server/BreCal/validators/schema_validation.py
Normal file
139
src/server/BreCal/validators/schema_validation.py
Normal 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)
|
||||
91
src/server/BreCal/validators/time_logic.py
Normal file
91
src/server/BreCal/validators/time_logic.py
Normal 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
|
||||
|
||||
|
||||
769
src/server/BreCal/validators/validation_rule_functions.py
Normal file
769
src/server/BreCal/validators/validation_rule_functions.py
Normal 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)
|
||||
|
||||
|
||||
128
src/server/BreCal/validators/validation_rules.py
Normal file
128
src/server/BreCal/validators/validation_rules.py
Normal 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)
|
||||
@ -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)
|
||||
|
||||
@ -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/.
|
||||
|
||||
0
src/server/tests/__init__.py
Normal file
0
src/server/tests/__init__.py
Normal file
0
src/server/tests/api/__init__.py
Normal file
0
src/server/tests/api/__init__.py
Normal file
0
src/server/tests/brecal_utils/__init__.py
Normal file
0
src/server/tests/brecal_utils/__init__.py
Normal file
52
src/server/tests/brecal_utils/test_test_handling.py
Normal file
52
src/server/tests/brecal_utils/test_test_handling.py
Normal 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
|
||||
52
src/server/tests/brecal_utils/test_time_handling.py
Normal file
52
src/server/tests/brecal_utils/test_time_handling.py
Normal 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()
|
||||
|
||||
|
||||
0
src/server/tests/impl/__init__.py
Normal file
0
src/server/tests/impl/__init__.py
Normal file
0
src/server/tests/schemas/__init__.py
Normal file
0
src/server/tests/schemas/__init__.py
Normal file
0
src/server/tests/services/__init__.py
Normal file
0
src/server/tests/services/__init__.py
Normal file
0
src/server/tests/stubs/__init__.py
Normal file
0
src/server/tests/stubs/__init__.py
Normal file
59
src/server/tests/stubs/test_stub_objects.py
Normal file
59
src/server/tests/stubs/test_stub_objects.py
Normal 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()
|
||||
21
src/server/tests/test_create_app.py
Normal file
21
src/server/tests/test_create_app.py
Normal 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()
|
||||
86
src/server/tests/test_import_modules.py
Normal file
86
src/server/tests/test_import_modules.py
Normal 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
Reference in New Issue
Block a user