Merge branch 'release/1.6.0'

This commit is contained in:
Daniel Schick 2024-12-11 12:09:38 +01:00
commit 3f37ee67e5
69 changed files with 3134 additions and 629 deletions

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,23 @@
DELETE FROM times WHERE
times.shipcall_id IN
(
SELECT s.id FROM shipcall s
JOIN shipcall_participant_map spm ON s.id = spm.shipcall_id
JOIN participant p ON spm.participant_id = p.id
WHERE p.id = 10
);
DELETE `history` FROM `history`
JOIN shipcall s on `history`.shipcall_id = s.id
JOIN shipcall_participant_map spm ON s.id = spm.shipcall_id
WHERE spm.participant_id = 10;
-- damit das hier funktioniert muss der FK in shipcall_participant_map von "RESTRICT" auf "SET NULL"
-- geändert werden
DELETE shipcall FROM shipcall
INNER JOIN shipcall_participant_map spm ON shipcall.id = spm.shipcall_id
JOIN participant p ON spm.participant_id = p.id
WHERE p.id = 10;
DELETE FROM shipcall_participant_map WHERE participant_id = 10;

37
misc/requirements.txt Normal file
View File

@ -0,0 +1,37 @@
bcrypt==4.2.0
blinker==1.8.2
cached-property==1.5.2
click==8.1.7
coro-context-manager==0.2.0
coverage==7.6.1
dsnparse==0.1.15
Flask==3.0.3
Flask-JWT-Extended==4.6.0
iniconfig==2.0.0
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.5
marshmallow==3.22.0
marshmallow-enum==1.5.1
marshmallow_dataclass==8.7.1
mypy-extensions==1.0.0
mysql-connector-python==9.0.0
numpy==2.1.1
packaging==24.1
pandas==2.2.3
pluggy==1.5.0
pydapper==0.10.0
PyJWT==2.9.0
pytest==8.3.3
pytest-cov==5.0.0
python-dateutil==2.9.0.post0
pytz==2024.2
schedule==1.2.2
six==1.16.0
tqdm==4.66.5
typeguard==4.3.0
typing-inspect==0.9.0
typing_extensions==4.12.2
tzdata==2024.1
webargs==8.6.0
Werkzeug==3.0.4

View File

@ -0,0 +1,57 @@
CREATE TABLE `port` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL COMMENT 'Name of port',
`locode` char(5) DEFAULT NULL COMMENT 'UNECE locode',
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`deleted` bit(1) DEFAULT b'0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Port as reference for shipcalls and berths';
-- Add default port to table
INSERT INTO port (id, name, locode) VALUES (1, 'Bremen', 'DEBRE');
-- Adding new ref column to berth
ALTER TABLE `berth`
ADD COLUMN `port_id` INT UNSIGNED DEFAULT NULL AFTER `authority_id`;
ALTER TABLE `berth` ALTER INDEX `FK_AUTHORITY_PART_idx` INVISIBLE;
-- adding a foreign key berth.port_id -> port.id
ALTER TABLE `berth`
ADD INDEX `FK_PORT_PART_idx` (`port_id` ASC) VISIBLE;
ALTER TABLE `berth`
ADD CONSTRAINT `FK_PORT`
FOREIGN KEY (`port_id`)
REFERENCES `port` (`id`)
ON DELETE RESTRICT
ON UPDATE RESTRICT;
-- adding new ref column to shipcall incl. foreign key
ALTER TABLE `shipcall`
ADD COLUMN `port_id` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'Selected port for this shipcall' AFTER `evaluation_notifications_sent`,
CHANGE COLUMN `time_ref_point` `time_ref_point` INT NULL DEFAULT '0' COMMENT 'Index of a location which is the reference point for all time value entries, e.g. berth or Geeste' AFTER `port_id`,
ADD INDEX `FK_SHIPCALL_PORT_idx` (`port_id` ASC) VISIBLE;
;
ALTER TABLE `shipcall`
ADD CONSTRAINT `FK_SHIPCALL_PORT`
FOREIGN KEY (`port_id`)
REFERENCES `port` (`id`)
ON DELETE RESTRICT
ON UPDATE RESTRICT;
CREATE TABLE `participant_port_map` (
`id` int NOT NULL AUTO_INCREMENT,
`participant_id` int unsigned NOT NULL COMMENT 'Ref to participant',
`port_id` int unsigned NOT NULL COMMENT 'Ref to port',
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `FK_PP_PARTICIPANT` (`participant_id`),
KEY `FK_PP_PORT` (`port_id`),
CONSTRAINT `FK_PP_PARTICIPANT` FOREIGN KEY (`participant_id`) REFERENCES `participant` (`id`),
CONSTRAINT `FK_PP_PORT` FOREIGN KEY (`port_id`) REFERENCES `port` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Mapping table that assigns participants to a port';
-- all existing berths shall default to "bremen"
UPDATE berth SET port_id = 1 where port_id is null;

View File

@ -1 +1 @@
1.4.1.0 1.6.0.8

View File

@ -8,8 +8,8 @@
<SignAssembly>True</SignAssembly> <SignAssembly>True</SignAssembly>
<StartupObject>BreCalClient.App</StartupObject> <StartupObject>BreCalClient.App</StartupObject>
<AssemblyOriginatorKeyFile>..\..\misc\brecal.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>..\..\misc\brecal.snk</AssemblyOriginatorKeyFile>
<AssemblyVersion>1.5.0.12</AssemblyVersion> <AssemblyVersion>1.6.0.8</AssemblyVersion>
<FileVersion>1.5.0.12</FileVersion> <FileVersion>1.6.0.8</FileVersion>
<Title>Bremen calling client</Title> <Title>Bremen calling client</Title>
<Description>A Windows WPF client for the Bremen calling API.</Description> <Description>A Windows WPF client for the Bremen calling API.</Description>
<ApplicationIcon>containership.ico</ApplicationIcon> <ApplicationIcon>containership.ico</ApplicationIcon>

View File

@ -25,16 +25,19 @@ namespace BreCalClient
private static List<Participant> _participants = new(); private static List<Participant> _participants = new();
private static readonly List<ShipModel> _ships = new(); private static readonly List<ShipModel> _ships = new();
private static readonly List<ShipModel> _allShips = new(); private static readonly List<ShipModel> _allShips = new();
private static readonly List<Port> _ports = new();
private static readonly List<Port> _allPorts = new();
private readonly static ConcurrentDictionary<int, ShipModel> _shipLookupDict = new(); private readonly static ConcurrentDictionary<int, ShipModel> _shipLookupDict = new();
private readonly static ConcurrentDictionary<int, Berth> _berthLookupDict = new(); private readonly static ConcurrentDictionary<int, Berth> _berthLookupDict = new();
private readonly static Dictionary<int, Participant> _participantLookupDict = new(); private readonly static Dictionary<int, Participant> _participantLookupDict = new();
private readonly static ConcurrentDictionary<int, Port> _portLookupDict = new();
/// <summary> /// <summary>
/// List of TimeRef points /// List of TimeRef points
/// </summary> /// </summary>
// TODO: To make this portable the list of texts should come from a configuration file // TODO: To make this portable the list of texts should come from a configuration file
private readonly static List<string> _timeRefs = new List<string> private readonly static List<string> _timeRefs = new()
{ {
"ETB", "ETB",
"Geeste", "Geeste",
@ -45,12 +48,26 @@ namespace BreCalClient
#region Properties #region Properties
/// <summary>
/// fast ship lookup
/// </summary>
public static ConcurrentDictionary<int, ShipModel> ShipLookupDict { get { return _shipLookupDict; } } public static ConcurrentDictionary<int, ShipModel> ShipLookupDict { get { return _shipLookupDict; } }
/// <summary>
/// fast port lookup
/// </summary>
public static ConcurrentDictionary<int, Berth> BerthLookupDict { get { return _berthLookupDict; } } public static ConcurrentDictionary<int, Berth> BerthLookupDict { get { return _berthLookupDict; } }
/// <summary>
/// fast participant lookup
/// </summary>
public static Dictionary<int, Participant> ParticipantLookupDict { get { return _participantLookupDict; } } public static Dictionary<int, Participant> ParticipantLookupDict { get { return _participantLookupDict; } }
/// <summary>
/// fast port lookup
/// </summary>
public static ConcurrentDictionary<int, Port> PortLookupDict { get { return _portLookupDict; } }
/// <summary> /// <summary>
/// Participants that are agents /// Participants that are agents
/// </summary> /// </summary>
@ -91,6 +108,16 @@ namespace BreCalClient
/// </summary> /// </summary>
public static List<Berth> AllBerths { get { return _allBerths; } } public static List<Berth> AllBerths { get { return _allBerths; } }
/// <summary>
/// All active ports
/// </summary>
public static List<Port> Ports { get { return _ports; } }
/// <summary>
/// All ports including deleted ports
/// </summary>
public static List<Port> AllPorts { get { return _allPorts; } }
/// <summary> /// <summary>
/// All active ships /// All active ships
/// </summary> /// </summary>
@ -108,7 +135,33 @@ namespace BreCalClient
#endregion #endregion
#region methods #region public static methods
public static List<Berth> GetBerthsByPort(int port)
{
List<Berth> berths = new();
foreach(Berth berth in _berths)
{
if(berth.PortId == port)
berths.Add(berth);
}
return berths;
}
public static List<Participant> GetParticipants(int port, Extensions.ParticipantType type)
{
List<Participant> participants = new();
foreach(Participant participant in _participants)
{
if(participant.IsTypeFlagSet(type) && participant.Ports.Contains(port))
participants.Add(participant);
}
return participants;
}
#endregion
#region Internal initializer methods
internal static void InitializeParticipants(List<Participant> participants) internal static void InitializeParticipants(List<Participant> participants)
{ {
@ -157,6 +210,17 @@ namespace BreCalClient
} }
} }
internal static void InitializePorts(List<Port> ports)
{
foreach(var port in ports)
{
_portLookupDict[port.Id] = port;
if(!port.Deleted)
_ports.Add(port);
_allPorts.Add(port);
}
}
#endregion #endregion
} }

View File

@ -44,7 +44,9 @@
<xctk:DoubleUpDown x:Name="doubleUpDownWidth" Margin="2" Grid.Column="1" Grid.Row="4" FormatString="N2" IsReadOnly="True" IsEnabled="False" ShowButtonSpinner="False"/> <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" /> <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" /> <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.textHarbour}" Grid.Column="0" Grid.Row="6" HorizontalContentAlignment="Right" />
<ComboBox x:Name="comboBoxHarbour" Grid.Column="1" Margin="2" Grid.Row="6" DisplayMemberPath="Name" SelectedValuePath="Id" />
<Button x:Name="buttonEditShips" Grid.Column="1" Grid.Row="7" Margin="2" Content="{x:Static p:Resources.textEditShips}" Click="buttonEditShips_Click" Visibility="Hidden" />
<Label Content="{x:Static p:Resources.textType}" Grid.Column="2" Grid.Row="0" HorizontalContentAlignment="Right" /> <Label Content="{x:Static p:Resources.textType}" Grid.Column="2" Grid.Row="0" HorizontalContentAlignment="Right" />

View File

@ -6,6 +6,7 @@ using BreCalClient.misc.Api;
using BreCalClient.misc.Model; using BreCalClient.misc.Model;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using static BreCalClient.Extensions; using static BreCalClient.Extensions;
@ -41,13 +42,14 @@ namespace BreCalClient
public ShipApi? ShipApi { get; set; } public ShipApi? ShipApi { get; set; }
public bool IsCreate { get; set; } = false;
#endregion #endregion
#region Event handler #region Event handler
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
this.comboBoxAgency.ItemsSource = BreCalLists.Participants_Agent;
this.comboBoxShip.ItemsSource = BreCalLists.Ships; this.comboBoxShip.ItemsSource = BreCalLists.Ships;
Array types = Enum.GetValues(typeof(ShipcallType)); Array types = Enum.GetValues(typeof(ShipcallType));
@ -59,10 +61,8 @@ namespace BreCalClient
else first = false; else first = false;
} }
this.comboBoxArrivalBerth.ItemsSource = BreCalLists.Berths;
this.comboBoxDepartureBerth.ItemsSource = BreCalLists.Berths;
this.comboBoxTimeRef.ItemsSource = BreCalLists.TimeRefs; this.comboBoxTimeRef.ItemsSource = BreCalLists.TimeRefs;
this.comboBoxHarbour.ItemsSource = BreCalLists.Ports.Where(x => App.Participant.Ports.Contains(x.Id));
this.integerUpDownShiftingCount.Value = this.ShipcallModel.ShiftSequence; this.integerUpDownShiftingCount.Value = this.ShipcallModel.ShiftSequence;
@ -86,21 +86,21 @@ namespace BreCalClient
} }
private void comboBoxShip_SelectionChanged(object sender, SelectionChangedEventArgs e) private void comboBoxShip_SelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
if (this.comboBoxShip.SelectedItem != null) if (this.comboBoxShip.SelectedItem != null)
{ {
ShipModel? ship = this.comboBoxShip.SelectedItem as ShipModel; ShipModel? ship = this.comboBoxShip.SelectedItem as ShipModel;
this.integerUpDownIMO.Value = ship?.Ship.Imo; this.integerUpDownIMO.Value = ship?.Ship.Imo;
this.textBoxCallsign.Text = ship?.Ship.Callsign; this.textBoxCallsign.Text = ship?.Ship.Callsign;
this.doubleUpDownLength.Value = ship?.Ship.Length; this.doubleUpDownLength.Value = ship?.Ship.Length;
this.doubleUpDownWidth.Value = ship?.Ship.Width; this.doubleUpDownWidth.Value = ship?.Ship.Width;
} }
else else
{ {
this.integerUpDownIMO.Value = null; this.integerUpDownIMO.Value = null;
this.textBoxCallsign.Text = string.Empty; this.textBoxCallsign.Text = string.Empty;
this.doubleUpDownLength.Value = null; this.doubleUpDownLength.Value = null;
this.doubleUpDownWidth.Value = null; this.doubleUpDownWidth.Value = null;
} }
this.CheckForCompletion(); this.CheckForCompletion();
} }
@ -118,7 +118,7 @@ namespace BreCalClient
{ {
switch (type) switch (type)
{ {
case ShipcallType.Arrival: case ShipcallType.Arrival:
this.datePickerETD.Visibility = Visibility.Hidden; this.datePickerETD.Visibility = Visibility.Hidden;
this.labelETD.Visibility = Visibility.Hidden; this.labelETD.Visibility = Visibility.Hidden;
this.datePickerETA.Visibility = Visibility.Visible; this.datePickerETA.Visibility = Visibility.Visible;
@ -175,7 +175,7 @@ namespace BreCalClient
this.comboBoxAgency.SelectedIndex = -1; this.comboBoxAgency.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.AGENCY); this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.AGENCY);
} }
#endregion #endregion
#region private methods #region private methods
@ -188,10 +188,13 @@ namespace BreCalClient
void CheckForCompletion() void CheckForCompletion()
{ {
if (this.ShipcallModel.Shipcall?.Canceled ?? false) return; // Cancelled shipcall never clicks ok
bool isEnabled = true; bool isEnabled = true;
isEnabled &= this.comboBoxShip.SelectedItem != null; isEnabled &= this.comboBoxShip.SelectedItem != null;
isEnabled &= this.comboBoxCategories.SelectedItem != null; isEnabled &= this.comboBoxCategories.SelectedItem != null;
isEnabled &= this.comboBoxHarbour.SelectedItem != null;
if (comboBoxCategories.SelectedItem == null) if (comboBoxCategories.SelectedItem == null)
{ {
@ -207,7 +210,7 @@ namespace BreCalClient
isEnabled &= this.datePickerETD.Value.HasValue; isEnabled &= this.datePickerETD.Value.HasValue;
isEnabled &= !(this.datePickerETD.Value.IsTooOld() && this.datePickerETD.Value != this.ShipcallModel.Shipcall?.Etd); isEnabled &= !(this.datePickerETD.Value.IsTooOld() && this.datePickerETD.Value != this.ShipcallModel.Shipcall?.Etd);
isEnabled &= !this.datePickerETD.Value.IsTooFar(); isEnabled &= !this.datePickerETD.Value.IsTooFar();
break; break;
case ShipcallType.Arrival: case ShipcallType.Arrival:
isEnabled &= this.comboBoxArrivalBerth.SelectedItem != null; isEnabled &= this.comboBoxArrivalBerth.SelectedItem != null;
isEnabled &= this.datePickerETA.Value.HasValue; isEnabled &= this.datePickerETA.Value.HasValue;
@ -227,7 +230,7 @@ namespace BreCalClient
break; break;
} }
} }
isEnabled &= this.comboBoxAgency.SelectedItem != null; isEnabled &= this.comboBoxAgency.SelectedItem != null;
this.buttonOK.IsEnabled = isEnabled; this.buttonOK.IsEnabled = isEnabled;
@ -249,7 +252,7 @@ namespace BreCalClient
{ {
this.ShipcallModel.Shipcall.ArrivalBerthId = (this.comboBoxArrivalBerth.SelectedItem != null) ? ((Berth)this.comboBoxArrivalBerth.SelectedItem).Id : null; this.ShipcallModel.Shipcall.ArrivalBerthId = (this.comboBoxArrivalBerth.SelectedItem != null) ? ((Berth)this.comboBoxArrivalBerth.SelectedItem).Id : null;
this.ShipcallModel.Shipcall.DepartureBerthId = (this.comboBoxDepartureBerth.SelectedItem != null) ? ((Berth)this.comboBoxDepartureBerth.SelectedItem).Id : null; this.ShipcallModel.Shipcall.DepartureBerthId = (this.comboBoxDepartureBerth.SelectedItem != null) ? ((Berth)this.comboBoxDepartureBerth.SelectedItem).Id : null;
} }
else // shifting else // shifting
{ {
this.ShipcallModel.Shipcall.DepartureBerthId = (this.comboBoxArrivalBerth.SelectedItem != null) ? ((Berth)this.comboBoxArrivalBerth.SelectedItem).Id : null; this.ShipcallModel.Shipcall.DepartureBerthId = (this.comboBoxArrivalBerth.SelectedItem != null) ? ((Berth)this.comboBoxArrivalBerth.SelectedItem).Id : null;
@ -264,11 +267,11 @@ namespace BreCalClient
ParticipantAssignment pa = new() ParticipantAssignment pa = new()
{ {
ParticipantId = participant.Id, ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.AGENCY Type = (int)Extensions.ParticipantType.AGENCY
}; };
this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.AGENCY] = pa; this.ShipcallModel.AssignedParticipants[Extensions.ParticipantType.AGENCY] = pa;
} }
else else
{ {
// AGENCY was set before and now is set to nothing // AGENCY was set before and now is set to nothing
@ -309,7 +312,7 @@ namespace BreCalClient
// set the time reference value (which point do all times refer to?) // set the time reference value (which point do all times refer to?)
this.ShipcallModel.Shipcall.TimeRefPoint = this.comboBoxTimeRef.SelectedIndex; this.ShipcallModel.Shipcall.TimeRefPoint = this.comboBoxTimeRef.SelectedIndex;
this.ShipcallModel.Shipcall.PortId = ((Port) this.comboBoxHarbour.SelectedItem).Id;
} }
} }
@ -321,8 +324,8 @@ namespace BreCalClient
this.comboBoxTimeRef.SelectedIndex = this.ShipcallModel.Shipcall.TimeRefPoint ?? 1; this.comboBoxTimeRef.SelectedIndex = this.ShipcallModel.Shipcall.TimeRefPoint ?? 1;
this.comboBoxCategories.SelectedItem = new EnumToStringConverter().Convert(this.ShipcallModel.Shipcall.Type, typeof(ShipcallType), new object(), System.Globalization.CultureInfo.CurrentCulture); this.comboBoxCategories.SelectedItem = new EnumToStringConverter().Convert(this.ShipcallModel.Shipcall.Type, typeof(ShipcallType), new object(), System.Globalization.CultureInfo.CurrentCulture);
if (this.ShipcallModel.Shipcall.Eta != DateTime.MinValue) if (this.ShipcallModel.Shipcall.Eta != DateTime.MinValue)
this.datePickerETA.Value = this.ShipcallModel.Shipcall.Eta; this.datePickerETA.Value = this.ShipcallModel.Shipcall.Eta;
// this.textBoxVoyage.Text = this.ShipcallModel.Shipcall.Voyage;
this.datePickerETD.Value = this.ShipcallModel.Shipcall.Etd; this.datePickerETD.Value = this.ShipcallModel.Shipcall.Etd;
if (BreCalLists.Ships.Find(x => x.Ship.Id == this.ShipcallModel.Shipcall.ShipId) != null) if (BreCalLists.Ships.Find(x => x.Ship.Id == this.ShipcallModel.Shipcall.ShipId) != null)
{ {
@ -333,6 +336,16 @@ namespace BreCalClient
} }
this.checkBoxCancelled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false; this.checkBoxCancelled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false;
if (BreCalLists.PortLookupDict.ContainsKey(this.ShipcallModel.Shipcall.PortId))
this.comboBoxHarbour.SelectedValue = this.ShipcallModel.Shipcall.PortId;
List<Berth> availableBerths = BreCalLists.GetBerthsByPort(this.ShipcallModel.Shipcall.PortId);
this.comboBoxArrivalBerth.ItemsSource = availableBerths;
this.comboBoxDepartureBerth.ItemsSource = availableBerths;
// Filter agency combobox by port
List<Participant> availableAgencies = BreCalLists.GetParticipants(this.ShipcallModel.Shipcall.PortId, ParticipantType.AGENCY);
this.comboBoxAgency.ItemsSource = availableAgencies;
if (this.ShipcallModel.Shipcall.Type != ShipcallType.Shifting) // incoming, outgoing if (this.ShipcallModel.Shipcall.Type != ShipcallType.Shifting) // incoming, outgoing
{ {
this.comboBoxArrivalBerth.SelectedValue = this.ShipcallModel.Shipcall.ArrivalBerthId; this.comboBoxArrivalBerth.SelectedValue = this.ShipcallModel.Shipcall.ArrivalBerthId;
@ -345,14 +358,18 @@ namespace BreCalClient
} }
if (this.ShipcallModel.Shipcall.Participants == null) this.ShipcallModel.Shipcall.Participants = new(); if (this.ShipcallModel.Shipcall.Participants == null) this.ShipcallModel.Shipcall.Participants = new();
if(this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY)) if(this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
{ {
if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId)) if (BreCalLists.ParticipantLookupDict.ContainsKey(this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId))
{ {
this.comboBoxAgency.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId; this.comboBoxAgency.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId;
} }
} }
this.comboBoxHarbour.SelectionChanged += this.comboBoxHarbour_SelectionChanged;
this.comboBoxHarbour.IsEnabled = this.ShipcallModel.AllowPortChange;
} }
} }
@ -363,6 +380,8 @@ namespace BreCalClient
bool editRightGrantedForBSMD = false; bool editRightGrantedForBSMD = false;
if (this.ShipcallModel.Shipcall?.Canceled ?? false) return; // do not allow edit on canceled shipcall
// Special case: Selected Agency allows BSMD to edit their fields // Special case: Selected Agency allows BSMD to edit their fields
if (this.comboBoxAgency.SelectedIndex >= 0) if (this.comboBoxAgency.SelectedIndex >= 0)
{ {
@ -386,6 +405,7 @@ namespace BreCalClient
this.datePickerETD.IsEnabled = isAgency || isBsmd; this.datePickerETD.IsEnabled = isAgency || isBsmd;
this.labelBSMDGranted.Visibility = editRightGrantedForBSMD ? Visibility.Visible : Visibility.Hidden; this.labelBSMDGranted.Visibility = editRightGrantedForBSMD ? Visibility.Visible : Visibility.Hidden;
this.comboBoxHarbour.IsEnabled = this.IsCreate && this.ShipcallModel.AllowPortChange;
this.comboBoxCategories_SelectionChanged(null, null); this.comboBoxCategories_SelectionChanged(null, null);
} }
@ -412,7 +432,7 @@ namespace BreCalClient
private void comboBoxDepartureBerth_SelectionChanged(object sender, SelectionChangedEventArgs e) private void comboBoxDepartureBerth_SelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
this.CheckForCompletion(); this.CheckForCompletion();
} }
private void buttonEditShips_Click(object sender, RoutedEventArgs e) private void buttonEditShips_Click(object sender, RoutedEventArgs e)
{ {
@ -420,7 +440,7 @@ namespace BreCalClient
{ {
ShipApi = this.ShipApi ShipApi = this.ShipApi
}; };
shipListDialog.ShowDialog(); shipListDialog.ShowDialog();
// reload combobox // reload combobox
@ -428,6 +448,24 @@ namespace BreCalClient
this.comboBoxShip.ItemsSource = BreCalLists.Ships; this.comboBoxShip.ItemsSource = BreCalLists.Ships;
} }
private void comboBoxHarbour_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Port port = (Port)this.comboBoxHarbour.SelectedItem;
if (port == null) return;
// Filter berth selection combobox by port
List<Berth> availableBerths = BreCalLists.GetBerthsByPort(port.Id);
this.comboBoxArrivalBerth.ItemsSource = null;
this.comboBoxArrivalBerth.ItemsSource = availableBerths;
this.comboBoxDepartureBerth.ItemsSource = null;
this.comboBoxDepartureBerth.ItemsSource = availableBerths;
// Filter agency combobox by port
List<Participant> availableAgencies = BreCalLists.GetParticipants(port.Id, ParticipantType.AGENCY);
this.comboBoxAgency.ItemsSource = null;
this.comboBoxAgency.ItemsSource = availableAgencies;
}
#endregion #endregion
} }

View File

