Compare commits

...

19 Commits

Author SHA1 Message Date
529872b590 Allow new shipcalls up to 1 day in the past for serverside validation 2024-11-07 09:26:23 +01:00
f1e1355986 more validation input fixes 2024-10-18 11:49:18 +02:00
e911da20ef Fixed tidal window validation and description output 2024-10-18 09:51:29 +02:00
2ee9af4b9d Version bump to 1.5.0.7 2024-10-17 15:41:50 +02:00
5e2cb3f745 Fixed some more small bugs in validation when only a partial times dataset is put 2024-10-17 14:49:51 +02:00
704c58222c Fix text filter if there is whitespace in the text, simplified some events 2024-10-17 07:42:36 +02:00
401e0d4ae8 bugfix for shipcall PUT validation 2024-10-16 16:16:54 +02:00
scopesorting
c0902c65ee
regardless of the BSMD flag, BSMD users are now able to perform shipc… (#51)
* regardless of the BSMD flag, BSMD users are now able to perform shipcall PUT-requests

* regardless of the BSMD flag, BSMD users are now able to perform shipcall PUT-requests

* docstrings and BSMD-flag handling
2024-10-15 15:19:08 +02:00
8fe2a9ebca Fixed error in validation when times data was updated for operations 2024-09-26 15:00:41 +02:00
d62250fb4f Limit shifting number to 127 to avoid int -> uint overflow on insert 2024-09-25 07:57:58 +02:00
28404fb8b6 Version bump to 1.5.0.6 2024-09-23 08:56:55 +02:00
40e1c91755 Merge branch 'feature/extra_warnings' into release/1.5.0 2024-09-23 08:52:39 +02:00
7921a138d4 fixed bug in ship create 2024-09-23 08:39:25 +02:00
0c8a5cfc2c fixed site module include path 2024-09-23 08:24:39 +02:00
27179da2a2 fixed some missing client warnings 2024-09-22 14:43:52 +02:00
df050cb83b
Merge pull request #50 from puls200/hotfix/20240912
Hotfix/20240912
2024-09-18 08:27:36 +02:00
Max Metz
5e50e09966 when a ship is deleted, the IMO is no longer considered to exist 2024-09-17 15:33:48 +02:00
0bd526e08e Version bump to 1.5.0.5 2024-09-17 10:12:11 +02:00
Max Metz
8df9034574 BSMD-flag check was executed on the wrong ID. Now, it correctly uses the assigned agency's ID to determine the presence of a BSMD flag 2024-09-12 11:07:45 +02:00
23 changed files with 740 additions and 531 deletions

View File

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

View File

@ -85,7 +85,7 @@
<Label x:Name="labelBSMDGranted" Grid.Row="7" Grid.Column="3" Grid.ColumnSpan="1" Content="{x:Static p:Resources.textBSMDGranted}" Visibility="Hidden" FontWeight="DemiBold" />
<Label x:Name="labelShiftingCount" Grid.Row="6" Grid.Column="2" HorizontalAlignment="Right" Content="{x:Static p:Resources.textShiftingSequence}" />
<xctk:IntegerUpDown x:Name="integerUpDownShiftingCount" Grid.Row="6" Grid.Column="3" Margin="2" Minimum="0" Maximum="255" />
<xctk:IntegerUpDown x:Name="integerUpDownShiftingCount" Grid.Row="6" Grid.Column="3" Margin="2" Minimum="0" Maximum="127" />
<StackPanel Grid.Row="8" Grid.Column="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" IsEnabled="False" />

View File

@ -73,7 +73,7 @@ namespace BreCalClient
{
if (!CheckValues(out string message))
{
MessageBox.Show(message, "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{
@ -145,6 +145,13 @@ namespace BreCalClient
return false;
}
if((this.datePickerETA_End.Value.HasValue && !this.datePickerETA.Value.HasValue) ||
(this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textStartTimeMissing;
return false;
}
return true;
}

View File

@ -82,7 +82,7 @@ namespace BreCalClient
{
if (!CheckValues(out string message))
{
System.Windows.MessageBox.Show(message, "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{
@ -155,6 +155,13 @@ namespace BreCalClient
return false;
}
if((this.datePickerETD_End.Value.HasValue && !this.datePickerETD.Value.HasValue) ||
(this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textStartTimeMissing;
return false;
}
return true;
}

View File

@ -73,7 +73,7 @@ namespace BreCalClient
{
if (!CheckValues(out string message))
{
System.Windows.MessageBox.Show(message, "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{
@ -164,6 +164,14 @@ namespace BreCalClient
return false;
}
if((this.datePickerETA_End.Value.HasValue && !this.datePickerETA.Value.HasValue) ||
(this.datePickerETD_End.Value.HasValue && !this.datePickerETD.Value.HasValue) ||
(this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textStartTimeMissing;
return false;
}
return true;
}

View File

@ -49,7 +49,8 @@ namespace BreCalClient
{
if (!CheckValues(out string message))
{
System.Windows.MessageBox.Show(message, "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning,
MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{
@ -116,6 +117,24 @@ namespace BreCalClient
return false;
}
if (this.datePickerETDBerth.Value.HasValue && (this.datePickerETDBerth.Value.Value < DateTime.Now) && (this.datePickerETABerth_End.Value == null))
{
message = BreCalClient.Resources.Resources.textETDInThePast;
return false;
}
if (this.datePickerETDBerth_End.Value.HasValue && this.datePickerETDBerth_End.Value < DateTime.Now)
{
message = BreCalClient.Resources.Resources.textETDInThePast;
return false;
}
if (this.datePickerETDBerth.Value.HasValue && this.datePickerETDBerth_End.Value.HasValue && this.datePickerETDBerth.Value > this.datePickerETDBerth_End.Value)
{
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
return false;
}
if (this.datePickerLockTime.Value.HasValue && (this.datePickerLockTime.Value.Value < DateTime.Now))
{
message = BreCalClient.Resources.Resources.textLockTimeInThePast;
@ -135,6 +154,13 @@ namespace BreCalClient
return false;
}
if((this.datePickerETABerth_End.Value.HasValue && !this.datePickerETABerth.Value.HasValue) ||
(this.datePickerETDBerth_End.Value.HasValue && !this.datePickerETDBerth.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textStartTimeMissing;
return false;
}
return true;
}

View File

@ -64,7 +64,7 @@ namespace BreCalClient
{
if (!CheckValues(out string message))
{
System.Windows.MessageBox.Show(message, "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{
@ -150,6 +150,13 @@ namespace BreCalClient
return false;
}
if((this.datePickerOperationEnd_End.Value.HasValue && !this.datePickerOperationEnd.Value.HasValue) ||
(this.datePickerOperationStart_End.Value.HasValue && !this.datePickerOperationStart.Value.HasValue))
{
message = BreCalClient.Resources.Resources.textStartTimeMissing;
return false;
}
return true;
}

View File

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

View File

@ -119,6 +119,15 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to .
/// </summary>
public static string arrow_right_blue {
get {
return ResourceManager.GetString("arrow_right_blue", resourceCulture);
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
@ -139,6 +148,15 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to .
/// </summary>
public static string arrow_up_green {
get {
return ResourceManager.GetString("arrow_up_green", resourceCulture);
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
@ -1199,6 +1217,15 @@ namespace BreCalClient.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to If an end time is set, a start time is also required.
/// </summary>
public static string textStartTimeMissing {
get {
return ResourceManager.GetString("textStartTimeMissing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Terminal.
/// </summary>

View File

@ -117,16 +117,13 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Arrival" xml:space="preserve">
<value>Einkommend</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="arrow_down_red" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>arrow_down_red.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="arrow_right_blue" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>arrow_right_blue.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="arrow_up_green" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>arrow_up_green.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="clipboard" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>clipboard.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
@ -136,15 +133,24 @@
<data name="containership" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>containership.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="Departure" xml:space="preserve">
<value>Ausgehend</value>
</data>
<data name="emergency_stop_button" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>emergency_stop_button.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="logo_bremen_calling" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>logo_bremen_calling.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="Shifting" xml:space="preserve">
<value>Verholung</value>
</data>
<data name="ship2" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>ship2.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="textAdd" xml:space="preserve">
<value>Hinzufügen</value>
</data>
<data name="textAgencies" xml:space="preserve">
<value>Agenturen</value>
</data>
@ -160,9 +166,18 @@
<data name="textBerth" xml:space="preserve">
<value>Liegeplatz</value>
</data>
<data name="textBerthRemarks" xml:space="preserve">
<value>Liegeplatz Informationen</value>
</data>
<data name="textBerths" xml:space="preserve">
<value>Liegeplätze</value>
</data>
<data name="textBothTideTimesNecessary" xml:space="preserve">
<value>Beide Tidenzeiten sollten angegeben werden (von - bis)</value>
</data>
<data name="textBSMDGranted" xml:space="preserve">
<value>Freigabe zur Bearb. f. BSMD erteilt</value>
</data>
<data name="textBunkering" xml:space="preserve">
<value>Bunkeraufnahme</value>
</data>
@ -181,59 +196,122 @@
<data name="textChange" xml:space="preserve">
<value>Ändern</value>
</data>
<data name="textChangeContactInfo" xml:space="preserve">
<value>Kontaktdaten bearbeiten</value>
</data>
<data name="textChangeHistory" xml:space="preserve">
<value>Verlauf</value>
</data>
<data name="textChangePassword" xml:space="preserve">
<value>Passwort ändern</value>
</data>
<data name="textClearAll" xml:space="preserve">
<value>Alle Eintragungen zurücksetzen?</value>
</data>
<data name="textClearAssignment" xml:space="preserve">
<value>Zuordnung entfernen</value>
</data>
<data name="textClearFilters" xml:space="preserve">
<value>Filter löschen</value>
</data>
<data name="textClearValue" xml:space="preserve">
<value>Eingabe löschen</value>
</data>
<data name="textClose" xml:space="preserve">
<value>Schliessen</value>
</data>
<data name="textConfirmation" xml:space="preserve">
<value>Bestätigung</value>
</data>
<data name="textDelete" xml:space="preserve">
<value>Löschen</value>
</data>
<data name="textDeleted" xml:space="preserve">
<value>Gelöscht</value>
</data>
<data name="textDepartureTerminal" xml:space="preserve">
<value>Terminal Abfahrt</value>
</data>
<data name="textDraft" xml:space="preserve">
<value>Tiefgang (m)</value>
</data>
<data name="textEdit" xml:space="preserve">
<value>Bearbeiten</value>
</data>
<data name="textEditShip" xml:space="preserve">
<value>Schiff bearbeiten</value>
</data>
<data name="textEditShipcall" xml:space="preserve">
<value>Schiffsanlauf bearbeiten</value>
</data>
<data name="textEditShips" xml:space="preserve">
<value>Schiffe anlegen / bearbeiten</value>
</data>
<data name="textEditTimes" xml:space="preserve">
<value>Zeiten bearbeiten</value>
</data>
<data name="textETABerth" xml:space="preserve">
<value>ETA Liegeplatz</value>
<data name="textEmail" xml:space="preserve">
<value>E-Mail</value>
</data>
<data name="textETDBerth" xml:space="preserve">
<value>ETD Liegeplatz</value>
<data name="textEndValueBeforeStartValue" xml:space="preserve">
<value>Endzeit liegt vor Startzeit</value>
</data>
<data name="textEnterKeyword" xml:space="preserve">
<value>Schiff / Bemerkung</value>
</data>
<data name="textError" xml:space="preserve">
<value>Error</value>
</data>
<data name="textETABerth" xml:space="preserve">
<value>ETA Liegeplatz</value>
</data>
<data name="textETAInThePast" xml:space="preserve">
<value>Zeitpunkt ETA liegt in der Vergangenheit</value>
</data>
<data name="textETDBerth" xml:space="preserve">
<value>ETD Liegeplatz</value>
</data>
<data name="textETDInThePast" xml:space="preserve">
<value>Zeitpunkt ETD liegt in der Vergangenheit</value>
</data>
<data name="textExit" xml:space="preserve">
<value>Verlassen</value>
</data>
<data name="textFrom" xml:space="preserve">
<value>von</value>
</data>
<data name="textInterval" xml:space="preserve">
<value>Zeitraum</value>
</data>
<data name="textLengthWidth" xml:space="preserve">
<value>L/B (m)</value>
</data>
<data name="textLogin" xml:space="preserve">
<value>Anmelden</value>
</data>
<data name="textFixed" xml:space="preserve">
<value>Fest</value>
</data>
<data name="textFixedOrder" xml:space="preserve">
<value>Als feste Bestellung vermerkt</value>
</data>
<data name="textFrom" xml:space="preserve">
<value>von</value>
</data>
<data name="textIncoming" xml:space="preserve">
<value>Einkommend</value>
</data>
<data name="textInfoChangePW" xml:space="preserve">
<value>App Info anzeigen und Passwort ändern</value>
</data>
<data name="textInterval" xml:space="preserve">
<value>Zeitraum</value>
</data>
<data name="textLength" xml:space="preserve">
<value>Länge</value>
</data>
<data name="textLengthWidth" xml:space="preserve">
<value>L/B (m)</value>
</data>
<data name="textLockTime" xml:space="preserve">
<value>Zeit Schleuse</value>
</data>
<data name="textOperationsEnd" xml:space="preserve">
<value>Operation Ende</value>
<data name="textLockTimeInThePast" xml:space="preserve">
<value>Schleusenzeit liegt in der Vergangenheit</value>
</data>
<data name="textOperationsStart" xml:space="preserve">
<value>Operation Start</value>
<data name="textLogin" xml:space="preserve">
<value>Anmelden</value>
</data>
<data name="textMineOnly" xml:space="preserve">
<value>nur eigene</value>
</data>
<data name="textMooredLock" xml:space="preserve">
<value>auch in Schleuse</value>
@ -250,21 +328,45 @@
<data name="textNotRotated" xml:space="preserve">
<value>Ungedreht</value>
</data>
<data name="textZoneEntryTime" xml:space="preserve">
<value>Reviereintritt</value>
</data>
<data name="textOK" xml:space="preserve">
<value>OK</value>
</data>
<data name="textOldPassword" xml:space="preserve">
<value>Altes Passwort</value>
</data>
<data name="textOperation" xml:space="preserve">
<value>Vorgang</value>
</data>
<data name="textOperationEndInThePast" xml:space="preserve">
<value>Operation Endzeit liegt in der Vergangenheit</value>
</data>
<data name="textOperationsEnd" xml:space="preserve">
<value>Operation Ende</value>
</data>
<data name="textOperationsStart" xml:space="preserve">
<value>Operation Start</value>
</data>
<data name="textOperationStartInThePast" xml:space="preserve">
<value>Operation Startzeit liegt in der Vergangenheit</value>
</data>
<data name="textOutgoing" xml:space="preserve">
<value>Ausgehend</value>
</data>
<data name="textParticipant" xml:space="preserve">
<value>Teilnehmer</value>
</data>
<data name="textParticipants" xml:space="preserve">
<value>Teilnehmer</value>
</data>
<data name="textPassword" xml:space="preserve">
<value>Passwort</value>
</data>
<data name="textPasswordChanged" xml:space="preserve">
<value>Passwort geändert.</value>
</data>
<data name="textPhone" xml:space="preserve">
<value>Telefon</value>
</data>
<data name="textPierside" xml:space="preserve">
<value>Anlegeseite</value>
</data>
@ -274,12 +376,24 @@
<data name="textPilotRequired" xml:space="preserve">
<value>Lotsorder</value>
</data>
<data name="textPilots" xml:space="preserve">
<value>Flusslotsen</value>
</data>
<data name="textPort" xml:space="preserve">
<value>Backbord</value>
</data>
<data name="textPortAuthority" xml:space="preserve">
<value>Hafenamt</value>
</data>
<data name="textRainSensitiveCargo" xml:space="preserve">
<value>Regensensitive Ladung</value>
</data>
<data name="textRecommendedTugs" xml:space="preserve">
<value>Anzahl Schlepper</value>
</data>
<data name="textRemarks" xml:space="preserve">
<value>Info</value>
</data>
<data name="textRepeatNewPassword" xml:space="preserve">
<value>Neues Passwort wiederholen</value>
</data>
@ -295,27 +409,75 @@
<data name="textSearch" xml:space="preserve">
<value>Suche</value>
</data>
<data name="textShifting" xml:space="preserve">
<value>Verholung</value>
</data>
<data name="textShiftingFrom" xml:space="preserve">
<value>Verholung von</value>
</data>
<data name="textShiftingSequence" xml:space="preserve">
<value>Verhol. Nr.</value>
</data>
<data name="textShiftingTo" xml:space="preserve">
<value>Verholung nach</value>
</data>
<data name="textShip" xml:space="preserve">
<value>Schiff</value>
</data>
<data name="textShipLength" xml:space="preserve">
<value>Schiffslänge</value>
</data>
<data name="textShips" xml:space="preserve">
<value>Schiffe</value>
</data>
<data name="textShowCancelledShipcalls" xml:space="preserve">
<value>Stornierte anzeigen</value>
</data>
<data name="textShowHistory" xml:space="preserve">
<value>Änderungshistorie der Anläufe anzeigen</value>
</data>
<data name="textSortOrder" xml:space="preserve">
<value>Sortierung</value>
</data>
<data name="textStarboard" xml:space="preserve">
<value>Steuerbord</value>
</data>
<data name="textTerminal" xml:space="preserve">
<value>Terminal</value>
</data>
<data name="textTidalBothValues" xml:space="preserve">
<value>Für das Tidenfenster müssen beide Zeiten angegeben werden</value>
</data>
<data name="textTidalWindow" xml:space="preserve">
<value>Tidenfenster</value>
</data>
<data name="textTideTimesInThePast" xml:space="preserve">
<value>Tidenzeit liegt in der Vergangenheit</value>
</data>
<data name="textTimestamp" xml:space="preserve">
<value>Zeitpunkt</value>
</data>
<data name="textTo" xml:space="preserve">
<value>bis</value>
</data>
<data name="textTooFarInTheFuture" xml:space="preserve">
<value>Eine Zeiteingabe ist zu weit in der Zukunft</value>
</data>
<data name="textTooltipSetFixedOrder" xml:space="preserve">
<value>Als feste Bestellung vermerken</value>
</data>
<data name="textTooltipUnSetFixedOrder" xml:space="preserve">
<value>Feste Bestellung zurücknehmen</value>
</data>
<data name="textTriggerManualRefresh" xml:space="preserve">
<value>Manuelle Aktualisierung der Anläufe auslösen</value>
</data>
<data name="textTug" xml:space="preserve">
<value>Schlepper</value>
</data>
<data name="textTugCompany" xml:space="preserve">
<value>Schlepper-Reederei</value>
</data>
<data name="textTugRequired" xml:space="preserve">
<value>Schlepperorder</value>
</data>
@ -328,9 +490,27 @@
<data name="textUsername" xml:space="preserve">
<value>Benutzername</value>
</data>
<data name="textUserNamePasswordEmpty" xml:space="preserve">
<value>Benutzername / Passwort leer!</value>
</data>
<data name="textVoyage" xml:space="preserve">
<value>Reise</value>
</data>
<data name="textWarning" xml:space="preserve">
<value>Warnung</value>
</data>
<data name="textWidth" xml:space="preserve">
<value>Breite</value>
</data>
<data name="textWrongCredentials" xml:space="preserve">
<value>Benutzername / Passwort falsch</value>
</data>
<data name="textZoneEntryInThePast" xml:space="preserve">
<value>Zeit Reviereintritt liegt in der Vergangenheit</value>
</data>
<data name="textZoneEntryTime" xml:space="preserve">
<value>Reviereintritt</value>
</data>
<data name="trafficlight_green" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>trafficlight_green.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
@ -355,196 +535,19 @@
<data name="umbrella_open" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>umbrella_open.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="worker2" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>worker2.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="textClearAssignment" xml:space="preserve">
<value>Zuordnung entfernen</value>
</data>
<data name="textClearValue" xml:space="preserve">
<value>Eingabe löschen</value>
</data>
<data name="textConfirmation" xml:space="preserve">
<value>Bestätigung</value>
</data>
<data name="textPasswordChanged" xml:space="preserve">
<value>Passwort geändert.</value>
</data>
<data name="textClearFilters" xml:space="preserve">
<value>Filter löschen</value>
</data>
<data name="textShowCancelledShipcalls" xml:space="preserve">
<value>Stornierte anzeigen</value>
</data>
<data name="textBerthRemarks" xml:space="preserve">
<value>Liegeplatz Informationen</value>
</data>
<data name="textBSMDGranted" xml:space="preserve">
<value>Freigabe zur Bearb. f. BSMD erteilt</value>
</data>
<data name="textRemarks" xml:space="preserve">
<value>Info</value>
</data>
<data name="textIncoming" xml:space="preserve">
<value>Einkommend</value>
</data>
<data name="textOutgoing" xml:space="preserve">
<value>Ausgehend</value>
</data>
<data name="textShifting" xml:space="preserve">
<value>Verholung</value>
</data>
<data name="textLength" xml:space="preserve">
<value>Länge</value>
</data>
<data name="textShiftingFrom" xml:space="preserve">
<value>Verholung von</value>
</data>
<data name="textShiftingTo" xml:space="preserve">
<value>Verholung nach</value>
</data>
<data name="textWidth" xml:space="preserve">
<value>Breite</value>
</data>
<data name="textChangeContactInfo" xml:space="preserve">
<value>Kontaktdaten bearbeiten</value>
</data>
<data name="textEmail" xml:space="preserve">
<value>E-Mail</value>
</data>
<data name="textPhone" xml:space="preserve">
<value>Telefon</value>
</data>
<data name="textWrongCredentials" xml:space="preserve">
<value>Benutzername / Passwort falsch</value>
</data>
<data name="textUserNamePasswordEmpty" xml:space="preserve">
<value>Benutzername / Passwort leer!</value>
</data>
<data name="textPort" xml:space="preserve">
<value>Backbord</value>
</data>
<data name="textStarboard" xml:space="preserve">
<value>Steuerbord</value>
</data>
<data name="textAdd" xml:space="preserve">
<value>Hinzufügen</value>
</data>
<data name="textDelete" xml:space="preserve">
<value>Löschen</value>
</data>
<data name="textEdit" xml:space="preserve">
<value>Bearbeiten</value>
</data>
<data name="textEditShips" xml:space="preserve">
<value>Schiffe anlegen / bearbeiten</value>
</data>
<data name="textDeleted" xml:space="preserve">
<value>Gelöscht</value>
</data>
<data name="textEditShip" xml:space="preserve">
<value>Schiff bearbeiten</value>
</data>
<data name="textTugCompany" xml:space="preserve">
<value>Schlepper-Reederei</value>
</data>
<data name="textChangeHistory" xml:space="preserve">
<value>Verlauf</value>
</data>
<data name="textInfoChangePW" xml:space="preserve">
<value>App Info anzeigen und Passwort ändern</value>
</data>
<data name="textShowHistory" xml:space="preserve">
<value>Änderungshistorie der Anläufe anzeigen</value>
</data>
<data name="textMineOnly" xml:space="preserve">
<value>nur eigene</value>
</data>
<data name="textOperation" xml:space="preserve">
<value>Vorgang</value>
</data>
<data name="textParticipant" xml:space="preserve">
<value>Teilnehmer</value>
</data>
<data name="textTimestamp" xml:space="preserve">
<value>Zeitpunkt</value>
</data>
<data name="textFixedOrder" xml:space="preserve">
<value>Als feste Bestellung vermerkt</value>
</data>
<data name="textTooltipSetFixedOrder" xml:space="preserve">
<value>Als feste Bestellung vermerken</value>
</data>
<data name="textTooltipUnSetFixedOrder" xml:space="preserve">
<value>Feste Bestellung zurücknehmen</value>
</data>
<data name="textTriggerManualRefresh" xml:space="preserve">
<value>Manuelle Aktualisierung der Anläufe auslösen</value>
</data>
<data name="Arrival" xml:space="preserve">
<value>Einkommend</value>
</data>
<data name="Departure" xml:space="preserve">
<value>Ausgehend</value>
</data>
<data name="Shifting" xml:space="preserve">
<value>Verholung</value>
</data>
<data name="Undefined" xml:space="preserve">
<value>Unbekannt</value>
</data>
<data name="textShips" xml:space="preserve">
<value>Schiffe</value>
<data name="worker2" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>worker2.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="textPilots" xml:space="preserve">
<value>Flusslotsen</value>
<data name="arrow_right_blue" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>arrow_right_blue.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="textPortAuthority" xml:space="preserve">
<value>Hafenamt</value>
<data name="arrow_up_green" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>arrow_up_green.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="textShiftingSequence" xml:space="preserve">
<value>Verhol. Nr.</value>
</data>
<data name="textClearAll" xml:space="preserve">
<value>Alle Eintragungen zurücksetzen?</value>
</data>
<data name="textBothTideTimesNecessary" xml:space="preserve">
<value>Beide Tidenzeiten sollten angegeben werden (von - bis)</value>
</data>
<data name="textEndValueBeforeStartValue" xml:space="preserve">
<value>Endzeit liegt vor Startzeit</value>
</data>
<data name="textError" xml:space="preserve">
<value>Error</value>
</data>
<data name="textETAInThePast" xml:space="preserve">
<value>Zeitpunkt ETA liegt in der Vergangenheit</value>
</data>
<data name="textETDInThePast" xml:space="preserve">
<value>Zeitpunkt ETD liegt in der Vergangenheit</value>
</data>
<data name="textLockTimeInThePast" xml:space="preserve">
<value>Schleusenzeit liegt in der Vergangenheit</value>
</data>
<data name="textOperationEndInThePast" xml:space="preserve">
<value>Operation Endzeit liegt in der Vergangenheit</value>
</data>
<data name="textOperationStartInThePast" xml:space="preserve">
<value>Operation Startzeit liegt in der Vergangenheit</value>
</data>
<data name="textTideTimesInThePast" xml:space="preserve">
<value>Tidenzeit liegt in der Vergangenheit</value>
</data>
<data name="textWarning" xml:space="preserve">
<value>Warnung</value>
</data>
<data name="textZoneEntryInThePast" xml:space="preserve">
<value>Zeit Reviereintritt liegt in der Vergangenheit</value>
</data>
<data name="textTidalBothValues" xml:space="preserve">
<value>Für das Tidenfenster müssen beide Zeiten angegeben werden</value>
</data>
<data name="textTooFarInTheFuture" xml:space="preserve">
<value>Eine Zeiteingabe ist zu weit in der Zukunft</value>
<data name="textStartTimeMissing" xml:space="preserve">
<value>Wenn eine Ende-Zeit angegeben wird, muss auch eine Start-Zeit angegeben werden</value>
</data>
</root>

View File

@ -589,4 +589,13 @@
<data name="_lock" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>lock.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="arrow_right_blue" xml:space="preserve">
<value />
</data>
<data name="arrow_up_green" xml:space="preserve">
<value />
</data>
<data name="textStartTimeMissing" xml:space="preserve">
<value>If an end time is set, a start time is also required</value>
</data>
</root>

View File

@ -89,8 +89,7 @@
<ColumnDefinition Width="30" />
<ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions>
<xctk:WatermarkTextBox x:Name="textBoxSearch" Grid.Column="0" Margin="2" Watermark="{x:Static p:Resources.textEnterKeyword}" PreviewTextInput="textBoxSearch_PreviewTextInput"
DataObject.Pasting="textBoxSearch_Pasting" TextChanged="textBoxSearch_TextChanged" />
<xctk:WatermarkTextBox x:Name="textBoxSearch" Grid.Column="0" Margin="2" Watermark="{x:Static p:Resources.textEnterKeyword}" TextChanged="textBoxSearch_TextChanged" />
<CheckBox x:Name="checkBoxOwn" VerticalAlignment="Center" Grid.Column="1" HorizontalAlignment="Right" Margin="2" Checked="checkBoxOwn_Checked" Unchecked="checkBoxOwn_Checked" />
<Label Content="{x:Static p:Resources.textMineOnly}" Grid.Column="2" />
</Grid>

View File

@ -185,21 +185,9 @@ namespace BreCalClient
SearchFilterChanged?.Invoke();
}
private void textBoxSearch_Pasting(object sender, System.Windows.DataObjectPastingEventArgs e)
{
this.SearchFilter.SearchString = e.SourceDataObject.ToString();
SearchFilterChanged?.Invoke();
}
private void textBoxSearch_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
this.SearchFilter.SearchString = this.textBoxSearch.Text + e.Text;
SearchFilterChanged?.Invoke();
}
private void textBoxSearch_TextChanged(object sender, TextChangedEventArgs e)
{
this.SearchFilter.SearchString = this.textBoxSearch.Text;
this.SearchFilter.SearchString = this.textBoxSearch.Text.Trim();
SearchFilterChanged?.Invoke();
}

View File

@ -51,6 +51,7 @@ def create_app(test_config=None, instance_path=None):
try:
import os
print(f'Instance path = {app.instance_path}')
if not os.path.exists(app.instance_path):
os.makedirs(app.instance_path)
except OSError:
pass

View File

@ -6,6 +6,9 @@ import typing
from BreCal.schemas.model import Shipcall, Ship, Participant, Berth, User, Times, ShipcallParticipantMap
from BreCal.database.enums import ParticipantType
from BreCal.local_db import getPoolConnection
from BreCal.database.sql_queries import SQLQuery
from BreCal.schemas import model
def pandas_series_to_data_model():
return
@ -89,6 +92,13 @@ def execute_sql_query_standalone(query, param={}, pooledConnection=None, model=N
schemas = commands.query_single_or_default(query, sentinel, param=param) if model is None else commands.query_single_or_default(query, sentinel, param=param, model=model)
if schemas is sentinel:
raise Exception("no such record")
elif command_type=="single_or_none":
sentinel = object()
# pulls a *single* row from the query. Typically, these queries require an ID within the param dictionary.
# when providing a model, such as model.Shipcall, the dataset is immediately translated into a data model.
schemas = commands.query_single_or_default(query, sentinel, param=param) if model is None else commands.query_single_or_default(query, sentinel, param=param, model=model)
schemas = None if schemas is sentinel else schemas
elif command_type=="execute_scalar":
schemas = commands.execute_scalar(query)
@ -101,6 +111,26 @@ def execute_sql_query_standalone(query, param={}, pooledConnection=None, model=N
pooledConnection.close()
return schemas
def get_assigned_participant_of_type(shipcall_id:int, participant_type:typing.Union[int,model.ParticipantType])->typing.Optional[model.Participant]:
"""obtains the ShipcallParticipantMap of a given shipcall and finds the participant id of a desired type. Finally, returns the respective Participant"""
spm_shipcall_data = execute_sql_query_standalone(
query=SQLQuery.get_shipcall_participant_map_by_shipcall_id_and_type(),
param={"id":shipcall_id, "type":int(participant_type)},
command_type="query") # returns a list of matches
if len(spm_shipcall_data)==0:
return None
query = 'SELECT * FROM participant WHERE id=?participant_id?'
assigned_participant = execute_sql_query_standalone(
query=query,
param={"participant_id":spm_shipcall_data[0]["participant_id"]},
model=model.Participant,
command_type="single_or_none"
) # returns a list of matches
return assigned_participant
class SQLHandler():
"""
An object that reads SQL queries from the sql_connection and stores it in pandas DataFrames. The object can read all available tables

View File

@ -12,3 +12,11 @@ def get_user_data_for_id(user_id:int, expiration_time:int=90):
user_data["exp"] = (datetime.datetime.now()+datetime.timedelta(minutes=expiration_time)).timestamp()
return user_data
def get_times_data_for_id(times_id:int):
"""helper function to load previous times data from the database"""
query = "SELECT * FROM times where id = ?id?"
pdata = execute_sql_query_standalone(query=query, param={"id":times_id})
pdata = pdata[0] if len(pdata)>0 else None
assert pdata is not None, f"could not find times with id {times_id}"
return pdata

View File

@ -74,7 +74,7 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict
# existance checks in content
# datetime checks in loadedModel (datetime.datetime objects). Dates should be in the future.
time_now = datetime.datetime.now()
time_now = datetime.datetime.now() - datetime.timedelta(days=1)
type_ = loadedModel.get("type", int(ShipcallType.undefined))
if int(type_)==int(ShipcallType.undefined):
raise ValidationError({"type":f"providing 'type' is mandatory. Missing key!"})
@ -85,7 +85,7 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict
if content.get("arrival_berth_id", None) is None:
raise ValidationError({"arrival_berth_id":f"providing 'arrival_berth_id' is mandatory. Missing key!"})
if not eta >= time_now:
raise ValidationError({"eta":f"'eta' must be in the future. Incorrect datetime provided."})
raise ValidationError({"eta":f"'eta' is too far in the past. Incorrect datetime provided."})
elif int(type_)==int(ShipcallType.departure):
etd = loadedModel.get("etd")
if (content.get("etd", None) is None):
@ -93,7 +93,7 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict
if content.get("departure_berth_id", None) is None:
raise ValidationError({"departure_berth_id":f"providing 'departure_berth_id' is mandatory. Missing key!"})
if not etd >= time_now:
raise ValidationError({"etd":f"'etd' must be in the future. Incorrect datetime provided."})
raise ValidationError({"etd":f"'etd' is too far in the past. Incorrect datetime provided."})
elif int(type_)==int(ShipcallType.shifting):
eta = loadedModel.get("eta")
etd = loadedModel.get("etd")
@ -103,17 +103,17 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict
if (content.get("arrival_berth_id", None) is None) or (content.get("departure_berth_id", None) is None):
raise ValidationError({"arrival_berth_id_or_departure_berth_id":f"providing 'arrival_berth_id' & 'departure_berth_id' is mandatory. Missing key!"})
if (not eta >= time_now) or (not etd >= time_now) or (not eta >= etd):
raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must be in the future. Incorrect datetime provided."})
raise ValidationError({"eta_or_etd":f"'eta' and 'etd' are too far in the past. Incorrect datetime provided."})
tidal_window_from = loadedModel.get("tidal_window_from", None)
tidal_window_to = loadedModel.get("tidal_window_to", None)
if tidal_window_to is not None:
if not tidal_window_to >= time_now:
raise ValidationError({"tidal_window_to":f"'tidal_window_to' must be in the future. Incorrect datetime provided."})
raise ValidationError({"tidal_window_to":f"'tidal_window_to' is too far in the past. Incorrect datetime provided."})
if tidal_window_from is not None:
if not tidal_window_from >= time_now:
raise ValidationError({"tidal_window_from":f"'tidal_window_from' must be in the future. Incorrect datetime provided."})
raise ValidationError({"tidal_window_from":f"'tidal_window_from' is too far in the past. Incorrect datetime provided."})
# #TODO: assert tidal_window_from > tidal_window_to

View File

@ -97,7 +97,7 @@ class InputValidationShip():
ships = json.loads(response)
# extract only the 'imo' values
ship_imos = [ship.get("imo") for ship in ships]
ship_imos = [ship.get("imo") for ship in ships if not ship.get("deleted")]
# check, if the imo in the POST-request already exists in the list
imo_already_exists = loadedModel.get("imo") in ship_imos

View File

@ -12,6 +12,7 @@ from BreCal.impl.berths import GetBerths
from BreCal.database.enums import ParticipantType, ParticipantFlag
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 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_string_has_special_characters
@ -126,8 +127,7 @@ class InputValidationShipcall():
if is_put_data:
# the type of a shipcall may not be changed. It can only be set with the initial POST-request.
InputValidationShipcall.check_shipcall_type_is_unchanged(loadedModel)
else:
# time values must use future-dates
InputValidationShipcall.check_times_are_in_future(loadedModel, content)
# some arguments must not be provided
@ -327,10 +327,10 @@ class InputValidationShipcall():
def check_times_are_in_future(loadedModel:dict, content:dict):
"""
Dates should be in the future. Depending on the ShipcallType, specific values should be checked
Perfornms datetime checks in the loadedModel (datetime.datetime objects).
Performs datetime checks in the loadedModel (datetime.datetime objects).
"""
# obtain the current datetime to check, whether the provided values are in the future
time_now = datetime.datetime.now()
# obtain the current datetime to check, whether the provided values are after ref time
time_ref = datetime.datetime.now() - datetime.timedelta(days=1)
type_ = loadedModel.get("type", ShipcallType.undefined.name)
if isinstance(type_, str): # convert the name string to a ShipcallType data model
@ -348,14 +348,14 @@ class InputValidationShipcall():
tidal_window_to = loadedModel.get("tidal_window_to", None)
# Estimated arrival or departure times
InputValidationShipcall.check_times_in_future_based_on_type(type_, time_now, eta, etd)
InputValidationShipcall.check_times_in_future_based_on_type(type_, time_ref, eta, etd)
# Tidal Window
InputValidationShipcall.check_tidal_window_in_future(type_, time_now, tidal_window_from, tidal_window_to)
InputValidationShipcall.check_tidal_window_in_future(type_, time_ref, tidal_window_from, tidal_window_to)
return
@staticmethod
def check_times_in_future_based_on_type(type_, time_now, eta, etd):
def check_times_in_future_based_on_type(type_, time_ref, eta, etd):
"""
checks, whether the ETA & ETD times are in the future.
based on the type, this function checks:
@ -366,6 +366,8 @@ class InputValidationShipcall():
if (eta is None) and (etd is None):
return
time_in_a_year = datetime.datetime.now().replace(datetime.datetime.now().year + 1)
if type_ is None:
raise ValidationError({"type":f"when providing 'eta' or 'etd', one must provide the type of the shipcall, so the datetimes can be verified."})
@ -379,20 +381,24 @@ class InputValidationShipcall():
if eta is None: # null values -> no violation
return
if not eta > time_now:
raise ValidationError({"eta":f"'eta' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETA: {eta}."})
if not eta > time_ref:
raise ValidationError({"eta":f"'eta' is too far in the past. Incorrect datetime provided. Current Time: {time_ref}. ETA: {eta}."})
if etd is not None:
raise ValidationError({"etd":f"'etd' should not be set when the shipcall type is 'arrival'."})
if eta > time_in_a_year:
raise ValidationError({"eta":f"'eta' is more than a year in the future. ETA: {eta}."})
elif int(type_)==int(ShipcallType.departure):
if etd is None: # null values -> no violation
return
if not etd > time_now:
raise ValidationError({"etd":f"'etd' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETD: {etd}."})
if not etd > time_ref:
raise ValidationError({"etd":f"'etd' is too far in the past. Incorrect datetime provided. Current Time: {time_ref}. ETD: {etd}."})
if eta is not None:
raise ValidationError({"eta":f"'eta' should not be set when the shipcall type is 'departure'."})
if etd > time_in_a_year:
raise ValidationError({"etd":f"'etd' is more than a year in the future. ETD: {etd}."})
elif int(type_)==int(ShipcallType.shifting):
if (eta is None) and (etd is None): # null values -> no violation
@ -403,28 +409,43 @@ class InputValidationShipcall():
# rules, a user is only allowed to provide *both* values.
raise ValidationError({"eta_or_etd":f"For shifting shipcalls one should always provide, both, eta and etd."})
if (not eta > time_now) or (not etd > time_now):
raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETA: {eta}. ETD: {etd}"})
if (not eta > time_ref) or (not etd > time_ref):
raise ValidationError({"eta_or_etd":f"'eta' and 'etd' is too far in the past. Incorrect datetime provided. Current Time: {time_ref}. ETA: {eta}. ETD: {etd}"})
if (not etd < eta):
raise ValidationError({"eta_or_etd":f"The estimated time of departure ('etd') must take place *before the estimated time of arrival ('eta'). The ship cannot arrive, before it has departed. Found: ETD: {etd}, ETA: {eta}"})
if (eta is not None and etd is None) or (eta is None and etd is not None):
raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must both be provided when the shipcall type is 'shifting'."})
if eta > time_in_a_year:
raise ValidationError({"eta":f"'eta' is more than a year in the future. ETA: {eta}."})
if etd > time_in_a_year:
raise ValidationError({"etd":f"'etd' is more than a year in the future. ETD: {etd}."})
return
@staticmethod
def check_tidal_window_in_future(type_, time_now, 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)
if tidal_window_to is not None:
if not tidal_window_to >= time_now:
raise ValidationError({"tidal_window_to":f"'tidal_window_to' must be in the future. Incorrect datetime provided."})
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."})
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}."})
if tidal_window_from is not None:
if not tidal_window_from >= time_now:
raise ValidationError({"tidal_window_from":f"'tidal_window_from' must be in the future. Incorrect datetime provided."})
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."})
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}."})
if (tidal_window_to is not None) and (tidal_window_from is not None):
if tidal_window_to < tidal_window_from:
raise ValidationError({"tidal_window_to_or_tidal_window_from":f"'tidal_window_to' must take place after 'tidal_window_from'. Incorrect datetime provided. Found 'tidal_window_to': {tidal_window_to}, 'tidal_window_from': {tidal_window_to}."})
if (tidal_window_to is not None and tidal_window_from is None) or (tidal_window_to is None and tidal_window_from is not None):
raise ValidationError({"tidal_window_to_or_tidal_window_from":f"'tidal_window_to' and 'tidal_window_from' must both be provided."})
return
@staticmethod
@ -465,6 +486,9 @@ class InputValidationShipcall():
def check_shipcall_id_exists(loadedModel):
"""simply checks, whether the defined shipcall ID exists in the database. Otherwise, a PUT-request must fail."""
shipcall_id = loadedModel.get("id")
if shipcall_id is None:
raise ValidationError({"id":"a shipcall id must be provided"})
query = 'SELECT * FROM shipcall where (id = ?shipcall_id?)'
shipcalls = execute_sql_query_standalone(query=query, model=Shipcall, param={"shipcall_id" : shipcall_id})
if len(shipcalls)==0:
@ -479,12 +503,12 @@ class InputValidationShipcall():
a) belong to the ASSIGNED agency participant group
b) belong to a BSMD participant, if the assigned agency has enabled the bit flag
When there is not yet an assigned agency for the respective shipcall, the request fails, and the user is considered as not authorized.
When there is not yet an assigned agency for the respective shipcall, only BSMD users are authorized.
This mechanism prevents self-assignment of an agency to arbitrary shipcalls.
"""
### preparation ###
# use the decoded JWT token and extract the participant type & participant id
participant_id = user_data.get("participant_id")
user_participant_id = user_data.get("participant_id")
participant_type = get_participant_type_from_user_data(user_data)
user_is_bsmd = (ParticipantType.BSMD in participant_type)
@ -494,44 +518,42 @@ class InputValidationShipcall():
### AGENCY in SPM ###
# determine, who is assigned as the agency for the shipcall
if shipcall_participant_map is None:
query = 'SELECT * FROM shipcall_participant_map where (shipcall_id = ?shipcall_id? AND type=?participant_type?)'
assigned_agency = execute_sql_query_standalone(query=query, model=ShipcallParticipantMap, param={"shipcall_id" : shipcall_id, "participant_type":int(ParticipantType.AGENCY)})
else:
assigned_agency = [spm for spm in shipcall_participant_map if int(spm.type) == int(ParticipantType.AGENCY)]
# query = 'SELECT * FROM shipcall_participant_map where (shipcall_id = ?shipcall_id? AND type=?participant_type?)'
# assigned_agency = execute_sql_query_standalone(query=query, model=ShipcallParticipantMap, param={"shipcall_id" : shipcall_id, "participant_type":int(ParticipantType.AGENCY)})
assigned_agency = get_assigned_participant_of_type(shipcall_id, participant_type=ParticipantType.AGENCY)
an_agency_is_assigned = True if assigned_agency is not None else False
else:
# Agency assigned? User must belong to the assigned agency or be a BSMD user, in case the flag is set
assigned_agency = [spm for spm in shipcall_participant_map if int(spm.type) == int(ParticipantType.AGENCY)]
an_agency_is_assigned = len(assigned_agency)==1
if len(assigned_agency)>1:
raise ValidationError({"internal_error":f"Internal error? Found more than one assigned agency for the shipcall with ID {shipcall_id}. Found: {assigned_agency}"})
if an_agency_is_assigned:
# Agency assigned? User must belong to the assigned agency or be a BSMD user, in case the flag is set
assigned_agency = assigned_agency[0]
# determine, whether the assigned agency has set the BSMD-flag to allow BSMD users to edit their assigned shipcalls
query = 'SELECT * FROM participant where (id = ?participant_id?)'
agency_participant = execute_sql_query_standalone(query=query, param={"participant_id" : participant_id}, command_type="single", model=Participant)
if an_agency_is_assigned:
assert isinstance(assigned_agency, Participant), f"expecting the assigency agency to be a Participant object. Found: {type(assigned_agency)}"
assert isinstance(assigned_agency.flags, int), f"this method has currently only been developed with 'flags' being set as an integer. Found: {type(assigned_agency.flags)}"
assert isinstance(agency_participant.flags, int), f"this method has currently only been developed with 'flags' being set as an integer. Found: {type(agency_participant.flags)}"
agency_has_bsmd_flag = agency_participant.flags==1 # once the flags are an IntFlag, change the boolean check to: (ParticipantFlag.BSMD in agency_participant.flags)
# determine, whether the assigned agency has set the BSMD-flag to allow BSMD users to edit their assigned shipcalls
agency_has_bsmd_flag = assigned_agency.flags==1 # once the flags are an IntFlag, change the boolean check to: (ParticipantFlag.BSMD in agency_participant.flags)
### USER authority ###
# determine, whether the user is a) the assigned agency or b) a BSMD participant
user_is_assigned_agency = (participant_id == assigned_agency.participant_id)
user_is_assigned_agency = (user_participant_id == assigned_agency.id)
# when the BSMD flag is set: the user must be either BSMD or the assigned agency
# when the BSMD flag is not set: the user must be the assigned agency
user_is_authorized = (user_is_bsmd or user_is_assigned_agency) if agency_has_bsmd_flag else user_is_assigned_agency
user_is_authorized = (user_is_bsmd or user_is_assigned_agency) #if agency_has_bsmd_flag else user_is_assigned_agency
if not user_is_authorized:
raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). Assigned Agency: {assigned_agency} with Flags: {agency_participant.flags}") # Forbidden: 403
raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). Assigned Agency: {assigned_agency} with Flags: {assigned_agency.flags}") # Forbidden: 403
else:
# when there is no assigned agency, only BSMD users can update the shipcall
user_is_authorized = user_is_bsmd
if not user_is_authorized:
if not user_is_bsmd:
raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). There is no assigned agency yet, so only BSMD users can change datasets.") # part of a pytest.raises. Forbidden: 403
return

View File

@ -15,6 +15,8 @@ from BreCal.database.enums import ParticipantType, ParticipantFlag
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, check_if_shipcall_id_is_valid, get_shipcall_id_dictionary, get_participant_type_from_user_data, get_participant_id_dictionary, check_if_participant_id_is_valid_standalone
from BreCal.database.sql_queries import SQLQuery
from BreCal.database.sql_handler import execute_sql_query_standalone
from BreCal.database.sql_handler import get_assigned_participant_of_type
from BreCal.database.sql_utils import get_times_data_for_id
from BreCal.validators.validation_base_utils import check_if_int_is_valid_flag, check_if_string_has_special_characters
import werkzeug
@ -97,6 +99,10 @@ class InputValidationTimes():
# 1.) Check for the presence of required fields
InputValidationTimes.check_times_required_fields_put_data(content)
# 1.5) Load model from database and set existing fields from dict to model
old_times = get_times_data_for_id(content["id"])
loadedModel = {**old_times, **loadedModel}
# 2.) Only users of the same participant_id, which the times dataset refers to, can update the entry
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=loadedModel, times_id=None)
@ -118,6 +124,7 @@ class InputValidationTimes():
InputValidationTimes.check_if_entry_is_already_deleted(times_id)
# 2.) Only users of the same participant_id, which the times dataset refers to, can delete the entry
if not check_if_user_is_bsmd_type(user_data):
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id)
return
@ -167,12 +174,12 @@ class InputValidationTimes():
if ParticipantType.BSMD in loadedModel["participant_type"]:
raise ValidationError({"participant_type":f"current user belongs to BSMD. Cannot post times datasets. Found user data: {user_data}"})
if (loadedModel["etd_interval_end"] is not None) and (loadedModel["etd_berth"] is not None):
if ("etd_interval_end" in loadedModel and loadedModel["etd_interval_end"] is not None) and ("etd_berth" in loadedModel and loadedModel["etd_berth"] is not None):
time_end_after_time_start = loadedModel["etd_interval_end"] >= loadedModel["etd_berth"]
if not time_end_after_time_start:
raise ValidationError({"etd":f"The provided time interval for the estimated departure time is invalid. The interval end takes place before the interval start. Found interval data: {loadedModel['etd_berth']} to {loadedModel['etd_interval_end']}"})
if (loadedModel["eta_interval_end"] is not None) and (loadedModel["eta_berth"] is not None):
if ("eta_interval_end" in loadedModel and loadedModel["eta_interval_end"] is not None) and ("eta_berth" in loadedModel and loadedModel["eta_berth"] is not None):
time_end_after_time_start = loadedModel["eta_interval_end"] >= loadedModel["eta_berth"]
if not time_end_after_time_start:
raise ValidationError({"eta":f"The provided time interval for the estimated arrival time is invalid. The interval begin takes place after the interval end. Found interval data: {loadedModel['eta_berth']} to {loadedModel['eta_interval_end']}"})
@ -379,45 +386,69 @@ class InputValidationTimes():
# identify the user's participant id
user_participant_id = user_data["participant_id"]
""" # #TODO:
First of all, this method is shared for PUT and DELETE requests.
PUT) is based on the loadedModel
DELETE) is based on the times_id
Both of them share the {user_data}-argument
These arguments are used to obtain shipcall_id, participant_type (of the times entry) and times_assigned_participant
there should be the following authorization approaches
a) the user has the participant ID of the assigned entry for a given role
for this, we need:
1) user_participant_id
2) times_participant_type
3) SPM: assigned participant of the respective type (times_assigned_participant)
_ = get_assigned_participant_of_type(shipcall_id, participant_type=ParticipantType.WHATTYPE)
b) the user is the assigned agency (or the BSMD if allowed)
for this, we need:
1) assigned_agency
assigned_agency = get_assigned_participant_of_type(shipcall_id, participant_type=ParticipantType.AGENCY)
2) agency's flag
assigned_agency.flags
3) user_is_bsmd boolean
"""
# commonly used in the PUT-request
if loadedModel is not None:
shipcall_id = loadedModel["shipcall_id"]
participant_type = loadedModel["participant_type"]
# get the matching entry from the shipcall participant map. Raise an error, when there is no match.
participant_id_of_times_dataset = InputValidationTimes.get_participant_id_from_shipcall_participant_map(shipcall_id, participant_type)
(shipcall_id, times_assigned_participant) = InputValidationTimes.prepare_authority_check_for_put_request(loadedModel)
# commonly used in the DELETE-request
if times_id is not None:
if pdata is None: # regular behavior. pdata is only defined in unit tests.
# perform an SQL query. Creates a pooled connection internally, queries the database, then closes the connection.
query = "SELECT participant_id, participant_type, shipcall_id FROM times WHERE id = ?id?"
pdata = execute_sql_query_standalone(query=query, param={"id":times_id}, pooledConnection=None)
# #TODO_refactor:
(shipcall_id, times_assigned_participant) = InputValidationTimes.prepare_authority_check_for_delete_request(times_id, pdata)
# extracts the participant_id from the first matching entry, if applicable
if not len(pdata)>0:
# this case is usually covered by the InputValidationTimes.check_if_entry_is_already_deleted method already
raise ValidationError({"times_id":f"Unknown times_id. Could not find a matching entry for ID: {times_id}"})
else:
participant_type = pdata[0].get("participant_type")
shipcall_id = pdata[0].get("shipcall_id")
# get the matching entry from the shipcall participant map, where the role matches. Raise an error, when there is no match.
assigned_agency = get_assigned_participant_of_type(shipcall_id, participant_type=ParticipantType.AGENCY)
# get the matching entry from the shipcall participant map. Raise an error, when there is no match.
participant_id_of_times_dataset = pdata[0].get("participant_id")
# participant_id_of_times_dataset = InputValidationTimes.get_participant_id_from_shipcall_participant_map(shipcall_id, participant_type)
# a) the user has the participant ID of the assigned entry for a given role
user_is_assigned_role = user_participant_id == times_assigned_participant.id
# when the user's participant id is different from the times dataset, an exception is raised
if user_participant_id != participant_id_of_times_dataset:
# for some AGENCY participants, users with the BSMD flag may also edit the datasets
special_case__bsmd_may_edit_agency_dataset = InputValidationTimes.check_if_bsmd_may_edit_agency_dataset(user_participant_id, participant_id_of_times_dataset, participant_type)
if special_case__bsmd_may_edit_agency_dataset:
# b) the user is the assigned agency
user_is_assigned_agency = user_participant_id == assigned_agency.id
# c) the user is BSMD, if the assigned agency allows that
assigned_agency_has_bsmd_flag = assigned_agency.flags == 1
user_is_bsmd_type = check_if_user_is_bsmd_type(user_data={"participant_id":user_participant_id})
user_is_bsmd_and_assigned_agency_has_flag = assigned_agency_has_bsmd_flag & user_is_bsmd_type
if user_is_assigned_role:
return
else:
raise ValidationError({"user_participant_type":f"The dataset may only be changed by a user belonging to the same participant group as the times dataset is referring to. User participant_id: {user_participant_id}; Dataset participant_id: {participant_id_of_times_dataset}"})
elif user_is_assigned_agency:
return
elif user_is_bsmd_and_assigned_agency_has_flag:
return
else:
times_participant_id = loadedModel.get("participant_id")
raise ValidationError({"user_participant_type": f"The dataset may only be changed by a user belonging to the same participant group as the times dataset is referring to. Alternatively, the assigned agency may edit and delete the dataset. As a special case, BSMD users may edit and delete times datasets, when the assigned agency allows that. User participant_id: {user_participant_id}; Dataset participant_id: {times_participant_id}"})
@staticmethod
def get_participant_id_from_shipcall_participant_map(shipcall_id:int, participant_type:int, spm_shipcall_data=None)->int:
def get_participant_id_from_shipcall_participant_map(shipcall_id:typing.Optional[int], participant_type:int, spm_shipcall_data=None)->int:
"""use shipcall_id and participant_type to identify the matching participant_id"""
if shipcall_id is None:
raise ValidationError({"shipcall_id":f"Could not find a referenced shipcall_id within the request."})
@ -480,6 +511,42 @@ class InputValidationTimes():
has_bsmd_flag = ParticipantFlag.BSMD in [ParticipantFlag(participant.get("flags"))]
return has_bsmd_flag
@staticmethod
def prepare_authority_check_for_put_request(loadedModel)->typing.Tuple[int,Participant]:
"""extracts the loadedModel to obtain relevant arguments"""
shipcall_id = loadedModel["shipcall_id"]
participant_type = loadedModel["participant_type"]
# get the matching entry from the shipcall participant map, where the role matches. Raise an error, when there is no match.
times_assigned_participant = get_assigned_participant_of_type(shipcall_id, participant_type=participant_type)
if times_assigned_participant is None:
raise ValidationError({"participant_type":"the requested participant type is not assigned to the shipcall."})
return (shipcall_id, times_assigned_participant)
@staticmethod
def prepare_authority_check_for_delete_request(times_id, pdata=None)->typing.Tuple[int,Participant]:
if pdata is None: # regular behavior. pdata is only defined in unit tests.
# perform an SQL query. Creates a pooled connection internally, queries the database, then closes the connection.
query = "SELECT participant_id, participant_type, shipcall_id FROM times WHERE id = ?id?"
pdata = execute_sql_query_standalone(query=query, param={"id":times_id}, pooledConnection=None)
# extracts the participant_id from the first matching entry, if applicable
if not len(pdata)>0:
# this case is usually covered by the InputValidationTimes.check_if_entry_is_already_deleted method already
raise ValidationError({"times_id":f"Unknown times_id. Could not find a matching entry for ID: {times_id}"})
else:
participant_type = pdata[0].get("participant_type")
shipcall_id = pdata[0].get("shipcall_id")
# get the matching entry from the shipcall participant map, where the role matches. Raise an error, when there is no match.
times_assigned_participant = get_assigned_participant_of_type(shipcall_id, participant_type=participant_type)
if times_assigned_participant is None:
raise ValidationError({"participant_type":"the requested participant type is not assigned to the shipcall."})
return (shipcall_id, times_assigned_participant)
def deprecated_build_post_data_type_dependent_required_fields_dict()->dict[ShipcallType,dict[ParticipantType,typing.Optional[list[str]]]]:
"""

View File

@ -41,7 +41,7 @@ def unbundle_validation_error_message(message):
unbundle_(message, unbundled=unbundled)
if len(unbundled)>0:
error_field = "ValidationError in the following field(s): " + " & ".join([unb["error_field"] for unb in unbundled])
error_description = "Error Description(s): " + " & ".join([unb["error_description"] for unb in unbundled])
error_description = " " . join([unb["error_description"] for unb in unbundled])
else:
error_field = "ValidationError"
error_description = "unknown validation error"

View File

@ -3,7 +3,7 @@ import sys
import logging
sys.path.insert(0, '/var/www/brecal_test/src/server')
sys.path.insert(0, '/var/www/venv/lib/python3.10/site-packages/')
sys.path.insert(0, '/var/www/venv/lib/python3.12/site-packages/')
import schedule

View File

@ -226,17 +226,17 @@ def test_shipcall_post_request_fails_when_type_arrival_and_not_in_future(get_stu
# accept
post_data = original_post_data.copy()
post_data["type"] = ShipcallType.arrival.name
post_data["eta"] = (datetime.datetime.now() + datetime.timedelta(hours=3)).isoformat()
post_data["eta"] = (datetime.datetime.now() + datetime.timedelta(days=2)).isoformat()
response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data)
assert response.status_code == 201
# error
post_data = original_post_data.copy()
post_data["type"] = ShipcallType.arrival.name
post_data["eta"] = (datetime.datetime.now() - datetime.timedelta(hours=3)).isoformat()
post_data["eta"] = (datetime.datetime.now() - datetime.timedelta(days=2)).isoformat()
response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data)
with pytest.raises(ValidationError, match="must be in the future. Incorrect datetime provided"):
with pytest.raises(ValidationError, match="is too far in the past. Incorrect datetime provided"):
assert response.status_code==400
raise ValidationError(response.json())
return
@ -256,10 +256,10 @@ def test_shipcall_post_request_fails_when_type_departure_and_not_in_future(get_s
# error
post_data = original_post_data.copy()
post_data["type"] = ShipcallType.departure.name
post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(hours=3)).isoformat()
post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(days=3)).isoformat()
response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data)
with pytest.raises(ValidationError, match="must be in the future. Incorrect datetime provided"):
with pytest.raises(ValidationError, match="is too far in the past. Incorrect datetime provided"):
assert response.status_code==400
raise ValidationError(response.json())
return
@ -280,11 +280,11 @@ def test_shipcall_post_request_fails_when_type_shifting_and_not_in_future(get_st
# error
post_data = original_post_data.copy()
post_data["type"] = ShipcallType.shifting.name
post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(hours=3)).isoformat()
post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(days=3)).isoformat()
post_data["eta"] = (datetime.datetime.now() + datetime.timedelta(hours=3,minutes=1)).isoformat()
response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data)
with pytest.raises(ValidationError, match="must be in the future. Incorrect datetime provided"):
with pytest.raises(ValidationError, match="is too far in the past. Incorrect datetime provided"):
assert response.status_code==400
raise ValidationError(response.json())
return