Merge branch 'feature/api_enhancements' into develop

This commit is contained in:
Daniel Schick 2024-03-27 10:08:48 +01:00
commit 862ef9fe88
47 changed files with 5603 additions and 3824 deletions

6
.vscode/launch.json vendored
View File

@ -15,9 +15,9 @@
"SECRET_KEY" : "zdiTz8P3jXOc7jztIQAoelK4zztyuCpJ" // https://randomkeygen.com/
},
"args": [
"run" //,
// "--no-debugger",
//"--no-reload"
"run",
// "--no-debugger",
"--no-reload"
],
"jinja": true,
"justMyCode": true

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,87 @@
-- add notification handling columns to shipcall
-- evaluation_time: Time when the "traffic light" was last changed
-- evaluation_notifications_sent: Flag to indicate if notifications were sent for the current evaluation
ALTER TABLE `bremen_calling_devel`.`shipcall`
ADD COLUMN `evaluation_time` DATETIME NULL DEFAULT NULL AFTER `evaluation_message`,
ADD COLUMN `evaluation_notifications_sent` BIT NULL AFTER `evaluation_time`;
-- prepare notification table for historic notification data
-- removed reference to participant and times and dropped unnecessary columns
-- added reference to shipcall
ALTER TABLE `bremen_calling_devel`.`notification`
DROP FOREIGN KEY `FK_NOT_TIMES`,
DROP FOREIGN KEY `FK_NOT_PART`;
ALTER TABLE `bremen_calling_devel`.`notification`
DROP COLUMN `deleted`,
DROP COLUMN `acknowledged`,
DROP COLUMN `participant_id`,
DROP COLUMN `times_id`,
ADD COLUMN `shipcall_id` INT UNSIGNED NULL AFTER `id`,
ADD INDEX `FK_NOTIFICATION_SHIPCALL_idx` (`shipcall_id` ASC) VISIBLE,
DROP INDEX `FK_NOT_PART` ,
DROP INDEX `FK_NOT_TIMES` ;
;
ALTER TABLE `bremen_calling_devel`.`notification`
ADD CONSTRAINT `FK_NOTIFICATION_SHIPCALL`
FOREIGN KEY (`shipcall_id`)
REFERENCES `bremen_calling_devel`.`shipcall` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION;
-- added notification flags
-- participant reference is now mandatory
ALTER TABLE `bremen_calling_devel`.`user`
DROP FOREIGN KEY `FK_USER_PART`;
ALTER TABLE `bremen_calling_devel`.`user`
ADD COLUMN `notify_email` BIT NULL DEFAULT NULL AFTER `api_key`,
ADD COLUMN `notify_whatsapp` BIT NULL DEFAULT NULL AFTER `notify_email`,
ADD COLUMN `notify_signal` BIT NULL DEFAULT NULL AFTER `notify_whatsapp`,
ADD COLUMN `notify_popup` BIT NULL DEFAULT NULL AFTER `notify_signal`,
CHANGE COLUMN `participant_id` `participant_id` INT UNSIGNED NOT NULL ;
ALTER TABLE `bremen_calling_devel`.`user`
ADD CONSTRAINT `FK_USER_PART`
FOREIGN KEY (`participant_id`)
REFERENCES `bremen_calling_devel`.`participant` (`id`);
-- History table for change tracking
CREATE TABLE `bremen_calling_devel`.`history` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`participant_id` INT UNSIGNED NOT NULL,
`user_id` INT UNSIGNED NOT NULL,
`shipcall_id` INT UNSIGNED NOT NULL,
`timestamp` DATETIME NOT NULL COMMENT 'Time of saving',
`eta` DATETIME NULL COMMENT 'Current ETA / ETD value (depends if shipcall or times were saved)',
`type` INT NOT NULL COMMENT 'shipcall or times',
`operation` INT NOT NULL COMMENT 'insert, update or delete',
PRIMARY KEY (`id`))
COMMENT = 'This table stores a history of changes made to shipcalls so that everyone can see who changed what and when';
-- and foreign keys
ALTER TABLE `bremen_calling_devel`.`history`
ADD INDEX `FK_HISTORY_PARTICIPANT_idx` (`participant_id` ASC) VISIBLE,
ADD INDEX `FK_HISTORY_SHIPCALL_idx` (`shipcall_id` ASC) VISIBLE;
;
ALTER TABLE `bremen_calling_devel`.`history`
ADD CONSTRAINT `FK_HISTORY_PARTICIPANT`
FOREIGN KEY (`participant_id`)
REFERENCES `bremen_calling_devel`.`participant` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
ADD CONSTRAINT `FK_HISTORY_SHIPCALL`
FOREIGN KEY (`shipcall_id`)
REFERENCES `bremen_calling_devel`.`shipcall` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
ADD CONSTRAINT `FK_HISTORY_USER`
FOREIGN KEY (`user_id`)
REFERENCES `bremen_calling_devel`.`user` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION;

View File

@ -17,6 +17,7 @@
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\add.png" />
<None Remove="Resources\arrow_down_green.png" />
<None Remove="Resources\arrow_down_red.png" />
<None Remove="Resources\arrow_right_blue.png" />
@ -31,6 +32,7 @@
<None Remove="Resources\containership.png" />
<None Remove="Resources\delete.png" />
<None Remove="Resources\delete2.png" />
<None Remove="Resources\edit.png" />
<None Remove="Resources\emergency_stop_button.png" />
<None Remove="Resources\logo_bremen_calling.png" />
<None Remove="Resources\ship2.png" />
@ -68,6 +70,7 @@
<Generator>OpenApiCodeGenerator</Generator>
<LastGenOutput>BreCalApi.cs</LastGenOutput>
</None>
<Resource Include="Resources\add.png" />
<Resource Include="Resources\arrow_down_green.png" />
<Resource Include="Resources\arrow_down_red.png" />
<Resource Include="Resources\arrow_right_blue.png" />
@ -82,6 +85,7 @@
<Resource Include="Resources\containership.png" />
<Resource Include="Resources\delete.png" />
<Resource Include="Resources\delete2.png" />
<Resource Include="Resources\edit.png" />
<Resource Include="Resources\emergency_stop_button.png" />
<Resource Include="Resources\logo_bremen_calling.png" />
<Resource Include="Resources\ship2.png" />
@ -106,7 +110,7 @@
<PackageReference Include="JsonSubTypes" Version="2.0.1" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Polly" Version="7.2.4" />
<PackageReference Include="Polly" Version="8.2.0" />
<PackageReference Include="RestSharp" Version="110.2.0" />
</ItemGroup>

View File

@ -0,0 +1,258 @@
// Copyright (c) 2017 schick Informatik
// Description: DataGrid mit etwas "verbesserten" Funktionen
//
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Controls.Primitives;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
namespace BreCalClient
{
/// <summary>
/// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
///
/// Step 1a) Using this custom control in a XAML file that exists in the current project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:enictrl="clr-namespace:ENI2.Controls"
///
///
/// Step 1b) Using this custom control in a XAML file that exists in a different project.
/// Add this XmlNamespace attribute to the root element of the markup file where it is
/// to be used:
///
/// xmlns:enictrl="clr-namespace:ENI2.Controls;assembly=ENI2.Controls"
///
/// You will also need to add a project reference from the project where the XAML file lives
/// to this project and Rebuild to avoid compilation errors:
///
/// Right click on the target project in the Solution Explorer and
/// "Add Reference"->"Projects"->[Browse to and select this project]
///
///
/// Step 2)
/// Go ahead and use your control in the XAML file.
///
/// <MyNamespace:ENIDataGrid/>
///
/// </summary>
public class ENIDataGrid : DataGrid
{
public event Action<object>? EditRequested;
public event Action<object>? DeleteRequested;
public event Action? CreateRequested;
public event Action? RefreshGrid;
public event Action<object>? PrintRequested;
public event Action<object>? ExportRequested;
public event Action<object>? ShowTextRequested;
public void Initialize()
{
this.MouseDoubleClick += dataGrid_MouseDoubleClick;
this.PreviewKeyDown += ENIDataGrid_PreviewKeyDown;
this.ContextMenu = new ContextMenu();
this.CanUserAddRows = false;
this.IsReadOnly = false;
MenuItem addItem = new MenuItem();
addItem.Header = BreCalClient.Resources.Resources.textAdd;
addItem.Icon = new Image { Source = Util.LoadImage(BreCalClient.Resources.Resources.add) };
addItem.Click += new RoutedEventHandler(this.addItem);
this.ContextMenu.Items.Add(addItem);
MenuItem deleteItem = new MenuItem();
deleteItem.Header = BreCalClient.Resources.Resources.textDelete;
deleteItem.Icon = new Image { Source = Util.LoadImage(BreCalClient.Resources.Resources.delete) };
deleteItem.Click += this.deleteItem;
this.ContextMenu.Items.Add(deleteItem);
MenuItem editItem = new MenuItem();
editItem.Header = BreCalClient.Resources.Resources.textEdit;
editItem.Icon = new Image { Source = Util.LoadImage(BreCalClient.Resources.Resources.edit) };
editItem.Click += this.editItem;
this.ContextMenu.Items.Add(editItem);
}
private void ENIDataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
if(sender is ENIDataGrid)
{
var grid = sender as ENIDataGrid;
if (Key.Delete == e.Key)
this.deleteItem(null, null);
}
}
#region public
public DataGridRow GetRow(int index)
{
DataGridRow row = (DataGridRow)this.ItemContainerGenerator.ContainerFromIndex(index);
if(row == null)
{
this.UpdateLayout();
this.ScrollIntoView(this.Items[index]);
row = (DataGridRow)this.ItemContainerGenerator.ContainerFromIndex(index);
}
return row;
}
public DataGridCell? GetCell(DataGridRow row, int column)
{
if (row != null)
{
DataGridCellsPresenter? presenter = GetVisualChild<DataGridCellsPresenter>(row);
if (presenter == null)
{
this.ScrollIntoView(row, this.Columns[column]);
presenter = GetVisualChild<DataGridCellsPresenter>(row);
}
DataGridCell? cell = (DataGridCell?)presenter?.ItemContainerGenerator.ContainerFromIndex(column);
return cell;
}
return null;
}
public DataGridCell? GetCell(int rowIndex, int columnIndex)
{
DataGridRow row = this.GetRow(rowIndex);
return this.GetCell(row, columnIndex);
}
#endregion
#region protected
protected void addItem(object sender, RoutedEventArgs e)
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-us");
if (!this.IsReadOnly)
{
this.CreateRequested?.Invoke();
}
e.Handled = true;
}
protected void deleteItem(object? sender, RoutedEventArgs? e)
{
if((this.SelectedItems != null) && (this.SelectedItems.Count > 0) && !this.IsReadOnly)
{
MessageBoxResult result = MessageBox.Show("Are your sure?", "Please confirm", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
List<object> deleteList = new List<object>();
foreach (object deleteItem in this.SelectedItems)
deleteList.Add(deleteItem);
foreach (object deleteItem in deleteList)
{
if (deleteItem != null)
{
this.DeleteRequested?.Invoke(deleteItem);
}
}
this.RefreshGrid?.Invoke();
}
}
}
protected void editItem(object sender, RoutedEventArgs e)
{
if((this.SelectedItems != null) && (this.SelectedItems.Count == 1) && !this.IsReadOnly)
{
if (this.SelectedItems[0] is object selectedEntity)
this.EditRequested?.Invoke(selectedEntity);
}
}
protected void printItem(object sender, RoutedEventArgs e)
{
if ((this.SelectedItems != null) && (this.SelectedItems.Count == 1) )
{
if (this.SelectedItems[0] is object selectedEntity)
this.PrintRequested?.Invoke(selectedEntity);
}
}
protected void exportItem(object sender, RoutedEventArgs e)
{
if ((this.SelectedItems != null) && (this.SelectedItems.Count == 1))
{
if (this.SelectedItems[0] is object selectedEntity)
this.ExportRequested?.Invoke(selectedEntity);
}
}
protected void showTextItem(object sender, RoutedEventArgs e)
{
if ((this.SelectedItems != null) && (this.SelectedItems.Count == 1))
{
if (this.SelectedItems[0] is object selectedEntity)
this.ShowTextRequested?.Invoke(selectedEntity);
}
}
#endregion
#region private
private void dataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender != null)
{
if ((sender is DataGrid grid) && (grid.SelectedItems != null) && (grid.SelectedItems.Count == 1) && !this.IsReadOnly)
{
DataGridRow? dgr = grid.ItemContainerGenerator.ContainerFromItem(grid.SelectedItem) as DataGridRow;
if (grid.SelectedItem is object selectedEntity)
this.EditRequested?.Invoke(selectedEntity);
}
}
}
#endregion
#region private static
private static T? GetVisualChild<T>(Visual parent) where T : Visual
{
T? child = default;
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual? v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
#endregion
}
}