@ -1,6 +1,6 @@
// Copyright (c) 2023 schick Informatik // Copyright (c) 2023 schick Informatik
// Description: Input control for incoming shipcalls // Description: Input control for incoming shipcalls
// //
using BreCalClient.misc.Model; using BreCalClient.misc.Model;
using System; using System;
@ -33,7 +33,7 @@ namespace BreCalClient
public ShipcallControlModel ShipcallModel { get; set; } = new(); public ShipcallControlModel ShipcallModel { get; set; } = new();
public Times Times { get; set; } = new(); public Times Times { get; set; } = new();
#endregion #endregion
@ -41,19 +41,27 @@ namespace BreCalClient
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
this.comboBoxMooring.ItemsSource = BreCalLists.Participants_Mooring;
this.comboBoxPilot.ItemsSource = BreCalLists.Participants_Pilot;
this.comboBoxTug.ItemsSource = BreCalLists.Participants_Tug;
this.comboBoxTerminal.ItemsSource = BreCalLists.Participants_Terminal;
this.comboBoxArrivalBerth.ItemsSource = BreCalLists.Berths; if ((this.ShipcallModel != null) && (this.ShipcallModel.Shipcall != null))
{
int portId = this.ShipcallModel.Shipcall.PortId;
this.comboBoxArrivalBerth.ItemsSource = BreCalLists.GetBerthsByPort(portId);
this.comboBoxMooring.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.MOORING);
this.comboBoxPilot.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.PILOT);
this.comboBoxTug.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.TUG);
this.comboBoxTerminal.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.TERMINAL);
}
this.CopyToControls(); this.CopyToControls();
this.Title = this.ShipcallModel.Title; this.Title = this.ShipcallModel?.Title;
Participant? p = null; Participant? p = null;
if(this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY)) if (this.ShipcallModel != null)
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId); {
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId);
}
bool allowBSMD = false; bool allowBSMD = false;
if (p != null) if (p != null)
@ -65,7 +73,7 @@ namespace BreCalClient
(App.Participant.IsTypeFlagSet(ParticipantType.BSMD) && allowBSMD); (App.Participant.IsTypeFlagSet(ParticipantType.BSMD) && allowBSMD);
this.EnableControls(); this.EnableControls();
} }
private void buttonOK_Click(object sender, RoutedEventArgs e) private void buttonOK_Click(object sender, RoutedEventArgs e)
@ -93,7 +101,7 @@ namespace BreCalClient
#region private methods #region private methods
private bool CheckValues(out string message) private bool CheckValues(out string message)
{ {
message = ""; message = "";
if ((this.datePickerETA.Value != this.Times.EtaBerth) || (this.datePickerETA_End.Value != this.Times.EtaIntervalEnd)) // something has changed if ((this.datePickerETA.Value != this.Times.EtaBerth) || (this.datePickerETA_End.Value != this.Times.EtaIntervalEnd)) // something has changed
@ -130,7 +138,7 @@ namespace BreCalClient
message = BreCalClient.Resources.Resources.textTidalBothValues; message = BreCalClient.Resources.Resources.textTidalBothValues;
return false; return false;
} }
} }
if(this.datePickerETA.Value.IsTooFar() || this.datePickerETA_End.Value.IsTooFar() || this.datePickerTidalWindowFrom.Value.IsTooFar() || this.datePickerTidalWindowTo.Value.IsTooFar()) if(this.datePickerETA.Value.IsTooFar() || this.datePickerETA_End.Value.IsTooFar() || this.datePickerTidalWindowFrom.Value.IsTooFar() || this.datePickerTidalWindowTo.Value.IsTooFar())
{ {
@ -138,7 +146,8 @@ namespace BreCalClient
return false; return false;
} }
if((this.datePickerETA_End.Value.HasValue && !this.datePickerETA.Value.HasValue) || (this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue)) if((this.datePickerETA_End.Value.HasValue && !this.datePickerETA.Value.HasValue) ||
(this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue))
{ {
message = BreCalClient.Resources.Resources.textStartTimeMissing; message = BreCalClient.Resources.Resources.textStartTimeMissing;
return false; return false;
@ -153,7 +162,7 @@ namespace BreCalClient
{ {
this.Times.EtaBerth = this.datePickerETA.Value; this.Times.EtaBerth = this.datePickerETA.Value;
this.Times.EtaIntervalEnd = this.datePickerETA_End.Value; this.Times.EtaIntervalEnd = this.datePickerETA_End.Value;
if (this.comboBoxPierside.SelectedIndex >= 0) if (this.comboBoxPierside.SelectedIndex >= 0)
{ {
this.ShipcallModel.Shipcall.PierSide = (this.comboBoxPierside.SelectedIndex == 0); this.ShipcallModel.Shipcall.PierSide = (this.comboBoxPierside.SelectedIndex == 0);
@ -163,8 +172,8 @@ namespace BreCalClient
this.ShipcallModel.Shipcall.PierSide = null; this.ShipcallModel.Shipcall.PierSide = null;
} }
this.Times.BerthInfo = this.textBoxBerthRemarks.Text.Trim(); this.Times.BerthInfo = this.textBoxBerthRemarks.Text.Trim();
this.Times.BerthId = (int?)this.comboBoxArrivalBerth.SelectedValue; this.Times.BerthId = (int?)this.comboBoxArrivalBerth.SelectedValue;
this.ShipcallModel.Shipcall.Draft = (float?)this.doubleUpDownDraft.Value; this.ShipcallModel.Shipcall.Draft = (float?)this.doubleUpDownDraft.Value;
this.ShipcallModel.Shipcall.TidalWindowFrom = this.datePickerTidalWindowFrom.Value; this.ShipcallModel.Shipcall.TidalWindowFrom = this.datePickerTidalWindowFrom.Value;
@ -172,9 +181,9 @@ namespace BreCalClient
this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked; this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked;
this.ShipcallModel.Shipcall.Anchored = this.checkBoxAnchored.IsChecked; this.ShipcallModel.Shipcall.Anchored = this.checkBoxAnchored.IsChecked;
this.ShipcallModel.Shipcall.RecommendedTugs = this.integerUpDownRecommendedTugs.Value; this.ShipcallModel.Shipcall.RecommendedTugs = this.integerUpDownRecommendedTugs.Value;
this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked; this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked;
this.ShipcallModel.Shipcall.Bunkering = this.checkBoxBunkering.IsChecked; this.ShipcallModel.Shipcall.Bunkering = this.checkBoxBunkering.IsChecked;
this.ShipcallModel.Shipcall.ReplenishingTerminal = this.checkBoxReplenishingTerminal.IsChecked; this.ShipcallModel.Shipcall.ReplenishingTerminal = this.checkBoxReplenishingTerminal.IsChecked;
@ -186,7 +195,7 @@ namespace BreCalClient
Participant? participant = (Participant?)this.comboBoxMooring.SelectedItem; Participant? participant = (Participant?)this.comboBoxMooring.SelectedItem;
if (participant != null) if (participant != null)
{ {
ParticipantAssignment participantAssignment = new() { ParticipantAssignment participantAssignment = new() {
ParticipantId = participant.Id, ParticipantId = participant.Id,
Type = (int)Extensions.ParticipantType.MOORING Type = (int)Extensions.ParticipantType.MOORING
}; };
@ -234,14 +243,14 @@ namespace BreCalClient
if(this.Times.EtaBerth.HasValue) if(this.Times.EtaBerth.HasValue)
{ {
this.datePickerETA.Value = this.Times.EtaBerth.Value; this.datePickerETA.Value = this.Times.EtaBerth.Value;
} }
else else
{ {
// if not set through times use value of BSMD entry // if not set through times use value of BSMD entry
if (this.ShipcallModel.Shipcall.Eta != DateTime.MinValue) if (this.ShipcallModel.Shipcall.Eta != DateTime.MinValue)
this.datePickerETA.Value = this.ShipcallModel.Shipcall.Eta; this.datePickerETA.Value = this.ShipcallModel.Shipcall.Eta;
} }
this.datePickerETA_End.Value = this.Times.EtaIntervalEnd; this.datePickerETA_End.Value = this.Times.EtaIntervalEnd;
if (Times.BerthId.HasValue) if (Times.BerthId.HasValue)
@ -261,12 +270,12 @@ namespace BreCalClient
this.checkBoxCanceled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false; this.checkBoxCanceled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false;
this.checkBoxAnchored.IsChecked = this.ShipcallModel.Shipcall.Anchored ?? false; this.checkBoxAnchored.IsChecked = this.ShipcallModel.Shipcall.Anchored ?? false;
this.integerUpDownRecommendedTugs.Value = this.ShipcallModel.Shipcall.RecommendedTugs; this.integerUpDownRecommendedTugs.Value = this.ShipcallModel.Shipcall.RecommendedTugs;
this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false; this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false;
this.checkBoxBunkering.IsChecked = this.ShipcallModel.Shipcall.Bunkering ?? false; this.checkBoxBunkering.IsChecked = this.ShipcallModel.Shipcall.Bunkering ?? false;
this.checkBoxReplenishingLock.IsChecked = this.ShipcallModel.Shipcall.ReplenishingLock ?? false; this.checkBoxReplenishingLock.IsChecked = this.ShipcallModel.Shipcall.ReplenishingLock ?? false;
this.checkBoxReplenishingTerminal.IsChecked = this.ShipcallModel.Shipcall.ReplenishingTerminal ?? false; this.checkBoxReplenishingTerminal.IsChecked = this.ShipcallModel.Shipcall.ReplenishingTerminal ?? false;
@ -276,7 +285,7 @@ namespace BreCalClient
else else
this.labelETA.Content = string.Format("ETA {0}", BreCalLists.TimeRefs[this.ShipcallModel.Shipcall.TimeRefPoint ?? 0]); this.labelETA.Content = string.Format("ETA {0}", BreCalLists.TimeRefs[this.ShipcallModel.Shipcall.TimeRefPoint ?? 0]);
if(!string.IsNullOrEmpty(this.Times.Remarks)) if(!string.IsNullOrEmpty(this.Times.Remarks))
this.textBoxRemarks.Text = this.Times.Remarks; this.textBoxRemarks.Text = this.Times.Remarks;
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.MOORING)) if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.MOORING))
@ -309,8 +318,8 @@ namespace BreCalClient
{ {
this.comboBoxTug.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.TUG].ParticipantId; this.comboBoxTug.SelectedValue = this.ShipcallModel.AssignedParticipants[ParticipantType.TUG].ParticipantId;
} }
} }
} }
} }
@ -327,10 +336,10 @@ namespace BreCalClient
this.checkBoxCanceled.IsEnabled = _editing; this.checkBoxCanceled.IsEnabled = _editing;
this.checkBoxAnchored.IsEnabled = _editing; this.checkBoxAnchored.IsEnabled = _editing;
this.comboBoxTug.IsEnabled = _editing; this.comboBoxTug.IsEnabled = _editing;
this.integerUpDownRecommendedTugs.IsEnabled = _editing; this.integerUpDownRecommendedTugs.IsEnabled = _editing;
this.comboBoxPilot.IsEnabled = _editing; this.comboBoxPilot.IsEnabled = _editing;
this.comboBoxMooring.IsEnabled = _editing; this.comboBoxMooring.IsEnabled = _editing;
this.checkBoxMooredLock.IsEnabled = _editing; this.checkBoxMooredLock.IsEnabled = _editing;
@ -357,7 +366,7 @@ namespace BreCalClient
private void CheckOKButton() private void CheckOKButton()
{ {
this.buttonOK.IsEnabled = _editing && RequiredFieldsSet(); this.buttonOK.IsEnabled = _editing && RequiredFieldsSet() && !(this.ShipcallModel.Shipcall?.Canceled ?? false);
} }
#endregion #endregion
@ -392,17 +401,17 @@ namespace BreCalClient
{ {
this.comboBoxTerminal.SelectedIndex = -1; this.comboBoxTerminal.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.TERMINAL); this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.TERMINAL);
} }
private void contextMenuItemClearPierside_Click(object sender, RoutedEventArgs e) private void contextMenuItemClearPierside_Click(object sender, RoutedEventArgs e)
{ {
this.comboBoxPierside.SelectedIndex = -1; this.comboBoxPierside.SelectedIndex = -1;
} }
private void doubleUpDownDraft_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e) private void doubleUpDownDraft_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{ {
this.CheckOKButton(); this.CheckOKButton();
} }
private void datePickerETA_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e) private void datePickerETA_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{ {
@ -412,7 +421,7 @@ namespace BreCalClient
private void comboBoxArrivalBerth_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) private void comboBoxArrivalBerth_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{ {
this.CheckOKButton(); this.CheckOKButton();
} }
private void datePickerETA_End_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e) private void datePickerETA_End_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{ {

View File

@ -1,6 +1,6 @@
// Copyright (c) 2023 schick Informatik // Copyright (c) 2023 schick Informatik
// Description: Input control for outgoing shipcalls // Description: Input control for outgoing shipcalls
// //
using BreCalClient.misc.Model; using BreCalClient.misc.Model;
using System; using System;
@ -34,7 +34,7 @@ namespace BreCalClient
public ShipcallControlModel ShipcallModel { get; set; } = new(); public ShipcallControlModel ShipcallModel { get; set; } = new();
public Times Times { get; set; } = new(); public Times Times { get; set; } = new();
#endregion #endregion
@ -42,20 +42,27 @@ namespace BreCalClient
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
this.comboBoxMooring.ItemsSource = BreCalLists.Participants_Mooring;
this.comboBoxPilot.ItemsSource = BreCalLists.Participants_Pilot;
this.comboBoxTug.ItemsSource = BreCalLists.Participants_Tug;
this.comboBoxTerminal.ItemsSource = BreCalLists.Participants_Terminal;
this.comboBoxDepartureBerth.ItemsSource = BreCalLists.Berths; if ((this.ShipcallModel != null) && (this.ShipcallModel.Shipcall != null))
{
int portId = this.ShipcallModel.Shipcall.PortId;
this.comboBoxDepartureBerth.ItemsSource = BreCalLists.GetBerthsByPort(portId);
this.comboBoxMooring.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.MOORING);
this.comboBoxPilot.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.PILOT);
this.comboBoxTug.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.TUG);
this.comboBoxTerminal.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.TERMINAL);
}
this.CopyToControls(); this.CopyToControls();
this.Title = this.ShipcallModel.Title; this.Title = this.ShipcallModel?.Title;
Participant? p = null; Participant? p = null;
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY)) if (this.ShipcallModel != null)
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId); {
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId);
}
bool allowBSMD = false; bool allowBSMD = false;
if (p != null) if (p != null)
{ {
@ -66,10 +73,10 @@ namespace BreCalClient
(App.Participant.IsTypeFlagSet(ParticipantType.BSMD) && allowBSMD); (App.Participant.IsTypeFlagSet(ParticipantType.BSMD) && allowBSMD);
this.EnableControls(); this.EnableControls();
} }
private void buttonOK_Click(object sender, RoutedEventArgs e) private void buttonOK_Click(object sender, RoutedEventArgs e)
{ {
@ -107,7 +114,7 @@ namespace BreCalClient
message = BreCalClient.Resources.Resources.textETDInThePast; message = BreCalClient.Resources.Resources.textETDInThePast;
return false; return false;
} }
} }
if (this.datePickerETD.Value.HasValue && this.datePickerETD_End.Value.HasValue && this.datePickerETD.Value > this.datePickerETD_End.Value) if (this.datePickerETD.Value.HasValue && this.datePickerETD_End.Value.HasValue && this.datePickerETD.Value > this.datePickerETD_End.Value)
{ {
@ -122,7 +129,7 @@ namespace BreCalClient
message = BreCalClient.Resources.Resources.textTideTimesInThePast; message = BreCalClient.Resources.Resources.textTideTimesInThePast;
return false; return false;
} }
} }
if (this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue && this.datePickerTidalWindowFrom.Value > this.datePickerTidalWindowTo.Value) if (this.datePickerTidalWindowFrom.Value.HasValue && this.datePickerTidalWindowTo.Value.HasValue && this.datePickerTidalWindowFrom.Value > this.datePickerTidalWindowTo.Value)
{ {
@ -142,7 +149,8 @@ namespace BreCalClient
return false; return false;
} }
if((this.datePickerETD_End.Value.HasValue && !this.datePickerETD.Value.HasValue) || (this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue)) if((this.datePickerETD_End.Value.HasValue && !this.datePickerETD.Value.HasValue) ||
(this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue))
{ {
message = BreCalClient.Resources.Resources.textStartTimeMissing; message = BreCalClient.Resources.Resources.textStartTimeMissing;
return false; return false;
@ -168,16 +176,16 @@ namespace BreCalClient
} }
this.Times.BerthId = (int?)this.comboBoxDepartureBerth.SelectedValue; this.Times.BerthId = (int?)this.comboBoxDepartureBerth.SelectedValue;
this.Times.BerthInfo = this.textBoxBerthRemarks.Text.Trim(); this.Times.BerthInfo = this.textBoxBerthRemarks.Text.Trim();
this.ShipcallModel.Shipcall.Draft = (float?)this.doubleUpDownDraft.Value; this.ShipcallModel.Shipcall.Draft = (float?)this.doubleUpDownDraft.Value;
this.ShipcallModel.Shipcall.TidalWindowFrom = this.datePickerTidalWindowFrom.Value; this.ShipcallModel.Shipcall.TidalWindowFrom = this.datePickerTidalWindowFrom.Value;
this.ShipcallModel.Shipcall.TidalWindowTo = this.datePickerTidalWindowTo.Value; this.ShipcallModel.Shipcall.TidalWindowTo = this.datePickerTidalWindowTo.Value;
this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked; this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked;
this.ShipcallModel.Shipcall.RecommendedTugs = this.integerUpDownRecommendedTugs.Value; this.ShipcallModel.Shipcall.RecommendedTugs = this.integerUpDownRecommendedTugs.Value;
this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked; this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked;
this.ShipcallModel.Shipcall.RainSensitiveCargo = this.checkBoxRainsensitiveCargo.IsChecked; this.ShipcallModel.Shipcall.RainSensitiveCargo = this.checkBoxRainsensitiveCargo.IsChecked;
if(!string.IsNullOrEmpty(this.textBoxRemarks.Text.Trim())) if(!string.IsNullOrEmpty(this.textBoxRemarks.Text.Trim()))
@ -241,7 +249,7 @@ namespace BreCalClient
if (this.ShipcallModel.Shipcall.Etd != DateTime.MinValue) if (this.ShipcallModel.Shipcall.Etd != DateTime.MinValue)
this.datePickerETD.Value = this.ShipcallModel.Shipcall.Etd; this.datePickerETD.Value = this.ShipcallModel.Shipcall.Etd;
} }
this.datePickerETD_End.Value = this.Times.EtdIntervalEnd; this.datePickerETD_End.Value = this.Times.EtdIntervalEnd;
if (this.Times.BerthId.HasValue) if (this.Times.BerthId.HasValue)
@ -259,10 +267,10 @@ namespace BreCalClient
this.datePickerTidalWindowFrom.Value = this.ShipcallModel.Shipcall.TidalWindowFrom; this.datePickerTidalWindowFrom.Value = this.ShipcallModel.Shipcall.TidalWindowFrom;
this.datePickerTidalWindowTo.Value = this.ShipcallModel.Shipcall.TidalWindowTo; this.datePickerTidalWindowTo.Value = this.ShipcallModel.Shipcall.TidalWindowTo;
this.checkBoxCanceled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false; this.checkBoxCanceled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false;
this.integerUpDownRecommendedTugs.Value = this.ShipcallModel.Shipcall.RecommendedTugs; this.integerUpDownRecommendedTugs.Value = this.ShipcallModel.Shipcall.RecommendedTugs;
this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false; this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false;
this.checkBoxRainsensitiveCargo.IsChecked = this.ShipcallModel.Shipcall.RainSensitiveCargo ?? false; this.checkBoxRainsensitiveCargo.IsChecked = this.ShipcallModel.Shipcall.RainSensitiveCargo ?? false;
@ -270,7 +278,7 @@ namespace BreCalClient
if ((this.ShipcallModel.Shipcall.TimeRefPoint ?? 0) == 0) if ((this.ShipcallModel.Shipcall.TimeRefPoint ?? 0) == 0)
this.labelETD.Content = BreCalClient.Resources.Resources.textETDBerth; this.labelETD.Content = BreCalClient.Resources.Resources.textETDBerth;
else else
this.labelETD.Content = string.Format("ETD {0}", BreCalLists.TimeRefs[this.ShipcallModel.Shipcall.TimeRefPoint ?? 0]); this.labelETD.Content = string.Format("ETD {0}", BreCalLists.TimeRefs[this.ShipcallModel.Shipcall.TimeRefPoint ?? 0]);
if (!string.IsNullOrEmpty(this.Times.Remarks)) if (!string.IsNullOrEmpty(this.Times.Remarks))
this.textBoxRemarks.Text = this.Times.Remarks; this.textBoxRemarks.Text = this.Times.Remarks;
@ -321,9 +329,9 @@ namespace BreCalClient
this.datePickerTidalWindowTo.IsEnabled = _editing; this.datePickerTidalWindowTo.IsEnabled = _editing;
this.checkBoxCanceled.IsEnabled = _editing; this.checkBoxCanceled.IsEnabled = _editing;
this.comboBoxTug.IsEnabled = _editing; this.comboBoxTug.IsEnabled = _editing;
this.integerUpDownRecommendedTugs.IsEnabled = _editing; this.integerUpDownRecommendedTugs.IsEnabled = _editing;
this.comboBoxPilot.IsEnabled = _editing; this.comboBoxPilot.IsEnabled = _editing;
this.comboBoxMooring.IsEnabled = _editing; this.comboBoxMooring.IsEnabled = _editing;
this.checkBoxMooredLock.IsEnabled = _editing; this.checkBoxMooredLock.IsEnabled = _editing;
@ -348,7 +356,7 @@ namespace BreCalClient
private void CheckOKButton() private void CheckOKButton()
{ {
this.buttonOK.IsEnabled = _editing && RequiredFieldsSet(); this.buttonOK.IsEnabled = _editing && RequiredFieldsSet() && !(this.ShipcallModel.Shipcall?.Canceled ?? false);
} }
#endregion #endregion
@ -383,12 +391,12 @@ namespace BreCalClient
{ {
this.comboBoxTerminal.SelectedIndex = -1; this.comboBoxTerminal.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.TERMINAL); this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.TERMINAL);
} }
private void contextMenuItemClearPierside_Click(object sender, RoutedEventArgs e) private void contextMenuItemClearPierside_Click(object sender, RoutedEventArgs e)
{ {
this.comboBoxPierside.SelectedIndex = -1; this.comboBoxPierside.SelectedIndex = -1;
} }
private void datePickerETD_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e) private void datePickerETD_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{ {
@ -405,7 +413,7 @@ namespace BreCalClient
CheckOKButton(); CheckOKButton();
} }
#endregion #endregion
} }
} }

View File

@ -4,7 +4,9 @@
using BreCalClient.misc.Model; using BreCalClient.misc.Model;
using System; using System;
using System.Collections.Generic;
using System.Windows; using System.Windows;
using System.Windows.Documents;
using static BreCalClient.Extensions; using static BreCalClient.Extensions;
namespace BreCalClient namespace BreCalClient
@ -40,21 +42,30 @@ namespace BreCalClient
#region event handler #region event handler
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
this.comboBoxMooring.ItemsSource = BreCalLists.Participants_Mooring;
this.comboBoxPilot.ItemsSource = BreCalLists.Participants_Pilot;
this.comboBoxTug.ItemsSource = BreCalLists.Participants_Tug;
this.comboBoxTerminal.ItemsSource = BreCalLists.Participants_Terminal;
this.comboBoxDepartureBerth.ItemsSource = BreCalLists.Berths; if ((this.ShipcallModel != null) && (this.ShipcallModel.Shipcall != null))
this.comboBoxArrivalBerth.ItemsSource = BreCalLists.Berths; {
int portId = this.ShipcallModel.Shipcall.PortId;
List<Berth> availableBerths = BreCalLists.GetBerthsByPort(portId);
this.comboBoxArrivalBerth.ItemsSource = availableBerths;
this.comboBoxDepartureBerth.ItemsSource = availableBerths;
this.comboBoxMooring.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.MOORING);
this.comboBoxPilot.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.PILOT);
this.comboBoxTug.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.TUG);
this.comboBoxTerminal.ItemsSource = BreCalLists.GetParticipants(portId, ParticipantType.TERMINAL);
}
this.CopyToControls(); this.CopyToControls();
this.Title = this.ShipcallModel.Title;
Participant? p = null; Participant? p = null;
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId); if (this.ShipcallModel != null)
{
this.Title = this.ShipcallModel.Title;
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId);
}
bool allowBSMD = false; bool allowBSMD = false;
if (p != null) if (p != null)
@ -383,7 +394,7 @@ namespace BreCalClient
private void CheckOKButton() private void CheckOKButton()
{ {
this.buttonOK.IsEnabled = _editing && RequiredFieldsSet(); this.buttonOK.IsEnabled = _editing && RequiredFieldsSet() && !(this.ShipcallModel.Shipcall?.Canceled ?? false);
} }
#endregion #endregion

View File

