Merge branch 'release/1.5.0' of github.com:puls200/brecal into release/1.5.0

This commit is contained in:
Max Metz 2024-09-05 11:55:34 +02:00
commit 7ad8011c52
15 changed files with 253 additions and 48 deletions

View File

@ -112,7 +112,7 @@ Usually the "Z" is missing at the end indicating local time.
#### Required fields #### Required fields
* eta / etd (depending on value of type: 1: eta, 2: etd, 3: both) * eta / etd (depending on value of type: 1: eta, 2: etd, 3: etd)
* type * type
* ship_id * ship_id
* arrival_berth_id / departure_berth_id (depending on type, see above) * arrival_berth_id / departure_berth_id (depending on type, see above)
@ -147,6 +147,7 @@ The id field is required, missing fields will not be updated.
| Field | Validation | | Field | Validation |
|-------|------------| |-------|------------|
| eta_berth, etd_berth, lock_time, zone_entry, operations_start, operations_end | if set these values must be in the future| | eta_berth, etd_berth, lock_time, zone_entry, operations_start, operations_end | if set these values must be in the future|
| eta_interval_end, etd_interval_end | if set these values must be in the future. They must be larger than their ETA/ETD counterparts. |
| remarks, berth_info | must be <= 512 chars | | remarks, berth_info | must be <= 512 chars |
| participant_type | must not be BSMD | | participant_type | must not be BSMD |
@ -188,6 +189,7 @@ shipcall_id, participant_id, participant_type
1. A dataset may only be changed by a user belonging to the same participant as the times dataset is referring to. 1. A dataset may only be changed by a user belonging to the same participant as the times dataset is referring to.
2. See reference and value checking as specified in /times POST. 2. See reference and value checking as specified in /times POST.
3. The shipcall type may not be changed.
#### Required fields #### Required fields

View File