View File

@ -0,0 +1,54 @@
<Window x:Class="BreCalClient.EditShipDialog"
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:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d"
Left="{local:SettingBinding W1Left}" Top="{local:SettingBinding W1Top}"
Title="{x:Static p:Resources.textEditShip}" Height="250" Width="500" Loaded="Window_Loaded" ResizeMode="NoResize">
<Grid x:Name="shipGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".3*" />
<ColumnDefinition Width=".6*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<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="{x:Static p:Resources.textTugCompany}" 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" DisplayMemberPath="Name" />
<Button x:Name="buttonResetParticipant" Grid.Column="1" Margin="2" Click="buttonResetParticipant_Click">
<Image Source="./Resources/delete2.png"/>
</Button>
</Grid>
<Label Content="IMO" HorizontalAlignment="Right" Grid.Row="2" />
<xctk:IntegerUpDown Name="integerUpDownIMO" Grid.Column="1" Grid.Row="2" Value="{Binding Imo, Mode=OneWay}" Margin="2" Minimum="1000000" Maximum="9999999" ShowButtonSpinner="False"/>
<Label Content="{x:Static p:Resources.textCallsign}" HorizontalAlignment="Right" Grid.Row="3" />
<TextBox x:Name="textBoxCallsign" Grid.Column="1" Grid.Row="3" Margin="2" VerticalContentAlignment="Center" Text="{Binding Callsign, Mode=OneWay}" />
<Label Content="{x:Static p:Resources.textLength}" HorizontalAlignment="Right" Grid.Row="4" />
<xctk:DoubleUpDown Name="doubleUpDownLength" Grid.Row="4" Grid.Column="1" Value="{Binding Length, Mode=OneWay}" Margin="2" Minimum="0" />
<Label Content="{x:Static p:Resources.textWidth}" HorizontalAlignment="Right" Grid.Row="5" />
<xctk:DoubleUpDown Name="doubleUpDownWidth" Grid.Row="5" Grid.Column="1" Value="{Binding Width, Mode=OneWay}" Margin="2" Minimum="0"/>
<StackPanel Grid.Column="1" Grid.Row="7" Orientation="Horizontal" FlowDirection="RightToLeft">
<Button x:Name="buttonCancel" Width="80" Content="{x:Static p:Resources.textCancel}" Margin="2" Click="buttonCancel_Click" />
<Button x:Name="buttonOK" Width="80" Content="OK" Margin="2" Click="buttonOK_Click"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,71 @@
using BreCalClient.misc.Model;
using System.Collections.Generic;
using System.Windows;
namespace BreCalClient
{
/// <summary>
/// Interaction logic for EditShipDialog.xaml
/// </summary>
public partial class EditShipDialog : Window
{
public EditShipDialog()
{
InitializeComponent();
}
public Ship Ship { get; set; } = new();
public List<Participant> Participants { get; } = new List<Participant>();
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
this.Close();
}
private void buttonOK_Click(object sender, RoutedEventArgs e)
{
this.Ship.Name = this.textBoxName.Text.Trim();
if (this.comboBoxParticipants.SelectedItem != null)
{
this.Ship.ParticipantId = ((Participant)this.comboBoxParticipants.SelectedItem).Id;
this.Ship.IsTug = true;
}
else
{
this.Ship.IsTug = false;
}
this.Ship.Imo = this.integerUpDownIMO.Value;
this.Ship.Callsign = this.textBoxCallsign.Text.Trim();
this.Ship.Length = (float?) this.doubleUpDownLength.Value;
this.Ship.Width = (float?) this.doubleUpDownWidth.Value;
this.DialogResult = true;
this.Close();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = this.Ship;
this.comboBoxParticipants.ItemsSource = this.Participants;
if(this.Ship.ParticipantId != null)
{
foreach(Participant p in this.Participants)
{
if(this.Ship.ParticipantId == p.Id)
this.comboBoxParticipants.SelectedItem = p;
}
}
}
private void buttonResetParticipant_Click(object sender, RoutedEventArgs e)
{
this.comboBoxParticipants.SelectedItem = null;
}
}
}

View File

@ -32,15 +32,16 @@
<Label Content="{x:Static p:Resources.textShip}" Grid.Column="0" Grid.Row="0" HorizontalContentAlignment="Right"/>
<ComboBox x:Name="comboBoxShip" Margin="2" Grid.Column="1" Grid.Row="0" SelectionChanged="comboBoxShip_SelectionChanged" SelectedValuePath="Ship.Id" IsEditable="True" IsTextSearchEnabled="True" IsTextSearchCaseSensitive="False" />
<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" ShowButtonSpinner="False"/>
<xctk:IntegerUpDown x:Name="integerUpDownIMO" IsReadOnly="True" Margin="2" Grid.Column="1" Grid.Row="1" ShowButtonSpinner="False" IsEnabled="False"/>
<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" />
<TextBox x:Name="textBoxCallsign" IsReadOnly="True" Grid.Column="1" Grid.Row="2" Margin="2" IsEnabled="False"/>
<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" />
<xctk:DoubleUpDown x:Name="doubleUpDownLength" Margin="2" Grid.Column="1" Grid.Row="3" FormatString="N2" IsReadOnly="True" IsEnabled="False" ShowButtonSpinner="False"/>
<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" />
<xctk:DoubleUpDown x:Name="doubleUpDownWidth" Margin="2" Grid.Column="1" Grid.Row="4" FormatString="N2" IsReadOnly="True" IsEnabled="False" ShowButtonSpinner="False"/>
<Label Content="{x:Static p:Resources.textCancelled}" Grid.Column="0" Grid.Row="5" HorizontalContentAlignment="Right" />
<CheckBox x:Name="checkBoxCancelled" Grid.Column="1" Grid.Row="5" Margin="2" VerticalContentAlignment="Center" />
<Button x:Name="buttonEditShips" Grid.Column="1" Grid.Row="6" Margin="2" Content="{x:Static p:Resources.textEditShips}" Click="buttonEditShips_Click" Visibility="Hidden" />
<Label Content="{x:Static p:Resources.textType}" Grid.Column="2" Grid.Row="0" HorizontalContentAlignment="Right" />
<Label Content="ETA" Grid.Column="2" Grid.Row="2" HorizontalContentAlignment="Right"/>
@ -73,7 +74,7 @@
</ComboBox.ContextMenu>
</ComboBox>
<Label x:Name="labelBSMDGranted" Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="2" Content="{x:Static p:Resources.textBSMDGranted}" Visibility="Hidden" FontWeight="DemiBold" />
<Label x:Name="labelBSMDGranted" Grid.Row="5" Grid.Column="3" Grid.ColumnSpan="1" Content="{x:Static p:Resources.textBSMDGranted}" Visibility="Hidden" FontWeight="DemiBold" />
<StackPanel Grid.Row="6" 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"/>

View File