@ -48,7 +48,7 @@ namespace BreCalClient
{ {
if (!CheckValues(out string message)) if (!CheckValues(out string message))
{ {
System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning,
MessageBoxButton.OK, MessageBoxImage.Warning); MessageBoxButton.OK, MessageBoxImage.Warning);
} }
else else
@ -173,13 +173,13 @@ namespace BreCalClient
{ {
this.textBoxRemarks.Text = this.Times.Remarks; this.textBoxRemarks.Text = this.Times.Remarks;
this.datePickerETABerth.Value = this.Times.EtaBerth; this.datePickerETABerth.Value = this.Times.EtaBerth;
if(this.datePickerETABerth.IsEnabled && (this.Times.EtaBerth == null) && (this.AgencyTimes?.EtaBerth != null) && (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival) && (this.AgencyTimes?.EtaBerth > DateTime.Now)) if(this.datePickerETABerth.IsEnabled && (this.Times.EtaBerth == null) && (this.AgencyTimes?.EtaBerth != null) && (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival) && (this.AgencyTimes?.EtaBerth > DateTime.Now.AddDays(-1)))
{ {
this.datePickerETABerth.Value = this.AgencyTimes.EtaBerth; this.datePickerETABerth.Value = this.AgencyTimes.EtaBerth;
if (this.datePickerETABerth.Template.FindName("PART_TextBox", this.datePickerETABerth) is WatermarkTextBox tb) { tb.Focus(); tb.SelectAll(); } if (this.datePickerETABerth.Template.FindName("PART_TextBox", this.datePickerETABerth) is WatermarkTextBox tb) { tb.Focus(); tb.SelectAll(); }
} }
this.datePickerETDBerth.Value = this.Times.EtdBerth; this.datePickerETDBerth.Value = this.Times.EtdBerth;
if(this.datePickerETDBerth.IsEnabled && (this.Times.EtdBerth == null) && (this.AgencyTimes?.EtdBerth != null) && ((ShipcallModel.Shipcall?.Type == ShipcallType.Departure) || (ShipcallModel.Shipcall?.Type == ShipcallType.Shifting)) && (this.AgencyTimes?.EtdBerth > DateTime.Now)) if(this.datePickerETDBerth.IsEnabled && (this.Times.EtdBerth == null) && (this.AgencyTimes?.EtdBerth != null) && ((ShipcallModel.Shipcall?.Type == ShipcallType.Departure) || (ShipcallModel.Shipcall?.Type == ShipcallType.Shifting)) && (this.AgencyTimes?.EtdBerth > DateTime.Now.AddDays(-1)))
{ {
this.datePickerETDBerth.Value = this.AgencyTimes.EtdBerth; this.datePickerETDBerth.Value = this.AgencyTimes.EtdBerth;
if (this.datePickerETDBerth.Template.FindName("PART_TextBox", this.datePickerETDBerth) is WatermarkTextBox tb) tb.SelectAll(); if (this.datePickerETDBerth.Template.FindName("PART_TextBox", this.datePickerETDBerth) is WatermarkTextBox tb) tb.SelectAll();
@ -190,14 +190,14 @@ namespace BreCalClient
this.datePickerATA.Value = this.Times.Ata; this.datePickerATA.Value = this.Times.Ata;
this.datePickerATD.Value = this.Times.Atd; this.datePickerATD.Value = this.Times.Atd;
this.datePickerETABerth_End.Value = this.Times.EtaIntervalEnd; this.datePickerETABerth_End.Value = this.Times.EtaIntervalEnd;
if (this.datePickerETABerth_End.IsEnabled && (this.Times.EtaIntervalEnd == null) && (this.Times.EtaBerth == null) && (this.AgencyTimes?.EtaIntervalEnd != null) && (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival)) if (this.datePickerETABerth_End.IsEnabled && (this.Times.EtaIntervalEnd == null) && (this.Times.EtaBerth == null) && (this.AgencyTimes?.EtaIntervalEnd != null) && (ShipcallModel.Shipcall?.Type == ShipcallType.Arrival) && (this.AgencyTimes?.EtaBerth > DateTime.Now.AddDays(-1)))
{ {
this.datePickerETABerth_End.Value = this.AgencyTimes.EtaIntervalEnd; this.datePickerETABerth_End.Value = this.AgencyTimes.EtaIntervalEnd;
//if (this.datePickerETABerth_End.Template.FindName("PART_TextBox", this.datePickerETABerth_End) is WatermarkTextBox tb) { tb.Focus(); tb.SelectAll(); } //if (this.datePickerETABerth_End.Template.FindName("PART_TextBox", this.datePickerETABerth_End) is WatermarkTextBox tb) { tb.Focus(); tb.SelectAll(); }
} }
this.datePickerETDBerth_End.Value = this.Times.EtdIntervalEnd; this.datePickerETDBerth_End.Value = this.Times.EtdIntervalEnd;
if (this.datePickerETDBerth_End.IsEnabled && (this.Times.EtdIntervalEnd == null) && (this.Times.EtdBerth == null) && (this.AgencyTimes?.EtdIntervalEnd != null) && ((ShipcallModel.Shipcall?.Type == ShipcallType.Departure) || (ShipcallModel.Shipcall?.Type == ShipcallType.Shifting))) if (this.datePickerETDBerth_End.IsEnabled && (this.Times.EtdIntervalEnd == null) && (this.Times.EtdBerth == null) && (this.AgencyTimes?.EtdIntervalEnd != null) && ((ShipcallModel.Shipcall?.Type == ShipcallType.Departure) || (ShipcallModel.Shipcall?.Type == ShipcallType.Shifting)) && (this.AgencyTimes?.EtdBerth > DateTime.Now.AddDays(-1)))
{ {
this.datePickerETDBerth_End.Value = this.AgencyTimes.EtdIntervalEnd; this.datePickerETDBerth_End.Value = this.AgencyTimes.EtdIntervalEnd;
//if (this.datePickerETDBerth_End.Template.FindName("PART_TextBox", this.datePickerETDBerth_End) is WatermarkTextBox tb) { tb.Focus(); tb.SelectAll(); } //if (this.datePickerETDBerth_End.Template.FindName("PART_TextBox", this.datePickerETDBerth_End) is WatermarkTextBox tb) { tb.Focus(); tb.SelectAll(); }
@ -259,7 +259,7 @@ namespace BreCalClient
// setting en/dis-abled // setting en/dis-abled
if (this.Times.ParticipantId != App.Participant.Id) if ((this.Times.ParticipantId != App.Participant.Id) || (this.ShipcallModel.Shipcall?.Canceled ?? false))
{ {
this.buttonFixedOrder.IsEnabled = false; this.buttonFixedOrder.IsEnabled = false;
this.buttonOK.IsEnabled = false; this.buttonOK.IsEnabled = false;

View File

@ -1,6 +1,6 @@
// Copyright (c) 2023 schick Informatik // Copyright (c) 2023 schick Informatik
// Description: Terminals have all different fields so a different dialog // Description: Terminals have all different fields so a different dialog
// //
using BreCalClient.misc.Model; using BreCalClient.misc.Model;
using System; using System;
@ -30,7 +30,10 @@ namespace BreCalClient
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
this.comboBoxBerth.ItemsSource = BreCalLists.Berths; if ((this.ShipcallModel != null) && (this.ShipcallModel.Shipcall != null))
this.comboBoxBerth.ItemsSource = BreCalLists.GetBerthsByPort(this.ShipcallModel.Shipcall.PortId);
else
this.comboBoxBerth.ItemsSource = BreCalLists.Berths;
this.CopyToControls(); this.CopyToControls();
this.EnableControls(); this.EnableControls();
} }
@ -115,7 +118,7 @@ namespace BreCalClient
message = BreCalClient.Resources.Resources.textOperationStartInThePast; message = BreCalClient.Resources.Resources.textOperationStartInThePast;
return false; return false;
} }
} }
if (this.datePickerOperationStart.Value.HasValue && this.datePickerOperationStart_End.Value.HasValue && this.datePickerOperationStart.Value > this.datePickerOperationStart_End.Value) if (this.datePickerOperationStart.Value.HasValue && this.datePickerOperationStart_End.Value.HasValue && this.datePickerOperationStart.Value > this.datePickerOperationStart_End.Value)
{ {
@ -130,7 +133,7 @@ namespace BreCalClient
message = BreCalClient.Resources.Resources.textOperationEndInThePast; message = BreCalClient.Resources.Resources.textOperationEndInThePast;
return false; return false;
} }
} }
if (this.datePickerOperationEnd.Value.HasValue && this.datePickerOperationEnd_End.Value.HasValue && this.datePickerOperationEnd.Value > this.datePickerOperationEnd_End.Value) if (this.datePickerOperationEnd.Value.HasValue && this.datePickerOperationEnd_End.Value.HasValue && this.datePickerOperationEnd.Value > this.datePickerOperationEnd_End.Value)
{ {
@ -187,7 +190,7 @@ namespace BreCalClient
case ShipcallType.Arrival: case ShipcallType.Arrival:
this.labelEnd.Visibility = Visibility.Hidden; this.labelEnd.Visibility = Visibility.Hidden;
this.datePickerOperationEnd.Visibility = Visibility.Hidden; this.datePickerOperationEnd.Visibility = Visibility.Hidden;
this.datePickerOperationEnd_End.Visibility = Visibility.Hidden; this.datePickerOperationEnd_End.Visibility = Visibility.Hidden;
this.rowEnd.Height = new(0); this.rowEnd.Height = new(0);
break; break;
case ShipcallType.Departure: case ShipcallType.Departure:
@ -200,7 +203,7 @@ namespace BreCalClient
this.textBoxBerthRemarks.Visibility = Visibility.Hidden; this.textBoxBerthRemarks.Visibility = Visibility.Hidden;
break; break;
case ShipcallType.Shifting: case ShipcallType.Shifting:
this.rowStart.Height = new(0); this.rowStart.Height = new(0);
this.labelBerth.Visibility = Visibility.Hidden; this.labelBerth.Visibility = Visibility.Hidden;
this.comboBoxBerth.Visibility = Visibility.Hidden; this.comboBoxBerth.Visibility = Visibility.Hidden;
this.labelPierside.Visibility = Visibility.Hidden; this.labelPierside.Visibility = Visibility.Hidden;
@ -214,7 +217,7 @@ namespace BreCalClient
private void EnableControls() private void EnableControls()
{ {
if (this.Times.ParticipantId != App.Participant.Id) if ((this.Times.ParticipantId != App.Participant.Id) || (this.ShipcallModel.Shipcall?.Canceled ?? false))
{ {
this.buttonOK.IsEnabled = false; this.buttonOK.IsEnabled = false;
return; return;
@ -229,10 +232,10 @@ namespace BreCalClient
this.textBoxBerthRemarks.IsReadOnly = ShipcallModel.Shipcall?.Type != ShipcallType.Arrival; this.textBoxBerthRemarks.IsReadOnly = ShipcallModel.Shipcall?.Type != ShipcallType.Arrival;
this.textBoxRemarks.IsReadOnly = false; this.textBoxRemarks.IsReadOnly = false;
this.buttonClearAll.IsEnabled = true; this.buttonClearAll.IsEnabled = true;
} }
#endregion #endregion
} }
} }

View File

@ -73,9 +73,19 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Button Margin="2" Grid.Column="0" Content="{x:Static p:Resources.textNewDots}" x:Name="buttonNew" Visibility="Hidden" Click="buttonNew_Click" Background="Transparent"/> <Button Margin="2" Grid.Column="0" Content="{x:Static p:Resources.textNewDots}" x:Name="buttonNew" Visibility="Hidden" Click="buttonNew_Click" Background="Transparent"/>
<Label Content="{x:Static p:Resources.textSortOrder}" Grid.Column="1" HorizontalContentAlignment="Right"/> <Label Content="{x:Static p:Resources.textSortOrder}" Grid.Column="1" HorizontalContentAlignment="Right"/>
<ComboBox x:Name="comboBoxSortOrder" Margin="2" Grid.Column="2" SelectionChanged="comboBoxSortOrder_SelectionChanged" /> <Grid Grid.Column="2" Grid.Row="1">
<CheckBox x:Name="checkboxShowCancelledCalls" Grid.Column="3" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="2" Checked="checkboxShowCancelledCalls_Checked" Unchecked="checkboxShowCancelledCalls_Checked" /> <Grid.ColumnDefinitions>
<Label Content="{x:Static p:Resources.textShowCancelledShipcalls}" Grid.Column="4" /> <ColumnDefinition Width=".5*" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions>
<ComboBox x:Name="comboBoxSortOrder" Margin="2" Grid.Column="0" SelectionChanged="comboBoxSortOrder_SelectionChanged" />
<CheckBox x:Name="checkboxShowCancelledCalls" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="2" Checked="checkboxShowCancelledCalls_Checked" Unchecked="checkboxShowCancelledCalls_Checked" />
<Label Content="{x:Static p:Resources.textShowCancelledShipcalls}" Grid.Column="2" />
</Grid>
<Label Content="{x:Static p:Resources.textHarbour}" Grid.Column="3" HorizontalAlignment="Right" />
<xctk:CheckComboBox x:Name="comboBoxPorts" Margin="2" Grid.Column="4" ItemSelectionChanged="comboBoxPorts_ItemSelectionChanged" DisplayMemberPath="Name" />
<Button Margin="2" Grid.Column="6" Content="{x:Static p:Resources.textClearFilters}" x:Name="buttonClearFilter" Click="buttonClearFilter_Click" Background="Transparent" /> <Button Margin="2" Grid.Column="6" Content="{x:Static p:Resources.textClearFilters}" x:Name="buttonClearFilter" Click="buttonClearFilter_Click" Background="Transparent" />
</Grid> </Grid>
<Grid Grid.Row="2"> <Grid Grid.Row="2">

View File

@ -25,6 +25,7 @@ using System.Net;
using System.Windows.Input; using System.Windows.Input;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System.Linq;
namespace BreCalClient namespace BreCalClient
@ -167,15 +168,25 @@ namespace BreCalClient
{ {
if (_loginResult.Id > 0) if (_loginResult.Id > 0)
{ {
Mouse.OverrideCursor = Cursors.Wait; Mouse.OverrideCursor = Cursors.Wait;
this.busyIndicator.IsBusy = false;
this._userApi.Configuration.ApiKey["Authorization"] = _loginResult.Token; this._userApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this._shipcallApi.Configuration.ApiKey["Authorization"] = _loginResult.Token; this._shipcallApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this._timesApi.Configuration.ApiKey["Authorization"] = _loginResult.Token; this._timesApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this._staticApi.Configuration.ApiKey["Authorization"] = _loginResult.Token; this._staticApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this._shipApi.Configuration.ApiKey["Authorization"] = _loginResult.Token; this._shipApi.Configuration.ApiKey["Authorization"] = _loginResult.Token;
this.LoadStaticLists(); bool loadingSuccessful = await this.LoadStaticLists();
this.labelUsername.Text = $"{_loginResult.FirstName} {_loginResult.LastName}"; if (loadingSuccessful)
{
this.labelUsername.Text = $"{_loginResult.FirstName} {_loginResult.LastName}";
this.busyIndicator.IsBusy = false;
}
else
{
Mouse.OverrideCursor = null;
textUsername.Text = "";
textPassword.Password = "";
textUsername.Focus();
}
} }
} }
labelGeneralStatus.Text = $"Connection {ConnectionStatus.SUCCESSFUL}"; labelGeneralStatus.Text = $"Connection {ConnectionStatus.SUCCESSFUL}";
@ -184,10 +195,10 @@ namespace BreCalClient
{ {
if ((ex.ErrorContent != null && ((string)ex.ErrorContent).StartsWith("{"))) { if ((ex.ErrorContent != null && ((string)ex.ErrorContent).StartsWith("{"))) {
Error? anError = JsonConvert.DeserializeObject<Error>((string)ex.ErrorContent); Error? anError = JsonConvert.DeserializeObject<Error>((string)ex.ErrorContent);
if ((anError != null) && anError.Message.Equals("invalid credentials")) if ((anError != null) && anError.ErrorField.Equals("invalid credentials"))
this.labelLoginResult.Content = BreCalClient.Resources.Resources.textWrongCredentials; this.labelLoginResult.Content = BreCalClient.Resources.Resources.textWrongCredentials;
else else
this.labelLoginResult.Content = anError?.Message ?? ex.Message; this.labelLoginResult.Content = anError?.ErrorField ?? ex.Message;
} }
else { else {
this.labelLoginResult.Content = ex.Message; this.labelLoginResult.Content = ex.Message;
@ -246,7 +257,8 @@ namespace BreCalClient
EditShipcallControl esc = new() EditShipcallControl esc = new()
{ {
ShipEditingEnabled = App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD), ShipEditingEnabled = App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD),
ShipApi = _shipApi ShipApi = _shipApi,
IsCreate = true
}; };
if (model != null) if (model != null)
esc.ShipcallModel = model; esc.ShipcallModel = model;
@ -287,7 +299,9 @@ namespace BreCalClient
} }
}; };
scmOut.Shipcall.ShipId = esc.ShipcallModel.Shipcall.ShipId; scmOut.Shipcall.ShipId = esc.ShipcallModel.Shipcall.ShipId;
scmOut.Shipcall.PortId = esc.ShipcallModel.Shipcall.PortId;
scmOut.Ship = esc.ShipcallModel.Ship; scmOut.Ship = esc.ShipcallModel.Ship;
scmOut.AllowPortChange = false;
DateTime eta = esc.ShipcallModel.Shipcall?.Eta ?? DateTime.Now; DateTime eta = esc.ShipcallModel.Shipcall?.Eta ?? DateTime.Now;
scmOut.Shipcall.Etd = eta.AddDays(2); scmOut.Shipcall.Etd = eta.AddDays(2);
scmOut.Shipcall.DepartureBerthId = esc.ShipcallModel.Shipcall?.ArrivalBerthId; scmOut.Shipcall.DepartureBerthId = esc.ShipcallModel.Shipcall?.ArrivalBerthId;
@ -337,7 +351,7 @@ namespace BreCalClient
{ {
this.Dispatcher.Invoke(new Action(() => this.Dispatcher.Invoke(new Action(() =>
{ {
ShowErrorDialog(ex.Message, "Error saving user information"); ShowErrorDialog(ex.Message, "Error saving user information");
})); }));
} }
} }
@ -349,6 +363,7 @@ namespace BreCalClient
{ {
this.searchFilterControl.ClearFilters(); this.searchFilterControl.ClearFilters();
this.checkboxShowCancelledCalls.IsChecked = false; this.checkboxShowCancelledCalls.IsChecked = false;
this.comboBoxPorts.UnSelectAll();
this.FilterShipcalls(); this.FilterShipcalls();
} }
@ -366,6 +381,24 @@ namespace BreCalClient
this.SearchFilterControl_SearchFilterChanged(); this.SearchFilterControl_SearchFilterChanged();
} }
private void comboBoxPorts_ItemSelectionChanged(object sender, Xceed.Wpf.Toolkit.Primitives.ItemSelectionChangedEventArgs e)
{
this.searchFilterControl.SearchFilter.Ports.Clear();
List<Berth> berths = new List<Berth>();
foreach (Port port in comboBoxPorts.SelectedItems)
{
this.searchFilterControl.SearchFilter.Ports.Add(port.Id);
berths.AddRange(BreCalLists.GetBerthsByPort(port.Id));
}
// create list of berths from selected port(s) or return all berths
if (berths.Count == 0)
berths = BreCalLists.AllBerths;
this.searchFilterControl.SetBerths(berths);
this.SearchFilterControl_SearchFilterChanged();
}
private async void comboBoxSortOrder_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) private async void comboBoxSortOrder_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{ {
_sortOrder = (Extensions.SortOrder) this.comboBoxSortOrder.SelectedIndex; _sortOrder = (Extensions.SortOrder) this.comboBoxSortOrder.SelectedIndex;
@ -405,14 +438,22 @@ namespace BreCalClient
#region network operations #region network operations
private async void LoadStaticLists() private async Task<bool> LoadStaticLists()
{ {
if (_loginResult == null) return; if (_loginResult == null) return false;
BreCalLists.InitializePorts(await _staticApi.GetPortsAsync());
BreCalLists.InitializeBerths(await _staticApi.BerthsGetAsync()); BreCalLists.InitializeBerths(await _staticApi.BerthsGetAsync());
BreCalLists.InitializeShips(await _shipApi.ShipsGetAsync()); BreCalLists.InitializeShips(await _shipApi.ShipsGetAsync());
BreCalLists.InitializeParticipants(await _staticApi.ParticipantsGetAsync()); BreCalLists.InitializeParticipants(await _staticApi.ParticipantsGetAsync());
if(BreCalLists.Participants.Count == 0)
{
MessageBox.Show(BreCalClient.Resources.Resources.textNoPortAssigned, BreCalClient.Resources.Resources.textError, MessageBoxButton.OK, MessageBoxImage.Error);
Mouse.OverrideCursor = Cursors.Wait;
return false;
}
this.searchFilterControl.SetBerths(BreCalLists.Berths); this.searchFilterControl.SetBerths(BreCalLists.Berths);
foreach (Participant participant in BreCalLists.Participants) foreach (Participant participant in BreCalLists.Participants)
@ -448,18 +489,27 @@ namespace BreCalClient
SearchFilterModel.filterMap[_loginResult.Id] = currentFilter; SearchFilterModel.filterMap[_loginResult.Id] = currentFilter;
} }
this.searchFilterControl.SetFilterFromModel(currentFilter); this.searchFilterControl.SetFilterFromModel(currentFilter);
if ((currentFilter.Ports != null) && (this.comboBoxPorts.ItemsSource != null))
{
foreach (Port p in this.comboBoxPorts.ItemsSource)
{
if (currentFilter.Ports.Contains(p.Id)) this.comboBoxPorts.SelectedItems.Add(p);
}
}
} }
_ = Task.Run(() => RefreshShipcalls()); _ = Task.Run(() => RefreshShipcalls());
_ = Task.Run(() => RefreshShips()); _ = Task.Run(() => RefreshShips());
return true;
} }
public async Task RefreshShips() public async Task RefreshShips()
{ {
while (true) while (true)
{ {
Thread.Sleep(SHIPS_UPDATE_INTERVAL_SECONDS * 1000); Thread.Sleep(SHIPS_UPDATE_INTERVAL_SECONDS * 1000);
BreCalLists.InitializeShips(await _shipApi.ShipsGetAsync()); BreCalLists.InitializeShips(await _shipApi.ShipsGetAsync());
} }
@ -604,7 +654,7 @@ namespace BreCalClient
{ {
ShipcallControl sc = new() ShipcallControl sc = new()
{ {
Height = 135, Height = 145,
ShipcallControlModel = scm ShipcallControlModel = scm
}; };
sc.EditTimesRequested += Sc_EditTimesRequested; sc.EditTimesRequested += Sc_EditTimesRequested;
@ -674,7 +724,7 @@ namespace BreCalClient
// first add everything // first add everything
this._visibleControlModels.AddRange(_allShipcallsDict.Values); this._visibleControlModels.AddRange(_allShipcallsDict.Values);
// now remove elements whose filter criteria are met // now remove elements whose filter criteria are met
if(sfm.Berths.Count > 0 ) if(sfm.Berths.Count > 0 )
{ {
@ -700,6 +750,11 @@ namespace BreCalClient
_ = this._visibleControlModels.RemoveAll(x => { if (x.Shipcall == null) return false; else return !sfm.Categories.Contains(x.Shipcall.Type); }); _ = this._visibleControlModels.RemoveAll(x => { if (x.Shipcall == null) return false; else return !sfm.Categories.Contains(x.Shipcall.Type); });
} }
if(sfm.Ports.Count > 0 )
{
_ = this._visibleControlModels.RemoveAll(x => { if(x.Shipcall == null) return false; else return !sfm.Ports.Contains(x.Shipcall.PortId); });
}
if(!string.IsNullOrEmpty(sfm.SearchString)) if(!string.IsNullOrEmpty(sfm.SearchString))
{ {
_ = this._visibleControlModels.RemoveAll(x => !(x.ContainsRemarkText(sfm.SearchString) || (x.Ship?.Name.Contains(sfm.SearchString, StringComparison.InvariantCultureIgnoreCase) ?? false))); _ = this._visibleControlModels.RemoveAll(x => !(x.ContainsRemarkText(sfm.SearchString) || (x.Ship?.Name.Contains(sfm.SearchString, StringComparison.InvariantCultureIgnoreCase) ?? false)));
@ -1085,30 +1140,32 @@ namespace BreCalClient
if (msg.error_field != null) if (msg.error_field != null)
{ {
caption = $"{caption}: {msg.error_field}"; caption = $"{caption}: {msg.error_field}";
} }
if(msg.error_description != null) if(msg.error_description != null)
{ {
message = msg.error_description; message = msg.error_description;
} }
} }
} }
catch (Exception) { } catch (Exception) { }
} }
_log.ErrorFormat("{0} - {1}", caption, message); _log.ErrorFormat("{0} - {1}", caption, message);
Dispatcher.Invoke(new Action(() => Dispatcher.Invoke(new Action(() =>
{ {
MessageBox.Show(message, caption, MessageBoxButton.OK, MessageBoxImage.Error); MessageBox.Show(message, caption, MessageBoxButton.OK, MessageBoxImage.Error);
})); }));
} }
private void EnableControlsForParticipant() private void EnableControlsForParticipant()
{ {
if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD)) if (App.Participant.IsTypeFlagSet(Extensions.ParticipantType.BSMD))
this.buttonNew.Visibility = Visibility.Visible; this.buttonNew.Visibility = Visibility.Visible;
this.comboBoxPorts.ItemsSource = BreCalLists.AllPorts.Where(x => App.Participant.Ports.Contains(x.Id));
} }
private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)

View File

@ -4,8 +4,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
--> -->
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<ApplicationRevision>1</ApplicationRevision> <ApplicationRevision>2</ApplicationRevision>
<ApplicationVersion>1.4.1.0</ApplicationVersion> <ApplicationVersion>1.6.0.4</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled> <BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Debug</Configuration> <Configuration>Debug</Configuration>
<CreateDesktopShortcut>True</CreateDesktopShortcut> <CreateDesktopShortcut>True</CreateDesktopShortcut>
@ -36,8 +36,10 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<TargetFramework>net6.0-windows</TargetFramework> <TargetFramework>net6.0-windows</TargetFramework>
<UpdateEnabled>True</UpdateEnabled> <UpdateEnabled>True</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode> <UpdateMode>Foreground</UpdateMode>
<UpdateRequired>False</UpdateRequired> <UpdateRequired>True</UpdateRequired>
<WebPageFileName>Publish.html</WebPageFileName> <WebPageFileName>Publish.html</WebPageFileName>
<MinimumRequiredVersion>1.6.0.4</MinimumRequiredVersion>
<SkipPublishVerification>false</SkipPublishVerification>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PublishFile Include="containership.ico"> <PublishFile Include="containership.ico">

View File

@ -4,8 +4,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
--> -->
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<ApplicationRevision>12</ApplicationRevision> <ApplicationRevision>8</ApplicationRevision>
<ApplicationVersion>1.5.0.12</ApplicationVersion> <ApplicationVersion>1.6.0.8</ApplicationVersion>
<BootstrapperEnabled>False</BootstrapperEnabled> <BootstrapperEnabled>False</BootstrapperEnabled>
<Configuration>Release</Configuration> <Configuration>Release</Configuration>
<CreateWebPageOnPublish>True</CreateWebPageOnPublish> <CreateWebPageOnPublish>True</CreateWebPageOnPublish>
@ -30,7 +30,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<TargetFramework>net6.0-windows</TargetFramework> <TargetFramework>net6.0-windows</TargetFramework>
<UpdateEnabled>True</UpdateEnabled> <UpdateEnabled>True</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode> <UpdateMode>Foreground</UpdateMode>
<UpdateRequired>False</UpdateRequired> <UpdateRequired>True</UpdateRequired>
<WebPageFileName>Publish.html</WebPageFileName> <WebPageFileName>Publish.html</WebPageFileName>
<CreateDesktopShortcut>True</CreateDesktopShortcut> <CreateDesktopShortcut>True</CreateDesktopShortcut>
<ErrorReportUrl>https://www.bsmd-emswe.eu/</ErrorReportUrl> <ErrorReportUrl>https://www.bsmd-emswe.eu/</ErrorReportUrl>
@ -39,6 +39,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<SuiteName>Bremen calling client</SuiteName> <SuiteName>Bremen calling client</SuiteName>
<SupportUrl>https://www.bsmd-emswe.eu/</SupportUrl> <SupportUrl>https://www.bsmd-emswe.eu/</SupportUrl>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SkipPublishVerification>false</SkipPublishVerification>
<MinimumRequiredVersion>1.6.0.8</MinimumRequiredVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PublishFile Include="containership.ico"> <PublishFile Include="containership.ico">