@ -32,7 +32,7 @@ ___
| 0002 | Zeiten für einen Eintrag weichen voneinander ab | Bedingungen:<br/> - Header der Zeile ist zugeordnet (Agentur, Festmacher usw. - außer BSMD-Spalte)<br/> - Zeiten ungleich (leere Einträge nicht berücksichtigen => 0001) | | | 0002 | Zeiten für einen Eintrag weichen voneinander ab | Bedingungen:<br/> - Header der Zeile ist zugeordnet (Agentur, Festmacher usw. - außer BSMD-Spalte)<br/> - Zeiten ungleich (leere Einträge nicht berücksichtigen => 0001) | |
| 0002 - A | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / einkommend | Schnittmenge aus:<br/>times_agency:<br/> - ETA Berth <br/>____und____<br/>times_mooring:<br/> - ETA Berth <br/>____und____<br/>times_portauthority:<br/>- ETA Berth <br/>____und____<br/>times_pilot:<br/> - ETA Berth <br/>____und____<br/>times_tug:<br/> - ETA Berth | rot | | 0002 - A | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / einkommend | Schnittmenge aus:<br/>times_agency:<br/> - ETA Berth <br/>____und____<br/>times_mooring:<br/> - ETA Berth <br/>____und____<br/>times_portauthority:<br/>- ETA Berth <br/>____und____<br/>times_pilot:<br/> - ETA Berth <br/>____und____<br/>times_tug:<br/> - ETA Berth | rot |
| 0002 - B | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / ausgehend | Schnittmenge aus:<br/>times_agency:<br/> - ETD Berth <br/>____und____<br/>times_mooring:<br/> - ETD Berth <br/>____und____<br/>times_portauthority:<br/>- ETD Berth <br/>____und____<br/>times_pilot:<br/> - ETD Berth <br/>____und____<br/>times_tug:<br/> - ETD Berth | rot | | 0002 - B | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / ausgehend | Schnittmenge aus:<br/>times_agency:<br/> - ETD Berth <br/>____und____<br/>times_mooring:<br/> - ETD Berth <br/>____und____<br/>times_portauthority:<br/>- ETD Berth <br/>____und____<br/>times_pilot:<br/> - ETD Berth <br/>____und____<br/>times_tug:<br/> - ETD Berth | rot |
| 0002 - C | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / Verholung | Schnittmenge aus:<br />times_agency:<br/> - ETA Berth <br/> - ETD Berth <br/>____und____<br/>times_mooring:<br/> - ETA Berth <br/> - ETD Berth <br/>____und____<br/>times_portauthority:<br/> - ETA Berth <br/>- ETD Berth <br/>____und____<br/>times_pilot:<br/> - ETA Berth <br/> - ETD Berth <br/>____und____<br/>times_tug:<br/> - ETA Berth <br/> - ETD Berth | rot | | 0002 - C | Agentur + Festmacher + Hafenamt + Lotsen + Schlepper / Verholung | Schnittmenge aus:<br/>times_agency:<br/> - ETD Berth <br/>____und____<br/>times_mooring:<br/> - ETD Berth <br/>____und____<br/>times_portauthority:<br/>- ETD Berth <br/>____und____<br/>times_pilot:<br/> - ETD Berth <br/>____und____<br/>times_tug:<br/> - ETD Berth | rot |
| 0003 | Arbeitszeit überschneidet sich mit Fahrtzeit | Bedingungen:<br/> - Header der Zeile ist zugeordnet (Terminal)<br/> - Zeiten passt nicht zu Ankunft / Abfahrt (leere Einträge nicht berücksichtigen => 0001) | | | 0003 | Arbeitszeit überschneidet sich mit Fahrtzeit | Bedingungen:<br/> - Header der Zeile ist zugeordnet (Terminal)<br/> - Zeiten passt nicht zu Ankunft / Abfahrt (leere Einträge nicht berücksichtigen => 0001) | |
| 0003 - A | Terminal / einkommend | times_terminal:<br/> - Operation Start<br/>___vor (kleiner als)____<br/> times_agency:<br/> - ETA Berth | rot, aktuell __deaktiviert__! | | 0003 - A | Terminal / einkommend | times_terminal:<br/> - Operation Start<br/>___vor (kleiner als)____<br/> times_agency:<br/> - ETA Berth | rot, aktuell __deaktiviert__! |
| 0003 - B | Terminal / ausgehend + Verholung | times_terminal:<br/> - Operation Ende<br/>___nach (größer als)____<br/> times_agency:<br/> - ETD Berth | rot, aktuell __deaktiviert__! | | 0003 - B | Terminal / ausgehend + Verholung | times_terminal:<br/> - Operation Ende<br/>___nach (größer als)____<br/> times_agency:<br/> - ETD Berth | rot, aktuell __deaktiviert__! |

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.0</AssemblyVersion> <AssemblyVersion>1.5.0.1</AssemblyVersion>
<FileVersion>1.5.0.0</FileVersion> <FileVersion>1.5.0.1</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

@ -1072,7 +1072,21 @@ namespace BreCalClient
dynamic? msg = JsonConvert.DeserializeObject(m.Value); dynamic? msg = JsonConvert.DeserializeObject(m.Value);
if (msg != null) if (msg != null)
{ {
message = msg.message; if (msg.message != null)
{
caption = $"{caption}: {msg.message}";
}
if((msg.errors != null) && msg.errors.Count > 0)
{
message = "";
foreach(string error in msg.errors)
{
message += error;
message += Environment.NewLine;
}
}
} }
} }
catch (Exception) { } catch (Exception) { }

View File

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

View File

@ -266,6 +266,11 @@ class SQLQuery():
query = "SELECT id, name, imo, callsign, participant_id, length, width, is_tug, bollard_pull, eni, created, modified, deleted FROM ship ORDER BY name" query = "SELECT id, name, imo, callsign, participant_id, length, width, is_tug, bollard_pull, eni, created, modified, deleted FROM ship ORDER BY name"
return query return query
@staticmethod
def get_ship_by_id()->str:
query = "SELECT * FROM ship where id = ?id?"
return query
@staticmethod @staticmethod
def get_times()->str: def get_times()->str:
query = "SELECT id, eta_berth, eta_berth_fixed, etd_berth, etd_berth_fixed, lock_time, lock_time_fixed, " + \ query = "SELECT id, eta_berth, eta_berth_fixed, etd_berth, etd_berth_fixed, lock_time, lock_time_fixed, " + \

