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;
@ -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)
{ {
@ -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;
} }
} }
@ -322,7 +325,7 @@ namespace BreCalClient
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;
@ -353,6 +366,10 @@ namespace BreCalClient
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);
} }
@ -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

@ -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)
{
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId); 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)
@ -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;
@ -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

View File

@ -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 != null)
{
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY)) if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId); 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)
{ {
@ -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;
@ -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

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
@ -41,20 +43,29 @@ 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))
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 != null)
{
this.Title = this.ShipcallModel.Title;
if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY)) if (this.ShipcallModel.AssignedParticipants.ContainsKey(ParticipantType.AGENCY))
p = BreCalLists.Participants.Find(x => x.Id == this.ShipcallModel.AssignedParticipants[ParticipantType.AGENCY].ParticipantId); 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

@ -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

@ -30,6 +30,9 @@ namespace BreCalClient
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
if ((this.ShipcallModel != null) && (this.ShipcallModel.Shipcall != null))
this.comboBoxBerth.ItemsSource = BreCalLists.GetBerthsByPort(this.ShipcallModel.Shipcall.PortId);
else
this.comboBoxBerth.ItemsSource = BreCalLists.Berths; this.comboBoxBerth.ItemsSource = BreCalLists.Berths;
this.CopyToControls(); this.CopyToControls();
this.EnableControls(); this.EnableControls();
@ -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;

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
@ -168,14 +169,24 @@ 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();
if (loadingSuccessful)
{
this.labelUsername.Text = $"{_loginResult.FirstName} {_loginResult.LastName}"; 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;
@ -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,11 +489,20 @@ 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;
} }
@ -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;
@ -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)));
@ -1109,6 +1164,8 @@ namespace BreCalClient
{ {
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" />
</Grid>
<Grid Grid.Row="0" Grid.Column="2">
<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="labelMooring" PreviewMouseUp="labelMooring_PreviewMouseUp"/> HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelMooring" PreviewMouseUp="labelMooring_PreviewMouseUp"/>
<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" <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" />
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelPortAuthority" PreviewMouseUp="labelPortAuthority_PreviewMouseUp" /> </Grid>
<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" <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"/> HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelPilot" PreviewMouseUp="labelPilot_PreviewMouseUp"/>
<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" <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"/> HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelTug" PreviewMouseUp="labelTug_PreviewMouseUp"/>
<Label Grid.Row="0" Grid.Column="6" Grid.RowSpan="1" FontSize="12" Content="- / -" Foreground="White" Background="{Binding Source={x:Static sets:Settings.Default}, Path=BG_COLOR}" VerticalAlignment="Stretch" <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" />
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Name="labelTerminal" PreviewMouseUp="labelTerminal_PreviewMouseUp" /> </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);
//if (bsmdTimes != null)
this.textBlockBerth.Text = this.ShipcallControlModel?.GetBerthText(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;
@ -114,10 +124,12 @@ namespace RoleEditor
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)

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
@ -16,7 +17,10 @@ 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
@ -13,9 +14,14 @@ 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
@ -32,9 +33,10 @@ 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:
@ -83,6 +85,9 @@ def PutShipcalls():
# 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)

View File

@ -71,6 +71,9 @@ 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)
@ -100,6 +103,10 @@ 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)

View File

@ -67,6 +67,9 @@ def PutTimes():
# 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,6 +94,9 @@ 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"))

View File

@ -94,7 +94,7 @@ class RequestCode_HTTP_403_FORBIDDEN(RequestStatusCode):
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)
@ -110,7 +110,7 @@ class RequestCode_HTTP_404_NOT_FOUND(RequestStatusCode):
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)
@ -126,6 +126,6 @@ class RequestCode_HTTP_500_INTERNAL_SERVER_ERROR(RequestStatusCode):
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,7 +10,8 @@ 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
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, " + "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, " + "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 " + "evaluation_message, evaluation_time, evaluation_notifications_sent, s.created as created, s.modified as modified, time_ref_point " +
@ -24,6 +25,23 @@ def create_sql_query_shipcall_get(options:dict)->str:
"((t.id IS NOT NULL AND t.etd_berth >= DATE(NOW() - INTERVAL %d DAY)) OR " + "((t.id IS NOT NULL AND t.etd_berth >= DATE(NOW() - INTERVAL %d DAY)) OR " +
"(etd >= DATE(NOW() - INTERVAL %d DAY)))) " + "(etd >= DATE(NOW() - INTERVAL %d DAY)))) " +
"ORDER BY eta") % (options["past_days"], options["past_days"], options["past_days"], options["past_days"]) "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

@ -7,3 +7,4 @@ 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

@ -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:
@ -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:
@ -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
@ -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):
@ -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)])
@ -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

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,6 +8,7 @@ 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():
@ -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)
@ -120,7 +121,7 @@ def check_if_ship_id_is_valid(ship_id):
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
@ -130,6 +131,12 @@ def check_if_berth_id_is_valid(berth_id):
# 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):
@ -173,7 +180,7 @@ 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
@ -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:
@ -201,7 +213,7 @@ def check_if_participant_ids_are_valid(participants:list[dict]):
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,