View File

@ -4,8 +4,8 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
--> -->
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<ApplicationRevision>8</ApplicationRevision> <ApplicationRevision>6</ApplicationRevision>
<ApplicationVersion>1.5.0.8</ApplicationVersion> <ApplicationVersion>1.6.0.8</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled> <BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Debug</Configuration> <Configuration>Debug</Configuration>
<CreateDesktopShortcut>True</CreateDesktopShortcut> <CreateDesktopShortcut>True</CreateDesktopShortcut>
@ -30,7 +30,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<TargetFramework>net6.0-windows</TargetFramework> <TargetFramework>net6.0-windows</TargetFramework>
<UpdateEnabled>True</UpdateEnabled> <UpdateEnabled>True</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode> <UpdateMode>Foreground</UpdateMode>
<UpdateRequired>False</UpdateRequired> <UpdateRequired>True</UpdateRequired>
<WebPageFileName>Publish.html</WebPageFileName> <WebPageFileName>Publish.html</WebPageFileName>
<CreateDesktopShortcut>True</CreateDesktopShortcut> <CreateDesktopShortcut>True</CreateDesktopShortcut>
<ErrorReportUrl>https://www.bsmd-emswe.eu/</ErrorReportUrl> <ErrorReportUrl>https://www.bsmd-emswe.eu/</ErrorReportUrl>
@ -41,6 +41,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<PublishDir>bin\Debug\net6.0-windows\win-x64\app.publish\</PublishDir> <PublishDir>bin\Debug\net6.0-windows\win-x64\app.publish\</PublishDir>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SkipPublishVerification>false</SkipPublishVerification> <SkipPublishVerification>false</SkipPublishVerification>
<MinimumRequiredVersion>1.6.0.8</MinimumRequiredVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PublishFile Include="containership.ico"> <PublishFile Include="containership.ico">

View File

@ -749,6 +749,15 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Harbour.
/// </summary>
public static string textHarbour {
get {
return ResourceManager.GetString("textHarbour", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Incoming. /// Looks up a localized string similar to Incoming.
/// </summary> /// </summary>
@ -866,6 +875,15 @@ namespace BreCalClient.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to No port assigned to this participant.
/// </summary>
public static string textNoPortAssigned {
get {
return ResourceManager.GetString("textNoPortAssigned", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Not rotated. /// Looks up a localized string similar to Not rotated.
/// </summary> /// </summary>

View File

@ -286,6 +286,9 @@
<data name="textFrom" xml:space="preserve"> <data name="textFrom" xml:space="preserve">
<value>von</value> <value>von</value>
</data> </data>
<data name="textHarbour" xml:space="preserve">
<value>Hafen</value>
</data>
<data name="textIncoming" xml:space="preserve"> <data name="textIncoming" xml:space="preserve">
<value>Einkommend</value> <value>Einkommend</value>
</data> </data>
@ -550,4 +553,7 @@
<data name="textStartTimeMissing" xml:space="preserve"> <data name="textStartTimeMissing" xml:space="preserve">
<value>Wenn eine Ende-Zeit angegeben wird, muss auch eine Start-Zeit angegeben werden</value> <value>Wenn eine Ende-Zeit angegeben wird, muss auch eine Start-Zeit angegeben werden</value>
</data> </data>
<data name="textNoPortAssigned" xml:space="preserve">
<value>Es ist keine Hafenzuordnung vorhanden</value>
</data>
</root> </root>

View File

@ -328,6 +328,9 @@
<data name="textFrom" xml:space="preserve"> <data name="textFrom" xml:space="preserve">
<value>from</value> <value>from</value>
</data> </data>
<data name="textHarbour" xml:space="preserve">
<value>Harbour</value>
</data>
<data name="textIncoming" xml:space="preserve"> <data name="textIncoming" xml:space="preserve">
<value>Incoming</value> <value>Incoming</value>
</data> </data>
@ -598,4 +601,7 @@
<data name="textStartTimeMissing" xml:space="preserve"> <data name="textStartTimeMissing" xml:space="preserve">
<value>If an end time is set, a start time is also required</value> <value>If an end time is set, a start time is also required</value>
</data> </data>
<data name="textNoPortAssigned" xml:space="preserve">
<value>No port assigned to this participant</value>
</data>
</root> </root>

View File

@ -25,6 +25,8 @@ namespace BreCalClient
public List<int> Berths { get; set; } = new(); public List<int> Berths { get; set; } = new();
public List<int> Ports { get; set; } = new();
public string? SearchString { get; set; } public string? SearchString { get; set; }
public double? ShipLengthFrom { get; set; } public double? ShipLengthFrom { get; set; }

View File

@ -20,12 +20,12 @@
<ColumnDefinition Width=".15*" /> <ColumnDefinition Width=".15*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="32" /> <RowDefinition Height="42" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" > <Grid Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" >
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="32"/> <RowDefinition Height="42"/>
<RowDefinition Height=".125*"/> <RowDefinition Height=".125*"/>
<RowDefinition Height=".125*"/> <RowDefinition Height=".125*"/>
<RowDefinition Height=".125*"/> <RowDefinition Height=".125*"/>
@ -76,39 +76,76 @@
<TextBlock x:Name="textBlockLengthWidth" Padding="0"/> <TextBlock x:Name="textBlockLengthWidth" Padding="0"/>
</Viewbox> </Viewbox>
<Viewbox Grid.Row="4" Grid.Column="0" HorizontalAlignment="Left"> <Viewbox Grid.Row="4" Grid.Column="0" HorizontalAlignment="Left">
<TextBlock Text="{x:Static p:Resources.textDraft}" Padding="0" />
</Viewbox>
<Viewbox Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left">
<TextBlock x:Name="textBlockDraft" Padding="0"/>
</Viewbox>
<Viewbox Grid.Row="5" Grid.Column="0" HorizontalAlignment="Left">
<TextBlock Text="ETA" x:Name="labelETA"/> <TextBlock Text="ETA" x:Name="labelETA"/>
</Viewbox> </Viewbox>
<Viewbox Grid.Row="5" Grid.Column="1" HorizontalAlignment="Left"> <Viewbox Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left">
<TextBlock x:Name="textBlockETA" Padding="0" FontWeight="DemiBold"/> <TextBlock x:Name="textBlockETA" Padding="0" FontWeight="DemiBold"/>
</Viewbox> </Viewbox>
<Viewbox Grid.Row="6" Grid.Column="0" HorizontalAlignment="Left"> <Viewbox Grid.Row="5" Grid.Column="0" HorizontalAlignment="Left">
<TextBlock Text="{x:Static p:Resources.textBerth}"/> <TextBlock Text="{x:Static p:Resources.textBerth}"/>
</Viewbox> </Viewbox>
<Viewbox Grid.Row="6" Grid.Column="1" HorizontalAlignment="Left"> <Viewbox Grid.Row="5" Grid.Column="1" HorizontalAlignment="Left">
<TextBlock x:Name="textBlockBerth" Padding="0" FontWeight="DemiBold" /> <TextBlock x:Name="textBlockBerth" Padding="0" FontWeight="DemiBold" />
</Viewbox> </Viewbox>
<Viewbox Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Left">
<TextBlock x:Name="textBlockHarbour" Padding="0" FontWeight="DemiBold" />
</Viewbox>
</Grid> </Grid>
<Grid Grid.Row="0" Grid.Column="1">
<Label Grid.Row="0" Grid.Column="1" Grid.RowSpan="1" FontSize="12" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch" <Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="14" />
</Grid.RowDefinitions>
<Label Padding="0" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" FontSize="13" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelAgent" PreviewMouseUp="labelAgent_PreviewMouseUp"/> HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelAgent" PreviewMouseUp="labelAgent_PreviewMouseUp"/>
<Label Grid.Row="0" Grid.Column="2" Grid.RowSpan="1" FontSize="12" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch" <Label Name="labelLastChangeAgency" FontSize="9" Padding="0" VerticalContentAlignment="Center" Grid.Row="1" Foreground="WhiteSmoke" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" HorizontalContentAlignment="Center" />
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelMooring" PreviewMouseUp="labelMooring_PreviewMouseUp"/> </Grid>
<Label Grid.Row="0" Grid.Column="3" Grid.RowSpan="1" FontSize="12" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch" <Grid Grid.Row="0" Grid.Column="2">
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelPortAuthority" PreviewMouseUp="labelPortAuthority_PreviewMouseUp" /> <Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="4" Grid.RowSpan="1" FontSize="12" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch" <RowDefinition Height="*" />
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelPilot" PreviewMouseUp="labelPilot_PreviewMouseUp"/> <RowDefinition Height="14" />
<Label Grid.Row="0" Grid.Column="5" Grid.RowSpan="1" FontSize="12" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch" </Grid.RowDefinitions>
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelTug" PreviewMouseUp="labelTug_PreviewMouseUp"/> <Label Padding="0" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" FontSize="13" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
<Label Grid.Row="0" Grid.Column="6" Grid.RowSpan="1" FontSize="12" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelMooring" PreviewMouseUp="labelMooring_PreviewMouseUp"/>
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelTerminal" PreviewMouseUp="labelTerminal_PreviewMouseUp" /> <Label Name="labelLastChangeMooring" FontSize="9" Padding="0" VerticalContentAlignment="Center" Grid.Row="1" Foreground="WhiteSmoke" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" HorizontalContentAlignment="Center" />
</Grid>
<Grid Grid.Row="0" Grid.Column="3">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="14" />
</Grid.RowDefinitions>
<Label Padding="0" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" FontSize="13" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelPortAuthority" PreviewMouseUp="labelPortAuthority_PreviewMouseUp"/>
<Label Name="labelLastChangePortAuthority" FontSize="9" Padding="0" VerticalContentAlignment="Center" Grid.Row="1" Foreground="WhiteSmoke" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" HorizontalContentAlignment="Center" />
</Grid>
<Grid Grid.Row="0" Grid.Column="4">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="14" />
</Grid.RowDefinitions>
<Label Padding="0" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" FontSize="13" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelPilot" PreviewMouseUp="labelPilot_PreviewMouseUp"/>
<Label Name="labelLastChangePilot" FontSize="9" Padding="0" VerticalContentAlignment="Center" Grid.Row="1" Foreground="WhiteSmoke" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" HorizontalContentAlignment="Center" />
</Grid>
<Grid Grid.Row="0" Grid.Column="5">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="14" />
</Grid.RowDefinitions>
<Label Padding="0" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" FontSize="13" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelTug" PreviewMouseUp="labelTug_PreviewMouseUp"/>
<Label Name="labelLastChangeTug" FontSize="9" Padding="0" VerticalContentAlignment="Center" Grid.Row="1" Foreground="WhiteSmoke" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" HorizontalContentAlignment="Center" />
</Grid>
<Grid Grid.Row="0" Grid.Column="6">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="14" />
</Grid.RowDefinitions>
<Label Padding="0" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" FontSize="13" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelTerminal" PreviewMouseUp="labelTerminal_PreviewMouseUp"/>
<Label Name="labelLastChangeTerminal" FontSize="9" Padding="0" VerticalContentAlignment="Center" Grid.Row="1" Foreground="WhiteSmoke" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" HorizontalContentAlignment="Center" />
</Grid>
<!-- AGENCY --> <!-- AGENCY -->
<Border Grid.Row="2" Grid.Column="1" BorderThickness="1, 0, 0, 0" BorderBrush="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Padding="3,0,0,0"> <Border Grid.Row="2" Grid.Column="1" BorderThickness="1, 0, 0, 0" BorderBrush="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" Padding="3,0,0,0">

View File

@ -72,7 +72,7 @@ namespace BreCalClient
string agentName = ""; string agentName = "";
string? name; string? name;
_agency = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.AGENCY); _agency = this.ShipcallControlModel?.GetParticipantForType(Extensions.ParticipantType.AGENCY);
name = _agency?.Name; name = _agency?.Name;
if (name != null) agentName = name; if (name != null) agentName = name;
this.labelAgent.Content = name ?? "- / -"; this.labelAgent.Content = name ?? "- / -";
@ -83,10 +83,9 @@ namespace BreCalClient
this.labelAgencyETAETDValue.Content = ""; this.labelAgencyETAETDValue.Content = "";
this.textBlockAgencyRemarks.Text = ""; this.textBlockAgencyRemarks.Text = "";
this.textBlockAgencyBerthRemarks.Text = ""; this.textBlockAgencyBerthRemarks.Text = "";
this.textBlockDraft.Text = "";
} }
_mooring = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.MOORING); _mooring = this.ShipcallControlModel?.GetParticipantForType(Extensions.ParticipantType.MOORING);
name = _mooring?.Name; name = _mooring?.Name;
this.labelMooring.Content = name ?? "- / -"; this.labelMooring.Content = name ?? "- / -";
if(_mooring == null) if(_mooring == null)
@ -95,7 +94,7 @@ namespace BreCalClient
this.textBlockMooringRemarks.Text = ""; this.textBlockMooringRemarks.Text = "";
} }
_pilot = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.PILOT); _pilot = this.ShipcallControlModel?.GetParticipantForType(Extensions.ParticipantType.PILOT);
name = _pilot?.Name; name = _pilot?.Name;
this.labelPilot.Content = name ?? "- / - "; this.labelPilot.Content = name ?? "- / - ";
if(_pilot == null) if(_pilot == null)
@ -104,7 +103,7 @@ namespace BreCalClient
this.textBlockPilotRemarks.Text = ""; this.textBlockPilotRemarks.Text = "";
} }
_tug = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.TUG); _tug = this.ShipcallControlModel?.GetParticipantForType(Extensions.ParticipantType.TUG);
name = _tug?.Name; name = _tug?.Name;
this.labelTug.Content = name ?? "- / - "; this.labelTug.Content = name ?? "- / - ";
if(_tug == null) if(_tug == null)
@ -113,7 +112,7 @@ namespace BreCalClient
this.textBlockTugRemarks.Text = ""; this.textBlockTugRemarks.Text = "";
} }
_port_administration = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.PORT_ADMINISTRATION); _port_administration = this.ShipcallControlModel?.GetParticipantForType(Extensions.ParticipantType.PORT_ADMINISTRATION);
name = _port_administration?.Name; name = _port_administration?.Name;
this.labelPortAuthority.Content = name ?? "- / - "; this.labelPortAuthority.Content = name ?? "- / - ";
if(_port_administration == null) if(_port_administration == null)
@ -122,7 +121,7 @@ namespace BreCalClient
this.textBlockPortAuthorityRemarks.Text = ""; this.textBlockPortAuthorityRemarks.Text = "";
} }
_terminal = this.ShipcallControlModel.GetParticipantForType(Extensions.ParticipantType.TERMINAL); _terminal = this.ShipcallControlModel?.GetParticipantForType(Extensions.ParticipantType.TERMINAL);
name = _terminal?.Name; name = _terminal?.Name;
this.labelTerminal.Content = name ?? "- / - "; this.labelTerminal.Content = name ?? "- / - ";
if(_terminal == null) if(_terminal == null)
@ -269,22 +268,9 @@ namespace BreCalClient
else else
this.imageEvaluation.ToolTip = null; this.imageEvaluation.ToolTip = null;
//Times? bsmdTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.BSMD); this.textBlockBerth.Text = this.ShipcallControlModel?.GetBerthText(null);
//if (bsmdTimes != null)
this.textBlockBerth.Text = this.ShipcallControlModel?.GetBerthText(null);
//else
// this.textBlockBerth.Text = this.ShipcallControlModel?.Berth;
this.textBlockCallsign.Text = this.ShipcallControlModel?.Ship?.Callsign; this.textBlockCallsign.Text = this.ShipcallControlModel?.Ship?.Callsign;
if (this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival) this.textBlockETA.Text = this.ShipcallControlModel?.GetETAETD(true);
{
this.textBlockETA.Text = this.ShipcallControlModel?.Shipcall?.Eta?.ToString("dd.MM. HH:mm");
}
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");
}
this.textBlockIMO.Text = this.ShipcallControlModel?.Ship?.Imo.ToString(); this.textBlockIMO.Text = this.ShipcallControlModel?.Ship?.Imo.ToString();
this.textBlockLengthWidth.Text = $"{this.ShipcallControlModel?.Ship?.Length} / {this.ShipcallControlModel?.Ship?.Width}"; this.textBlockLengthWidth.Text = $"{this.ShipcallControlModel?.Ship?.Length} / {this.ShipcallControlModel?.Ship?.Width}";
@ -294,6 +280,7 @@ namespace BreCalClient
if (this.ShipcallControlModel?.Shipcall?.Type != ShipcallType.Arrival) if (this.ShipcallControlModel?.Shipcall?.Type != ShipcallType.Arrival)
{ {
this.labelETA.Text = "ETD";
this.labelETAETDAgent.Content = "ETD"; this.labelETAETDAgent.Content = "ETD";
this.labelETAETDMooring.Content = "ETD"; this.labelETAETDMooring.Content = "ETD";
this.labelETAETDPilot.Content = "ETD"; this.labelETAETDPilot.Content = "ETD";
@ -315,6 +302,9 @@ namespace BreCalClient
if (this.ShipcallControlModel != null) if (this.ShipcallControlModel != null)
{ {
this.textBlockHarbour.Text = ((ShipcallControlModel != null) && (ShipcallControlModel.Shipcall != null) && BreCalLists.PortLookupDict.ContainsKey(ShipcallControlModel.Shipcall.PortId)) ?
BreCalLists.PortLookupDict[ShipcallControlModel.Shipcall.PortId].Name : "";
Extensions.ParticipantType? lastToUpdate = this.ShipcallControlModel?.LastParticipantTypeToUpdate();
Times? agencyTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.AGENCY); Times? agencyTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.AGENCY);
if (agencyTimes != null) if (agencyTimes != null)
@ -323,7 +313,19 @@ namespace BreCalClient
this.labelAgencyETAETDValue.Content = agencyTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival); this.labelAgencyETAETDValue.Content = agencyTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival);
this.textBlockAgencyRemarks.Text = agencyTimes.Remarks.TruncateDots(50); this.textBlockAgencyRemarks.Text = agencyTimes.Remarks.TruncateDots(50);
this.textBlockAgencyBerthRemarks.Text = agencyTimes.BerthInfo.TruncateDots(50); this.textBlockAgencyBerthRemarks.Text = agencyTimes.BerthInfo.TruncateDots(50);
this.textBlockDraft.Text = ShipcallControlModel?.Shipcall?.Draft?.ToString("N2"); DateTime? lc = this.ShipcallControlModel?.GetLastChangeForType(Extensions.ParticipantType.AGENCY);
this.labelLastChangeAgency.Content = lc.HasValue ? lc.Value.ToString("dd.MM.yyyy HH:mm") : string.Empty;
Grid.SetRowSpan(this.labelAgent, 1);
if(lastToUpdate == Extensions.ParticipantType.AGENCY)
{
this.labelLastChangeAgency.Foreground = Brushes.White;
this.labelLastChangeAgency.FontWeight = FontWeights.DemiBold;
}
else
{
this.labelLastChangeAgency.Foreground = Brushes.LightGray;
this.labelLastChangeAgency.FontWeight = FontWeights.Normal;
}
} }
else else
{ {
@ -332,7 +334,7 @@ namespace BreCalClient
this.labelAgencyETAETDValue.Content = "- / -"; this.labelAgencyETAETDValue.Content = "- / -";
this.textBlockAgencyRemarks.Text = ""; this.textBlockAgencyRemarks.Text = "";
this.textBlockAgencyBerthRemarks.Text = ""; this.textBlockAgencyBerthRemarks.Text = "";
this.textBlockDraft.Text = ""; Grid.SetRowSpan(this.labelAgent, 2);
} }
Times? mooringTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.MOORING); Times? mooringTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.MOORING);
@ -356,12 +358,26 @@ namespace BreCalClient
labelTimesMooringATD.Content = mooringTimes.Atd.Value.ToString("dd.MM.yyyy HH:mm"); labelTimesMooringATD.Content = mooringTimes.Atd.Value.ToString("dd.MM.yyyy HH:mm");
} }
DateTime? lc = this.ShipcallControlModel?.GetLastChangeForType(Extensions.ParticipantType.MOORING);
this.labelLastChangeMooring.Content = lc.HasValue ? lc.Value.ToString("dd.MM.yyyy HH:mm") : string.Empty;
Grid.SetRowSpan(this.labelMooring, 1);
if (lastToUpdate == Extensions.ParticipantType.MOORING)
{
this.labelLastChangeMooring.Foreground = Brushes.White;
this.labelLastChangeMooring.FontWeight = FontWeights.DemiBold;
}
else
{
this.labelLastChangeMooring.Foreground = Brushes.LightGray;
this.labelLastChangeMooring.FontWeight = FontWeights.Normal;
}
} }
else else
{ {
this.labelMooringETAETDValue.Content = "- / "; this.labelMooringETAETDValue.Content = "- / ";
this.textBlockMooringRemarks.Text = ""; this.textBlockMooringRemarks.Text = "";
this.imageMooringLocked.Visibility = Visibility.Hidden; this.imageMooringLocked.Visibility = Visibility.Hidden;
Grid.SetRowSpan(this.labelMooring, 2);
} }
Times? portAuthorityTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.PORT_ADMINISTRATION); Times? portAuthorityTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.PORT_ADMINISTRATION);
@ -375,12 +391,27 @@ namespace BreCalClient
{ {
labelPortAuthorityLockTime.Content = portAuthorityTimes.LockTime.Value.ToString("dd.MM.yyyy HH:mm"); labelPortAuthorityLockTime.Content = portAuthorityTimes.LockTime.Value.ToString("dd.MM.yyyy HH:mm");
} }
DateTime? lc = this.ShipcallControlModel?.GetLastChangeForType(Extensions.ParticipantType.PORT_ADMINISTRATION);
this.labelLastChangePortAuthority.Content = lc.HasValue ? lc.Value.ToString("dd.MM.yyyy HH:mm") : string.Empty;
Grid.SetRowSpan(this.labelPortAuthority, 1);
if (lastToUpdate == Extensions.ParticipantType.PORT_ADMINISTRATION)
{
this.labelLastChangePortAuthority.Foreground = Brushes.White;
this.labelLastChangePortAuthority.FontWeight = FontWeights.DemiBold;
}
else
{
this.labelLastChangePortAuthority.Foreground = Brushes.LightGray;
this.labelLastChangePortAuthority.FontWeight = FontWeights.Normal;
}
} }
else else
{ {
this.labelPortAuthorityETAETDValue.Content = "- / -"; this.labelPortAuthorityETAETDValue.Content = "- / -";
this.textBlockPortAuthorityRemarks.Text = ""; this.textBlockPortAuthorityRemarks.Text = "";
this.imagePortAuthorityLocked.Visibility = Visibility.Hidden; this.imagePortAuthorityLocked.Visibility = Visibility.Hidden;
Grid.SetRowSpan(this.labelPortAuthority, 2);
} }
Times? pilotTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.PILOT); Times? pilotTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.PILOT);
@ -389,12 +420,27 @@ namespace BreCalClient
this.labelPilotETAETDValue.Content = pilotTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival); this.labelPilotETAETDValue.Content = pilotTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival);
this.textBlockPilotRemarks.Text = pilotTimes.Remarks.TruncateDots(50); this.textBlockPilotRemarks.Text = pilotTimes.Remarks.TruncateDots(50);
this.imagePilotLocked.Visibility = (pilotTimes.EtaBerthFixed ?? false) ? Visibility.Visible : Visibility.Hidden; this.imagePilotLocked.Visibility = (pilotTimes.EtaBerthFixed ?? false) ? Visibility.Visible : Visibility.Hidden;
DateTime? lc = this.ShipcallControlModel?.GetLastChangeForType(Extensions.ParticipantType.PILOT);
this.labelLastChangePilot.Content = lc.HasValue ? lc.Value.ToString("dd.MM.yyyy HH:mm") : string.Empty;
Grid.SetRowSpan(this.labelPilot, 1);
if (lastToUpdate == Extensions.ParticipantType.PILOT)
{
this.labelLastChangePilot.Foreground = Brushes.White;
this.labelLastChangePilot.FontWeight = FontWeights.DemiBold;
}
else
{
this.labelLastChangePilot.Foreground = Brushes.LightGray;
this.labelLastChangePilot.FontWeight = FontWeights.Normal;
}
} }
else else
{ {
this.labelPilotETAETDValue.Content = "- / -"; this.labelPilotETAETDValue.Content = "- / -";
this.textBlockPilotRemarks.Text = ""; this.textBlockPilotRemarks.Text = "";
this.imagePilotLocked.Visibility = Visibility.Hidden; this.imagePilotLocked.Visibility = Visibility.Hidden;
Grid.SetRowSpan(this.labelPilot, 2);
} }
Times? tugTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.TUG); Times? tugTimes = this.ShipcallControlModel?.GetTimesForParticipantType(Extensions.ParticipantType.TUG);
@ -403,12 +449,27 @@ namespace BreCalClient
this.labelTugETAETDValue.Content = tugTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival); this.labelTugETAETDValue.Content = tugTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival);
this.textBlockTugRemarks.Text = tugTimes.Remarks.TruncateDots(50); this.textBlockTugRemarks.Text = tugTimes.Remarks.TruncateDots(50);
this.imageTugLocked.Visibility = (tugTimes.EtaBerthFixed ?? false) ? Visibility.Visible : Visibility.Hidden; this.imageTugLocked.Visibility = (tugTimes.EtaBerthFixed ?? false) ? Visibility.Visible : Visibility.Hidden;
DateTime? lc = this.ShipcallControlModel?.GetLastChangeForType(Extensions.ParticipantType.TUG);
this.labelLastChangeTug.Content = lc.HasValue ? lc.Value.ToString("dd.MM.yyyy HH:mm") : string.Empty;
Grid.SetRowSpan(this.labelTug, 1);
if (lastToUpdate == Extensions.ParticipantType.TUG)
{
this.labelLastChangeTug.Foreground = Brushes.White;
this.labelLastChangeTug.FontWeight = FontWeights.DemiBold;
}
else
{
this.labelLastChangeTug.Foreground = Brushes.LightGray;
this.labelLastChangeTug.FontWeight = FontWeights.Normal;
}
} }
else else
{ {
this.labelTugETAETDValue.Content = "- / -"; this.labelTugETAETDValue.Content = "- / -";
this.textBlockTugRemarks.Text = ""; this.textBlockTugRemarks.Text = "";
this.imageTugLocked.Visibility = Visibility.Hidden; this.imageTugLocked.Visibility = Visibility.Hidden;
Grid.SetRowSpan(this.labelTug, 2);
} }
this.rowDefinitionTerminalBerth.Height = (this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival) ? new(14) : new(0); this.rowDefinitionTerminalBerth.Height = (this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival) ? new(14) : new(0);
@ -422,6 +483,19 @@ namespace BreCalClient
this.labelOperationsStart.Content = terminalTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival); this.labelOperationsStart.Content = terminalTimes.DisplayTime(this.ShipcallControlModel?.Shipcall?.Type == ShipcallType.Arrival);
this.textBlockTerminalRemarks.Text = terminalTimes.Remarks.TruncateDots(40); this.textBlockTerminalRemarks.Text = terminalTimes.Remarks.TruncateDots(40);
this.textBlockTerminalBerthRemarks.Text = terminalTimes.BerthInfo.TruncateDots(40); this.textBlockTerminalBerthRemarks.Text = terminalTimes.BerthInfo.TruncateDots(40);
DateTime? lc = this.ShipcallControlModel?.GetLastChangeForType(Extensions.ParticipantType.TERMINAL);
this.labelLastChangeTerminal.Content = lc.HasValue ? lc.Value.ToString("dd.MM.yyyy HH:mm") : string.Empty;
Grid.SetRowSpan(this.labelTerminal, 1);
if (lastToUpdate == Extensions.ParticipantType.TERMINAL)
{
this.labelLastChangeTerminal.Foreground = Brushes.White;
this.labelLastChangeTerminal.FontWeight = FontWeights.DemiBold;
}
else
{
this.labelLastChangeTerminal.Foreground = Brushes.LightGray;
this.labelLastChangeTerminal.FontWeight = FontWeights.Normal;
}
} }
else else
{ {
@ -429,6 +503,7 @@ namespace BreCalClient
this.labelOperationsStart.Content = ""; this.labelOperationsStart.Content = "";
this.textBlockTerminalRemarks.Text = ""; this.textBlockTerminalRemarks.Text = "";
this.textBlockTerminalBerthRemarks.Text = ""; this.textBlockTerminalBerthRemarks.Text = "";
Grid.SetRowSpan(this.labelTerminal, 2);
} }
} }