View File

@ -436,6 +436,21 @@ class TimesSchema(Schema):
# when 'value' is 'None', a ValidationError is not issued. # when 'value' is 'None', a ValidationError is not issued.
valid_time = validate_time_is_in_not_too_distant_future(raise_validation_error=True, value=value, months=12) valid_time = validate_time_is_in_not_too_distant_future(raise_validation_error=True, value=value, months=12)
return return
@validates("eta_interval_end")
def validate_eta_interval_end(self, value):
# violation when time is not in the future, but also does not exceed a threshold for the 'reasonable' future
# when 'value' is 'None', a ValidationError is not issued.
valid_time = validate_time_is_in_not_too_distant_future(raise_validation_error=True, value=value, months=12)
return
@validates("etd_interval_end")
def validate_etd_interval_end(self, value):
# violation when time is not in the future, but also does not exceed a threshold for the 'reasonable' future
# when 'value' is 'None', a ValidationError is not issued.
valid_time = validate_time_is_in_not_too_distant_future(raise_validation_error=True, value=value, months=12)
return
# deserialize PUT object target # deserialize PUT object target

View File

@ -0,0 +1,12 @@
import datetime
from BreCal.schemas import model
from BreCal.schemas.model import ParticipantType
def get_schema_model_stub_departure():
schemaModel = {'id': 0, 'eta_berth': None, 'eta_berth_fixed': None, 'etd_berth': datetime.datetime(2024, 9, 7, 15, 12, 58), 'etd_berth_fixed': None, 'lock_time': None, 'lock_time_fixed': None, 'zone_entry': None, 'zone_entry_fixed': None, 'operations_start': None, 'operations_end': None, 'remarks': 'test', 'participant_id': 10, 'berth_id': 146, 'berth_info': '', 'pier_side': None, 'shipcall_id': 115, 'participant_type': ParticipantType.AGENCY, 'ata': None, 'atd': None, 'eta_interval_end': None, 'etd_interval_end': None, 'created': None, 'modified': None}
return schemaModel
def get_schema_model_stub_arrival():
schemaModel = {'id': 0, 'eta_berth': datetime.datetime(2024, 9, 7, 15, 12, 58), 'eta_berth_fixed': None, 'etd_berth': None, 'etd_berth_fixed': None, 'lock_time': None, 'lock_time_fixed': None, 'zone_entry': None, 'zone_entry_fixed': None, 'operations_start': None, 'operations_end': None, 'remarks': 'test', 'participant_id': 10, 'berth_id': 146, 'berth_info': '', 'pier_side': None, 'shipcall_id': 115, 'participant_type': ParticipantType.AGENCY, 'ata': None, 'atd': None, 'eta_interval_end': None, 'etd_interval_end': None, 'created': None, 'modified': None}
return schemaModel

View File

@ -6,6 +6,8 @@ from marshmallow import ValidationError
from string import ascii_letters, digits 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_queries import SQLQuery
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
@ -47,14 +49,14 @@ class InputValidationShip():
# 1.) Only users of type BSMD are allowed to PUT # 1.) Only users of type BSMD are allowed to PUT
InputValidationShip.check_user_is_bsmd_type(user_data) InputValidationShip.check_user_is_bsmd_type(user_data)
# 2.) The IMO number field may not be changed # 2.) ID field is mandatory
InputValidationShip.content_contains_ship_id(content)
# 3.) The IMO number field may not be changed
InputValidationShip.put_content_may_not_contain_imo_number(content) InputValidationShip.put_content_may_not_contain_imo_number(content)
# 3.) Check for reasonable Values (see BreCal.schemas.model.ShipSchema) # 4.) Check for reasonable Values (see BreCal.schemas.model.ShipSchema)
InputValidationShip.optionally_evaluate_bollard_pull_value(content) InputValidationShip.optionally_evaluate_bollard_pull_value(content)
# 4.) ID field is mandatory
InputValidationShip.content_contains_ship_id(content)
return return
@staticmethod @staticmethod
@ -101,11 +103,15 @@ class InputValidationShip():
@staticmethod @staticmethod
def put_content_may_not_contain_imo_number(content:dict): def put_content_may_not_contain_imo_number(content:dict):
# IMO is a required field, so it will never be None outside of tests. If so, there is no violation
put_data_ship_imo = content.get("imo",None) put_data_ship_imo = content.get("imo",None)
if put_data_ship_imo is None:
return
# grab the ship by its ID and compare, whether the IMO is unchanged
ship = execute_sql_query_standalone(SQLQuery.get_ship_by_id(), param={"id":content.get("id")}, command_type="single", model=Ship)
# #TODO: Aktuelle IMO abfragen und nach Änderung suchen, bevor eine Fehlermeldung erstellt wird if put_data_ship_imo != ship.imo:
if put_data_ship_imo is not None:
raise ValidationError(f"The IMO number field may not be changed since it serves the purpose of a primary (matching) key.") raise ValidationError(f"The IMO number field may not be changed since it serves the purpose of a primary (matching) key.")
return return