@ -2,6 +2,7 @@
// Description: Windows dialog to create / edit ship calls
//
using BreCalClient.misc.Api;
using BreCalClient.misc.Model;
using System;
using System.Windows;
@ -31,6 +32,14 @@ namespace BreCalClient
}
}
public bool ShipEditingEnabled
{
get { return this.buttonEditShips.Visibility == Visibility.Visible; }
set { this.buttonEditShips.Visibility = value ? Visibility.Visible : Visibility.Hidden; }
}
public ShipApi ShipApi { get; set; }
#endregion
#region Event handler
@ -39,8 +48,8 @@ namespace BreCalClient
{
this.comboBoxAgency.ItemsSource = BreCalLists.Participants_Agent;
this.comboBoxShip.ItemsSource = BreCalLists.Ships;
this.comboBoxCategories.ItemsSource = Enum.GetValues(typeof(TypeEnum));
this.comboBoxShip.ItemsSource = BreCalLists.AllShips;
this.comboBoxCategories.ItemsSource = Enum.GetValues(typeof(ShipcallType));
this.comboBoxArrivalBerth.ItemsSource = BreCalLists.Berths;
this.comboBoxDepartureBerth.ItemsSource = BreCalLists.Berths;
@ -91,12 +100,12 @@ namespace BreCalClient
private void comboBoxCategories_SelectionChanged(object? sender, SelectionChangedEventArgs? e)
{
TypeEnum? type = this.comboBoxCategories.SelectedItem as TypeEnum?;
ShipcallType? type = this.comboBoxCategories.SelectedItem as ShipcallType?;
if (type != null)
{
switch (type)
{
case TypeEnum.Incoming:
case ShipcallType.Arrival:
this.datePickerETA.IsEnabled = true;
this.datePickerETD.IsEnabled = false;
this.datePickerETD.Value = null;
@ -104,7 +113,7 @@ namespace BreCalClient
this.comboBoxDepartureBerth.IsEnabled = false;
this.comboBoxArrivalBerth.IsEnabled = true;
break;
case TypeEnum.Outgoing:
case ShipcallType.Departure:
this.datePickerETA.IsEnabled = false;
this.datePickerETD.IsEnabled = true;
this.datePickerETA.Value = null;
@ -112,7 +121,7 @@ namespace BreCalClient
this.comboBoxArrivalBerth.IsEnabled = false;
this.comboBoxDepartureBerth.IsEnabled = true;
break;
case TypeEnum.Shifting:
case ShipcallType.Shifting:
this.datePickerETA.IsEnabled = true;
this.datePickerETD.IsEnabled = true;
this.comboBoxArrivalBerth.IsEnabled = true;
@ -150,18 +159,18 @@ namespace BreCalClient
}
else
{
TypeEnum callType = (TypeEnum)comboBoxCategories.SelectedItem;
ShipcallType callType = (ShipcallType)comboBoxCategories.SelectedItem;
switch (callType)
{
case TypeEnum.Outgoing:
case ShipcallType.Departure:
isEnabled &= this.comboBoxDepartureBerth.SelectedItem != null;
isEnabled &= this.datePickerETD.Value.HasValue;
break;
case TypeEnum.Incoming:
case ShipcallType.Arrival:
isEnabled &= this.comboBoxArrivalBerth.SelectedItem != null;
isEnabled &= this.datePickerETA.Value.HasValue;
break;
case TypeEnum.Shifting:
case ShipcallType.Shifting:
isEnabled &= ((this.comboBoxDepartureBerth.SelectedItem != null) && (this.comboBoxArrivalBerth.SelectedItem != null));
isEnabled &= this.datePickerETD.Value.HasValue;
isEnabled &= this.datePickerETA.Value.HasValue;
@ -178,7 +187,7 @@ namespace BreCalClient
{
if (this.ShipcallModel.Shipcall != null)
{
this.ShipcallModel.Shipcall.Type = (int)this.comboBoxCategories.SelectedItem;
this.ShipcallModel.Shipcall.Type = (ShipcallType) this.comboBoxCategories.SelectedItem;
this.ShipcallModel.Shipcall.Eta = this.datePickerETA.Value;
this.ShipcallModel.Shipcall.Etd = this.datePickerETD.Value;
@ -186,7 +195,7 @@ namespace BreCalClient
this.ShipcallModel.Ship = ((ShipModel)this.comboBoxShip.SelectedItem).Ship;
this.ShipcallModel.Shipcall.Canceled = this.checkBoxCancelled.IsChecked;
if (this.ShipcallModel.Shipcall.Type != 3) // incoming, outgoing
if (this.ShipcallModel.Shipcall.Type != ShipcallType.Shifting) // incoming, outgoing
{
this.ShipcallModel.Shipcall.ArrivalBerthId = (this.comboBoxArrivalBerth.SelectedItem != null) ? ((Berth)this.comboBoxArrivalBerth.SelectedItem).Id : null;
this.ShipcallModel.Shipcall.DepartureBerthId = (this.comboBoxDepartureBerth.SelectedItem != null) ? ((Berth)this.comboBoxDepartureBerth.SelectedItem).Id : null;
@ -204,6 +213,7 @@ namespace BreCalClient
ParticipantAssignment pa = new()
{
ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.AGENCY
};
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.AGENCY] = pa;
@ -253,7 +263,7 @@ namespace BreCalClient
if (this.ShipcallModel == null) return;
if (this.ShipcallModel.Shipcall != null)
{
this.comboBoxCategories.SelectedItem = (TypeEnum)this.ShipcallModel.Shipcall.Type;
this.comboBoxCategories.SelectedItem = 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;
@ -261,7 +271,7 @@ namespace BreCalClient
this.comboBoxShip.SelectedValue = this.ShipcallModel.Shipcall.ShipId;
this.checkBoxCancelled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false;
if (this.ShipcallModel.Shipcall.Type != 3) // incoming, outgoing
if (this.ShipcallModel.Shipcall.Type != ShipcallType.Shifting) // incoming, outgoing
{
this.comboBoxArrivalBerth.SelectedValue = this.ShipcallModel.Shipcall.ArrivalBerthId;
this.comboBoxDepartureBerth.SelectedValue = this.ShipcallModel.Shipcall.DepartureBerthId;
@ -342,6 +352,20 @@ namespace BreCalClient
this.CheckForCompletion();
}
private void buttonEditShips_Click(object sender, RoutedEventArgs e)
{
ShipListDialog shipListDialog = new()
{
ShipApi = this.ShipApi
};
shipListDialog.ShowDialog();
// reload combobox
this.comboBoxShip.ItemsSource = null;
this.comboBoxShip.ItemsSource = BreCalLists.AllShips;
}
#endregion
}

View File

@ -31,7 +31,7 @@ namespace BreCalClient
public Times Times { get; set; } = new();
public Extensions.TypeEnum CallType { get; set; }
public ShipcallType CallType { get; set; }
#endregion

View File

@ -30,7 +30,7 @@ namespace BreCalClient
public Times Times { get; set; } = new();
public Extensions.TypeEnum CallType { get; set; }
public ShipcallType CallType { get; set; }
#endregion

View File

@ -30,7 +30,7 @@ namespace BreCalClient
public Times Times { get; set; } = new();
public Extensions.TypeEnum CallType { get; set; }
public ShipcallType CallType { get; set; }
#endregion

View File

@ -27,7 +27,7 @@ namespace BreCalClient
public Times Times { get; set; } = new();
public Extensions.TypeEnum CallType { get; set; }
public ShipcallType CallType { get; set; }
#endregion
@ -75,12 +75,12 @@ namespace BreCalClient
switch (CallType)
{
case Extensions.TypeEnum.Incoming:
case ShipcallType.Arrival:
this.labelETA.FontWeight = FontWeights.Bold;
this.datePickerETABerth.ContextMenu.IsEnabled = false;
break;
case Extensions.TypeEnum.Outgoing:
case Extensions.TypeEnum.Shifting:
case ShipcallType.Departure:
case ShipcallType.Shifting:
this.labelETD.FontWeight = FontWeights.Bold;
this.datePickerETDBerth.ContextMenu.IsEnabled = false;
break;
@ -89,7 +89,7 @@ namespace BreCalClient
private void EnableControls()
{
Extensions.ParticipantType pType = (Extensions.ParticipantType) (this.Times.ParticipantType ?? 0);
Extensions.ParticipantType pType = (Extensions.ParticipantType) this.Times.ParticipantType;
if (this.Times.ParticipantId != App.Participant.Id) return; // if this is not "my" entry, there is no editing!
switch (pType)
@ -97,24 +97,24 @@ namespace BreCalClient
case Extensions.ParticipantType.MOORING:
case Extensions.ParticipantType.PORT_ADMINISTRATION:
case Extensions.ParticipantType.TUG:
this.datePickerETABerth.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
this.datePickerETABerth.IsEnabled = (CallType == ShipcallType.Arrival);
//this.checkBoxEtaBerthFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
this.datePickerETDBerth.IsEnabled = (CallType == Extensions.TypeEnum.Outgoing || CallType == Extensions.TypeEnum.Shifting);
this.datePickerETDBerth.IsEnabled = (CallType == ShipcallType.Departure || CallType == ShipcallType.Shifting);
//this.checkBoxEtDBerthFixed.IsEnabled = (CallType == Extensions.TypeEnum.Outgoing || CallType == Extensions.TypeEnum.Shifting);
this.datePickerLockTime.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
this.datePickerLockTime.IsEnabled = (CallType == ShipcallType.Arrival || CallType == ShipcallType.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);
this.datePickerETABerth.IsEnabled = (CallType == ShipcallType.Arrival);
//this.checkBoxEtaBerthFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
this.datePickerETDBerth.IsEnabled = (CallType == Extensions.TypeEnum.Outgoing || CallType == Extensions.TypeEnum.Shifting);
this.datePickerETDBerth.IsEnabled = (CallType == ShipcallType.Departure || CallType == ShipcallType.Shifting);
//this.checkBoxEtDBerthFixed.IsEnabled = (CallType == Extensions.TypeEnum.Outgoing || CallType == Extensions.TypeEnum.Shifting);
this.datePickerLockTime.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
this.datePickerLockTime.IsEnabled = (CallType == ShipcallType.Arrival || CallType == ShipcallType.Shifting);
//this.checkBoxLockTimeFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming || CallType == Extensions.TypeEnum.Shifting);
this.datePickerZoneEntry.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
this.datePickerZoneEntry.IsEnabled = (CallType == ShipcallType.Arrival);
//this.checkBoxZoneEntryFixed.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
this.textBoxRemarks.IsEnabled = true;
break;

View File

@ -21,7 +21,7 @@ namespace BreCalClient
public Times Times { get; set; } = new();
public Extensions.TypeEnum CallType { get; set; }
public ShipcallType CallType { get; set; }
#endregion
@ -72,12 +72,12 @@ namespace BreCalClient
private void CopyToModel()
{
switch(this.comboBoxPierside.SelectedIndex)
this.Times.PierSide = this.comboBoxPierside.SelectedIndex switch
{
case 0: this.Times.PierSide = true; break;
case 1: this.Times.PierSide= false; break;
default: this.Times.PierSide = null; break;
}
0 => true,
1 => false,
_ => null,
};
this.Times.OperationsStart = this.datePickerOperationStart.Value;
this.Times.OperationsEnd = this.datePickerOperationEnd.Value;
this.Times.BerthId = (this.comboBoxBerth.SelectedItem != null) ? ((Berth)this.comboBoxBerth.SelectedItem).Id : null;
@ -97,12 +97,12 @@ namespace BreCalClient
switch (CallType)
{
case Extensions.TypeEnum.Incoming:
case ShipcallType.Arrival:
this.labelStart.FontWeight = FontWeights.Bold;
this.datePickerOperationStart.ContextMenu.IsEnabled = false;
break;
case Extensions.TypeEnum.Outgoing:
case Extensions.TypeEnum.Shifting:
case ShipcallType.Departure:
case ShipcallType.Shifting:
this.labelEnd.FontWeight = FontWeights.Bold;
this.datePickerOperationEnd.ContextMenu.IsEnabled = false;
break;
@ -114,11 +114,11 @@ namespace BreCalClient
{
if (this.Times.ParticipantId != App.Participant.Id) return;
this.datePickerOperationStart.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
this.datePickerOperationEnd.IsEnabled = (CallType == Extensions.TypeEnum.Outgoing) || (CallType == Extensions.TypeEnum.Shifting);
this.comboBoxBerth.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
this.comboBoxPierside.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
this.textBoxBerthRemarks.IsEnabled = (CallType == Extensions.TypeEnum.Incoming);
this.datePickerOperationStart.IsEnabled = CallType == ShipcallType.Arrival;
this.datePickerOperationEnd.IsEnabled = (CallType == ShipcallType.Departure) || (CallType == ShipcallType.Shifting);
this.comboBoxBerth.IsEnabled = CallType == ShipcallType.Arrival;
this.comboBoxPierside.IsEnabled = CallType == ShipcallType.Arrival;
this.textBoxBerthRemarks.IsEnabled = CallType == ShipcallType.Arrival;
this.textBoxRemarks.IsEnabled = true;
this.buttonOK.IsEnabled = true;
}

View File

@ -47,16 +47,6 @@ namespace BreCalClient
ALLOW_BSMD = 1,
}
/// <summary>
/// Should actually be defined in yaml
/// </summary>
public enum TypeEnum
{
Incoming = 1,
Outgoing = 2,
Shifting = 3
}
public enum SortOrder
{
SHIP_NAME,
@ -81,15 +71,15 @@ namespace BreCalClient
public static string Truncate(this string value, int maxLength)
{
if (string.IsNullOrEmpty(value)) return value;
return value.Length <= maxLength ? value : value.Substring(0, maxLength);
return value.Length <= maxLength ? value : value[..maxLength];
}
public static string TruncateDots(this string value, int maxLength)
{
if(string.IsNullOrEmpty(value)) return value;
if (value.Length <= maxLength) return value;
if (value.Length > (maxLength + 1)) return $"{value.Substring(0, maxLength)}..";
return value.Substring(0, maxLength);
if (value.Length > (maxLength + 1)) return $"{value[..maxLength]}..";
return value[..maxLength];
}
#endregion

View File

@ -13,7 +13,7 @@ namespace BreCalClient
string Title { get; set; }
Extensions.TypeEnum CallType { get; set; }
ShipcallType CallType { get; set; }
bool? ShowDialog();

View File

@ -18,11 +18,11 @@ using BreCalClient.misc.Model;
using static BreCalClient.Extensions;
using System.Collections.Concurrent;
using Newtonsoft.Json;
using System.Security.Principal;
using Polly;
using System.Net.Http;
using System.Net;
using Polly.Retry;
namespace BreCalClient
{
@ -45,7 +45,12 @@ namespace BreCalClient
private readonly ConcurrentDictionary<int, ShipcallControl> _allShipCallsControlDict = new();
private readonly List<ShipcallControlModel> _visibleControlModels = new();
private readonly DefaultApi _api;
private readonly ShipcallApi _shipcallApi;
private readonly UserApi _userApi;
private readonly TimesApi _timesApi;
private readonly StaticApi _staticApi;
private readonly ShipApi _shipApi;
private CancellationTokenSource _tokenSource = new();
private LoginResult? _loginResult;
private bool _refreshImmediately = false;
@ -75,8 +80,16 @@ namespace BreCalClient
public MainWindow()
{
InitializeComponent();
_api = new DefaultApi(Properties.Settings.Default.API_URL);
_api.Configuration.ApiKeyPrefix["Authorization"] = "Bearer";
_userApi = new UserApi(Properties.Settings.Default.API_URL);
_userApi.Configuration.ApiKeyPrefix["Authorization"] = "Bearer";
_shipcallApi = new ShipcallApi(Properties.Settings.Default.API_URL);
_shipcallApi.Configuration.ApiKeyPrefix["Authorization"] = "Bearer";
_timesApi = new TimesApi(Properties.Settings.Default.API_URL);
_timesApi.Configuration.ApiKeyPrefix["Authorization"] = "Bearer";
_staticApi = new StaticApi(Properties.Settings.Default.API_URL);
_staticApi.Configuration.ApiKeyPrefix["Authorization"] = "Bearer";
_shipApi = new ShipApi(Properties.Settings.Default.API_URL);
_shipApi.Configuration.ApiKeyPrefix["Authorization"] = "Bearer";
const int maxDelayInMilliseconds = 32 * 1000;
var jitterer = new Random();
@ -140,13 +153,17 @@ namespace BreCalClient
try
{
_loginResult = await _api.LoginPostAsync(_credentials);
_loginResult = await _userApi.LoginAsync(_credentials);
if (_loginResult != null)
{
if (_loginResult.Id > 0)
{
this.busyIndicator.IsBusy = false;
this._api.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this._userApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this._shipcallApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this._timesApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this._staticApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this._shipApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this.LoadStaticLists();
this.labelUsername.Text = $"{_loginResult.FirstName} {_loginResult.LastName}";
}
@ -179,12 +196,16 @@ namespace BreCalClient
bool result = false;
try
{
_loginResult = _api.LoginPost(_credentials);
_loginResult = _userApi.Login(_credentials);
if (_loginResult != null)
{
if (_loginResult.Id > 0)
{
this._api.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this._userApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this._timesApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this._shipcallApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this._staticApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this._shipApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
result = true;
}
}
@ -212,7 +233,11 @@ namespace BreCalClient
private void NewWithModel(ShipcallControlModel? model)
{
EditShipcallControl esc = new();
EditShipcallControl esc = new()
{
ShipEditingEnabled = App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD),
ShipApi = _shipApi
};
if (model != null)
esc.ShipcallModel = model;
@ -228,7 +253,7 @@ namespace BreCalClient
esc.ShipcallModel.Shipcall?.Participants.Add(pa);
try
{
this._api.ShipcallsPost(esc.ShipcallModel.Shipcall); // save new ship call
this._shipcallApi.ShipcallCreate(esc.ShipcallModel.Shipcall); // save new ship call
this.AddShipcall(esc.ShipcallModel);
}
catch (Exception ex)
@ -240,11 +265,11 @@ namespace BreCalClient
_tokenSource.Cancel(); // force timer loop end
// if this was an arrival, create the matching departure call and open it
if (esc.ShipcallModel.Shipcall?.Type == (int)Extensions.TypeEnum.Incoming)
if (esc.ShipcallModel.Shipcall?.Type == ShipcallType.Arrival)
{
ShipcallControlModel scmOut = new();
scmOut.Shipcall = new();
scmOut.Shipcall.Type = (int)Extensions.TypeEnum.Outgoing;
scmOut.Shipcall.Type = ShipcallType.Departure;
scmOut.Shipcall.ShipId = esc.ShipcallModel.Shipcall.ShipId;
scmOut.Ship = esc.ShipcallModel.Ship;
DateTime eta = esc.ShipcallModel.Shipcall?.Eta ?? DateTime.Now;
@ -283,7 +308,7 @@ namespace BreCalClient
};
try
{
await _api.UserPutAsync(ud);
await _userApi.UserUpdateAsync(ud);
MessageBox.Show(BreCalClient.Resources.Resources.textPasswordChanged, BreCalClient.Resources.Resources.textConfirmation, MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
@ -330,9 +355,9 @@ namespace BreCalClient
private async void LoadStaticLists()
{
BreCalLists.InitializeBerths(await _api.BerthsGetAsync());
BreCalLists.InitializeShips(await _api.ShipsGetAsync());
BreCalLists.InitializeParticipants(await _api.ParticipantsGetAsync());
BreCalLists.InitializeBerths(await _staticApi.BerthsGetAsync());
BreCalLists.InitializeShips(await _shipApi.ShipsGetAsync());
BreCalLists.InitializeParticipants(await _staticApi.ParticipantsGetAsync());
this.searchFilterControl.SetBerths(BreCalLists.Berths);
@ -372,9 +397,9 @@ namespace BreCalClient
try
{
if(this.searchPastDays != 0)
shipcalls = await _api.ShipcallsGetAsync(this.searchPastDays);
shipcalls = await _shipcallApi.ShipcallsGetAsync(this.searchPastDays);
else
shipcalls = await _api.ShipcallsGetAsync();
shipcalls = await _shipcallApi.ShipcallsGetAsync();
this.Dispatcher.Invoke(new Action(() =>
{
@ -401,7 +426,7 @@ namespace BreCalClient
foreach (Shipcall shipcall in shipcalls)
{
// load times for each shipcall
List<Times> currentTimes = await _api.TimesGetAsync(shipcall.Id);
List<Times> currentTimes = await _timesApi.TimesGetAsync(shipcall.Id);
if(!_allShipcallsDict.ContainsKey(shipcall.Id))
{
@ -456,7 +481,8 @@ namespace BreCalClient
Shipcall shipcall = scm.Shipcall;
if (BreCalLists.ShipLookupDict.ContainsKey(shipcall.ShipId))
scm.Ship = BreCalLists.ShipLookupDict[shipcall.ShipId].Ship;
if (shipcall.Type == 1)
if (shipcall.Type == ShipcallType.Arrival)
{
if (BreCalLists.BerthLookupDict.ContainsKey(shipcall.ArrivalBerthId ?? 0))
scm.Berth = BreCalLists.BerthLookupDict[shipcall.ArrivalBerthId ?? 0].Name;
@ -489,7 +515,8 @@ namespace BreCalClient
Shipcall shipcall = scm.Shipcall;
if (BreCalLists.ShipLookupDict.ContainsKey(shipcall.ShipId))
scm.Ship = BreCalLists.ShipLookupDict[shipcall.ShipId].Ship;
if (shipcall.Type == 1)
if (shipcall.Type == ShipcallType.Arrival)
{
if (BreCalLists.BerthLookupDict.ContainsKey(shipcall.ArrivalBerthId ?? 0))
scm.Berth = BreCalLists.BerthLookupDict[shipcall.ArrivalBerthId ?? 0].Name;
@ -543,8 +570,8 @@ namespace BreCalClient
if(sfm.Berths.Count > 0 )
{
this._visibleControlModels.RemoveAll(x => (!sfm.Berths.Contains((x.Shipcall?.ArrivalBerthId) ?? -1) && (x.Shipcall?.Type == (int) Extensions.TypeEnum.Incoming)) ||
(!sfm.Berths.Contains((x.Shipcall?.DepartureBerthId) ?? -1) && (x.Shipcall?.Type != (int) Extensions.TypeEnum.Incoming)));
this._visibleControlModels.RemoveAll(x => (!sfm.Berths.Contains((x.Shipcall?.ArrivalBerthId) ?? -1) && (x.Shipcall?.Type == ShipcallType.Arrival)) ||
(!sfm.Berths.Contains((x.Shipcall?.DepartureBerthId) ?? -1) && (x.Shipcall?.Type != ShipcallType.Arrival)));
}
if(sfm.Agencies.Count > 0 )
@ -562,7 +589,7 @@ namespace BreCalClient
if(sfm.Categories.Count > 0 )
{
_ = this._visibleControlModels.RemoveAll(x => !sfm.Categories.Contains((x.Shipcall?.Type) ?? -1));
_ = this._visibleControlModels.RemoveAll(x => { if (x.Shipcall == null) return false; else return !sfm.Categories.Contains(x.Shipcall.Type); });
}
if(!string.IsNullOrEmpty(sfm.SearchString))
@ -608,8 +635,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.Eta ?? DateTime.Now : x.Etd ?? DateTime.Now;
DateTime yDate = (y.Shipcall.Type == (int) Extensions.TypeEnum.Incoming) ? y.Eta ?? DateTime.Now : y.Etd ?? DateTime.Now;
DateTime xDate = (x.Shipcall.Type == ShipcallType.Arrival) ? x.Eta ?? DateTime.Now : x.Etd ?? DateTime.Now;
DateTime yDate = (y.Shipcall.Type == ShipcallType.Arrival) ? y.Eta ?? DateTime.Now : y.Etd ?? DateTime.Now;
return DateTime.Compare(xDate, yDate);
});
break;
@ -665,7 +692,9 @@ namespace BreCalClient
{
EditShipcallControl esc = new()
{
ShipcallModel = obj.ShipcallControlModel
ShipcallModel = obj.ShipcallControlModel,
ShipApi = _shipApi,
ShipEditingEnabled = App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD)
};
if(esc.ShowDialog() ?? false)
@ -673,10 +702,10 @@ namespace BreCalClient
try
{
obj.ShipcallControlModel.Shipcall?.Participants.Clear();
obj.ShipcallControlModel.UpdateTimesAssignments(this._api);
obj.ShipcallControlModel.UpdateTimesAssignments(this._timesApi);
foreach(ParticipantAssignment pa in obj.ShipcallControlModel.AssignedParticipants.Values)
obj.ShipcallControlModel.Shipcall?.Participants.Add(pa);
await _api.ShipcallsPutAsync(obj.ShipcallControlModel.Shipcall);
await _shipcallApi.ShipcallUpdateAsync(obj.ShipcallControlModel.Shipcall);
obj.RefreshData();
_refreshImmediately = true;
_tokenSource.Cancel();
@ -689,7 +718,7 @@ namespace BreCalClient
}
}
private async void Sc_EditTimesRequested(ShipcallControl obj, Times? times, Extensions.ParticipantType participantType)
private async void Sc_EditTimesRequested(ShipcallControl obj, Times? times, ParticipantType participantType)
{
if( obj.ShipcallControlModel == null) { return; }
@ -700,7 +729,7 @@ namespace BreCalClient
etc.Title = obj.ShipcallControlModel.Title;
if(obj.ShipcallControlModel.Shipcall != null)
etc.CallType = (TypeEnum) obj.ShipcallControlModel.Shipcall.Type;
etc.CallType = (ShipcallType) obj.ShipcallControlModel.Shipcall.Type;
bool wasEdit = false;
if (times != null)
@ -725,7 +754,7 @@ namespace BreCalClient
{
if (wasEdit)
{
await _api.TimesPutAsync(etc.Times);
await _timesApi.TimesUpdateAsync(etc.Times);
}
else
{
@ -734,7 +763,7 @@ namespace BreCalClient
{
etc.Times.ShipcallId = obj.ShipcallControlModel.Shipcall.Id;
}
Id apiResultId = await _api.TimesPostAsync(etc.Times);
Id apiResultId = await _timesApi.TimesCreateAsync(etc.Times);
etc.Times.Id = apiResultId.VarId;
obj.ShipcallControlModel?.Times.Add(etc.Times);
}
@ -753,13 +782,13 @@ namespace BreCalClient
IEditShipcallTimesControl? editControl = null;
switch(sc.ShipcallControlModel?.Shipcall?.Type)
{
case (int)TypeEnum.Incoming:
case ShipcallType.Arrival:
editControl = new EditTimesAgencyIncomingControl();
break;
case (int)TypeEnum.Outgoing:
case ShipcallType.Departure:
editControl = new EditTimesAgencyOutgoingControl();
break;
case (int)TypeEnum.Shifting:
case ShipcallType.Shifting:
editControl = new EditTimesAgencyShiftingControl();
break;
}
@ -783,7 +812,7 @@ namespace BreCalClient
{
try
{
sc.ShipcallControlModel?.UpdateTimesAssignments(_api); // if the agent changed the assignment of the participant to another
sc.ShipcallControlModel?.UpdateTimesAssignments(_timesApi); // 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))
@ -797,7 +826,7 @@ namespace BreCalClient
if (wasEdit)
{
await _api.TimesPutAsync(editControl.Times);
await _timesApi.TimesUpdateAsync(editControl.Times);
}
else
{
@ -805,7 +834,7 @@ namespace BreCalClient
{
editControl.Times.ShipcallId = sc.ShipcallControlModel.Shipcall.Id;
}
Id resultAPI_Id = await _api.TimesPostAsync(editControl.Times);
Id resultAPI_Id = await _timesApi.TimesCreateAsync(editControl.Times);
editControl.Times.Id = resultAPI_Id.VarId;
sc.ShipcallControlModel?.Times.Add(editControl.Times);
@ -813,7 +842,7 @@ namespace BreCalClient
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);
await _shipcallApi.ShipcallUpdateAsync(editControl.ShipcallModel.Shipcall);
_refreshImmediately = true;
_tokenSource.Cancel();
}

View File

@ -60,6 +60,16 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
public static byte[] add {
get {
object obj = ResourceManager.GetObject("add", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
@ -180,6 +190,16 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
public static byte[] edit {
get {
object obj = ResourceManager.GetObject("edit", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
@ -220,6 +240,15 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Add.
/// </summary>
public static string textAdd {
get {
return ResourceManager.GetString("textAdd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Agencies.
/// </summary>
@ -409,6 +438,24 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Delete.
/// </summary>
public static string textDelete {
get {
return ResourceManager.GetString("textDelete", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Deleted.
/// </summary>
public static string textDeleted {
get {
return ResourceManager.GetString("textDeleted", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Departure terminal.
/// </summary>
@ -427,6 +474,24 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Edit.
/// </summary>
public static string textEdit {
get {
return ResourceManager.GetString("textEdit", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Edit ship.
/// </summary>
public static string textEditShip {
get {
return ResourceManager.GetString("textEditShip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Edit ship call.
/// </summary>
@ -436,6 +501,15 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Edit ships.
/// </summary>
public static string textEditShips {
get {
return ResourceManager.GetString("textEditShips", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Edit times.
/// </summary>
@ -904,6 +978,15 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Tug company.
/// </summary>
public static string textTugCompany {
get {
return ResourceManager.GetString("textTugCompany", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Tug required.
/// </summary>

View File

@ -427,4 +427,25 @@
<data name="textStarboard" xml:space="preserve">
<value>Steuerbord</value>
</data>
<data name="textAdd" xml:space="preserve">
<value>Hinzufügen</value>
</data>
<data name="textDelete" xml:space="preserve">
<value>Löschen</value>
</data>
<data name="textEdit" xml:space="preserve">
<value>Bearbeiten</value>
</data>
<data name="textEditShips" xml:space="preserve">
<value>Schiffe anlegen / bearbeiten</value>
</data>
<data name="textDeleted" xml:space="preserve">
<value>Gelöscht</value>
</data>
<data name="textEditShip" xml:space="preserve">
<value>Schiff bearbeiten</value>
</data>
<data name="textTugCompany" xml:space="preserve">
<value>Schlepper-Reederei</value>
</data>
</root>

View File

@ -118,6 +118,9 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="add" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>add.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="arrow_down_green" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>arrow_down_green.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
@ -154,6 +157,9 @@
<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="edit" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>edit.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>
@ -166,6 +172,9 @@
<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="textAdd" xml:space="preserve">
<value>Add</value>
</data>
<data name="textAgencies" xml:space="preserve">
<value>Agencies</value>
</data>
@ -229,15 +238,30 @@
<data name="textConfirmation" xml:space="preserve">
<value>Confirmation</value>
</data>
<data name="textDelete" xml:space="preserve">
<value>Delete</value>
</data>
<data name="textDeleted" xml:space="preserve">
<value>Deleted</value>
</data>
<data name="textDepartureTerminal" xml:space="preserve">
<value>Departure terminal</value>
</data>
<data name="textDraft" xml:space="preserve">
<value>Draft (m)</value>
</data>
<data name="textEdit" xml:space="preserve">
<value>Edit</value>
</data>
<data name="textEditShip" xml:space="preserve">
<value>Edit ship</value>
</data>
<data name="textEditShipcall" xml:space="preserve">
<value>Edit ship call</value>
</data>
<data name="textEditShips" xml:space="preserve">
<value>Edit ships</value>
</data>
<data name="textEditTimes" xml:space="preserve">
<value>Edit times</value>
</data>
@ -394,6 +418,9 @@
<data name="textTug" xml:space="preserve">
<value>Tug</value>
</data>
<data name="textTugCompany" xml:space="preserve">
<value>Tug company</value>
</data>
<data name="textTugRequired" xml:space="preserve">
<value>Tug required</value>
</data>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -100,8 +100,8 @@ namespace BreCalClient
}
if(sfm.Categories != null)
{
foreach(int category in sfm.Categories)
this.comboBoxCategories.SelectedItems.Add((Extensions.TypeEnum)category);
foreach(ShipcallType category in sfm.Categories)
this.comboBoxCategories.SelectedItems.Add(category);
}
if (sfm.SearchString != null) this.textBoxSearch.Text = sfm.SearchString;
this.upDownShiplengthFrom.Value = sfm.ShipLengthFrom;
@ -124,7 +124,7 @@ namespace BreCalClient
private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
this.comboBoxCategories.ItemsSource = Enum.GetValues(typeof(Extensions.TypeEnum));
this.comboBoxCategories.ItemsSource = Enum.GetValues(typeof(ShipcallType));
}
private void datePickerETAFrom_SelectedDateChanged(object sender, SelectionChangedEventArgs e)
@ -142,7 +142,7 @@ namespace BreCalClient
private void comboBoxCategories_ItemSelectionChanged(object sender, Xceed.Wpf.Toolkit.Primitives.ItemSelectionChangedEventArgs e)
{
_model.Categories.Clear();
foreach(int category in comboBoxCategories.SelectedItems)
foreach(ShipcallType category in comboBoxCategories.SelectedItems)
_model.Categories.Add(category);
SearchFilterChanged?.Invoke();

View File

@ -19,7 +19,7 @@ namespace BreCalClient
public DateTime? EtaTo { get; set; }
public List<int> Categories { get; set; } = new();
public List<ShipcallType> Categories { get; set; } = new();
public List<int> Agencies { get; set; } = new();

View File

@ -0,0 +1,41 @@
<Window x:Class="BreCalClient.ShipListDialog"
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:p = "clr-namespace:BreCalClient.Resources"
xmlns:local="clr-namespace:BreCalClient"
mc:Ignorable="d" Left="{local:SettingBinding W1Left}" Top="{local:SettingBinding W1Top}"
Height="490" Width="800" ResizeMode="CanResize" Icon="Resources/containership.ico" Loaded="Window_Loaded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<local:ENIDataGrid x:Name="dataGridShips" Grid.Row="0" SelectionMode="Single" IsReadOnly="True" AlternatingRowBackground="LightBlue" AutoGenerateColumns="False"
CanUserAddRows="False" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<local:ENIDataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Ship.Deleted}" Value="True">
<Setter Property="Foreground" Value="DarkGray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</local:ENIDataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Path=Ship.Id}" IsReadOnly="True"/>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Ship.Name}" IsReadOnly="True"/>
<DataGridCheckBoxColumn Header="{x:Static p:Resources.textTug}" Binding="{Binding Path=Ship.IsTug, Mode=OneWay}" IsReadOnly="True"/>
<DataGridTextColumn Header="{x:Static p:Resources.textTugCompany}" Binding="{Binding Path=TugCompany, Mode=OneWay}" IsReadOnly="True"/>
<DataGridTextColumn Header="IMO" Binding="{Binding Path=Ship.Imo}" IsReadOnly="True"/>
<DataGridTextColumn Header="{x:Static p:Resources.textCallsign}" Binding="{Binding Path=Ship.Callsign}" IsReadOnly="True"/>
<DataGridTextColumn Header="{x:Static p:Resources.textLength}" Binding="{Binding Path=Ship.Length}" IsReadOnly="True"/>
<DataGridTextColumn Header="{x:Static p:Resources.textWidth}" Binding="{Binding Path=Ship.Width}" IsReadOnly="True"/>
<DataGridCheckBoxColumn Header="{x:Static p:Resources.textDeleted}" Binding="{Binding Path=Ship.Deleted, Mode=OneWay}" IsReadOnly="True" />
</DataGrid.Columns>
</local:ENIDataGrid>
<Button Grid.Row="1" x:Name="buttonClose" Content="{x:Static p:Resources.textClose}" HorizontalAlignment="Right" Margin="2" Click="buttonClose_Click"/>
</Grid>
</Window>

View File

@ -0,0 +1,116 @@
// Copyright (c) 2023 schick Informatik
// Description: Administration screen for ships
//
using BreCalClient.misc.Api;
using BreCalClient.misc.Model;
using System;
using System.Windows;
namespace BreCalClient
{
/// <summary>
/// Interaction logic for ShipListDialog.xaml
/// </summary>
public partial class ShipListDialog : Window
{
public ShipListDialog()
{
InitializeComponent();
}
#region Properties
public ShipApi? ShipApi { get; set; }
#endregion
#region Event handler
private void buttonClose_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.dataGridShips.Initialize();
this.dataGridShips.ItemsSource = BreCalLists.AllShips;
this.dataGridShips.CreateRequested += DataGridShips_CreateRequested; ;
this.dataGridShips.EditRequested += DataGridShips_EditRequested;
this.dataGridShips.DeleteRequested += DataGridShips_DeleteRequested;
}
private async void DataGridShips_DeleteRequested(object obj)
{
if (obj is ShipModel shipmodel)
{
if(this.ShipApi != null)
await this.ShipApi.ShipDeleteAsync(shipmodel.Ship.Id);
BreCalLists.Ships.Remove(shipmodel); // remove from "selectable" ships
shipmodel.Ship.Deleted = true; // set deleted marker on working instance
}
}
private async void DataGridShips_EditRequested(object obj)
{
if (obj is ShipModel shipmodel)
{
EditShipDialog esd = new()
{
Ship = shipmodel.Ship
};
esd.Participants.AddRange(BreCalLists.Participants_Tug);
if (esd.ShowDialog() ?? false)
{
try
{
if (this.ShipApi != null)
{
Id tmpId = await this.ShipApi.ShipUpdateAsync(shipmodel.Ship);
}
this.dataGridShips.ItemsSource = null;
this.dataGridShips.ItemsSource = BreCalLists.AllShips;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}
private void DataGridShips_CreateRequested()
{
ShipModel shipModel = new ShipModel(new Ship());
EditShipDialog esd = new()
{
Ship = shipModel.Ship
};
esd.Participants.AddRange(BreCalLists.Participants_Tug);
if(esd.ShowDialog() ?? false)
{
try
{
this.ShipApi?.ShipsCreateAsync(shipModel.Ship);
this.dataGridShips.ItemsSource = null;
BreCalLists.AllShips.Add(shipModel);
BreCalLists.Ships.Add(shipModel);
this.dataGridShips.ItemsSource = BreCalLists.AllShips;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
#endregion
}
}

View File

@ -16,6 +16,16 @@ namespace BreCalClient
public Ship Ship { get; private set; }
public string TugCompany
{
get { if(this.Ship.ParticipantId.HasValue)
{
return BreCalLists.ParticipantLookupDict[this.Ship.ParticipantId.Value].Name;
}
return "";
}
}
public override string ToString()
{
return String.Format("{0} ({1})", this.Ship.Name, this.Ship.Imo);

View File

@ -209,13 +209,13 @@ namespace BreCalClient
// this.labelShipName.Content = this.ShipcallControlModel?.Ship?.Name;
switch (this.ShipcallControlModel?.Shipcall?.Type)
{
case 1: // incoming
case ShipcallType.Arrival: // incoming
this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalDevelClient;component/Resources/arrow_down_red.png"));
break;
case 2: // outgoing
case ShipcallType.Departure: // outgoing
this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalDevelClient;component/Resources/arrow_up_blue.png"));
break;
case 3: // shifting
case ShipcallType.Shifting: // shifting
this.imageShipcallType.Source = new BitmapImage(new Uri("pack://application:,,,/BreCalDevelClient;component/Resources/arrow_right_green.png"));
break;
default:
@ -274,11 +274,11 @@ namespace BreCalClient
this.textBlockBerth.Text = this.ShipcallControlModel?.Berth;
this.textBlockCallsign.Text = this.ShipcallControlModel?.Ship?.Callsign;
if (this.ShipcallControlModel?.Shipcall?.Type == 1)
if (this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival)
{
this.textBlockETA.Text = this.ShipcallControlModel?.Shipcall?.Eta?.ToString("dd.MM. HH:mm");
}
if ((this.ShipcallControlModel?.Shipcall?.Type == 2) || (this.ShipcallControlModel?.Shipcall?.Type == 3))
if ((this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Departure) || (this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Shifting))
{
this.labelETA.Text = "ETD";
this.textBlockETA.Text = this.ShipcallControlModel?.Shipcall?.Etd?.ToString("dd.MM. HH:mm");
@ -290,7 +290,7 @@ namespace BreCalClient
// 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)
if (this.ShipcallControlModel?.Shipcall?.Type != ShipcallType.Arrival)
{
this.labelETAETDAgent.Content = "ETD";
this.labelETAETDMooring.Content = "ETD";
@ -310,7 +310,7 @@ namespace BreCalClient
this.labelAgencyETAETDValue.Content = agencyTimes.EtaBerth.HasValue ? agencyTimes.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockAgencyRemarks.Text = agencyTimes.Remarks;
this.textBlockAgencyBerthRemarks.Text = agencyTimes.BerthInfo;
if (this.ShipcallControlModel?.Shipcall?.Type != 1)
if (this.ShipcallControlModel?.Shipcall?.Type != ShipcallType.Arrival)
{
this.labelAgencyETAETDValue.Content = agencyTimes.EtdBerth.HasValue ? agencyTimes.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
}
@ -330,7 +330,7 @@ namespace BreCalClient
this.labelMooringETAETDValue.Content = mooringTimes.EtaBerth.HasValue ? mooringTimes.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockMooringRemarks.Text = mooringTimes.Remarks;
if (this.ShipcallControlModel?.Shipcall?.Type != 1)
if (this.ShipcallControlModel?.Shipcall?.Type != ShipcallType.Arrival)
{
this.labelMooringETAETDValue.Content = mooringTimes.EtdBerth.HasValue ? mooringTimes.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
}
@ -346,7 +346,7 @@ namespace BreCalClient
{
this.labelPortAuthorityETAETDValue.Content = portAuthorityTimes.EtaBerth.HasValue ? portAuthorityTimes.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockPortAuthorityRemarks.Text = portAuthorityTimes.Remarks;
if (this.ShipcallControlModel?.Shipcall?.Type != 1)
if (this.ShipcallControlModel?.Shipcall?.Type != ShipcallType.Arrival)
{
this.labelPortAuthorityETAETDValue.Content = portAuthorityTimes.EtdBerth.HasValue ? portAuthorityTimes.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
}
@ -362,7 +362,7 @@ namespace BreCalClient
{
this.labelPilotETAETDValue.Content = pilotTimes.EtaBerth.HasValue ? pilotTimes.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockPilotRemarks.Text = pilotTimes.Remarks;
if (this.ShipcallControlModel?.Shipcall?.Type != 1)
if (this.ShipcallControlModel?.Shipcall?.Type != ShipcallType.Arrival)
{
this.labelPilotETAETDValue.Content = pilotTimes.EtdBerth.HasValue ? pilotTimes.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
}
@ -378,7 +378,7 @@ namespace BreCalClient
{
this.labelTugETAETDValue.Content = tugTimes.EtaBerth.HasValue ? tugTimes.EtaBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockTugRemarks.Text = tugTimes.Remarks;
if (this.ShipcallControlModel?.Shipcall?.Type != 1)
if (this.ShipcallControlModel?.Shipcall?.Type != ShipcallType.Arrival)
{
this.labelTugETAETDValue.Content = tugTimes.EtdBerth.HasValue ? tugTimes.EtdBerth.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
}
@ -395,7 +395,7 @@ namespace BreCalClient
this.labelTerminalBerth.Content = this.ShipcallControlModel?.GetBerthText(terminalTimes);
this.labelOperationsStart.Content = terminalTimes.OperationsStart.HasValue ? terminalTimes.OperationsStart.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
this.textBlockTerminalRemarks.Text = terminalTimes.Remarks;
if (this.ShipcallControlModel?.Shipcall?.Type != 1)
if (this.ShipcallControlModel?.Shipcall?.Type != ShipcallType.Arrival)
{
this.labelOperationsStart.Content = terminalTimes.OperationsEnd.HasValue ? terminalTimes.OperationsEnd.Value.ToString("dd.MM.yyyy HH:mm") : "- / -";
}

View File

@ -96,8 +96,7 @@ namespace BreCalClient
get
{
if (this.Shipcall == null) return "";
Extensions.TypeEnum callType = (Extensions.TypeEnum) this.Shipcall.Type;
return string.Format("{0} {1}", callType, this.Ship?.Name);
return string.Format("{0} {1}", this.Shipcall.Type, this.Ship?.Name);
}
}
@ -160,15 +159,15 @@ namespace BreCalClient
public string? GetBerthText(Times times)
{
string? berthText = null;
if ((BreCalLists.AllBerths != null) && times.BerthId.HasValue && (this.Shipcall?.Type != (int)Extensions.TypeEnum.Shifting))
if ((BreCalLists.Berths != null) && times.BerthId.HasValue && (this.Shipcall?.Type != ShipcallType.Shifting))
{
Berth? berth = BreCalLists.AllBerths.Find((x) => x.Id == times.BerthId);
berthText = berth?.Name;
}
if ((berthText == null) && (times.ParticipantType != (int)Extensions.ParticipantType.TERMINAL))
if ((berthText == null) && (times.ParticipantType != (int) Extensions.ParticipantType.TERMINAL))
{
if (this.Shipcall?.Type == (int)Extensions.TypeEnum.Incoming)
if (this.Shipcall?.Type == ShipcallType.Arrival)
{
Berth? berth = BreCalLists.AllBerths?.Find((x) => x.Id == this.Shipcall?.ArrivalBerthId);
berthText = berth?.Name;
@ -189,7 +188,7 @@ namespace BreCalClient
/// 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)
internal async void UpdateTimesAssignments(TimesApi api)
{
foreach (Extensions.ParticipantType participantType in this.AssignedParticipants.Keys)
{
@ -198,7 +197,7 @@ namespace BreCalClient
if(times.ParticipantId != this.AssignedParticipants[participantType].ParticipantId)
{
times.ParticipantId = this.AssignedParticipants[participantType].ParticipantId;
await _api.TimesPutAsync(times);
await api.TimesUpdateAsync(times);
}
}
@ -225,7 +224,7 @@ namespace BreCalClient
foreach(Times times in deleteTimes)
{
_api.TimesDelete(times.Id);
api.TimesDelete(times.Id);
this.Times.Remove(times);
}
}

31
src/BreCalClient/Util.cs Normal file
View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
namespace BreCalClient
{
public static class Util
{
public static BitmapImage? LoadImage (byte[] imageData)
{
if (imageData == null || imageData.Length == 0) return null;
var image = new BitmapImage();
using (var mem = new MemoryStream(imageData))
{
mem.Position = 0;
image.BeginInit();
image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = null;
image.StreamSource = mem;
image.EndInit();
}
image.Freeze();
return image;
}
}
}

View File

@ -12,6 +12,7 @@ from .api import berths
from .api import ships
from .api import login
from .api import user
from .api import history
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
@ -59,7 +60,7 @@ def create_app(test_config=None):
app.register_blueprint(ships.bp)
app.register_blueprint(login.bp)
app.register_blueprint(user.bp)
app.register_blueprint(history.bp)
logging.basicConfig(filename='brecaldevel.log', level=logging.DEBUG, format='%(asctime)s | %(name)s | %(levelname)s | %(message)s')
local_db.initPool(os.path.dirname(app.instance_path))

View File

@ -0,0 +1,21 @@
from flask import Blueprint, request
from .. import impl
from ..services.auth_guard import auth_guard
import json
bp = Blueprint('history', __name__)
@bp.route('/history', methods=['get'])
@auth_guard() # no restriction by role
def GetParticipant():
if 'Authorization' in request.headers:
token = request.headers.get('Authorization')
options = {}
if not 'shipcall_id' in request.args:
return json.dumps("missing parameter"), 400
options["shipcall_id"] = request.args.get("shipcall_id")
return impl.history.GetHistory(options)
else:
return json.dumps("not authenticated"), 403

View File

@ -10,12 +10,10 @@ bp = Blueprint('notifications', __name__)
@bp.route('/notifications', methods=['get'])
@auth_guard() # no restriction by role
def GetNotifications():
if 'participant_id' in request.args:
if 'shipcall_id' in request.args:
options = {}
options["participant_id"] = request.args.get("participant_id")
options["shipcall_id"] = request.args.get("shipcall_id")
return impl.notifications.GetNotifications(options)
else:
logging.warning("attempt to load notifications without participant id")
logging.warning("attempt to load notifications without shipcall id")
return json.dumps("missing argument"), 400

View File

@ -45,6 +45,7 @@ def PutShipcalls():
try:
content = request.get_json(force=True)
logging.info(content)
loadedModel = model.ShipcallSchema().load(data=content, many=False, partial=True)
except Exception as ex:
logging.error(ex)

View File

@ -1,7 +1,10 @@
from flask import Blueprint, request
from .. import impl
from ..services.auth_guard import auth_guard
from marshmallow import EXCLUDE
from ..schemas import model
import json
import logging
bp = Blueprint('ships', __name__)
@ -14,3 +17,48 @@ def GetShips():
return impl.ships.GetShips(token)
else:
return json.dumps("not authenticated"), 403
@bp.route('/ships', methods=['post'])
@auth_guard() # no restriction by role
def PostShip():
try:
content = request.get_json(force=True)
loadedModel = model.ShipSchema().load(data=content, many=False, partial=True)
except Exception as ex:
logging.error(ex)
print(ex)
return json.dumps("bad format"), 400
return impl.ships.PostShip(loadedModel)
@bp.route('/ships', methods=['put'])
@auth_guard() # no restriction by role
def PutShip():
try:
content = request.get_json(force=True)
loadedModel = model.ShipSchema().load(data=content, many=False, partial=True, unknown=EXCLUDE)
except Exception as ex:
logging.error(ex)
print(ex)
return json.dumps("bad format"), 400
return impl.ships.PutShip(loadedModel)
@bp.route('/ships', methods=['delete'])
@auth_guard() # no restriction by role
def DeleteShip():
try:
content = request.get_json(force=True)
loadedModel = model.ShipSchema().load(data=content, many=False, partial=True, unknown=EXCLUDE)
except Exception as ex:
logging.error(ex)
print(ex)
return json.dumps("bad format"), 400
return impl.ships.DeleteShip(loadedModel)

View File

@ -6,3 +6,4 @@ from . import times
from . import ships
from . import login
from . import user
from . import history

View File

@ -10,8 +10,6 @@ def GetBerths(token):
No parameters, gets all entries
"""
# TODO: validate token
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)

View File

@ -0,0 +1,39 @@
import json
import logging
import pydapper
import pdb
from ..schemas import model
from ..schemas.model import History
from .. import local_db
def GetHistory(options):
"""
:param options: A dictionary containing all the paramters for the Operations
options["shipcall_id"]: **Id of shipcall**.
"""
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
if "shipcall_id" in options and options["shipcall_id"]:
data = commands.query("SELECT id, participant_id, shipcall_id, timestamp, eta, type, operation FROM history WHERE shipcall_id = ?shipcallid?",
model=History.from_query_row,
param={"shipcallid" : options["shipcall_id"]})
pooledConnection.close()
except Exception as ex:
pdb.pm()
logging.error(ex)
print(ex)
result = {}
result["message"] = "call failed"
return json.dumps("call failed"), 500
return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'}

View File

@ -14,7 +14,9 @@ def GetUser(options):
hash = bcrypt.hashpw(options["password"].encode('utf-8'), bcrypt.gensalt( 12 )).decode('utf8')
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
data = commands.query("SELECT id, participant_id, first_name, last_name, user_name, user_email, user_phone, password_hash, api_key, created, modified FROM user WHERE user_name = ?username? OR user_email = ?username?",
data = commands.query("SELECT id, participant_id, first_name, last_name, user_name, user_email, user_phone, password_hash, " +
"api_key, notify_email, notify_whatsapp, notify_signal, notify_popup, created, modified FROM user " +
"WHERE user_name = ?username? OR user_email = ?username?",
model=model.User, param={"username" : options["username"]})
# print(data)
if len(data) == 1:

View File

@ -8,22 +8,25 @@ from .. import local_db
def GetNotifications(options):
"""
:param options: A dictionary containing all the paramters for the Operations
options["participant_id"]: **Id of participant**. *Example: 2*. Id returned through loading of participant
options["shipcall_id"]: **Id of ship call**. *Example: 52*. Id given in ship call list
options["shipcall_id"]: **Id**. *Example: 42*. Id of referenced ship call.
"""
# Implement your business logic here
# All the parameters are present in the options argument
return json.dumps({
"acknowledged": "<boolean>",
"id": "<integer>",
"notification_type": "<string>",
"participant_id": "<integer>",
"times_id": "<integer>",
"timestamp": "<date-time>",
}), 200, {'Content-Type': 'application/json; charset=utf-8'}
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
data = commands.query("SELECT id, shipcall_id, level, type, message, created, modified FROM notification " +
"WHERE shipcall_id = ?scid?", model=model.Notification.from_query_row, param={"scid" : options["shipcall_id"]})
pooledConnection.close()
except Exception as ex:
logging.error(ex)
print(ex)
result = {}
result["message"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'}

View File

@ -5,6 +5,7 @@ import pydapper
from ..schemas import model
from .. import local_db
from ..services.auth_guard import check_jwt
from BreCal.database.update_database import evaluate_shipcall_state
@ -20,7 +21,7 @@ def GetShipcalls(options):
query = ("SELECT s.id as id, ship_id, type, eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, " +
"flags, s.pier_side, bunkering, replenishing_terminal, replenishing_lock, draft, tidal_window_from, " +
"tidal_window_to, rain_sensitive_cargo, recommended_tugs, anchored, moored_lock, canceled, evaluation, " +
"evaluation_message, s.created as created, s.modified as modified " +
"evaluation_message, evaluation_time, evaluation_notifications_sent, s.created as created, s.modified as modified " +
"FROM shipcall s " +
"LEFT JOIN times t ON t.shipcall_id = s.id AND t.participant_type = 8 " +
"WHERE " +
@ -32,7 +33,7 @@ def GetShipcalls(options):
"(etd >= DATE(NOW() - INTERVAL %d DAY)))) " +
"ORDER BY eta") % (options["past_days"], options["past_days"], options["past_days"], options["past_days"])
data = commands.query(query, model=model.Shipcall)
data = commands.query(query, model=model.Shipcall.from_query_row, buffered=True)
for shipcall in data:
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):
@ -112,13 +113,20 @@ def PostShipcalls(schemaModel):
commands.execute(query, schemaModel)
new_id = commands.execute_scalar("select last_insert_id()")
# add participant assignments
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"]})
# add participant assignments if we have a list of participants
if 'participants' in schemaModel:
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"]})
# 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
# evaluate_shipcall_state(mysql_connector_instance=pooledConnection, shipcall_id=new_id) # new_id (last insert id) refers to the shipcall id
# save history data
# TODO: set ETA properly
user_data = check_jwt()
query = "INSERT INTO history (participant_id, shipcall_id, user_id, timestamp, eta, type, operation) VALUES (?pid?, ?scid?, ?uid?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 1)"
commands.execute(query, {"scid" : new_id, "pid" : user_data["participant_id"], "uid" : user_data["id"]})
return json.dumps({"id" : new_id}), 201, {'Content-Type': 'application/json; charset=utf-8'}
@ -205,6 +213,13 @@ def PutShipcalls(schemaModel):
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
# save history data
# TODO: set ETA properly
user_data = check_jwt()
query = "INSERT INTO history (participant_id, shipcall_id, user_id, timestamp, eta, type, operation) VALUES (?pid?, ?scid?, ?uid?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 2)"
commands.execute(query, {"scid" : schemaModel["id"], "pid" : user_data["participant_id"], "uid" : user_data["id"]})
return json.dumps({"id" : schemaModel["id"]}), 200

View File

@ -10,8 +10,6 @@ def GetShips(token):
No parameters, gets all entries
"""
# TODO: validate token
try:
pooledConnection = local_db.getPoolConnection()
@ -35,3 +33,127 @@ def GetShips(token):
def PostShip(schemaModel):
"""
:param schemaModel: The deserialized model of the record to be inserted
"""
# TODO: Validate the incoming data
# This creates a *new* entry
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
query = "INSERT INTO ship ("
isNotFirst = False
for key in schemaModel.keys():
if key == "id":
continue
if key == "created":
continue
if key == "modified":
continue
if isNotFirst:
query += ","
isNotFirst = True
query += key
query += ") VALUES ("
isNotFirst = False
for key in schemaModel.keys():
if key == "id":
continue
if key == "created":
continue
if key == "modified":
continue
if isNotFirst:
query += ","
isNotFirst = True
query += "?" + key + "?"
query += ")"
commands.execute(query, schemaModel)
new_id = commands.execute_scalar("select last_insert_id()")
pooledConnection.close()
return json.dumps({"id" : new_id}), 201, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex:
logging.error(ex)
print(ex)
result = {}
result["message"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
def PutShip(schemaModel):
"""
:param schemaModel: The deserialized model of the record to be inserted
"""
# This updates an *existing* entry
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
query = "UPDATE ship SET "
isNotFirst = False
for key in schemaModel.keys():
if key == "id":
continue
if key == "created":
continue
if key == "modified":
continue
if isNotFirst:
query += ", "
isNotFirst = True
query += key + " = ?" + key + "? "
query += "WHERE id = ?id?"
affected_rows = commands.execute(query, param=schemaModel)
pooledConnection.close()
return json.dumps({"id" : schemaModel["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex:
logging.error(ex)
print(ex)
result = {}
result["message"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
def DeleteShip(options):
"""
:param options: A dictionary containing all the paramters for the Operations
options["id"]
"""
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
affected_rows = commands.execute("UPDATE ship SET deleted = 1 WHERE id = ?id?", param={"id" : options["id"]})
pooledConnection.close()
if affected_rows == 1:
return json.dumps({"id" : options["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'}
result = {}
result["message"] = "no such record"
return json.dumps(result), 404, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex:
logging.error(ex)
print(ex)
result = {}
result["message"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}

View File

@ -1,9 +1,11 @@
import json
import logging
import traceback
import pydapper
from ..schemas import model
from .. import local_db
from ..services.auth_guard import check_jwt
from BreCal.database.update_database import evaluate_shipcall_state
@ -14,8 +16,6 @@ def GetTimes(options):
"""
# TODO: validate token
try:
pooledConnection = local_db.getPoolConnection()
@ -27,6 +27,7 @@ def GetTimes(options):
pooledConnection.close()
except Exception as ex:
logging.error(traceback.format_exc())
logging.error(ex)
print(ex)
result = {}
@ -85,9 +86,16 @@ def PostTimes(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'
# save history data
# TODO: set ETA properly
user_data = check_jwt()
query = "INSERT INTO history (participant_id, shipcall_id, user_id, timestamp, eta, type, operation) VALUES (?pid?, ?scid?, ?uid?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 2, 1)"
commands.execute(query, {"scid" : schemaModel["shipcall_id"], "pid" : user_data["participant_id"], "uid" : user_data["id"]})
return json.dumps({"id" : new_id}), 201, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex:
logging.error(traceback.format_exc())
logging.error(ex)
print(ex)
result = {}
@ -132,13 +140,21 @@ def PutTimes(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'
# save history data
# TODO: set ETA properly
user_data = check_jwt()
if "participant_id" in user_data and "id" in user_data:
query = "INSERT INTO history (participant_id, shipcall_id, user_id, timestamp, eta, type, operation) VALUES (?pid?, ?scid?, ?uid?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 2, 2)"
commands.execute(query, {"pid" : user_data["participant_id"], "scid" : schemaModel["shipcall_id"], "uid" : user_data["id"]})
else:
logging.error("user_data does not contain participant_id or id")
# if affected_rows == 1: # this doesn't work as expected
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(traceback.format_exc())
logging.error(ex)
print(ex)
result = {}
@ -161,6 +177,14 @@ def DeleteTimes(options):
commands = pydapper.using(pooledConnection)
affected_rows = commands.execute("DELETE FROM times WHERE id = ?id?", param={"id" : options["id"]})
# TODO: howto get the shipcall id here? we will need to load the object first
# TODO: set ETA properly
# save history data
user_data = check_jwt()
query = "INSERT INTO history (participant_id, shipcall_id, user_id, timestamp, eta, type, operation) VALUES (?pid?, 0, ?uid?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 2, 3)"
commands.execute(query, {"pid" : user_data["participant_id"], "uid" : user_data["id"]})
if affected_rows == 1:
return json.dumps({"id" : options["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'}

View File

@ -1,5 +1,8 @@
from dataclasses import field
from dataclasses import field, dataclass
from marshmallow import Schema, fields, INCLUDE, ValidationError
from marshmallow.fields import Field
from marshmallow_enum import EnumField
from enum import IntEnum
from marshmallow_dataclass import dataclass
from typing import List
@ -10,6 +13,8 @@ import datetime
def obj_dict(obj):
if isinstance(obj, datetime.datetime):
return obj.isoformat()
if hasattr(obj, 'to_json'):
return obj.to_json()
return obj.__dict__
@dataclass
@ -23,6 +28,70 @@ class Berth(Schema):
modified: datetime
deleted: bool
class OperationType(IntEnum):
undefined = 0
insert = 1
update = 2
delete = 3
class ObjectType(IntEnum):
undefined = 0
shipcall = 1
times = 2
class EvaluationType(IntEnum):
undefined = 0
green = 1
yellow = 2
red = 3
class NotificationType(IntEnum):
undefined = 0
email = 1
push = 2
class ShipcallType(IntEnum):
undefined = 0
arrival = 1
departure = 2
shifting = 3
@dataclass
class History:
def __init__(self, id, participant_id, shipcall_id, timestamp, eta, type, operation):
self.id = id
self.participant_id = participant_id
self.shipcall_id = shipcall_id
self.timestamp = timestamp
self.eta = eta
self.type = type
self.operation = operation
pass
id: int
participant_id: int
shipcall_id: int
timestamp: datetime
eta: datetime
type: ObjectType
operation: OperationType
def to_json(self):
return {
"id": self.id,
"participant_id": self.participant_id,
"shipcall_id": self.shipcall_id,
"timestamp": self.timestamp.isoformat() if self.timestamp else "",
"eta": self.eta.isoformat() if self.eta else "",
"type": self.type.name,
"operation": self.operation.name
}
@classmethod
def from_query_row(self, id, participant_id, shipcall_id, timestamp, eta, type, operation):
return self(id, participant_id, shipcall_id, timestamp, eta, ObjectType(type), OperationType(operation))
class Error(Schema):
message = fields.String(required=True)
@ -31,17 +100,30 @@ class GetVerifyInlineResp(Schema):
pass
@dataclass
class Notification(Schema):
class Notification:
id: int
times_id: int
acknowledged: bool
shipcall_id: int
level: int
type: int
type: NotificationType
message: str
created: datetime
modified: datetime
def to_json(self):
return {
"id": self.id,
"shipcall_id": self.shipcall_id,
"level": self.level,
"type": self.type.name,
"message": self.message,
"created": self.created.isoformat() if self.created else "",
"modified": self.modified.isoformat() if self.modified else ""
}
@classmethod
def from_query_row(self, id, shipcall_id, level, type, message, created, modified):
return self(id, shipcall_id, level, NotificationType(type), message, created, modified)
@dataclass
class Participant(Schema):
id: int
@ -69,7 +151,7 @@ class ShipcallSchema(Schema):
id = fields.Int()
ship_id = fields.Int()
type = fields.Int()
type = EnumField(ShipcallType, by_value=True, required=True)
eta = fields.DateTime(Required = False, allow_none=True)
voyage = fields.Str(allow_none=True, metadata={'Required':False}) # Solving: RemovedInMarshmallow4Warning: Passing field metadata as keyword arguments is deprecated. Use the explicit `metadata=...` argument instead. Additional metadata: {'Required': False}
etd = fields.DateTime(Required = False, allow_none=True)
@ -90,8 +172,10 @@ 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)
evaluation = fields.Int(Required = False, allow_none=True)
evaluation = EnumField(EvaluationType, required=False, allow_none=True, by_value=True)
evaluation_message = fields.Str(allow_none=True, metadata={'Required':False}) # Solving: RemovedInMarshmallow4Warning: Passing field metadata as keyword arguments is deprecated. Use the explicit `metadata=...` argument instead. Additional metadata: {'Required': False}
evaluation_time = fields.DateTime(Required = False, allow_none=True)
evaluation_notifications_sent = fields.Bool(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)
@ -104,14 +188,15 @@ class Participant_Assignment:
pass
participant_id: int
type: int
type: int # a variant would be to use the IntFlag type (with appropriate serialization)
@dataclass
class Shipcall:
id: int
ship_id: int
type: str
type: ShipcallType
eta: datetime
voyage: str
etd: datetime
@ -132,12 +217,54 @@ class Shipcall:
anchored: bool
moored_lock: bool
canceled: bool
evaluation: int
evaluation: EvaluationType
evaluation_message: str
evaluation_time: datetime
evaluation_notifications_sent: bool
created: datetime
modified: datetime
participants: List[Participant_Assignment] = field(default_factory=list)
def to_json(self):
return {
"id": self.id,
"ship_id": self.ship_id,
"type": self.type.name,
"eta": self.eta.isoformat() if self.eta else "",
"voyage": self.voyage,
"etd": self.etd.isoformat() if self.etd else "",
"arrival_berth_id": self.arrival_berth_id,
"departure_berth_id": self.departure_berth_id,
"tug_required": self.tug_required,
"pilot_required": self.pilot_required,
"flags": self.flags,
"pier_side": self.pier_side,
"bunkering": self.bunkering,
"replenishing_terminal": self.replenishing_terminal,
"replenishing_lock": self.replenishing_lock,
"draft": self.draft,
"tidal_window_from": self.tidal_window_from.isoformat() if self.tidal_window_from else "",
"tidal_window_to": self.tidal_window_to.isoformat() if self.tidal_window_to else "",
"rain_sensitive_cargo": self.rain_sensitive_cargo,
"recommended_tugs": self.recommended_tugs,
"anchored": self.anchored,
"moored_lock": self.moored_lock,
"canceled": self.canceled,
"evaluation": self.evaluation.name,
"evaluation_message": self.evaluation_message,
"evaluation_time": self.evaluation_time.isoformat() if self.evaluation_time else "",
"evaluation_notifications_sent": self.evaluation_notifications_sent,
"created": self.created.isoformat() if self.created else "",
"modified": self.modified.isoformat() if self.modified else "",
"participants": [participant.__dict__ for participant in self.participants]
}
@classmethod
def from_query_row(self, 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, evaluation_time, evaluation_notifications_sent, created, modified):
return self(id, ship_id, ShipcallType(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, EvaluationType(evaluation), evaluation_message, evaluation_time, evaluation_notifications_sent, created, modified)
class ShipcallId(Schema):
pass
@ -185,7 +312,6 @@ class UserSchema(Schema):
@dataclass
class Times:
id: int
eta_berth: datetime
eta_berth_fixed: bool
@ -219,11 +345,15 @@ class User:
user_phone: str
password_hash: str
api_key: str
notify_email: bool
notify_whatsapp: bool
notify_signal: bool
notify_popup: bool
created: datetime
modified: datetime
@dataclass
class Ship(Schema):
class Ship:
id: int
name: str
imo: int
@ -233,11 +363,32 @@ class Ship(Schema):
width: float
is_tug: bool
bollard_pull: int
eni: str
eni: int
created: datetime
modified: datetime
deleted: bool
class ShipSchema(Schema):
def __init__(self):
super().__init__(unknown=None)
pass
id = fields.Int(Required=False)
name = fields.String(allow_none=False, metadata={'Required':True})
imo = fields.Int(allow_none=False, metadata={'Required':True})
callsign = fields.String(allow_none=True, metadata={'Required':False})
participant_id = fields.Int(allow_none=True, metadata={'Required':False})
length = fields.Float(allow_none=True, metadata={'Required':False})
width = fields.Float(allow_none=True, metadata={'Required':False})
is_tug = fields.Bool(allow_none=True, metadata={'Required':False}, default=False)
bollard_pull = fields.Int(allow_none=True, metadata={'Required':False})
eni = fields.Int(allow_none=True, metadata={'Required':False})
created = fields.DateTime(allow_none=True, metadata={'Required':False})
modified = fields.DateTime(allow_none=True, metadata={'Required':False})
deleted = fields.Bool(allow_none=True, metadata={'Required':False}, default=False)
class TimesId(Schema):
pass

View File

@ -27,7 +27,7 @@ def UpdateShipcalls(options:dict = {'past_days':2}):
commands = pydapper.using(pooledConnection)
query = ("SELECT id, ship_id, type, eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, "
"flags, pier_side, bunkering, replenishing_terminal, replenishing_lock, draft, tidal_window_from, tidal_window_to, rain_sensitive_cargo, recommended_tugs, "
"anchored, moored_lock, canceled, evaluation, evaluation_message, created, modified FROM shipcall WHERE ((type = 1 OR type = 3) AND eta >= DATE(NOW() - INTERVAL %d DAY)"
"anchored, moored_lock, canceled, evaluation, evaluation_message, evaluation_notifications_sent, evaluation_time, created, modified FROM shipcall WHERE ((type = 1 OR type = 3) AND eta >= DATE(NOW() - INTERVAL %d DAY)"
"OR (type = 2 AND etd >= DATE(NOW() - INTERVAL %d DAY))) "
"ORDER BY eta") % (options["past_days"], options["past_days"])

View File

@ -41,6 +41,8 @@ def get_shipcall_simple():
canceled = False
evaluation = None
evaluation_message = ""
evaluation_time = None
evaluation_notifications_sent = False
created = datetime.datetime.now()
modified = created+datetime.timedelta(seconds=10)
@ -72,6 +74,8 @@ def get_shipcall_simple():
canceled,
evaluation,
evaluation_message,
evaluation_time,
evaluation_notifications_sent,
created,
modified,
participants,