View File

@ -118,6 +118,8 @@ namespace BreCalClient
} }
} }
public bool AllowPortChange { get; set; } = true;
#endregion #endregion
#region public methods #region public methods
@ -217,7 +219,7 @@ namespace BreCalClient
return berthText; return berthText;
} }
public string GetETAETD() public string GetETAETD(bool useShortVersion = false)
{ {
DateTime theDate = DateTime.Now; DateTime theDate = DateTime.Now;
if(this.Shipcall != null) if(this.Shipcall != null)
@ -241,6 +243,8 @@ namespace BreCalClient
theDate = agentTimes.EtdBerth.Value; theDate = agentTimes.EtdBerth.Value;
} }
} }
if(useShortVersion)
return theDate.ToString("dd.MM. HH:mm");
return theDate.ToString(); return theDate.ToString();
} }
@ -309,6 +313,42 @@ namespace BreCalClient
return true; return true;
} }
public DateTime? GetLastChangeForType(Extensions.ParticipantType type)
{
DateTime? lastChange = null;
Times? times = GetTimesForParticipantType(type);
if(times != null)
{
if (times.Modified.HasValue) lastChange = times.Modified.Value;
else lastChange = times.Created;
}
return lastChange;
}
public Extensions.ParticipantType? LastParticipantTypeToUpdate()
{
Extensions.ParticipantType? last = null;
DateTime min = DateTime.MinValue;
foreach(Times times in this.Times)
{
if (times.Modified.HasValue)
{
if (times.Modified.Value > min)
{
min = times.Modified.Value;
last = (Extensions.ParticipantType)times.ParticipantType;
continue;
}
}
if(times.Created > min)
{
min = times.Created;
last = (Extensions.ParticipantType)times.ParticipantType;
}
}
return last;
}
#endregion #endregion
#region helper #region helper

View File

@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RoleEditor" xmlns:local="clr-namespace:RoleEditor"
mc:Ignorable="d" mc:Ignorable="d"
Title="Edit berth" Height="188" Width="450" Loaded="Window_Loaded"> Title="Edit berth" Height="216" Width="450" Loaded="Window_Loaded">
<Grid x:Name="berthGrid"> <Grid x:Name="berthGrid">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width=".3*" /> <ColumnDefinition Width=".3*" />
@ -16,6 +16,7 @@
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="28" /> <RowDefinition Height="28" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
@ -45,7 +46,9 @@
</Grid> </Grid>
<Label Content="Uses lock" HorizontalAlignment="Right" Grid.Row="3" /> <Label Content="Uses lock" HorizontalAlignment="Right" Grid.Row="3" />
<CheckBox x:Name="checkBoxLock" Grid.Row="3" Grid.Column="1" VerticalAlignment="Center" Margin="2" IsChecked="{Binding Path=Lock, Mode=OneWay}"/> <CheckBox x:Name="checkBoxLock" Grid.Row="3" Grid.Column="1" VerticalAlignment="Center" Margin="2" IsChecked="{Binding Path=Lock, Mode=OneWay}"/>
<StackPanel Grid.Column="1" Grid.Row="5" Orientation="Horizontal" FlowDirection="RightToLeft"> <Label Content="Port" HorizontalAlignment="Right" Grid.Row="4" />
<ComboBox Name="comboBoxPorts" Margin="2" Grid.Column="1" Grid.Row="4" SelectedItem="{Binding Port, Mode=OneWay}" />
<StackPanel Grid.Column="1" Grid.Row="6" Orientation="Horizontal" FlowDirection="RightToLeft">
<Button x:Name="buttonCancel" Width="80" Content="Cancel" Margin="2" Click="buttonCancel_Click" /> <Button x:Name="buttonCancel" Width="80" Content="Cancel" Margin="2" Click="buttonCancel_Click" />
<Button x:Name="buttonOK" Width="80" Content="OK" Margin="2" Click="buttonOK_Click"/> <Button x:Name="buttonOK" Width="80" Content="OK" Margin="2" Click="buttonOK_Click"/>
</StackPanel> </StackPanel>

View File

@ -20,6 +20,8 @@ namespace RoleEditor
public List<Participant> Authorities { get; } = new List<Participant>(); public List<Participant> Authorities { get; } = new List<Participant>();
public List<Port> Ports { get; } = new List<Port>();
private void buttonCancel_Click(object sender, RoutedEventArgs e) private void buttonCancel_Click(object sender, RoutedEventArgs e)
{ {
this.DialogResult = false; this.DialogResult = false;
@ -43,6 +45,12 @@ namespace RoleEditor
else else
this.Berth.Authority_Id = null; this.Berth.Authority_Id = null;
this.Berth.Port = this.comboBoxPorts.SelectedItem as Port;
if (this.Berth.Port != null)
this.Berth.Port_Id = this.Berth.Port.Id;
else
this.Berth.Port_Id = null;
this.DialogResult = true; this.DialogResult = true;
this.Close(); this.Close();
} }
@ -52,6 +60,7 @@ namespace RoleEditor
this.DataContext = this.Berth; this.DataContext = this.Berth;
this.comboBoxParticipants.ItemsSource = this.Owners; this.comboBoxParticipants.ItemsSource = this.Owners;
this.comboBoxAuthorities.ItemsSource = this.Authorities; this.comboBoxAuthorities.ItemsSource = this.Authorities;
this.comboBoxPorts.ItemsSource = this.Ports;
} }
private void buttonResetParticipant_Click(object sender, RoutedEventArgs e) private void buttonResetParticipant_Click(object sender, RoutedEventArgs e)

View File

@ -6,7 +6,7 @@
xmlns:local="clr-namespace:RoleEditor" xmlns:local="clr-namespace:RoleEditor"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d" mc:Ignorable="d"
Title="Bremen calling admin editor" Height="670" Width="800" Icon="Resources/lock_preferences.ico" Loaded="Window_Loaded"> Title="Bremen calling admin editor" Height="670" Width="1024" Icon="Resources/lock_preferences.ico" Loaded="Window_Loaded">
<Grid> <Grid>
<TabControl> <TabControl>
<TabItem Header="Participant, users and roles"> <TabItem Header="Participant, users and roles">
@ -16,8 +16,9 @@
<RowDefinition Height=".5*" /> <RowDefinition Height=".5*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".4*" />
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".3*" />
<ColumnDefinition Width=".3*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<GroupBox Header="Participant" Margin="2"> <GroupBox Header="Participant" Margin="2">
<Grid> <Grid>
@ -82,6 +83,69 @@
</Button> </Button>
</Grid> </Grid>
</GroupBox> </GroupBox>
<GroupBox Header="Port Assignment" Margin="2" Grid.Row="0" Grid.Column="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
<RowDefinition Height="28" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<ListBox x:Name="listBoxPortAssignment" Margin="2" Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" />
<Button x:Name="buttonAddPortAssignment" Margin="2" Grid.Row="0" Grid.Column="1" Click="buttonAddPortAssignment_Click">
<Image Source="./Resources/arrow_left_green.png"/>
</Button>
<Button x:Name="buttonRemovePortAssignment" Margin="2" Grid.Row="1" Grid.Column="1" Click="buttonRemovePortAssignment_Click">
<Image Source="./Resources/delete2.png"/>
</Button>
</Grid>
</GroupBox>
<GroupBox Header="Ports" Margin="2" Grid.Row="0" Grid.Column="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160" />
<ColumnDefinition Width=".38*" />
<ColumnDefinition Width=".62*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="listBoxPort" Margin="2" Grid.RowSpan="4" SelectionChanged="listBoxPort_SelectionChanged">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem x:Name="menuItemNewPort" Header="New.." Click="menuItemNewPort_Click">
<MenuItem.Icon>
<Image Source="Resources/add.png" />
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="menuItemDeletePort" Header="Delete" Click="menuItemDeletePort_Click">
<MenuItem.Icon>
<Image Source="Resources/delete2.png" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
<Label Grid.Row="0" Grid.Column="1" Content="Name" HorizontalAlignment="Right"/>
<Label Grid.Row="1" Grid.Column="1" Content="Locode" HorizontalAlignment="Right"/>
<TextBox x:Name="textBoxPortName" Grid.Row="0" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" MaxLength="128"/>
<TextBox x:Name="textBoxPortLocode" Grid.Row="1" Grid.Column="2" Margin="2" VerticalContentAlignment="Center" MaxLength="5" />
<Button x:Name="buttonPortSave" Grid.Row="2" Grid.Column="2" Click="buttonPortSave_Click" Margin="2">
<DockPanel>
<Image Source="./Resources/disk_blue.png" Margin="0,0,5,0" Height="24" DockPanel.Dock="Left" Width="16"/>
<TextBlock Text="Save" VerticalAlignment="Center" DockPanel.Dock="Right"/>
</DockPanel>
</Button>
</Grid>
</GroupBox>
<GroupBox Header="User" Margin="2" Grid.Row="1"> <GroupBox Header="User" Margin="2" Grid.Row="1">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
@ -145,7 +209,7 @@
</Button> </Button>
</Grid> </Grid>
</GroupBox> </GroupBox>
<GroupBox Header="Role" Margin="2" Grid.Column="1"> <GroupBox Header="Role" Margin="2" Grid.Column="1" Grid.Row="1">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="28"/> <RowDefinition Height="28"/>
@ -199,7 +263,7 @@
</Button> </Button>
</Grid> </Grid>
</GroupBox> </GroupBox>
<GroupBox Header="Securable" Margin="2" Grid.Row="1" Grid.Column="1"> <GroupBox Header="Securable" Margin="2" Grid.Row="1" Grid.Column="2">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="28"/> <RowDefinition Height="28"/>
@ -276,6 +340,7 @@
<DataGridCheckBoxColumn Header="Lock" Binding="{Binding Path=Lock}" IsReadOnly="True"/> <DataGridCheckBoxColumn Header="Lock" Binding="{Binding Path=Lock}" IsReadOnly="True"/>
<DataGridTextColumn Header="Terminal" Binding="{Binding Path=Terminal, Mode=OneWay}" IsReadOnly="True"/> <DataGridTextColumn Header="Terminal" Binding="{Binding Path=Terminal, Mode=OneWay}" IsReadOnly="True"/>
<DataGridTextColumn Header="Authority" Binding="{Binding Path=Authority_Text, Mode=OneWay}" IsReadOnly="True" /> <DataGridTextColumn Header="Authority" Binding="{Binding Path=Authority_Text, Mode=OneWay}" IsReadOnly="True" />
<DataGridTextColumn Header="Port" Binding="{Binding Path=Port}" IsReadOnly="True" />
<DataGridTextColumn Header="Deleted" Binding="{Binding Path=Deleted, Mode=OneWay}" IsReadOnly="True" /> <DataGridTextColumn Header="Deleted" Binding="{Binding Path=Deleted, Mode=OneWay}" IsReadOnly="True" />
</DataGrid.Columns> </DataGrid.Columns>
</local:ENIDataGrid> </local:ENIDataGrid>

View File

@ -38,6 +38,8 @@ namespace RoleEditor
private readonly ObservableCollection<SecurableAssignment> _assignedSecurables = new ObservableCollection<SecurableAssignment>(); private readonly ObservableCollection<SecurableAssignment> _assignedSecurables = new ObservableCollection<SecurableAssignment>();
private readonly ObservableCollection<Berth> _berths = new ObservableCollection<Berth>(); private readonly ObservableCollection<Berth> _berths = new ObservableCollection<Berth>();
private readonly ObservableCollection<Ship> _ships = new ObservableCollection<Ship>(); private readonly ObservableCollection<Ship> _ships = new ObservableCollection<Ship>();
private readonly ObservableCollection<Port> _ports = new ObservableCollection<Port>();
private readonly ObservableCollection<PortAssignment> _assignedPorts = new ObservableCollection<PortAssignment>();
private DBManager _dbManager; private DBManager _dbManager;
#endregion #endregion
@ -77,6 +79,10 @@ namespace RoleEditor
_securables.Add(s); _securables.Add(s);
this.listBoxSecurables.ItemsSource = _securables; this.listBoxSecurables.ItemsSource = _securables;
// load all ports
foreach (Port port in await Port.LoadAll(_dbManager)) _ports.Add(port);
this.listBoxPort.ItemsSource = _ports;
// load all berths // load all berths
foreach (Berth b in await Berth.LoadAll(_dbManager)) foreach (Berth b in await Berth.LoadAll(_dbManager))
{ {
@ -89,6 +95,10 @@ namespace RoleEditor
{ {
b.Authority = participants.Where(p => p.Id == b.Authority_Id).FirstOrDefault(); b.Authority = participants.Where(p => p.Id == b.Authority_Id).FirstOrDefault();
} }
if (b.Port_Id != null)
{
b.Port = _ports.Where(p => p.Id == b.Port_Id).FirstOrDefault();
}
} }
this.dataGridBerths.Initialize(); this.dataGridBerths.Initialize();
this.dataGridBerths.ItemsSource = _berths; this.dataGridBerths.ItemsSource = _berths;
@ -112,12 +122,14 @@ namespace RoleEditor
this.dataGridShips.CreateRequested += DataGridShips_CreateRequested; this.dataGridShips.CreateRequested += DataGridShips_CreateRequested;
this.dataGridShips.EditRequested += DataGridShips_EditRequested; this.dataGridShips.EditRequested += DataGridShips_EditRequested;
this.dataGridShips.DeleteRequested += DataGridShips_DeleteRequested; this.dataGridShips.DeleteRequested += DataGridShips_DeleteRequested;
// set other item sources (filled later after selection) // set other item sources (filled later after selection)
this.listBoxUser.ItemsSource = _users; this.listBoxUser.ItemsSource = _users;
this.listBoxRoleSecurables.ItemsSource = _assignedSecurables; this.listBoxRoleSecurables.ItemsSource = _assignedSecurables;
this.listBoxUserRoles.ItemsSource = _assignedRoles; this.listBoxUserRoles.ItemsSource = _assignedRoles;
this.listBoxPortAssignment.ItemsSource = _assignedPorts;
this.comboBoxParticipantType.ItemsSource = EnumHelper.GetAllValuesAndDescription(typeof(Participant.ParticipantType)); this.comboBoxParticipantType.ItemsSource = EnumHelper.GetAllValuesAndDescription(typeof(Participant.ParticipantType));
@ -192,6 +204,7 @@ namespace RoleEditor
ebd.Berth = b; ebd.Berth = b;
ebd.Owners.AddRange(this._terminals); ebd.Owners.AddRange(this._terminals);
ebd.Authorities.AddRange(this._authorities); ebd.Authorities.AddRange(this._authorities);
ebd.Ports.AddRange(this._ports.Where(p => !p.IsDeleted));
if (ebd.ShowDialog() ?? false) if (ebd.ShowDialog() ?? false)
{ {
await b.Save(_dbManager); await b.Save(_dbManager);
@ -208,6 +221,7 @@ namespace RoleEditor
ebd.Berth = b; ebd.Berth = b;
ebd.Owners.AddRange(this._terminals); ebd.Owners.AddRange(this._terminals);
ebd.Authorities.AddRange(this._authorities); ebd.Authorities.AddRange(this._authorities);
ebd.Ports.AddRange(_ports.Where(p => !p.IsDeleted));
if (ebd.ShowDialog() ?? false) if (ebd.ShowDialog() ?? false)
{ {
_berths.Add(b); _berths.Add(b);
@ -386,6 +400,59 @@ namespace RoleEditor
} }
} }
private async void buttonPortSave_Click(object sender, RoutedEventArgs e)
{
Port? p = this.listBoxPort.SelectedItem as Port;
if (p != null)
{
p.Name = this.textBoxPortName.Text.Trim();
p.Locode = this.textBoxPortLocode.Text.Trim();
await p.Save(_dbManager);
this.listBoxPort.ItemsSource = null;
this.listBoxPort.ItemsSource = _ports;
this.listBoxPort.SelectedItem = p;
}
}
private async void buttonAddPortAssignment_Click(object sender, RoutedEventArgs e)
{
if ((this.listBoxPort.SelectedItem is Port p) && (this.listBoxParticipant.SelectedItem is Participant pa))
{
// test if assignment is already present
bool foundMatchingAssignment = false;
foreach (PortAssignment portAssignment in _assignedPorts)
{
if ((portAssignment.PortId == p.Id) && (portAssignment.ParticipantId == pa.Id))
{
foundMatchingAssignment = true;
break;
}
}
if (!foundMatchingAssignment)
{
PortAssignment portAssignment = new PortAssignment();
portAssignment.PortId = (int)p.Id;
portAssignment.ParticipantId = (int)pa.Id;
portAssignment.AssignedParticipant = pa;
portAssignment.AssignedPort = p;
await portAssignment.Save(_dbManager);
_assignedPorts.Add(portAssignment);
}
}
}
private async void buttonRemovePortAssignment_Click(object sender, RoutedEventArgs e)
{
PortAssignment? pa = this.listBoxPortAssignment.SelectedItem as PortAssignment;
if (pa != null)
{
await pa.Delete(_dbManager);
if (_assignedPorts.Contains(pa))
_assignedPorts.Remove(pa);
}
}
#endregion #endregion
#region listbox selection callbacks #region listbox selection callbacks
@ -430,6 +497,19 @@ namespace RoleEditor
foreach (User u in await User.LoadForParticipant(p, _dbManager)) foreach (User u in await User.LoadForParticipant(p, _dbManager))
_users.Add(u); _users.Add(u);
} }
// -> load port assignments for this participant selection
this._assignedPorts.Clear();
if(p != null)
{
foreach (PortAssignment pa in await PortAssignment.LoadForParticipant(p, this._dbManager))
{
foreach (Port port in this._ports)
if (pa.PortId == port.Id)
pa.AssignedPort = port;
_assignedPorts.Add(pa);
}
}
} }
private async void listBoxRoles_SelectionChanged(object sender, SelectionChangedEventArgs e) private async void listBoxRoles_SelectionChanged(object sender, SelectionChangedEventArgs e)
@ -496,6 +576,13 @@ namespace RoleEditor
this.textBoxSecurableName.Text = (s != null) ? s.Name : string.Empty; this.textBoxSecurableName.Text = (s != null) ? s.Name : string.Empty;
} }
private void listBoxPort_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Port? p = this.listBoxPort.SelectedItem as Port;
this.textBoxPortName.Text = (p != null) ? p.Name : string.Empty;
this.textBoxPortLocode.Text = (p != null) ? p.Locode : string.Empty;
}
#endregion #endregion
#region menuitem callbacks #region menuitem callbacks
@ -597,6 +684,29 @@ namespace RoleEditor
} }
} }
private void menuItemNewPort_Click(object sender, RoutedEventArgs e)
{
Port p = new();
this._ports.Add(p);
this.listBoxPort.SelectedItem = p;
}
private async void menuItemDeletePort_Click(object sender, RoutedEventArgs e)
{
try
{
if (this.listBoxPort.SelectedItem is Port p)
{
await p.Delete(_dbManager);
this._ports.Remove(p);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
#endregion #endregion
#region Excel import #region Excel import
@ -634,7 +744,7 @@ namespace RoleEditor
{ {
if (reader.FieldCount < 2) if (reader.FieldCount < 2)
{ {
throw new InvalidDataException("Sheet must have at least 2 Columns of data"); throw new InvalidDataException("Sheet must have at least 3 Columns of data");
} }
if (reader.IsDBNull(0) && reader.IsDBNull(1)) continue; if (reader.IsDBNull(0) && reader.IsDBNull(1)) continue;
@ -649,8 +759,20 @@ namespace RoleEditor
if (_berths.Any(predicate: x => (x.Name != null) && x.Name.Equals(berth_name, StringComparison.OrdinalIgnoreCase))) if (_berths.Any(predicate: x => (x.Name != null) && x.Name.Equals(berth_name, StringComparison.OrdinalIgnoreCase)))
continue; continue;
string port_name = "";
if (!reader.IsDBNull(2)) port_name = reader.GetString(2);
// find port in list
if(!_ports.Any(x => (x.Name != null) && x.Name.Equals(port_name, StringComparison.OrdinalIgnoreCase)))
continue;
Port port = _ports.First(x => (x.Name != null) && x.Name.Equals(port_name, StringComparison.OrdinalIgnoreCase));
Berth b = new Berth(); Berth b = new Berth();
b.Name = berth_name; b.Name = berth_name;
b.Port = port;
b.Port_Id = port.Id;
bool found_participant = false; bool found_participant = false;
foreach(Participant p in this._participants) foreach(Participant p in this._participants)
@ -801,6 +923,6 @@ namespace RoleEditor
} }
#endregion #endregion
} }
} }

View File