View File

@ -123,6 +123,9 @@ class InputValidationShipcall():
# time values must use future-dates # time values must use future-dates
InputValidationShipcall.check_times_are_in_future(loadedModel, content) InputValidationShipcall.check_times_are_in_future(loadedModel, content)
# 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)
# some arguments must not be provided # some arguments must not be provided
InputValidationShipcall.check_forbidden_arguments(content, forbidden_keys=forbidden_keys) InputValidationShipcall.check_forbidden_arguments(content, forbidden_keys=forbidden_keys)
return return
@ -247,6 +250,16 @@ class InputValidationShipcall():
if not valid_participant_types: # #TODO: according to Daniel, there may eventually be multi-assignment of participants for the same role if not valid_participant_types: # #TODO: according to Daniel, there may eventually be multi-assignment of participants for the same role
raise ValidationError(f"every participant id and type should be listed only once. Found multiple entries for one of the participants.") raise ValidationError(f"every participant id and type should be listed only once. Found multiple entries for one of the participants.")
@staticmethod
def check_shipcall_type_is_unchanged(loadedModel:dict):
# the type of a shipcall may only be set on POST requests. Afterwards, shipcall types may not be changed.
query = SQLQuery.get_shipcall_by_id()
shipcall = execute_sql_query_standalone(query=query, model=Shipcall, param={"id":loadedModel.get("id")}, command_type="single")
if int(loadedModel["type"]) != int(shipcall.type):
raise ValidationError(f"The shipcall type may only be set in the initial POST-request. Afterwards, changing the shipcall type is not allowed.") # @pytest.raises
return
@staticmethod @staticmethod
def check_forbidden_arguments(content:dict, forbidden_keys=["evaluation", "evaluation_message"]): def check_forbidden_arguments(content:dict, forbidden_keys=["evaluation", "evaluation_message"]):
""" """

View File

@ -29,32 +29,32 @@ def build_post_data_type_dependent_required_fields_dict()->dict[ShipcallType,dic
ShipcallType.arrival:{ ShipcallType.arrival:{
ParticipantType.undefined:[], # should not be set in POST requests ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:["operations_start"], ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:["eta_berth"], ParticipantType.AGENCY:[],
ParticipantType.MOORING:["eta_berth"], ParticipantType.MOORING:[],
ParticipantType.PILOT:["eta_berth"], ParticipantType.PILOT:[],
ParticipantType.PORT_ADMINISTRATION:["eta_berth"], ParticipantType.PORT_ADMINISTRATION:[],
ParticipantType.TUG:["eta_berth"], ParticipantType.TUG:[],
}, },
ShipcallType.departure:{ ShipcallType.departure:{
ParticipantType.undefined:[], # should not be set in POST requests ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:["operations_end"], ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:["etd_berth"], ParticipantType.AGENCY:[],
ParticipantType.MOORING:["etd_berth"], ParticipantType.MOORING:[],
ParticipantType.PILOT:["etd_berth"], ParticipantType.PILOT:[],
ParticipantType.PORT_ADMINISTRATION:["etd_berth"], ParticipantType.PORT_ADMINISTRATION:[],
ParticipantType.TUG:["etd_berth"], ParticipantType.TUG:[],
}, },
ShipcallType.shifting:{ ShipcallType.shifting:{
ParticipantType.undefined:[], # should not be set in POST requests ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:["operations_start", "operations_end"], ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:["eta_berth", "etd_berth"], ParticipantType.AGENCY:[],
ParticipantType.MOORING:["eta_berth", "etd_berth"], ParticipantType.MOORING:[],
ParticipantType.PILOT:["eta_berth", "etd_berth"], ParticipantType.PILOT:[],
ParticipantType.PORT_ADMINISTRATION:["eta_berth", "etd_berth"], ParticipantType.PORT_ADMINISTRATION:[],
ParticipantType.TUG:["eta_berth", "etd_berth"], ParticipantType.TUG:[],
}, },
} }
return post_data_type_dependent_required_fields_dict return post_data_type_dependent_required_fields_dict
@ -167,6 +167,17 @@ class InputValidationTimes():
if ParticipantType.BSMD in loadedModel["participant_type"]: if ParticipantType.BSMD in loadedModel["participant_type"]:
raise ValidationError(f"current user belongs to BSMD. Cannot post times datasets. Found user data: {user_data}") raise ValidationError(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):
time_end_after_time_start = loadedModel["etd_interval_end"] >= loadedModel["etd_berth"]
if not time_end_after_time_start:
raise ValidationError(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):
time_end_after_time_start = loadedModel["eta_interval_end"] >= loadedModel["eta_berth"]
if not time_end_after_time_start:
raise ValidationError(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']}")
return return
@staticmethod @staticmethod
@ -471,3 +482,44 @@ class InputValidationTimes():
has_bsmd_flag = ParticipantFlag.BSMD in [ParticipantFlag(participant.get("flags"))] has_bsmd_flag = ParticipantFlag.BSMD in [ParticipantFlag(participant.get("flags"))]
return has_bsmd_flag return has_bsmd_flag
def deprecated_build_post_data_type_dependent_required_fields_dict()->dict[ShipcallType,dict[ParticipantType,typing.Optional[list[str]]]]:
"""
The required fields of a POST-request depend on ShipcallType and ParticipantType. This function creates
a dictionary, which maps those types to a list of required fields.
The participant types 'undefined' and 'bsmd' should not be used in POST-requests. They return 'None'.
"""
post_data_type_dependent_required_fields_dict = {
ShipcallType.arrival:{
ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:["eta_berth"],
ParticipantType.MOORING:["eta_berth"],
ParticipantType.PILOT:["eta_berth"],
ParticipantType.PORT_ADMINISTRATION:["eta_berth"],
ParticipantType.TUG:["eta_berth"],
},
ShipcallType.departure:{
ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:["etd_berth"],
ParticipantType.MOORING:["etd_berth"],
ParticipantType.PILOT:["etd_berth"],
ParticipantType.PORT_ADMINISTRATION:["etd_berth"],
ParticipantType.TUG:["etd_berth"],
},
ShipcallType.shifting:{
ParticipantType.undefined:[], # should not be set in POST requests
ParticipantType.BSMD:[], # should not be set in POST requests
ParticipantType.TERMINAL:[],
ParticipantType.AGENCY:["etd_berth"],
ParticipantType.MOORING:["etd_berth"],
ParticipantType.PILOT:["etd_berth"],
ParticipantType.PORT_ADMINISTRATION:["etd_berth"],
ParticipantType.TUG:["etd_berth"],
},
}
return post_data_type_dependent_required_fields_dict

View File

@ -27,7 +27,7 @@ error_message_dict = {
# 0002 A+B+C # 0002 A+B+C
"validation_rule_fct_shipcall_incoming_participants_disagree_on_eta":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of arrival (ETA) {Rule #0002A}", "validation_rule_fct_shipcall_incoming_participants_disagree_on_eta":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of arrival (ETA) {Rule #0002A}",
"validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of departure (ETD) {Rule #0002B}", "validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of departure (ETD) {Rule #0002B}",
"validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd":"There are deviating times between agency, mooring, port authority, pilot and tug for ETA and ETD {Rule #0002C}", "validation_rule_fct_shipcall_shifting_participants_disagree_on_etd":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of departure (ETD) {Rule #0002C}",
# 0003 A+B # 0003 A+B
"validation_rule_fct_eta_time_not_in_operation_window":"The estimated time of arrival will be AFTER the planned start of operations. {Rule #0003A}", "validation_rule_fct_eta_time_not_in_operation_window":"The estimated time of arrival will be AFTER the planned start of operations. {Rule #0003A}",
@ -723,21 +723,13 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
else: else:
return self.get_no_violation_default_output() return self.get_no_violation_default_output()
def validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd(self, shipcall, df_times, *args, **kwargs): def validation_rule_fct_shipcall_shifting_participants_disagree_on_etd(self, shipcall, df_times, *args, **kwargs):
""" """
Code: #0002-C Code: #0002-C
Type: Local Rule Type: Local Rule
Description: this validation checks, whether the participants expect different ETA or ETD times Description: this validation checks, whether the participants expect different ETD times
Filter: only applies to shifting shipcalls Filter: only applies to shifting shipcalls
""" """
violation_state_eta = self.check_participants_agree_on_estimated_time(
shipcall = shipcall,
query="eta_berth",
df_times=df_times,
applicable_shipcall_type=ShipcallType.SHIFTING
)
violation_state_etd = self.check_participants_agree_on_estimated_time( violation_state_etd = self.check_participants_agree_on_estimated_time(
shipcall = shipcall, shipcall = shipcall,
@ -746,16 +738,14 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
applicable_shipcall_type=ShipcallType.SHIFTING applicable_shipcall_type=ShipcallType.SHIFTING
) )
# apply 'eta_berth' check
# apply 'etd_berth' # apply 'etd_berth'
# violation: if either 'eta_berth' or 'etd_berth' is violated # violation: if either 'etd_berth' is violated
# functionally, this is the same as individually comparing all times for the participants # functionally, this is the same as individually comparing all times for the participants
# times_agency.eta_berth==times_mooring.eta_berth==times_portadministration.eta_berth==times_pilot.eta_berth==times_tug.eta_berth
# times_agency.etd_berth==times_mooring.etd_berth==times_portadministration.etd_berth==times_pilot.etd_berth==times_tug.etd_berth # times_agency.etd_berth==times_mooring.etd_berth==times_portadministration.etd_berth==times_pilot.etd_berth==times_tug.etd_berth
violation_state = (violation_state_eta) or (violation_state_etd) violation_state = (violation_state_etd)
if violation_state: if violation_state:
validation_name = "validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd" validation_name = "validation_rule_fct_shipcall_shifting_participants_disagree_on_etd"
return (StatusFlags.RED, validation_name) return (StatusFlags.RED, validation_name)
else: else:
return self.get_no_violation_default_output() return self.get_no_violation_default_output()

View File

@ -87,6 +87,13 @@ def test_sql_get_ships():
assert all([isinstance(ship, model.Ship) for ship in ships]) assert all([isinstance(ship, model.Ship) for ship in ships])
return return
def test_sql_get_ship_by_id():
query = #"SELECT * FROM ship where id = ?id?" # #TODO_refactor: put into the SQLQuery object
ship = execute_sql_query_standalone(SQLQuery.get_ship_by_id(), param={"id":1}, command_type="single", model=model.Ship)
assert isinstance(ship, model.Ship)
return
def test_sql_get_times(): def test_sql_get_times():
options = {'shipcall_id':153} options = {'shipcall_id':153}
times = execute_sql_query_standalone(query=SQLQuery.get_times(), model=model.Times, param={"scid" : options["shipcall_id"]}) times = execute_sql_query_standalone(query=SQLQuery.get_times(), model=model.Times, param={"scid" : options["shipcall_id"]})

View File

@ -901,3 +901,35 @@ def test_post_data_with_valid_data(get_stub_token):
assert response.status_code==201 assert response.status_code==201
return return
def test_input_validation_shipcall_put_fails_when_type_differs():
from marshmallow import ValidationError
from BreCal.stubs.shipcall import get_stub_valid_shipcall_arrival
from BreCal.schemas import model
from BreCal.database.sql_queries import SQLQuery
from BreCal.database.sql_handler import execute_sql_query_standalone
from BreCal.validators.input_validation_shipcall import InputValidationShipcall
# stub data
put_data = get_stub_valid_shipcall_arrival()
put_data["id"] = 3
loadedModel = model.ShipcallSchema().load(data=put_data, many=False, partial=True)
# load data from DB
shipcall_id = loadedModel.get("id")
query = SQLQuery.get_shipcall_by_id()
shipcall = execute_sql_query_standalone(query=query, model=model.Shipcall, param={"id":shipcall_id}, command_type="single")
# create failure case. Ensures that the type is not the same as in the loaded shipcall from the DB
failure_case = 1 if shipcall.type==2 else 2
loadedModel["type"] = model.ShipcallType(failure_case)
# should fail: different 'type'
with pytest.raises(ValidationError, match="The shipcall type may only be set in the initial POST-request. Afterwards, changing the shipcall type is not allowed"):
InputValidationShipcall.check_shipcall_type_is_unchanged(loadedModel)
# should pass: same 'type'
loadedModel["type"] = shipcall.type
InputValidationShipcall.check_shipcall_type_is_unchanged(loadedModel)
return

View File

@ -409,3 +409,60 @@ def test_input_validation_times_delete_request_fails_when_user_belongs_to_wrong_
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id, pdata=pdata) InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id, pdata=pdata)
return return
def test_input_validation_times_check_dataset_values_for_time_intervals():
import datetime
from BreCal.schemas import model
from BreCal.validators.input_validation_times import InputValidationTimes
from BreCal.stubs.times import get_schema_model_stub_arrival, get_schema_model_stub_departure
from marshmallow import ValidationError
### ETD (departure)
# expected to pass
schemaModel = get_schema_model_stub_departure()
schemaModel["etd_interval_end"] = schemaModel["etd_berth"] + datetime.timedelta(minutes=2)
schemaModel["etd_berth"] = schemaModel["etd_berth"].isoformat()
schemaModel["etd_interval_end"] = schemaModel["etd_interval_end"].isoformat()
content = schemaModel
loadedModel = model.TimesSchema().load(data=schemaModel, many=False, partial=True)
InputValidationTimes.check_dataset_values(user_data={}, loadedModel=loadedModel, content=content)
# expected to fail: the from-to-interval is incorrectly set.
schemaModel = get_schema_model_stub_departure()
schemaModel["etd_interval_end"] = schemaModel["etd_berth"] - datetime.timedelta(minutes=2)
schemaModel["etd_berth"] = schemaModel["etd_berth"].isoformat()
schemaModel["etd_interval_end"] = schemaModel["etd_interval_end"].isoformat()
content = schemaModel
loadedModel = model.TimesSchema().load(data=schemaModel, many=False, partial=True)
with pytest.raises(ValidationError, match="The provided time interval for the estimated departure time is invalid"):
InputValidationTimes.check_dataset_values(user_data={}, loadedModel=loadedModel, content=content)
### ETA (arrival)
# expected to pass
schemaModel = get_schema_model_stub_arrival()
schemaModel["eta_interval_end"] = schemaModel["eta_berth"] + datetime.timedelta(minutes=2)
schemaModel["eta_berth"] = schemaModel["eta_berth"].isoformat()
schemaModel["eta_interval_end"] = schemaModel["eta_interval_end"].isoformat()
content = schemaModel
loadedModel = model.TimesSchema().load(data=schemaModel, many=False, partial=True)
InputValidationTimes.check_dataset_values(user_data={}, loadedModel=loadedModel, content=content)
# expected to fail: the from-to-interval is incorrectly set.
schemaModel = get_schema_model_stub_arrival()
schemaModel["eta_interval_end"] = schemaModel["eta_berth"] - datetime.timedelta(minutes=2)
schemaModel["eta_berth"] = schemaModel["eta_berth"].isoformat()
schemaModel["eta_interval_end"] = schemaModel["eta_interval_end"].isoformat()
content = schemaModel
loadedModel = model.TimesSchema().load(data=schemaModel, many=False, partial=True)
with pytest.raises(ValidationError, match="The provided time interval for the estimated arrival time is invalid"):
InputValidationTimes.check_dataset_values(user_data={}, loadedModel=loadedModel, content=content)
return