@ -12,7 +12,7 @@ namespace RoleEditor.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.10.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@ -25,8 +25,8 @@ namespace RoleEditor.Properties {
[global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("Server=localhost;User ID=ds;Password=HalloWach_2323XXL!!;Database=bremen_calling;" + [global::System.Configuration.DefaultSettingValueAttribute("Server=localhost;User ID=ds;Password=HalloWach_2323XXL!!;Database=bremen_calling_" +
"Port=33306")] "test;Port=33306")]
public string ConnectionString { public string ConnectionString {
get { get {
return ((string)(this["ConnectionString"])); return ((string)(this["ConnectionString"]));

View File

@ -3,7 +3,7 @@
<Profiles /> <Profiles />
<Settings> <Settings>
<Setting Name="ConnectionString" Type="System.String" Scope="Application"> <Setting Name="ConnectionString" Type="System.String" Scope="Application">
<Value Profile="(Default)">Server=localhost;User ID=ds;Password=HalloWach_2323XXL!!;Database=bremen_calling;Port=33306</Value> <Value Profile="(Default)">Server=localhost;User ID=ds;Password=HalloWach_2323XXL!!;Database=bremen_calling_test;Port=33306</Value>
</Setting> </Setting>
</Settings> </Settings>
</SettingsFile> </SettingsFile>

View File

@ -6,6 +6,8 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<ApplicationIcon>Resources\lock_preferences.ico</ApplicationIcon> <ApplicationIcon>Resources\lock_preferences.ico</ApplicationIcon>
<FileVersion>1.6.0.4</FileVersion>
<AssemblyVersion>1.6.0.4</AssemblyVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,12 +1,7 @@
// Copyright (c) 2023- schick Informatik // Copyright (c) 2023- schick Informatik
// Description: Model class for berth entity // Description: Model class for berth entity
using System;
using System.Collections.Generic;
using System.Data; using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace brecal.model namespace brecal.model
{ {
@ -23,10 +18,14 @@ namespace brecal.model
public uint? Authority_Id { get; set; } public uint? Authority_Id { get; set; }
public uint? Port_Id { get; set; }
public Participant? Owner { get; set; } public Participant? Owner { get; set; }
public Participant? Authority { get; set; } public Participant? Authority { get; set; }
public Port? Port { get; set; }
public string? Terminal { get { if (Owner != null) return Owner.Name; else return "n/a"; } } public string? Terminal { get { if (Owner != null) return Owner.Name; else return "n/a"; } }
public string? Authority_Text { get { if (Authority != null) return Authority.Name; else return "n/a"; } } public string? Authority_Text { get { if (Authority != null) return Authority.Name; else return "n/a"; } }
@ -48,12 +47,12 @@ namespace brecal.model
public static void SetLoadQuery(IDbCommand cmd, params object?[] list) public static void SetLoadQuery(IDbCommand cmd, params object?[] list)
{ {
cmd.CommandText = "SELECT id, name, owner_id, authority_id, `lock`, created, modified, deleted FROM berth"; cmd.CommandText = "SELECT id, name, owner_id, authority_id, port_id, `lock`, created, modified, deleted FROM berth";
} }
public static List<DbEntity> LoadElems(IDataReader reader) public static List<DbEntity> LoadElems(IDataReader reader)
{ {
List<DbEntity> result = new List<DbEntity>(); List<DbEntity> result = new();
while (reader.Read()) while (reader.Read())
{ {
Berth b = new(); Berth b = new();
@ -61,10 +60,11 @@ namespace brecal.model
if (!reader.IsDBNull(1)) b.Name = reader.GetString(1); if (!reader.IsDBNull(1)) b.Name = reader.GetString(1);
if (!reader.IsDBNull(2)) b.Owner_Id = (uint) reader.GetInt32(2); if (!reader.IsDBNull(2)) b.Owner_Id = (uint) reader.GetInt32(2);
if (!reader.IsDBNull(3)) b.Authority_Id = (uint) reader.GetInt32(3); if (!reader.IsDBNull(3)) b.Authority_Id = (uint) reader.GetInt32(3);
if (!reader.IsDBNull(4)) b.Lock = reader.GetBoolean(4); if (!reader.IsDBNull(4)) b.Port_Id = (uint) reader.GetInt32(4);
if (!reader.IsDBNull(5)) b.Created = reader.GetDateTime(5); if (!reader.IsDBNull(5)) b.Lock = reader.GetBoolean(5);
if (!reader.IsDBNull(6)) b.Modified = reader.GetDateTime(6); if (!reader.IsDBNull(6)) b.Created = reader.GetDateTime(6);
if (!reader.IsDBNull(7)) b.Deleted = reader.GetInt16(7); if (!reader.IsDBNull(7)) b.Modified = reader.GetDateTime(7);
if (!reader.IsDBNull(8)) b.Deleted = reader.GetInt16(8);
result.Add(b); result.Add(b);
} }
return result; return result;
@ -76,7 +76,7 @@ namespace brecal.model
public override void SetCreate(IDbCommand cmd) public override void SetCreate(IDbCommand cmd)
{ {
cmd.CommandText = "INSERT INTO berth (owner_id, authority_id, name, `lock`) VALUES ( @PID, @AID, @NAME, @LOCK)"; cmd.CommandText = "INSERT INTO berth (owner_id, authority_id, port_id, name, `lock`) VALUES ( @PID, @AID, @PO_ID, @NAME, @LOCK)";
this.SetParameters(cmd); this.SetParameters(cmd);
} }
@ -92,7 +92,7 @@ namespace brecal.model
public override void SetUpdate(IDbCommand cmd) public override void SetUpdate(IDbCommand cmd)
{ {
cmd.CommandText = "UPDATE berth SET name = @NAME, owner_id = @PID, authority_id = @AID, `lock` = @LOCK WHERE id = @ID"; cmd.CommandText = "UPDATE berth SET name = @NAME, owner_id = @PID, authority_id = @AID, port_id = @PO_ID, `lock` = @LOCK WHERE id = @ID";
this.SetParameters(cmd); this.SetParameters(cmd);
} }
@ -109,14 +109,19 @@ namespace brecal.model
IDbDataParameter pid = cmd.CreateParameter(); IDbDataParameter pid = cmd.CreateParameter();
pid.ParameterName = "PID"; pid.ParameterName = "PID";
pid.Value = this.Owner_Id; pid.Value = this.Owner_Id.HasValue ? this.Owner_Id.Value : DBNull.Value;
cmd.Parameters.Add(pid); cmd.Parameters.Add(pid);
IDbDataParameter aid = cmd.CreateParameter(); IDbDataParameter aid = cmd.CreateParameter();
aid.ParameterName = "AID"; aid.ParameterName = "AID";
aid.Value = this.Authority_Id; aid.Value = this.Authority_Id.HasValue ? this.Authority_Id.Value : DBNull.Value;
cmd.Parameters.Add(aid); cmd.Parameters.Add(aid);
IDbDataParameter poid = cmd.CreateParameter();
poid.ParameterName = "PO_ID";
poid.Value = this.Port_Id.HasValue ? this.Port_Id.Value : DBNull.Value;
cmd.Parameters.Add(poid);
IDbDataParameter name = cmd.CreateParameter(); IDbDataParameter name = cmd.CreateParameter();
name.ParameterName = "NAME"; name.ParameterName = "NAME";
name.Value = this.Name; name.Value = this.Name;

View File

@ -70,7 +70,7 @@ namespace brecal.model
public static List<DbEntity> LoadElems(IDataReader reader) public static List<DbEntity> LoadElems(IDataReader reader)
{ {
List<DbEntity> result = new List<DbEntity>(); List<DbEntity> result = new();
while (reader.Read()) while (reader.Read())
{ {
Participant p = new(); Participant p = new();

115
src/brecal.model/Port.cs Normal file
View File

@ -0,0 +1,115 @@
// Copyright (c) 2023- schick Informatik
// Description: Port entity
using System.Data;
namespace brecal.model
{
public class Port : DbEntity
{
#region Properties
public string? Name { get; set; }
public string? Locode { get; set; }
public bool IsDeleted { get; set; } = false;
#endregion
#region overrides
public override void SetCreate(IDbCommand cmd)
{
cmd.CommandText = "INSERT INTO port (name, locode) VALUES ( @NAME, @LOCODE)";
this.SetParameters(cmd);
}
public override void SetDelete(IDbCommand cmd)
{
cmd.CommandText = "UPDATE port SET deleted = 1 WHERE id = @ID";
IDataParameter idParam = cmd.CreateParameter();
idParam.ParameterName = "ID";
idParam.Value = this.Id;
cmd.Parameters.Add(idParam);
}
public override void SetUpdate(IDbCommand cmd)
{
cmd.CommandText = "UPDATE port set name = @NAME, locode = @LOCODE WHERE id = @ID";
this.SetParameters(cmd);
}
#endregion
#region public static methods
public static async Task<List<Port>> LoadAll(IDBManager manager)
{
List<DbEntity> loadResultList = await manager.Load(SetLoadQuery, LoadElems);
List<Port> result = new();
foreach (Port p in loadResultList.Cast<Port>())
result.Add(p);
return result;
}
public static void SetLoadQuery(IDbCommand cmd, params object?[] list)
{
cmd.CommandText = "SELECT id, name, locode, created, modified, deleted FROM port WHERE deleted = 0";
}
public static List<DbEntity> LoadElems(IDataReader reader)
{
List<DbEntity> result = new();
while (reader.Read())
{
Port p = new();
p.Id = (uint)reader.GetInt32(0);
if (!reader.IsDBNull(1)) p.Name = reader.GetString(1);
if (!reader.IsDBNull(2)) p.Locode = reader.GetString(2);
if (!reader.IsDBNull(3)) p.Created = reader.GetDateTime(3);
if (!reader.IsDBNull(4)) p.Modified = reader.GetDateTime(4);
if (!reader.IsDBNull(5)) p.IsDeleted = reader.GetBoolean(5);
result.Add(p);
}
return result;
}
#endregion
#region private methods
private void SetParameters(IDbCommand cmd)
{
IDbDataParameter name = cmd.CreateParameter();
name.ParameterName = "NAME";
name.Value = this.Name;
cmd.Parameters.Add(name);
IDbDataParameter desc = cmd.CreateParameter();
desc.ParameterName = "LOCODE";
desc.Value = this.Locode;
cmd.Parameters.Add(desc);
IDataParameter idParam = cmd.CreateParameter();
idParam.ParameterName = "ID";
idParam.Value = this.Id;
cmd.Parameters.Add(idParam);
}
#endregion
#region overrides
public override string ToString()
{
return $"{Name} ({Locode})";
}
#endregion
}
}

View File

@ -0,0 +1,111 @@
// Copyright (c) 2023- schick Informatik
// Description: Participant Port Map Entity
using System.Data;
namespace brecal.model
{
public class PortAssignment : DbEntity
{
#region Properties
public int? ParticipantId { get; set; }
public int? PortId { get; set; }
public Participant? AssignedParticipant { get; set; }
public Port? AssignedPort { get; set; }
#endregion
#region public static methods
public static async Task<List<PortAssignment>> LoadForParticipant(Participant? p, IDBManager manager)
{
List<DbEntity> loadResultList = await manager.Load(SetLoadQuery, LoadElems, args: p);
List<PortAssignment> result = new();
foreach (PortAssignment pa in loadResultList.Cast<PortAssignment>())
{
pa.AssignedParticipant = p;
result.Add(pa);
}
return result;
}
public static void SetLoadQuery(IDbCommand cmd, params object?[] args)
{
cmd.CommandText = "SELECT id, participant_id, port_id FROM participant_port_map WHERE participant_id = @PID";
if (args.Length != 1 || args[0] is not Participant)
throw new ArgumentException("loader needs single participant as argument");
IDataParameter pid = cmd.CreateParameter();
pid.ParameterName = "PID";
if (args[0] is Participant p)
pid.Value = p.Id;
cmd.Parameters.Add(pid);
}
public static List<DbEntity> LoadElems(IDataReader reader)
{
List<DbEntity> result = new();
while (reader.Read())
{
PortAssignment ra = new();
ra.Id = (uint)reader.GetInt32(0);
if (!reader.IsDBNull(1)) ra.ParticipantId = reader.GetInt32(1);
if (!reader.IsDBNull(2)) ra.PortId = reader.GetInt32(2);
result.Add(ra);
}
return result;
}
#endregion
#region overrides
public override void SetUpdate(IDbCommand cmd)
{
throw new NotImplementedException();
}
public override void SetCreate(IDbCommand cmd)
{
cmd.CommandText = "INSERT INTO participant_port_map (participant_id, port_id) VALUES (@PID, @PORTID)";
IDbDataParameter participantId = cmd.CreateParameter();
participantId.ParameterName = "pID";
participantId.Value = this.ParticipantId;
cmd.Parameters.Add(participantId);
IDbDataParameter portId = cmd.CreateParameter();
portId.ParameterName = "PORTID";
portId.Value = this.PortId;
cmd.Parameters.Add(portId);
}
public override void SetDelete(IDbCommand cmd)
{
cmd.CommandText = "DELETE FROM participant_port_map WHERE id = @ID";
IDataParameter idParam = cmd.CreateParameter();
idParam.ParameterName = "ID";
idParam.Value = this.Id;
cmd.Parameters.Add(idParam);
}
public override string ToString()
{
if (this.AssignedPort == null)
{
return $"{Id}: <defunct port>";
}
else
{
return AssignedPort.Name ?? AssignedPort.Id.ToString();
}
}
#endregion
}
}

View File

@ -1,9 +1,7 @@
using System; // Copyright (c) 2023- schick Informatik
using System.Collections.Generic; // Description: Role Entity
using System.Data; using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace brecal.model namespace brecal.model
{ {

View File

@ -17,7 +17,7 @@ namespace brecal.mysql
public async Task<List<DbEntity>> Load(QueryFunc prepareAction, LoadFunc<IDataReader> loadAction, params object?[] args) public async Task<List<DbEntity>> Load(QueryFunc prepareAction, LoadFunc<IDataReader> loadAction, params object?[] args)
{ {
await using MySqlConnection connection = new MySqlConnection(_connectionString); await using MySqlConnection connection = new(_connectionString);
await connection.OpenAsync(); await connection.OpenAsync();
using MySqlCommand cmd = new(); using MySqlCommand cmd = new();
@ -31,7 +31,7 @@ namespace brecal.mysql
public async Task<object?> ExecuteScalar(Action<IDbCommand> prepareAction) public async Task<object?> ExecuteScalar(Action<IDbCommand> prepareAction)
{ {
await using MySqlConnection connection = new MySqlConnection(_connectionString); await using MySqlConnection connection = new(_connectionString);
await connection.OpenAsync(); await connection.OpenAsync();
using MySqlCommand cmd = new(); using MySqlCommand cmd = new();

View File

@ -13,6 +13,7 @@ from .api import ships
from .api import login from .api import login
from .api import user from .api import user
from .api import history from .api import history
from .api import ports
from BreCal.brecal_utils.file_handling import get_project_root, ensure_path from BreCal.brecal_utils.file_handling import get_project_root, ensure_path
from BreCal.brecal_utils.test_handling import execute_test_with_pytest, execute_coverage_test from BreCal.brecal_utils.test_handling import execute_test_with_pytest, execute_coverage_test
@ -66,6 +67,7 @@ def create_app(test_config=None, instance_path=None):
app.register_blueprint(login.bp) app.register_blueprint(login.bp)
app.register_blueprint(user.bp) app.register_blueprint(user.bp)
app.register_blueprint(history.bp) app.register_blueprint(history.bp)
app.register_blueprint(ports.bp)
logging.basicConfig(filename='brecal.log', level=logging.DEBUG, format='%(asctime)s | %(name)s | %(levelname)s | %(message)s') logging.basicConfig(filename='brecal.log', level=logging.DEBUG, format='%(asctime)s | %(name)s | %(levelname)s | %(message)s')
local_db.initPool(os.path.dirname(app.instance_path)) local_db.initPool(os.path.dirname(app.instance_path))

View File

@ -3,6 +3,7 @@ from flask import Blueprint, request
from webargs.flaskparser import parser from webargs.flaskparser import parser
from .. import impl from .. import impl
from ..services.auth_guard import auth_guard from ..services.auth_guard import auth_guard
from ..services.jwt_handler import decode_jwt
import json import json
from BreCal.validators.validation_error import create_dynamic_exception_response from BreCal.validators.validation_error import create_dynamic_exception_response
@ -15,8 +16,11 @@ def GetBerths():
try: try:
if 'Authorization' in request.headers: if 'Authorization' in request.headers:
token = request.headers.get('Authorization') token = request.headers.get('Authorization')
return impl.berths.GetBerths(token) payload = decode_jwt(token.split("Bearer ")[-1])
options = {}
options["participant_id"] = payload["participant_id"]
return impl.berths.GetBerths(options)
else: else:
return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated") return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated")

View File

@ -2,6 +2,7 @@ import logging
from flask import Blueprint, request from flask import Blueprint, request
from .. import impl from .. import impl
from ..services.auth_guard import auth_guard from ..services.auth_guard import auth_guard
from ..services.jwt_handler import decode_jwt
import json import json
from BreCal.validators.validation_error import create_dynamic_exception_response from BreCal.validators.validation_error import create_dynamic_exception_response
@ -12,10 +13,15 @@ bp = Blueprint('participants', __name__)
def GetParticipant(): def GetParticipant():
try: try:
if 'Authorization' in request.headers: if 'Authorization' in request.headers:
token = request.headers.get('Authorization') payload = decode_jwt(request.headers.get("Authorization").split("Bearer ")[-1])
options = {} options = {}
options["user_id"] = request.args.get("user_id") options["user_id"] = request.args.get("user_id")
if "participant_id" in payload:
options["participant_id"] = payload["participant_id"]
else:
return create_dynamic_exception_response(ex=None, status_code=403, message="not authorized")
return impl.participant.GetParticipant(options) return impl.participant.GetParticipant(options)
else: else:
return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated") return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated")

View File

@ -0,0 +1,24 @@
import logging
from flask import Blueprint, request
from webargs.flaskparser import parser
from .. import impl
from ..services.auth_guard import auth_guard
import json
from BreCal.validators.validation_error import create_dynamic_exception_response
bp = Blueprint('ports', __name__)
@bp.route('/ports', methods=['get'])
@auth_guard() # no restriction by role
def GetPorts():
try:
if 'Authorization' in request.headers:
token = request.headers.get('Authorization')
return impl.ports.GetPorts(token)
else:
return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated")
except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400)

View File

@ -7,6 +7,7 @@ from ..services.auth_guard import auth_guard, check_jwt
from BreCal.validators.input_validation import validate_posted_shipcall_data, check_if_user_is_bsmd_type from BreCal.validators.input_validation import validate_posted_shipcall_data, check_if_user_is_bsmd_type
from BreCal.validators.input_validation_shipcall import InputValidationShipcall from BreCal.validators.input_validation_shipcall import InputValidationShipcall
from BreCal.database.sql_handler import execute_sql_query_standalone from BreCal.database.sql_handler import execute_sql_query_standalone
from BreCal.services.jwt_handler import decode_jwt
from BreCal.validators.validation_error import create_validation_error_response, create_werkzeug_error_response, create_dynamic_exception_response from BreCal.validators.validation_error import create_validation_error_response, create_werkzeug_error_response, create_dynamic_exception_response
from . import verify_if_request_is_json from . import verify_if_request_is_json
@ -22,7 +23,7 @@ bp = Blueprint('shipcalls', __name__)
def GetShipcalls(): def GetShipcalls():
try: try:
if 'Authorization' in request.headers: if 'Authorization' in request.headers:
token = request.headers.get('Authorization') # see impl/login to see the token encoding, which is a JWT token. token = request.headers.get('Authorization') # see impl/login to see the token encoding, which is a JWT token.
""" """
from BreCal.services.jwt_handler import decode_jwt from BreCal.services.jwt_handler import decode_jwt
@ -32,14 +33,15 @@ def GetShipcalls():
# oneline: # oneline:
payload = decode_jwt(request.headers.get("Authorization").split("Bearer ")[-1]) payload = decode_jwt(request.headers.get("Authorization").split("Bearer ")[-1])
""" """
payload = decode_jwt(request.headers.get("Authorization").split("Bearer ")[-1])
options = {} options = {}
options["participant_id"] = request.args.get("participant_id")
options["past_days"] = request.args.get("past_days", default=1, type=int) options["past_days"] = request.args.get("past_days", default=1, type=int)
options["participant_id"] = payload["participant_id"]
return impl.shipcalls.GetShipcalls(options) return impl.shipcalls.GetShipcalls(options)
else: else:
return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated") return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated")
except Exception as ex: except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400) return create_dynamic_exception_response(ex=ex, status_code=400)
@ -61,10 +63,10 @@ def PostShipcalls():
# validate the posted shipcall data & the user's authority # validate the posted shipcall data & the user's authority
InputValidationShipcall.evaluate_post_data(user_data, loadedModel, content) InputValidationShipcall.evaluate_post_data(user_data, loadedModel, content)
return impl.shipcalls.PostShipcalls(loadedModel) return impl.shipcalls.PostShipcalls(loadedModel)
except ValidationError as ex: except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400) return create_validation_error_response(ex=ex, status_code=400)
except Exception as ex: except Exception as ex:
logging.error(traceback.format_exc()) logging.error(traceback.format_exc())
return create_dynamic_exception_response(ex=ex, status_code=400, message="bad format") return create_dynamic_exception_response(ex=ex, status_code=400, message="bad format")
@ -76,23 +78,26 @@ def PutShipcalls():
try: try:
verify_if_request_is_json(request) verify_if_request_is_json(request)
content = request.get_json(force=True) content = request.get_json(force=True)
loadedModel = model.ShipcallSchema().load(data=content, many=False, partial=True) loadedModel = model.ShipcallSchema().load(data=content, many=False, partial=True)
# read the user data from the JWT token (set when login is performed) # read the user data from the JWT token (set when login is performed)
user_data = check_jwt() user_data = check_jwt()
if not InputValidationShipcall.exists_shipcall_by_id(loadedModel.get("id")):
return create_dynamic_exception_response(ex=None, status_code=404, message="no shipcall found with the provided id")
# validate the PUT shipcall data and the user's authority # validate the PUT shipcall data and the user's authority
InputValidationShipcall.evaluate_put_data(user_data, loadedModel, content) InputValidationShipcall.evaluate_put_data(user_data, loadedModel, content)
return impl.shipcalls.PutShipcalls(loadedModel) return impl.shipcalls.PutShipcalls(loadedModel)
except ValidationError as ex: except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400) return create_validation_error_response(ex=ex, status_code=400)
except werkzeug.exceptions.Forbidden as ex: except werkzeug.exceptions.Forbidden as ex:
return create_werkzeug_error_response(ex=ex, status_code=403) return create_werkzeug_error_response(ex=ex, status_code=403)
except Exception as ex: except Exception as ex:
logging.error(traceback.format_exc()) logging.error(traceback.format_exc())
return create_dynamic_exception_response(ex=None, status_code=400, message="bad format") return create_dynamic_exception_response(ex=None, status_code=400, message="bad format")

View File

@ -23,7 +23,7 @@ def GetShips():
return impl.ships.GetShips(token) return impl.ships.GetShips(token)
else: else:
return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated") return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated")
except Exception as ex: except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400) return create_dynamic_exception_response(ex=ex, status_code=400)
@ -43,7 +43,7 @@ def PostShip():
is_bsmd = check_if_user_is_bsmd_type(user_data) is_bsmd = check_if_user_is_bsmd_type(user_data)
if not is_bsmd: if not is_bsmd:
raise ValidationError({"participant_type":f"current user does not belong to BSMD. Cannot post shipcalls. Found user data: {user_data}"}) raise ValidationError({"participant_type":f"current user does not belong to BSMD. Cannot post shipcalls. Found user data: {user_data}"})
content = request.get_json(force=True) content = request.get_json(force=True)
loadedModel = model.ShipSchema().load(data=content, many=False, partial=True) loadedModel = model.ShipSchema().load(data=content, many=False, partial=True)
@ -53,7 +53,7 @@ def PostShip():
except ValidationError as ex: except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400) return create_validation_error_response(ex=ex, status_code=400)
except Exception as ex: except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400, message=None) return create_dynamic_exception_response(ex=ex, status_code=400, message=None)
@ -71,13 +71,16 @@ def PutShip():
content = request.get_json(force=True) content = request.get_json(force=True)
loadedModel = model.ShipSchema().load(data=content, many=False, partial=True, unknown=EXCLUDE) loadedModel = model.ShipSchema().load(data=content, many=False, partial=True, unknown=EXCLUDE)
if not InputValidationShip.exists_ship_by_dict(model=loadedModel):
return create_dynamic_exception_response(ex=None, status_code=404, message="no ship found with the provided id")
# validate the request data & user permissions # validate the request data & user permissions
InputValidationShip.evaluate_put_data(user_data, loadedModel, content) InputValidationShip.evaluate_put_data(user_data, loadedModel, content)
return impl.ships.PutShip(loadedModel) return impl.ships.PutShip(loadedModel)
except ValidationError as ex: except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400) return create_validation_error_response(ex=ex, status_code=400)
except Exception as ex: except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400) return create_dynamic_exception_response(ex=ex, status_code=400)
@ -88,10 +91,10 @@ def DeleteShip():
try: try:
verify_if_request_is_json(request) verify_if_request_is_json(request)
# read the user data from the JWT token (set when login is performed) # read the user data from the JWT token (set when login is performed)
user_data = check_jwt() user_data = check_jwt()
if 'id' in request.args: if 'id' in request.args:
options = {} options = {}
options["id"] = request.args.get("id") options["id"] = request.args.get("id")
@ -100,12 +103,16 @@ def DeleteShip():
# validate the request data & user permissions # validate the request data & user permissions
ship_id = request.args.get("id") ship_id = request.args.get("id")
if not InputValidationShip.exists_ship_by_id(id=ship_id):
return create_dynamic_exception_response(ex=None, status_code=404, message="no ship found with the provided id")
InputValidationShip.evaluate_delete_data(user_data, ship_id) InputValidationShip.evaluate_delete_data(user_data, ship_id)
return impl.ships.DeleteShip(options) return impl.ships.DeleteShip(options)
except ValidationError as ex: except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400) return create_validation_error_response(ex=ex, status_code=400)
except Exception as ex: except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400) return create_dynamic_exception_response(ex=ex, status_code=400)

View File

@ -20,7 +20,7 @@ def GetTimes():
options = {} options = {}
options["shipcall_id"] = request.args.get("shipcall_id") options["shipcall_id"] = request.args.get("shipcall_id")
return impl.times.GetTimes(options) return impl.times.GetTimes(options)
except Exception as ex: except Exception as ex:
return create_dynamic_exception_response(ex=ex, status_code=400) return create_dynamic_exception_response(ex=ex, status_code=400)
@ -60,13 +60,16 @@ def PutTimes():
try: try:
verify_if_request_is_json(request) verify_if_request_is_json(request)
content = request.get_json(force=True) content = request.get_json(force=True)
loadedModel = model.TimesSchema().load(data=content, many=False, partial=True) loadedModel = model.TimesSchema().load(data=content, many=False, partial=True)
# read the user data from the JWT token (set when login is performed) # read the user data from the JWT token (set when login is performed)
user_data = check_jwt() user_data = check_jwt()
if not InputValidationTimes.exists_times_by_id(loadedModel.get("id")):
return create_dynamic_exception_response(ex=None, status_code=404, message="no times found with the provided id")
# validate the request # validate the request
InputValidationTimes.evaluate_put_data(user_data, loadedModel, content) InputValidationTimes.evaluate_put_data(user_data, loadedModel, content)
return impl.times.PutTimes(loadedModel) return impl.times.PutTimes(loadedModel)
@ -91,13 +94,16 @@ def DeleteTimes():
# read the user data from the JWT token (set when login is performed) # read the user data from the JWT token (set when login is performed)
user_data = check_jwt() user_data = check_jwt()
if not InputValidationTimes.exists_times_by_id(options["id"]):
return create_dynamic_exception_response(ex=None, status_code=404, message="no times found with the provided id")
# validate the request # validate the request
InputValidationTimes.evaluate_delete_data(user_data, times_id = request.args.get("id")) InputValidationTimes.evaluate_delete_data(user_data, times_id = request.args.get("id"))
return impl.times.DeleteTimes(options) return impl.times.DeleteTimes(options)
else: else:
return create_dynamic_exception_response(ex=None, status_code=400, message="Times delete missing argument: id") return create_dynamic_exception_response(ex=None, status_code=400, message="Times delete missing argument: id")
except ValidationError as ex: except ValidationError as ex:
return create_validation_error_response(ex=ex, status_code=400) return create_validation_error_response(ex=ex, status_code=400)

View File

@ -22,15 +22,15 @@ def get_request_code(code_id):
class RequestStatusCode(ABC): class RequestStatusCode(ABC):
def __init__(self): def __init__(self):
return return
@abstractmethod @abstractmethod
def __call__(self, data): def __call__(self, data):
raise NotImplementedError("any default status code object must be callable") raise NotImplementedError("any default status code object must be callable")
@abstractmethod @abstractmethod
def status_code(self): def status_code(self):
raise NotImplementedError("any default status code object should return an integer") raise NotImplementedError("any default status code object should return an integer")
@abstractmethod @abstractmethod
def response(self, data): def response(self, data):
raise NotImplementedError("the response method should return a binary json object. typically, json.dumps is used") raise NotImplementedError("the response method should return a binary json object. typically, json.dumps is used")
@ -38,7 +38,7 @@ class RequestStatusCode(ABC):
def headers(self): def headers(self):
return {'Content-Type': 'application/json; charset=utf-8'} return {'Content-Type': 'application/json; charset=utf-8'}
class RequestCode_HTTP_200_OK(RequestStatusCode): class RequestCode_HTTP_200_OK(RequestStatusCode):
def __init__(self) -> None: def __init__(self) -> None:
@ -46,13 +46,13 @@ class RequestCode_HTTP_200_OK(RequestStatusCode):
def __call__(self, data): def __call__(self, data):
return (self.response(data), self.status_code(), self.headers()) return (self.response(data), self.status_code(), self.headers())
def status_code(self): def status_code(self):
return 200 return 200
def response(self, data): def response(self, data):
return json.dumps(data, default=obj_dict) return json.dumps(data, default=obj_dict)
class RequestCode_HTTP_201_CREATED(RequestStatusCode): class RequestCode_HTTP_201_CREATED(RequestStatusCode):
def __init__(self) -> None: def __init__(self) -> None:
@ -60,10 +60,10 @@ class RequestCode_HTTP_201_CREATED(RequestStatusCode):
def __call__(self, data): def __call__(self, data):
return (self.response(data), self.status_code(), self.headers()) return (self.response(data), self.status_code(), self.headers())
def status_code(self): def status_code(self):
return 201 return 201
def response(self, new_id): def response(self, new_id):
return json.dumps({"id":new_id}) return json.dumps({"id":new_id})
@ -74,10 +74,10 @@ class RequestCode_HTTP_400_BAD_REQUEST(RequestStatusCode):
def __call__(self, data): def __call__(self, data):
return (self.response(data), self.status_code(), self.headers()) return (self.response(data), self.status_code(), self.headers())
def status_code(self): def status_code(self):
return 400 return 400
def response(self, data): def response(self, data):
return json.dumps(data) return json.dumps(data)
@ -88,15 +88,15 @@ class RequestCode_HTTP_403_FORBIDDEN(RequestStatusCode):
def __call__(self, data): def __call__(self, data):
return (self.response(data), self.status_code(), self.headers()) return (self.response(data), self.status_code(), self.headers())
def status_code(self): def status_code(self):
return 403 return 403
def response(self, message="invalid credentials"): def response(self, message="invalid credentials"):
result = {} result = {}
result["message"] = message result["error_field"] = message
return json.dumps(result) return json.dumps(result)
class RequestCode_HTTP_404_NOT_FOUND(RequestStatusCode): class RequestCode_HTTP_404_NOT_FOUND(RequestStatusCode):
def __init__(self) -> None: def __init__(self) -> None:
@ -104,13 +104,13 @@ class RequestCode_HTTP_404_NOT_FOUND(RequestStatusCode):
def __call__(self, data): def __call__(self, data):
return (self.response(data), self.status_code(), self.headers()) return (self.response(data), self.status_code(), self.headers())
def status_code(self): def status_code(self):
return 404 return 404
def response(self, message="no such record"): def response(self, message="no such record"):
result = {} result = {}
result["message"] = message result["error_field"] = message
return json.dumps(result) return json.dumps(result)
@ -120,12 +120,12 @@ class RequestCode_HTTP_500_INTERNAL_SERVER_ERROR(RequestStatusCode):
def __call__(self, data): def __call__(self, data):
return (self.response(data), self.status_code(), self.headers()) return (self.response(data), self.status_code(), self.headers())
def status_code(self): def status_code(self):
return 500 return 500
def response(self, message="credential lookup mismatch"): def response(self, message="credential lookup mismatch"):
result = {} result = {}
result["message"] = message result["error_field"] = message
return json.dumps(result) return json.dumps(result)

View File

@ -10,20 +10,38 @@ def create_sql_query_shipcall_get(options:dict)->str:
options : dict. A dictionary, which must contains the 'past_days' key (int). Determines the range options : dict. A dictionary, which must contains the 'past_days' key (int). Determines the range
by which shipcalls are filtered. by which shipcalls are filtered.
""" """
query = ("SELECT s.id as id, ship_id, type, eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, " + if "participant_id" not in options: # if no participant_id is given, all shipcalls are selected
"flags, s.pier_side, bunkering, replenishing_terminal, replenishing_lock, draft, tidal_window_from, " + query = ("SELECT s.id as id, ship_id, port_id, type, eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, " +
"tidal_window_to, rain_sensitive_cargo, recommended_tugs, anchored, moored_lock, canceled, evaluation, " + "flags, s.pier_side, bunkering, replenishing_terminal, replenishing_lock, draft, tidal_window_from, " +
"evaluation_message, evaluation_time, evaluation_notifications_sent, s.created as created, s.modified as modified, time_ref_point " + "tidal_window_to, rain_sensitive_cargo, recommended_tugs, anchored, moored_lock, canceled, evaluation, " +
"FROM shipcall s " + "evaluation_message, evaluation_time, evaluation_notifications_sent, s.created as created, s.modified as modified, time_ref_point " +
"LEFT JOIN times t ON t.shipcall_id = s.id AND t.participant_type = 8 " + "FROM shipcall s " +
"WHERE " + "LEFT JOIN times t ON t.shipcall_id = s.id AND t.participant_type = 8 " +
"(type = 1 AND " + "WHERE " +
"((t.id IS NOT NULL AND t.eta_berth >= DATE(NOW() - INTERVAL %d DAY)) OR " + "(type = 1 AND " +
"(eta >= DATE(NOW() - INTERVAL %d DAY)))) OR " + "((t.id IS NOT NULL AND t.eta_berth >= DATE(NOW() - INTERVAL %d DAY)) OR " +
"((type = 2 OR type = 3) AND " + "(eta >= DATE(NOW() - INTERVAL %d DAY)))) OR " +
"((t.id IS NOT NULL AND t.etd_berth >= DATE(NOW() - INTERVAL %d DAY)) OR " + "((type = 2 OR type = 3) AND " +
"(etd >= DATE(NOW() - INTERVAL %d DAY)))) " + "((t.id IS NOT NULL AND t.etd_berth >= DATE(NOW() - INTERVAL %d DAY)) OR " +
"ORDER BY eta") % (options["past_days"], options["past_days"], options["past_days"], options["past_days"]) "(etd >= DATE(NOW() - INTERVAL %d DAY)))) " +
"ORDER BY eta") % (options["past_days"], options["past_days"], options["past_days"], options["past_days"])
else:
query = ("SELECT s.id as id, ship_id, port_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, evaluation_time, evaluation_notifications_sent, s.created as created, s.modified as modified, time_ref_point " +
"FROM shipcall s " +
"LEFT JOIN times t ON t.shipcall_id = s.id AND t.participant_type = 8 " +
"WHERE " +
"port_id in (SELECT port_id FROM participant_port_map WHERE participant_id = %d)" +
" AND (" +
"(type = 1 AND " +
"((t.id IS NOT NULL AND t.eta_berth >= DATE(NOW() - INTERVAL %d DAY)) OR " +
"(eta >= DATE(NOW() - INTERVAL %d DAY)))) OR " +
"((type = 2 OR type = 3) AND " +
"((t.id IS NOT NULL AND t.etd_berth >= DATE(NOW() - INTERVAL %d DAY)) OR " +
"(etd >= DATE(NOW() - INTERVAL %d DAY))))) " +
"ORDER BY eta") % (options["participant_id"], options["past_days"], options["past_days"], options["past_days"], options["past_days"])
return query return query

View File

@ -4,19 +4,33 @@ import datetime
def get_user_data_for_id(user_id:int, expiration_time:int=90): def get_user_data_for_id(user_id:int, expiration_time:int=90):
"""debugging function, which is useful to pull user_data from the database, which may be used to create stub data and unit tests""" """debugging function, which is useful to pull user_data from the database, which may be used to create stub data and unit tests"""
query = "SELECT * FROM user where id = ?id?" query = "SELECT * FROM user where id = ?id?"
pdata = execute_sql_query_standalone(query=query, param={"id":user_id}) pdata = execute_sql_query_standalone(query=query, param={"id":user_id}, command_type="single_or_none")
pdata = pdata[0] if len(pdata)>0 else None
assert pdata is not None, f"could not find user with id {user_id}" assert pdata is not None, f"could not find user with id {user_id}"
user_data = {k:v for k,v in pdata.items() if k in ['id','participant_id','first_name','last_name','user_name','user_phone','user_email']} user_data = {k:v for k,v in pdata.items() if k in ['id','participant_id','first_name','last_name','user_name','user_phone','user_email']}
user_data["exp"] = (datetime.datetime.now()+datetime.timedelta(minutes=expiration_time)).timestamp() user_data["exp"] = (datetime.datetime.now()+datetime.timedelta(minutes=expiration_time)).timestamp()
return user_data return user_data
def get_times_data_for_id(times_id:int): def get_times_data_for_id(times_id:int):
"""helper function to load previous times data from the database""" """helper function to load previous times data from the database"""
query = "SELECT * FROM times where id = ?id?" query = "SELECT * FROM times where id = ?id?"
pdata = execute_sql_query_standalone(query=query, param={"id":times_id}) pdata = execute_sql_query_standalone(query=query, param={"id":times_id}, command_type="single_or_none")
pdata = pdata[0] if len(pdata)>0 else None return pdata
assert pdata is not None, f"could not find times with id {times_id}"
def get_ship_data_for_id(ship_id:int):
"""helper function to load previous ship data from the database"""
query = "SELECT * FROM ship where id = ?id?"
pdata = execute_sql_query_standalone(query=query, param={"id":ship_id}, command_type="single_or_none")
return pdata
def get_shipcall_data_for_id(shipcall_id:int):
"""helper function to load previous shipcall data from the database"""
query = "SELECT * FROM shipcall where id = ?id?"
pdata = execute_sql_query_standalone(query=query, param={"id":shipcall_id}, command_type="single_or_none")
return pdata
def get_port_ids_for_participant_id(participant_id:int):
"""helper function to load all port ids for a participant"""
query = "SELECT port_id FROM participant_port_map where participant_id = ?participant_id?"
pdata = execute_sql_query_standalone(query=query, param={"participant_id":participant_id})
return pdata return pdata

View File

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

View File

@ -4,9 +4,8 @@ import pydapper
from ..schemas import model from ..schemas import model
from .. import local_db from .. import local_db
from BreCal.database.sql_queries import SQLQuery
def GetBerths(token): def GetBerths(options):
""" """
No parameters, gets all entries No parameters, gets all entries
""" """
@ -14,16 +13,25 @@ def GetBerths(token):
try: try:
pooledConnection = local_db.getPoolConnection() pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection) commands = pydapper.using(pooledConnection)
# query = SQLQuery.get_berth()
# data = commands.query(query, model=model.Berth) # only load berths to ports that the participant is assigned to
data = commands.query("SELECT id, name, `lock`, owner_id, authority_id, created, modified, deleted FROM berth WHERE deleted = 0 ORDER BY name", model=model.Berth) if "participant_id" in options:
query = ("SELECT id, name, `lock`, owner_id, port_id, authority_id, created, modified, deleted FROM berth WHERE " +
"deleted = 0 AND + "
"port_id IN (SELECT port_id FROM participant_port_map WHERE participant_id = %d) " +
"ORDER BY name") % (options["participant_id"])
else:
query = ("SELECT id, name, `lock`, owner_id, port_id, authority_id, created, modified, deleted FROM berth WHERE " +
"deleted = 0 ORDER BY name")
data = commands.query(query, model=model.Berth)
return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex: except Exception as ex:
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps(result), 500 return json.dumps(result), 500
finally: finally:

View File

@ -35,7 +35,7 @@ def GetHistory(options):
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps("call failed"), 500 return json.dumps("call failed"), 500
return json.dumps(data, default=model.obj_dict), 200, {'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

@ -21,7 +21,7 @@ def GetUser(options):
"api_key, notify_email, notify_whatsapp, notify_signal, notify_popup, created, modified FROM user " + "api_key, notify_email, notify_whatsapp, notify_signal, notify_popup, created, modified FROM user " +
"WHERE user_name = ?username? OR user_email = ?username?", "WHERE user_name = ?username? OR user_email = ?username?",
model=model.User, param={"username" : options["username"]}) model=model.User, param={"username" : options["username"]})
if len(data) == 1: if len(data) == 1:
if bcrypt.checkpw(options["password"].encode("utf-8"), bytes(data[0].password_hash, "utf-8")): if bcrypt.checkpw(options["password"].encode("utf-8"), bytes(data[0].password_hash, "utf-8")):
result = { result = {
@ -39,18 +39,19 @@ def GetUser(options):
if len(data) > 1: if len(data) > 1:
result = {} result = {}
result["message"] = "credential lookup mismatch" result["error_field"] = "credential lookup mismatch"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
result = {} result = {}
result["message"] = "invalid credentials" result["error_field"] = "invalid credentials"
return json.dumps(result), 403, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 403, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex: except Exception as ex:
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed: " + str(ex) result["error_field"] = "call failed"
result["error_description"] = str(ex)
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
finally: finally:

View File

@ -27,7 +27,7 @@ def GetNotifications(options):
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} 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'} return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'}

View File

@ -10,7 +10,6 @@ def GetParticipant(options):
""" """
:param options: A dictionary containing all the paramters for the Operations :param options: A dictionary containing all the paramters for the Operations
options["user_id"]: **Id of user**. *Example: 2*. User id returned by login call. options["user_id"]: **Id of user**. *Example: 2*. User id returned by login call.
""" """
try: try:
@ -18,10 +17,39 @@ def GetParticipant(options):
commands = pydapper.using(pooledConnection) commands = pydapper.using(pooledConnection)
if "user_id" in options and options["user_id"]: if "user_id" in options and options["user_id"]:
# query = SQLQuery.get_participant_by_user_id() # query = SQLQuery.get_participant_by_user_id()
data = commands.query("SELECT p.id as id, p.name as name, p.street as street, p.postal_code as postal_code, p.city as city, p.type as type, p.flags as flags, p.created as created, p.modified as modified, p.deleted as deleted FROM participant p INNER JOIN user u WHERE u.participant_id = p.id and u.id = ?userid?", model=model.Participant, param={"userid" : options["user_id"]}) query = ("SELECT p.id as id, p.name as name, p.street as street, p.postal_code as postal_code, p.city as city, p.type as type, p.flags as flags, " +
"p.created as created, p.modified as modified, p.deleted as deleted FROM participant p " +
"INNER JOIN user u WHERE u.participant_id = p.id and u.id = %s") % options["user_id"]
data = commands.query(query, model=model.Participant)
for participant in data:
port_query = "SELECT port_id FROM participant_port_map WHERE participant_id=?id?"
for record in commands.query(port_query, model=model.Port_Assignment, param={"id" : participant.id}, buffered=False):
pa = model.Port_Assignment(record.port_id)
participant.ports.append(pa.port_id)
else: else:
# query = SQLQuery.get_participants() # query = SQLQuery.get_participants()
data = commands.query("SELECT id, name, street, postal_code, city, type, flags, created, modified, deleted FROM participant p ORDER BY p.name", model=model.Participant) if "participant_id" in options:
# list only participants that are assigned to the same ports than participant of caller
query = ("SELECT p.id as id, name, street, postal_code, city, type, flags, p.created, p.modified, p.deleted " +
"FROM participant p " +
"JOIN participant_port_map ON p.id = participant_port_map.participant_id " +
"WHERE participant_port_map.port_id IN " +
"(SELECT port_id FROM participant_port_map where participant_id = %s) " +
"GROUP BY id " +
"ORDER BY p.name") % options["participant_id"]
else:
query = ("SELECT p.id as id, name, street, postal_code, city, type, flags, p.created, p.modified, p.deleted " +
"FROM participant p " +
"JOIN participant_port_map ON p.id = participant_port_map.participant_id " +
"GROUP BY id " +
"ORDER BY p.name")
data = commands.query(query, model=model.Participant)
for participant in data:
port_query = "SELECT port_id FROM participant_port_map WHERE participant_id=?id?"
for record in commands.query(port_query, model=model.Port_Assignment, param={"id" : participant.id}, buffered=False):
pa = model.Port_Assignment(record.port_id)
participant.ports.append(pa.port_id)
return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'}
@ -29,7 +57,7 @@ def GetParticipant(options):
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps("call failed"), 500 return json.dumps("call failed"), 500
finally: finally:

View File

@ -0,0 +1,33 @@
import json
import logging
import pydapper
from ..schemas import model
from .. import local_db
from BreCal.database.sql_queries import SQLQuery
def GetPorts(token):
"""
No parameters, gets all entries
"""
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
data = commands.query("SELECT id, name, locode, created, modified, deleted FROM port ORDER BY name", model=model.Port)
return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex:
logging.error(ex)
print(ex)
result = {}
result["error_field"] = "call failed"
return json.dumps(result), 500
finally:
if pooledConnection is not None:
pooledConnection.close()

View File

@ -42,7 +42,7 @@ def GetShipcalls(options):
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
finally: finally:
@ -52,8 +52,8 @@ def GetShipcalls(options):
def PostShipcalls(schemaModel): def PostShipcalls(schemaModel):
""" """
This function *executes* a post-request for shipcalls. The function is accessible as part of an API route. This function *executes* a post-request for shipcalls. The function is accessible as part of an API route.
The common sequence is: The common sequence is:
a) issue a request to the Flask API a) issue a request to the Flask API
b) BreCal.api.shipcalls.PostShipcalls, to verify the incoming request (which includes an authentification guard) b) BreCal.api.shipcalls.PostShipcalls, to verify the incoming request (which includes an authentification guard)
@ -62,8 +62,8 @@ def PostShipcalls(schemaModel):
:param schemaModel: The deserialized dict of the request :param schemaModel: The deserialized dict of the request
e.g., e.g.,
{ {
'ship_id': 1, 'type': 1, 'eta': datetime.datetime(2023, 7, 23, 7, 18, 19), 'ship_id': 1, 'type': 1, 'eta': datetime.datetime(2023, 7, 23, 7, 18, 19),
'voyage': '43B', 'tug_required': False, 'pilot_required': True, 'flags': 0, 'voyage': '43B', 'tug_required': False, 'pilot_required': True, 'flags': 0,
'pier_side': False, 'bunkering': True, 'recommended_tugs': 2, 'type_value': 1, 'evaluation_value': 0} 'pier_side': False, 'bunkering': True, 'recommended_tugs': 2, 'type_value': 1, 'evaluation_value': 0}
} }
""" """
@ -151,7 +151,7 @@ def PostShipcalls(schemaModel):
commands.execute(query, {"scid" : new_id, "pid" : user_data["participant_id"], "uid" : user_data["id"]}) 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'} return json.dumps({"id" : new_id}), 201, {'Content-Type': 'application/json; charset=utf-8'}
except ValidationError as ex: except ValidationError as ex:
return create_validation_error_response(ex, status_code=400, create_log=True) return create_validation_error_response(ex, status_code=400, create_log=True)
@ -160,7 +160,7 @@ def PostShipcalls(schemaModel):
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
finally: finally:
@ -262,7 +262,7 @@ def PutShipcalls(schemaModel):
commands.execute(query, {"scid" : schemaModel["id"], "pid" : user_data["participant_id"], "uid" : user_data["id"]}) commands.execute(query, {"scid" : schemaModel["id"], "pid" : user_data["participant_id"], "uid" : user_data["id"]})
return json.dumps({"id" : schemaModel["id"]}), 200 return json.dumps({"id" : schemaModel["id"]}), 200
except ValidationError as ex: except ValidationError as ex:
return create_validation_error_response(ex, status_code=400, create_log=True) return create_validation_error_response(ex, status_code=400, create_log=True)
@ -271,7 +271,7 @@ def PutShipcalls(schemaModel):
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
finally: finally:

View File

@ -25,7 +25,7 @@ def GetShips(token):
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
finally: finally:
@ -91,7 +91,7 @@ def PostShip(schemaModel):
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
@ -133,7 +133,7 @@ def PutShip(schemaModel):
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
@ -157,12 +157,12 @@ def DeleteShip(options):
return json.dumps({"id" : options["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps({"id" : options["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'}
result = {} result = {}
result["message"] = "no such record" result["error_field"] = "no such record"
return json.dumps(result), 404, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 404, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex: except Exception as ex:
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}

View File

@ -35,7 +35,7 @@ def GetTimes(options):
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} 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'} return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'}
@ -104,7 +104,7 @@ def PostTimes(schemaModel):
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
finally: finally:
@ -164,7 +164,7 @@ def PutTimes(schemaModel):
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
finally: finally:
@ -195,14 +195,14 @@ def DeleteTimes(options):
return json.dumps({"id" : options["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps({"id" : options["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'}
result = {} result = {}
result["message"] = "no such record" result["error_field"] = "no such record"
return json.dumps(result), 404, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 404, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex: except Exception as ex:
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
finally: finally:

View File

@ -63,7 +63,7 @@ def PutUser(schemaModel):
commands.execute(query, param={"password_hash" : password_hash, "id" : schemaModel["id"]}) commands.execute(query, param={"password_hash" : password_hash, "id" : schemaModel["id"]})
else: else:
result = {} result = {}
result["message"] = "old password invalid" result["error_field"] = "old password invalid"
return json.dumps(result), 400, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 400, {'Content-Type': 'application/json; charset=utf-8'}
return json.dumps({"id" : schemaModel["id"]}), 200 return json.dumps({"id" : schemaModel["id"]}), 200
@ -72,7 +72,7 @@ def PutUser(schemaModel):
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
result["message"] = "call failed" result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
finally: finally:

View File

@ -29,6 +29,16 @@ class Berth(Schema):
lock: bool lock: bool
owner_id: int owner_id: int
authority_id: int authority_id: int
port_id: int
created: datetime
modified: datetime
deleted: bool
@dataclass
class Port(Schema):
id: int
name: str
locode: str
created: datetime created: datetime
modified: datetime modified: datetime
deleted: bool deleted: bool
@ -54,7 +64,7 @@ class EvaluationType(IntEnum):
def _missing_(cls, value): def _missing_(cls, value):
return cls.undefined return cls.undefined
class NotificationType(IntEnum): class NotificationType(IntEnum):
""" """
Any user has the attributes Any user has the attributes
'notify_email' -> NotificationType.email 'notify_email' -> NotificationType.email
@ -67,7 +77,7 @@ class NotificationType(IntEnum):
push = 2 push = 2
# whatsapp = 3 # whatsapp = 3
# signal = 4 # signal = 4
@classmethod @classmethod
def _missing_(cls, value): def _missing_(cls, value):
return cls.undefined return cls.undefined
@ -128,7 +138,7 @@ class GetVerifyInlineResp(Schema):
@dataclass @dataclass
class Notification: class Notification:
""" """
Base data class for any notification. Base data class for any notification.
Description: Description:
'An entry corresponds to an alarm given by a violated rule during times update' 'An entry corresponds to an alarm given by a violated rule during times update'
@ -168,6 +178,7 @@ class Participant(Schema):
created: datetime created: datetime
modified: datetime modified: datetime
deleted: bool deleted: bool
ports: List[int] = field(default_factory=list)
@validates("type") @validates("type")
def validate_type(self, value): def validate_type(self, value):
@ -175,7 +186,7 @@ class Participant(Schema):
max_int = sum([int(val) for val in list(ParticipantType._value2member_map_.values())]) max_int = sum([int(val) for val in list(ParticipantType._value2member_map_.values())])
min_int = 0 min_int = 0
valid_type = 0 <= value < max_int valid_type = 0 <= value < max_int
if not valid_type: if not valid_type:
raise ValidationError({"type":f"the provided integer is not supported for default behaviour of the ParticipantType IntFlag. Your choice: {value}. Supported values are: 0 <= value {max_int}"}) raise ValidationError({"type":f"the provided integer is not supported for default behaviour of the ParticipantType IntFlag. Your choice: {value}. Supported values are: 0 <= value {max_int}"})
@ -186,7 +197,7 @@ class Participant(Schema):
max_int = sum([int(val) for val in list(ParticipantFlag._value2member_map_.values())]) max_int = sum([int(val) for val in list(ParticipantFlag._value2member_map_.values())])
min_int = 0 min_int = 0
valid_type = 0 <= value < max_int valid_type = 0 <= value < max_int
if not valid_type: if not valid_type:
raise ValidationError({"flags":f"the provided integer is not supported for default behaviour of the ParticipantFlag IntFlag. Your choice: {value}. Supported values are: 0 <= value {max_int}"}) raise ValidationError({"flags":f"the provided integer is not supported for default behaviour of the ParticipantFlag IntFlag. Your choice: {value}. Supported values are: 0 <= value {max_int}"})
@ -205,6 +216,7 @@ class ShipcallSchema(Schema):
id = fields.Integer(required=True) id = fields.Integer(required=True)
ship_id = fields.Integer(required=True) ship_id = fields.Integer(required=True)
port_id = fields.Integer(required=True)
type = fields.Enum(ShipcallType, default=ShipcallType.undefined) type = fields.Enum(ShipcallType, default=ShipcallType.undefined)
eta = fields.DateTime(required=False, allow_none=True) eta = fields.DateTime(required=False, allow_none=True)
voyage = fields.String(allow_none=True, required=False, validate=[validate.Length(max=16)]) voyage = fields.String(allow_none=True, required=False, validate=[validate.Length(max=16)])
@ -243,15 +255,15 @@ class ShipcallSchema(Schema):
data['type_value'] = int(ShipcallType.undefined) data['type_value'] = int(ShipcallType.undefined)
if 'evaluation' in data: if 'evaluation' in data:
if data['evaluation']: if data['evaluation']:
data['evaluation_value'] = int(data['evaluation']) data['evaluation_value'] = int(data['evaluation'])
else: else:
data['evaluation_value'] = int(EvaluationType.undefined) data['evaluation_value'] = int(EvaluationType.undefined)
return data return data
@validates("type") @validates("type")
def validate_type(self, value): def validate_type(self, value):
valid_shipcall_type = int(value) in [item.value for item in ShipcallType] valid_shipcall_type = int(value) in [item.value for item in ShipcallType]
if not valid_shipcall_type: if not valid_shipcall_type:
raise ValidationError({"type":f"the provided type is not a valid shipcall type."}) raise ValidationError({"type":f"the provided type is not a valid shipcall type."})
@ -269,6 +281,16 @@ class Participant_Assignment:
def to_json(self): def to_json(self):
return self.__dict__ return self.__dict__
@dataclass
class Port_Assignment:
def __init__(self, port_id):
self.port_id = port_id
pass
port_id: int
def to_json(self):
return self.__dict__
@dataclass @dataclass
class Shipcall: class Shipcall:
@ -301,6 +323,7 @@ class Shipcall:
evaluation_time: datetime evaluation_time: datetime
evaluation_notifications_sent: bool evaluation_notifications_sent: bool
time_ref_point: int time_ref_point: int
port_id: int
created: datetime created: datetime
modified: datetime modified: datetime
participants: List[Participant_Assignment] = field(default_factory=list) participants: List[Participant_Assignment] = field(default_factory=list)
@ -335,6 +358,7 @@ class Shipcall:
"evaluation_time": self.evaluation_time.isoformat() if self.evaluation_time else "", "evaluation_time": self.evaluation_time.isoformat() if self.evaluation_time else "",
"evaluation_notifications_sent": self.evaluation_notifications_sent, "evaluation_notifications_sent": self.evaluation_notifications_sent,
"time_ref_point": self.time_ref_point, "time_ref_point": self.time_ref_point,
"port_id": self.port_id,
"created": self.created.isoformat() if self.created else "", "created": self.created.isoformat() if self.created else "",
"modified": self.modified.isoformat() if self.modified else "", "modified": self.modified.isoformat() if self.modified else "",
"participants": [participant.__dict__ for participant in self.participants] "participants": [participant.__dict__ for participant in self.participants]
@ -343,8 +367,8 @@ class Shipcall:
@classmethod @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, time_ref_point, created, modified): 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, time_ref_point, port_id, 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, time_ref_point, 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, time_ref_point, port_id, created, modified)
class ShipcallId(Schema): class ShipcallId(Schema):
pass pass
@ -436,7 +460,7 @@ class TimesSchema(Schema):
# when 'value' is 'None', a ValidationError is not issued. # when 'value' is 'None', a ValidationError is not issued.
valid_time = validate_time_is_in_not_too_distant_future(raise_validation_error=True, value=value, months=12) valid_time = validate_time_is_in_not_too_distant_future(raise_validation_error=True, value=value, months=12)
return return
@validates("eta_interval_end") @validates("eta_interval_end")
def validate_eta_interval_end(self, value): def validate_eta_interval_end(self, value):
# violation when time is not in the future, but also does not exceed a threshold for the 'reasonable' future # violation when time is not in the future, but also does not exceed a threshold for the 'reasonable' future
@ -472,12 +496,12 @@ class UserSchema(Schema):
valid_characters = list(map(str,range(0,10)))+["+", " "] valid_characters = list(map(str,range(0,10)))+["+", " "]
if not all([v in valid_characters for v in value]): if not all([v in valid_characters for v in value]):
raise ValidationError({"user_phone":f"one of the phone number values is not valid."}) raise ValidationError({"user_phone":f"one of the phone number values is not valid."})
@validates("user_email") @validates("user_email")
def validate_user_email(self, value): def validate_user_email(self, value):
if not "@" in value: if not "@" in value:
raise ValidationError({"user_email":f"invalid email address"}) raise ValidationError({"user_email":f"invalid email address"})
@dataclass @dataclass
class Times: class Times:
@ -568,7 +592,7 @@ class ShipSchema(Schema):
raise ValidationError({"name":f"'name' argument should have at least one character"}) raise ValidationError({"name":f"'name' argument should have at least one character"})
elif character_length>=64: elif character_length>=64:
raise ValidationError({"name":f"'name' argument should have at max. 63 characters"}) raise ValidationError({"name":f"'name' argument should have at max. 63 characters"})
if check_if_string_has_special_characters(value): if check_if_string_has_special_characters(value):
raise ValidationError({"name":f"'name' argument should not have special characters."}) raise ValidationError({"name":f"'name' argument should not have special characters."})
return return
@ -587,7 +611,7 @@ class ShipSchema(Schema):
callsign_length = len(str(value)) callsign_length = len(str(value))
if callsign_length>8: if callsign_length>8:
raise ValidationError({"callsign":f"'callsign' argument should not have more than 8 characters"}) raise ValidationError({"callsign":f"'callsign' argument should not have more than 8 characters"})
if check_if_string_has_special_characters(value): if check_if_string_has_special_characters(value):
raise ValidationError({"callsign":f"'callsign' argument should not have special characters."}) raise ValidationError({"callsign":f"'callsign' argument should not have special characters."})
return return

View File

@ -25,9 +25,9 @@ def auth_guard(role=None):
try: try:
user_data = check_jwt() user_data = check_jwt()
except Exception as e: except Exception as e:
return json.dumps({"message" : f'{e}', "status": 401}), 401 return json.dumps({"error_field" : f'{e}', "status": 401}), 401
if role and role not in user_data['roles']: if role and role not in user_data['roles']:
return json.dumps({"message": 'Authorization required.', "status" : 403}), 403 return json.dumps({"error_field": 'Authorization required.', "status" : 403}), 403
# get on to original route # get on to original route
return route_function(*args, **kwargs) return route_function(*args, **kwargs)
decorated_function.__name__ = route_function.__name__ decorated_function.__name__ = route_function.__name__

View File

@ -8,6 +8,7 @@ from string import ascii_letters, digits
from BreCal.schemas.model import Ship, Shipcall, Berth, User, Participant, ShipcallType from BreCal.schemas.model import Ship, Shipcall, Berth, User, Participant, ShipcallType
from BreCal.database.sql_handler import execute_sql_query_standalone from BreCal.database.sql_handler import execute_sql_query_standalone
from BreCal.database.sql_queries import SQLQuery from BreCal.database.sql_queries import SQLQuery
from BreCal.database.sql_utils import get_ship_data_for_id
from BreCal.impl.participant import GetParticipant from BreCal.impl.participant import GetParticipant
from BreCal.impl.ships import GetShips from BreCal.impl.ships import GetShips
from BreCal.impl.berths import GetBerths from BreCal.impl.berths import GetBerths
@ -17,6 +18,7 @@ from BreCal.validators.input_validation_utils import check_if_user_is_bsmd_type,
from BreCal.database.sql_handler import execute_sql_query_standalone from BreCal.database.sql_handler import execute_sql_query_standalone
from BreCal.validators.validation_base_utils import check_if_int_is_valid_flag from BreCal.validators.validation_base_utils import check_if_int_is_valid_flag
from BreCal.validators.validation_base_utils import check_if_string_has_special_characters from BreCal.validators.validation_base_utils import check_if_string_has_special_characters
import werkzeug import werkzeug
class InputValidationShip(): class InputValidationShip():
@ -32,6 +34,17 @@ class InputValidationShip():
def __init__(self) -> None: def __init__(self) -> None:
pass pass
@staticmethod
def exists_ship_by_dict(model:dict):
ship_id = model.get("id")
if(ship_id is not None):
return get_ship_data_for_id(ship_id) is not None
return False
@staticmethod
def exists_ship_by_id(id:int):
return get_ship_data_for_id(id) is not None
@staticmethod @staticmethod
def evaluate_post_data(user_data:dict, loadedModel:dict, content:dict): def evaluate_post_data(user_data:dict, loadedModel:dict, content:dict):
# 1.) Only users of type BSMD are allowed to POST # 1.) Only users of type BSMD are allowed to POST

View File

@ -11,6 +11,7 @@ from BreCal.impl.ships import GetShips
from BreCal.impl.berths import GetBerths from BreCal.impl.berths import GetBerths
from BreCal.database.enums import ParticipantType, ParticipantFlag from BreCal.database.enums import ParticipantType, ParticipantFlag
from BreCal.database.sql_utils import get_shipcall_data_for_id
from BreCal.validators.input_validation_utils import check_if_user_is_bsmd_type, check_if_ship_id_is_valid, check_if_berth_id_is_valid, check_if_participant_ids_are_valid, check_if_participant_ids_and_types_are_valid, get_shipcall_id_dictionary, get_participant_type_from_user_data from BreCal.validators.input_validation_utils import check_if_user_is_bsmd_type, check_if_ship_id_is_valid, check_if_berth_id_is_valid, check_if_participant_ids_are_valid, check_if_participant_ids_and_types_are_valid, get_shipcall_id_dictionary, get_participant_type_from_user_data
from BreCal.database.sql_handler import get_assigned_participant_of_type from BreCal.database.sql_handler import get_assigned_participant_of_type
from BreCal.database.sql_handler import execute_sql_query_standalone from BreCal.database.sql_handler import execute_sql_query_standalone
@ -98,6 +99,10 @@ class InputValidationShipcall():
InputValidationShipcall.check_shipcall_is_canceled(loadedModel, content) InputValidationShipcall.check_shipcall_is_canceled(loadedModel, content)
return return
@staticmethod
def exists_shipcall_by_id(id:int):
return get_shipcall_data_for_id(id) is not None
@staticmethod @staticmethod
def check_shipcall_values(loadedModel:dict, content:dict, forbidden_keys:list=["evaluation", "evaluation_message"], is_put_data:bool=False): def check_shipcall_values(loadedModel:dict, content:dict, forbidden_keys:list=["evaluation", "evaluation_message"], is_put_data:bool=False):
""" """
@ -235,21 +240,22 @@ class InputValidationShipcall():
ship_id = loadedModel.get("ship_id", None) ship_id = loadedModel.get("ship_id", None)
arrival_berth_id = loadedModel.get("arrival_berth_id", None) arrival_berth_id = loadedModel.get("arrival_berth_id", None)
departure_berth_id = loadedModel.get("departure_berth_id", None) departure_berth_id = loadedModel.get("departure_berth_id", None)
port_id = loadedModel.get("port_id", None)
participants = loadedModel.get("participants",[]) participants = loadedModel.get("participants",[])
valid_ship_id = check_if_ship_id_is_valid(ship_id=ship_id) valid_ship_id = check_if_ship_id_is_valid(ship_id=ship_id)
if not valid_ship_id: if not valid_ship_id:
raise ValidationError({"ship_id":f"provided an invalid ship id, which is not found in the database: {ship_id}"}) raise ValidationError({"ship_id":f"provided an invalid ship id, which is not found in the database: {ship_id}"})
valid_arrival_berth_id = check_if_berth_id_is_valid(berth_id=arrival_berth_id) valid_arrival_berth_id = check_if_berth_id_is_valid(berth_id=arrival_berth_id, port_id=port_id)
if not valid_arrival_berth_id: if not valid_arrival_berth_id:
raise ValidationError({"arrival_berth_id":f"provided an invalid arrival berth id, which is not found in the database: {arrival_berth_id}"}) raise ValidationError({"arrival_berth_id":f"provided an invalid arrival berth id, which is not found in the database: {arrival_berth_id}, or the berth is not assigned to the port: {port_id}"})
valid_departure_berth_id = check_if_berth_id_is_valid(berth_id=departure_berth_id) valid_departure_berth_id = check_if_berth_id_is_valid(berth_id=departure_berth_id, port_id=port_id)
if not valid_departure_berth_id: if not valid_departure_berth_id:
raise ValidationError({"departure_berth_id":f"provided an invalid departure berth id, which is not found in the database: {departure_berth_id}"}) raise ValidationError({"departure_berth_id":f"provided an invalid departure berth id, which is not found in the database: {departure_berth_id}, or the berth is not assigned to the port: {port_id}"})
valid_participant_ids = check_if_participant_ids_are_valid(participants=participants) valid_participant_ids = check_if_participant_ids_are_valid(participants=participants, port_id=port_id)
if not valid_participant_ids: if not valid_participant_ids:
raise ValidationError({"participants":f"one of the provided participant ids is invalid. Could not find one of these in the database: {participants}"}) raise ValidationError({"participants":f"one of the provided participant ids is invalid. Could not find one of these in the database: {participants}"})
@ -332,7 +338,7 @@ class InputValidationShipcall():
def check_times_are_in_future(loadedModel:dict, content:dict, existing_shipcall:object): def check_times_are_in_future(loadedModel:dict, content:dict, existing_shipcall:object):
""" """
Dates should be in the future. Depending on the ShipcallType, specific values should be checked Dates should be in the future. Depending on the ShipcallType, specific values should be checked
Performs datetime checks in the loadedModel (datetime.datetime objects). Perfornms datetime checks in the loadedModel (datetime.datetime objects).
""" """
# obtain the current datetime to check, whether the provided values are after ref time # obtain the current datetime to check, whether the provided values are after ref time
time_ref = datetime.datetime.now() - datetime.timedelta(days=1) time_ref = datetime.datetime.now() - datetime.timedelta(days=1)
@ -445,15 +451,16 @@ class InputValidationShipcall():
def check_tidal_window_in_future(type_, time_ref, tidal_window_from, tidal_window_to): def check_tidal_window_in_future(type_, time_ref, tidal_window_from, tidal_window_to):
time_in_a_year = datetime.datetime.now().replace(datetime.datetime.now().year + 1) time_in_a_year = datetime.datetime.now().replace(datetime.datetime.now().year + 1)
if tidal_window_to is not None: if tidal_window_to is not None:
if not tidal_window_to >= time_ref: if not tidal_window_to >= time_ref:
raise ValidationError({"tidal_window_to":f"'tidal_window_to' is too far in the past. Incorrect datetime provided."}) raise ValidationError({"tidal_window_to":f"'tidal_window_to' must be in the future. Incorrect datetime provided."})
if tidal_window_to > time_in_a_year: if tidal_window_to > time_in_a_year:
raise ValidationError({"tidal_window_to":f"'tidal_window_to' is more than a year in the future. Found: {tidal_window_to}."}) raise ValidationError({"tidal_window_to":f"'tidal_window_to' is more than a year in the future. Found: {tidal_window_to}."})
if tidal_window_from is not None: if tidal_window_from is not None:
if not tidal_window_from >= time_ref: if not tidal_window_from >= time_ref:
raise ValidationError({"tidal_window_from":f"'tidal_window_from' is too far in the past. Incorrect datetime provided."}) raise ValidationError({"tidal_window_from":f"'tidal_window_from' must be in the future. Incorrect datetime provided."})
if tidal_window_from > time_in_a_year: if tidal_window_from > time_in_a_year:
raise ValidationError({"tidal_window_from":f"'tidal_window_from' is more than a year in the future. Found: {tidal_window_from}."}) raise ValidationError({"tidal_window_from":f"'tidal_window_from' is more than a year in the future. Found: {tidal_window_from}."})
@ -491,7 +498,7 @@ class InputValidationShipcall():
# if the *existing* shipcall in the database is canceled, it may not be changed # if the *existing* shipcall in the database is canceled, it may not be changed
if shipcall.get("canceled", False): if shipcall.get("canceled", False):
raise ValidationError({"canceled":f"The shipcall with id 'shipcall_id' is canceled. A canceled shipcall may not be changed."}) raise ValidationError({"canceled":f"The shipcall with id {shipcall_id} is canceled. A canceled shipcall may not be changed."})
return return
@staticmethod @staticmethod

View File

@ -128,6 +128,10 @@ class InputValidationTimes():
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id) InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id)
return return
@staticmethod
def exists_times_by_id(id:int):
return get_times_data_for_id(id) is not None
@staticmethod @staticmethod
def check_if_entry_is_already_deleted(times_id:int): def check_if_entry_is_already_deleted(times_id:int):
""" """
@ -195,10 +199,11 @@ class InputValidationTimes():
""" """
# extract the IDs # extract the IDs
berth_id, shipcall_id, participant_id = content.get("berth_id"), content.get("shipcall_id"), content.get("participant_id") berth_id, shipcall_id, participant_id = content.get("berth_id"), content.get("shipcall_id"), content.get("participant_id")
port_id = content.get("port_id", None)
valid_berth_id_reference = check_if_berth_id_is_valid(berth_id) valid_berth_id_reference = check_if_berth_id_is_valid(berth_id, port_id)
if not valid_berth_id_reference: if not valid_berth_id_reference:
raise ValidationError({"berth_id":f"The referenced berth_id '{berth_id}' does not exist in the database."}) raise ValidationError({"berth_id":f"The referenced berth_id '{berth_id}' does not exist in the database or is not assigned to the port '{port_id}'."})
valid_shipcall_id_reference = check_if_shipcall_id_is_valid(shipcall_id) valid_shipcall_id_reference = check_if_shipcall_id_is_valid(shipcall_id)
if not valid_shipcall_id_reference: if not valid_shipcall_id_reference:

View File

@ -8,12 +8,13 @@ from BreCal.impl.berths import GetBerths
from BreCal.impl.shipcalls import GetShipcalls from BreCal.impl.shipcalls import GetShipcalls
from BreCal.database.enums import ParticipantType from BreCal.database.enums import ParticipantType
from BreCal.database.sql_utils import get_port_ids_for_participant_id
from marshmallow import ValidationError from marshmallow import ValidationError
def get_participant_id_dictionary(): def get_participant_id_dictionary():
""" """
get a dictionary of all participants, where the key is the participant's id, and the value is a dictionary get a dictionary of all participants, where the key is the participant's id, and the value is a dictionary
of common participant data (not a data model). of common participant data (not a data model).
""" """
# get all participants # get all participants
response,status_code,header = GetParticipant(options={}) response,status_code,header = GetParticipant(options={})
@ -26,7 +27,7 @@ def get_participant_id_dictionary():
def get_berth_id_dictionary(): def get_berth_id_dictionary():
# get all berths # get all berths
response,status_code,header = GetBerths(token=None) response,status_code,header = GetBerths(options={})
# build a dictionary of id:item pairs, so one can select the respective participant # build a dictionary of id:item pairs, so one can select the respective participant
berths = json.loads(response) berths = json.loads(response)
@ -72,7 +73,7 @@ def check_if_user_is_bsmd_type(user_data:dict)->bool:
given a dictionary of user data, determine the respective participant id and read, whether given a dictionary of user data, determine the respective participant id and read, whether
that participant is a .BSMD-type that participant is a .BSMD-type
Note: ParticipantType is an IntFlag. Note: ParticipantType is an IntFlag.
Hence, ParticipantType(1) is ParticipantType.BSMD, Hence, ParticipantType(1) is ParticipantType.BSMD,
and ParticipantType(7) is [ParticipantType.BSMD, ParticipantType.TERMINAL, ParticipantType.PILOT] and ParticipantType(7) is [ParticipantType.BSMD, ParticipantType.TERMINAL, ParticipantType.PILOT]
@ -84,7 +85,7 @@ def check_if_user_is_bsmd_type(user_data:dict)->bool:
participant_type = get_participant_type_from_user_data(user_data) participant_type = get_participant_type_from_user_data(user_data)
# boolean check: is the participant of type .BSMD? # boolean check: is the participant of type .BSMD?
is_bsmd = ParticipantType.BSMD in participant_type is_bsmd = ParticipantType.BSMD in participant_type
return is_bsmd return is_bsmd
def check_if_user_has_bsmd_flag(user_data:dict)->bool: def check_if_user_has_bsmd_flag(user_data:dict)->bool:
@ -92,7 +93,7 @@ def check_if_user_has_bsmd_flag(user_data:dict)->bool:
given a dictionary of user data, determine the respective participant id and read, whether given a dictionary of user data, determine the respective participant id and read, whether
that participant is a .BSMD-type that participant is a .BSMD-type
Note: ParticipantType is an IntFlag. Note: ParticipantType is an IntFlag.
Hence, ParticipantType(1) is ParticipantType.BSMD, Hence, ParticipantType(1) is ParticipantType.BSMD,
and ParticipantType(7) is [ParticipantType.BSMD, ParticipantType.TERMINAL, ParticipantType.PILOT] and ParticipantType(7) is [ParticipantType.BSMD, ParticipantType.TERMINAL, ParticipantType.PILOT]
@ -104,7 +105,7 @@ def check_if_user_has_bsmd_flag(user_data:dict)->bool:
participant_type = get_participant_type_from_user_data(user_data) participant_type = get_participant_type_from_user_data(user_data)
# boolean check: is the participant of type .BSMD? # boolean check: is the participant of type .BSMD?
is_bsmd = ParticipantType.BSMD in participant_type is_bsmd = ParticipantType.BSMD in participant_type
return is_bsmd return is_bsmd
@ -112,31 +113,37 @@ def check_if_ship_id_is_valid(ship_id):
"""check, whether the provided ID is valid. If it is 'None', it will be considered valid. This is, because a shipcall POST-request, does not have to include all IDs at once""" """check, whether the provided ID is valid. If it is 'None', it will be considered valid. This is, because a shipcall POST-request, does not have to include all IDs at once"""
if ship_id is None: if ship_id is None:
return True return True
# build a dictionary of id:item pairs, so one can select the respective participant # build a dictionary of id:item pairs, so one can select the respective participant
ships = get_ship_id_dictionary() ships = get_ship_id_dictionary()
# boolean check # boolean check
ship_id_is_valid = ship_id in list(ships.keys()) ship_id_is_valid = ship_id in list(ships.keys())
return ship_id_is_valid return ship_id_is_valid
def check_if_berth_id_is_valid(berth_id): def check_if_berth_id_is_valid(berth_id, port_id=None):
"""check, whether the provided ID is valid. If it is 'None', it will be considered valid. This is, because a shipcall POST-request, does not have to include all IDs at once""" """check, whether the provided ID is valid. If it is 'None', it will be considered valid. This is, because a shipcall POST-request, does not have to include all IDs at once"""
if berth_id is None: if berth_id is None:
return True return True
# build a dictionary of id:item pairs, so one can select the respective participant # build a dictionary of id:item pairs, so one can select the respective participant
berths = get_berth_id_dictionary() berths = get_berth_id_dictionary()
# boolean check # boolean check
berth_id_is_valid = berth_id in list(berths.keys()) berth_id_is_valid = berth_id in list(berths.keys())
if port_id is not None:
# check, whether the berth is assigned to the respective port
berth_is_assigned_to_port = berths.get(berth_id,{}).get("port_id") == port_id
berth_id_is_valid = berth_id_is_valid and berth_is_assigned_to_port
return berth_id_is_valid return berth_id_is_valid
def check_if_shipcall_id_is_valid(shipcall_id:int): def check_if_shipcall_id_is_valid(shipcall_id:int):
"""check, whether the provided ID is valid. If it is 'None', it will be considered valid. This is, because a request, may not have to include all IDs at once""" """check, whether the provided ID is valid. If it is 'None', it will be considered valid. This is, because a request, may not have to include all IDs at once"""
if shipcall_id is None: if shipcall_id is None:
return True return True
# build a dictionary of id:item pairs, so one can select the respective participant # build a dictionary of id:item pairs, so one can select the respective participant
shipcalls = get_shipcall_id_dictionary() shipcalls = get_shipcall_id_dictionary()
@ -149,7 +156,7 @@ def check_if_participant_id_is_valid_standalone(participant_id:int, participant_
"""check, whether the provided ID is valid. If it is 'None', it will be considered valid. This is, because a request, may not have to include all IDs at once""" """check, whether the provided ID is valid. If it is 'None', it will be considered valid. This is, because a request, may not have to include all IDs at once"""
if participant_id is None: if participant_id is None:
return True return True
# build a dictionary of id:item pairs, so one can select the respective participant # build a dictionary of id:item pairs, so one can select the respective participant
participants = get_participant_id_dictionary() participants = get_participant_id_dictionary()
@ -173,10 +180,10 @@ def check_if_participant_id_is_valid_standalone(participant_id:int, participant_
# when the participant_type is not provided, only evaluate the ID # when the participant_type is not provided, only evaluate the ID
return participant_id_is_valid return participant_id_is_valid
def check_if_participant_id_is_valid(participant:dict): def check_if_participant_id_is_valid(participant:dict, port_id=None):
""" """
check, whether the provided ID is valid. If it is 'None', it will be considered valid. This is, because a shipcall POST-request, does not have to include all IDs at once check, whether the provided ID is valid. If it is 'None', it will be considered valid. This is, because a shipcall POST-request, does not have to include all IDs at once
Following the common BreCal.schemas.model.ParticipantAssignmentSchema, a participant dictionary contains the keys: Following the common BreCal.schemas.model.ParticipantAssignmentSchema, a participant dictionary contains the keys:
'participant_id' : int 'participant_id' : int
'type' : ParticipantType 'type' : ParticipantType
@ -185,9 +192,14 @@ def check_if_participant_id_is_valid(participant:dict):
participant_id = participant.get("participant_id", None) participant_id = participant.get("participant_id", None)
participant_type = ParticipantType(int(participant.get("type", ParticipantType.undefined))) participant_type = ParticipantType(int(participant.get("type", ParticipantType.undefined)))
participant_id_is_valid = check_if_participant_id_is_valid_standalone(participant_id, participant_type) participant_id_is_valid = check_if_participant_id_is_valid_standalone(participant_id, participant_type)
if participant_id_is_valid and port_id is not None:
# check, whether the participant is assigned to the respective port
participant_is_assigned_to_port = any(p.get('port_id', None) == port_id for p in get_port_ids_for_participant_id(participant_id))
participant_id_is_valid &= participant_is_assigned_to_port
return participant_id_is_valid return participant_id_is_valid
def check_if_participant_ids_are_valid(participants:list[dict]): def check_if_participant_ids_are_valid(participants:list[dict], port_id=None):
""" """
args: args:
@ -199,9 +211,9 @@ def check_if_participant_ids_are_valid(participants:list[dict]):
# empty list -> invalid # empty list -> invalid
if participants is None: if participants is None:
return False return False
# check each participant id individually # check each participant id individually
valid_participant_ids = [check_if_participant_id_is_valid(participant) for participant in participants] valid_participant_ids = [check_if_participant_id_is_valid(participant, port_id) for participant in participants]
# boolean check, whether all participant ids are valid # boolean check, whether all participant ids are valid
return all(valid_participant_ids) return all(valid_participant_ids)

View File

@ -77,7 +77,7 @@ def create_werkzeug_error_response(ex:Forbidden, status_code:int=403, create_log
def create_dynamic_exception_response(ex, status_code:int=400, message:typing.Optional[str]=None, create_log:bool=True): def create_dynamic_exception_response(ex, status_code:int=400, message:typing.Optional[str]=None, create_log:bool=True):
message = repr(ex) if message is None else message message = repr(ex) if message is None else message
json_response = create_default_json_response_format(error_field="Exception", error_description=message) json_response = create_default_json_response_format(error_field="Exception", error_description=message)
json_response["message"] = "call failed" json_response["error_field"] = "call failed"
serialized_response = json.dumps(json_response, default=str) serialized_response = json.dumps(json_response, default=str)

View File

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