Code updated to 3.0.238

This commit is contained in:
Oliver Hauff 2021-06-26 20:57:55 +02:00
parent 3302d80678
commit 9c6a1fa92b
257 changed files with 7763 additions and 2861 deletions

View file

@ -1,10 +1,11 @@
using Serilog;
using System;
using System.Threading.Tasks;
using TINK.Model.Repository;
using TINK.Model.Repository.Request;
using TINK.Model.Repository.Response;
using TINK.Repository;
using TINK.Repository.Request;
using TINK.Repository.Response;
using TINK.Model.User.Account;
using TINK.Model.Device;
namespace TINK.Model.Connector
{
@ -122,7 +123,8 @@ namespace TINK.Model.Connector
public async Task DoReturn(
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike,
LocationDto location)
LocationDto location,
ISmartDevice smartDevice)
{
Log.ForContext<Command>().Error("Unexpected returning request detected. No user logged in.");
await Task.CompletedTask;
@ -132,7 +134,11 @@ namespace TINK.Model.Connector
/// Submits feedback to copri server.
/// </summary>
/// <param name="userFeedback">Feedback to submit.</param>
#if USCSHARP9
public async Task DoSubmitFeedback(ICommand.IUserFeedback userFeedback, Uri opertorUri)
#else
public async Task DoSubmitFeedback(IUserFeedback userFeedback, Uri opertorUri)
#endif
{
Log.ForContext<Command>().Error("Unexpected submit feedback request detected. No user logged in.");
await Task.CompletedTask;

View file

@ -1,11 +1,12 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.Repository;
using TINK.Model.Repository.Exception;
using TINK.Model.Repository.Request;
using TINK.Model.Repository.Response;
using TINK.Repository;
using TINK.Repository.Exception;
using TINK.Repository.Request;
using TINK.Repository.Response;
using TINK.Model.User.Account;
using TINK.Model.Device;
namespace TINK.Model.Connector
{
@ -30,7 +31,7 @@ namespace TINK.Model.Connector
/// Logs user in.
/// If log in succeeds either and session might be updated if it was no more valid (logged in by an different device).
/// If log in fails (password modified) session cookie is set to empty.
/// If communication fails an TINK.Model.Repository.Exception is thrown.
/// If communication fails an TINK.Repository.Exception is thrown.
/// </summary>
/// <param name="p_oAccount">Account to use for login.</param>
public Task<IAccount> DoLogin(string p_strMail, string p_strPassword, string p_strDeviceId)
@ -242,12 +243,13 @@ namespace TINK.Model.Connector
}
/// <summary> Request to return a bike.</summary>
/// <param name="latitude">Latitude of the bike.</param>
/// <param name="longitude">Longitude of the bike.</param>
/// <param name="bike">Bike to return.</param>
/// <param name="locaton">Position of the bike.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
public async Task DoReturn(
Bikes.Bike.BluetoothLock.IBikeInfoMutable bike,
LocationDto location)
LocationDto location,
ISmartDevice smartDevice)
{
if (bike == null)
{
@ -257,7 +259,7 @@ namespace TINK.Model.Connector
ReservationCancelReturnResponse l_oResponse;
try
{
l_oResponse = (await CopriServer.DoReturn(bike.Id, location, bike.OperatorUri)).GetIsReturnBikeResponseOk(bike.Id);
l_oResponse = (await CopriServer.DoReturn(bike.Id, location, smartDevice, bike.OperatorUri)).GetIsReturnBikeResponseOk(bike.Id);
}
catch (Exception)
{
@ -272,8 +274,12 @@ namespace TINK.Model.Connector
/// Submits feedback to copri server.
/// </summary>
/// <param name="userFeedback">Feedback to submit.</param>
#if USCSHARP9
public async Task DoSubmitFeedback(ICommand.IUserFeedback userFeedback, Uri opertorUri)
=> await CopriServer.DoSubmitFeedback(userFeedback.Message, userFeedback.IsBikeBroken, opertorUri);
=> await CopriServer.DoSubmitFeedback(userFeedback.BikeId, userFeedback.Message, userFeedback.IsBikeBroken, opertorUri);
#else
public async Task DoSubmitFeedback(IUserFeedback userFeedback, Uri opertorUri)
=> await CopriServer.DoSubmitFeedback(userFeedback.BikeId, userFeedback.Message, userFeedback.IsBikeBroken, opertorUri);
#endif
}
}

View file

@ -1,7 +1,8 @@
using System;
using System.Threading.Tasks;
using TINK.Model.Repository.Request;
using TINK.Repository.Request;
using TINK.Model.User.Account;
using TINK.Model.Device;
namespace TINK.Model.Connector
{
@ -44,9 +45,10 @@ namespace TINK.Model.Connector
Task DoBook(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike);
/// <summary> Request to return a bike.</summary>
/// <param name="location">Geolocation of lock when returning bike.</param>
/// <param name="bike">Bike to return.</param>
Task DoReturn(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, LocationDto geolocation = null);
/// <param name="location">Geolocation of lock when returning bike.</param>
/// <param name="smartDevice">Provides info about hard and software.</param>
Task DoReturn(Bikes.Bike.BluetoothLock.IBikeInfoMutable bike, LocationDto geolocation = null, ISmartDevice smartDevice = null);
/// <summary> True if connector has access to copri server, false if cached values are used. </summary>
bool IsConnected { get; }
@ -55,12 +57,15 @@ namespace TINK.Model.Connector
string SessionCookie { get; }
Task DoSubmitFeedback(IUserFeedback userFeedback, Uri opertorUri);
#if USCSHARP9
/// <summary>
/// Feedback given by user when returning bike.
/// </summary>
public interface IUserFeedback
{
/// <summary> Id of the bike to which the feedback is related to.</summary>
string BikeId { get; }
/// <summary>
/// Holds whether bike is broken or not.
/// </summary>
@ -74,12 +79,37 @@ namespace TINK.Model.Connector
/// </summary>
string Message { get; }
}
#endif
}
/// <summary>Defines delegate to be raised whenever login state changes.</summary>
/// <param name="p_oEventArgs">Holds session cookie and mail address if user logged in successfully.</param>
public delegate void LoginStateChangedEventHandler(object p_oSender, LoginStateChangedEventArgs p_oEventArgs);
#if !USCSHARP9
/// <summary>
/// Feedback given by user when returning bike.
/// </summary>
public interface IUserFeedback
{
/// <summary> Id of the bike to which the feedback is related to.</summary>
string BikeId { get; }
/// <summary>
/// Holds whether bike is broken or not.
/// </summary>
bool IsBikeBroken { get; }
/// <summary>
/// Holds either
/// - general feedback
/// - error description of broken bike
/// or both.
/// </summary>
string Message { get; }
}
#endif
/// <summary> Event arguments to notify about changes of logged in state.</summary>
public class LoginStateChangedEventArgs : EventArgs
{

View file

@ -1,9 +1,25 @@

namespace TINK.Model.Connector
{
#if USCSHARP9
public record UserFeedbackDto : ICommand.IUserFeedback
{
public string BikeId { get; init; }
public bool IsBikeBroken { get; init; }
public string Message { get; init; }
}
#else
#if USCSHARP9
public class UserFeedbackDto : ICommand.IUserFeedback
#else
public class UserFeedbackDto : IUserFeedback
#endif
{
public string BikeId { get; set; }
public bool IsBikeBroken { get; set; }
public string Message { get; set; }
}
#endif
}

View file

@ -1,6 +1,6 @@
using System;
using TINK.Model.Services.CopriApi;
using TINK.Model.Repository;
using TINK.Repository;
namespace TINK.Model.Connector
{

View file

@ -1,6 +1,6 @@
using System;
using TINK.Model.Services.CopriApi;
using TINK.Model.Repository;
using TINK.Repository;
namespace TINK.Model.Connector
{

View file

@ -1,13 +1,26 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace TINK.Model.Connector.Filter
{
public static class GroupFilterFactory
{
/// <summary>
/// Creates filter object.
/// </summary>
/// <param name="group">if value consists
/// - list of strings entries are used to filter (intersect) with or if value is
/// - null or an empty list null filter is applied, i.e. filtering is off.</param>
/// <returns>Filtering object.</returns>
/// <remarks>
/// Tread group values of null and empty lists as marker to turn filtering off to handle COPRI responses maximal flexible.
/// </remarks>
public static IGroupFilter Create(IEnumerable<string> group)
{
return group != null ? (IGroupFilter) new IntersectGroupFilter(group) : new NullGroupFilter();
return group != null && group.Count() > 0
? (IGroupFilter) new IntersectGroupFilter(group) :
new NullGroupFilter();
}
}
}

View file

@ -86,29 +86,33 @@ namespace TINK.Model.Connector
/// <returns></returns>
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
{
var result = await m_oInnerQuery.GetBikesAndStationsAsync();
// Bikes and stations from COPRI or cache
var providerBikesAndStations = await m_oInnerQuery.GetBikesAndStationsAsync();
return new Result<StationsAndBikesContainer>(
result.Source,
new StationsAndBikesContainer(
new StationDictionary(result.Response.StationsAll.CopriVersion, DoFilter(result.Response.StationsAll, Filter)),
new BikeCollection(DoFilter(result.Response.Bikes, Filter))),
result.Exception);
// Do filtering.
var filteredStationsDictionary = new StationDictionary(providerBikesAndStations.Response.StationsAll.CopriVersion, DoFilter(providerBikesAndStations.Response.StationsAll, Filter));
var filteredBikesDictionary = new BikeCollection(DoFilter(providerBikesAndStations.Response.Bikes, Filter));
var filteredBikesAndStations = new Result<StationsAndBikesContainer>(
providerBikesAndStations.Source,
new StationsAndBikesContainer(filteredStationsDictionary, filteredBikesDictionary),
providerBikesAndStations.Exception); ;
return filteredBikesAndStations;
}
/// <summary> Filter bikes by group. </summary>
/// <param name="p_oBikes">Bikes to filter.</param>
/// <param name="bikes">Bikes to filter.</param>
/// <returns>Filtered bikes.</returns>
private static Dictionary<int, BikeInfo> DoFilter(BikeCollection p_oBikes, IGroupFilter filter)
private static Dictionary<string, BikeInfo> DoFilter(BikeCollection bikes, IGroupFilter filter)
{
return p_oBikes.Where(x => filter.DoFilter(x.Group).Count() > 0).ToDictionary(x => x.Id);
return bikes.Where(x => filter.DoFilter(x.Group).Count() > 0).ToDictionary(x => x.Id);
}
/// <summary> Filter stations by broup. </summary>
/// <returns></returns>
private static Dictionary<int, Station.Station> DoFilter(StationDictionary p_oStations, IGroupFilter filter)
private static Dictionary<string, IStation> DoFilter(StationDictionary stations, IGroupFilter filter)
{
return p_oStations.Where(x => filter.DoFilter(x.Group).Count() > 0).ToDictionary((x => x.Id));
return stations.Where(x => filter.DoFilter(x.Group).Count() > 0).ToDictionary(x => x.Id);
}
}
}

View file

@ -90,18 +90,18 @@ namespace TINK.Model.Connector
}
/// <summary> Filter bikes by group. </summary>
/// <param name="p_oBikes">Bikes to filter.</param>
/// <param name="bikes">Bikes to filter.</param>
/// <returns>Filtered bikes.</returns>
public static Dictionary<int, BikeInfo> DoFilter(BikeCollection p_oBikes, IEnumerable<string> p_oFilter)
public static Dictionary<string, BikeInfo> DoFilter(BikeCollection bikes, IEnumerable<string> filter)
{
return p_oBikes.Where(x => x.Group.Intersect(p_oFilter).Count() > 0).ToDictionary(x => x.Id);
return bikes.Where(x => x.Group.Intersect(filter).Count() > 0).ToDictionary(x => x.Id);
}
/// <summary> Filter stations by broup. </summary>
/// <returns></returns>
public static Dictionary<int, Station.Station> DoFilter(StationDictionary p_oStations, IEnumerable<string> p_oFilter)
public static Dictionary<string, IStation> DoFilter(StationDictionary stations, IEnumerable<string> p_oFilter)
{
return p_oStations.Where(x => x.Group.Intersect(p_oFilter).Count() > 0).ToDictionary((x => x.Id));
return stations.Where(x => x.Group.Intersect(p_oFilter).Count() > 0).ToDictionary(x => x.Id);
}
}
}

View file

@ -1,5 +1,5 @@
using System;
using TINK.Model.Repository;
using TINK.Repository;
namespace TINK.Model.Connector
{

View file

@ -1,5 +1,5 @@
using System;
using TINK.Model.Repository;
using TINK.Repository;
namespace TINK.Model.Connector
{

View file

@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using TINK.Model.Bike;
using TINK.Model.Services.CopriApi;
using TINK.Model.Repository;
using TINK.Repository;
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
namespace TINK.Model.Connector
@ -70,7 +70,7 @@ namespace TINK.Model.Connector
Log.ForContext<CachedQuery>().Error("Unexpected call to get be bikes occpied detected. No user is logged in.");
return new Result<BikeCollection>(
typeof(CopriCallsMonkeyStore),
await Task.Run(() => new BikeCollection(new Dictionary<int, BikeInfo>())),
await Task.Run(() => new BikeCollection(new Dictionary<string, BikeInfo>())),
new System.Exception("Abfrage der reservierten/ gebuchten Räder nicht möglich. Kein Benutzer angemeldet."));
}

View file

@ -1,10 +1,9 @@
using MonkeyCache.FileStore;
using System;
using System;
using System.Linq;
using System.Threading.Tasks;
using TINK.Model.Bike;
using TINK.Model.Services.CopriApi;
using TINK.Model.Repository;
using TINK.Repository;
namespace TINK.Model.Connector
{
@ -31,73 +30,74 @@ namespace TINK.Model.Connector
/// <summary> Gets all stations including postions.</summary>
public async Task<Result<StationsAndBikesContainer>> GetBikesAndStationsAsync()
{
var resultStations = await server.GetStations();
var stationsResponse = await server.GetStations();
if (resultStations.Source == typeof(CopriCallsMonkeyStore)
|| resultStations.Exception != null)
if (stationsResponse.Source == typeof(CopriCallsMonkeyStore)
|| stationsResponse.Exception != null)
{
// Stations were read from cache ==> get bikes availbalbe and occupied from cache as well to avoid inconsistencies
return new Result<StationsAndBikesContainer>(
resultStations.Source,
stationsResponse.Source,
new StationsAndBikesContainer(
resultStations.Response.GetStationsAllMutable(),
stationsResponse.Response.GetStationsAllMutable(),
UpdaterJSON.GetBikesAll(
(await server.GetBikesAvailable(true)).Response,
(await server.GetBikesOccupied(true)).Response,
Mail,
DateTimeProvider)),
resultStations.Exception);
stationsResponse.Exception);
}
var l_oBikesAvailableResponse = await server.GetBikesAvailable();
if (l_oBikesAvailableResponse.Source == typeof(CopriCallsMonkeyStore)
|| l_oBikesAvailableResponse.Exception != null)
var bikesAvailableResponse = await server.GetBikesAvailable();
if (bikesAvailableResponse.Source == typeof(CopriCallsMonkeyStore)
|| bikesAvailableResponse.Exception != null)
{
// Bikes avilable were read from cache ==> get bikes occupied from cache as well to avoid inconsistencies
return new Result<StationsAndBikesContainer>(
l_oBikesAvailableResponse.Source,
bikesAvailableResponse.Source,
new StationsAndBikesContainer(
(await server.GetStations(true)).Response.GetStationsAllMutable(),
UpdaterJSON.GetBikesAll(l_oBikesAvailableResponse.Response,
UpdaterJSON.GetBikesAll(bikesAvailableResponse.Response,
(await server.GetBikesOccupied(true)).Response,
Mail,
DateTimeProvider)),
l_oBikesAvailableResponse.Exception);
bikesAvailableResponse.Exception);
}
var l_oBikesOccupiedResponse = await server.GetBikesOccupied();
if (l_oBikesOccupiedResponse.Source == typeof(CopriCallsMonkeyStore)
|| l_oBikesOccupiedResponse.Exception != null)
var bikesOccupiedResponse = await server.GetBikesOccupied();
if (bikesOccupiedResponse.Source == typeof(CopriCallsMonkeyStore)
|| bikesOccupiedResponse.Exception != null)
{
// Bikes occupied were read from cache ==> get bikes available from cache as well to avoid inconsistencies
return new Result<StationsAndBikesContainer>(
l_oBikesOccupiedResponse.Source,
bikesOccupiedResponse.Source,
new StationsAndBikesContainer(
(await server.GetStations(true)).Response.GetStationsAllMutable(),
UpdaterJSON.GetBikesAll(
(await server.GetBikesAvailable(true)).Response,
l_oBikesOccupiedResponse.Response,
bikesOccupiedResponse.Response,
Mail,
DateTimeProvider)),
l_oBikesOccupiedResponse.Exception);
bikesOccupiedResponse.Exception);
}
// Both types bikes could read from copri => update cache
server.AddToCache(resultStations);
server.AddToCache(l_oBikesAvailableResponse);
server.AddToCache(l_oBikesOccupiedResponse);
server.AddToCache(stationsResponse);
server.AddToCache(bikesAvailableResponse);
server.AddToCache(bikesOccupiedResponse);
var exceptions = new[] { resultStations?.Exception, l_oBikesAvailableResponse?.Exception, l_oBikesOccupiedResponse?.Exception }.Where(x => x != null).ToArray();
var exceptions = new[] { stationsResponse?.Exception, bikesAvailableResponse?.Exception, bikesOccupiedResponse?.Exception }.Where(x => x != null).ToArray();
var stationsMutable = stationsResponse.Response.GetStationsAllMutable();
var bikesMutable = UpdaterJSON.GetBikesAll(
bikesAvailableResponse.Response,
bikesOccupiedResponse.Response,
Mail,
DateTimeProvider);
return new Result<StationsAndBikesContainer>(
resultStations.Source,
new StationsAndBikesContainer(
resultStations.Response.GetStationsAllMutable(),
UpdaterJSON.GetBikesAll(
l_oBikesAvailableResponse.Response,
l_oBikesOccupiedResponse.Response,
Mail,
DateTimeProvider)),
stationsResponse.Source,
new StationsAndBikesContainer(stationsMutable, bikesMutable),
exceptions.Length > 0 ? new AggregateException(exceptions) : null);
}

View file

@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using TINK.Model.Bike;
using TINK.Model.Services.CopriApi;
using TINK.Model.Repository;
using TINK.Repository;
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
namespace TINK.Model.Connector
@ -44,7 +44,7 @@ namespace TINK.Model.Connector
Log.ForContext<Query>().Error("Unexpected call to get be bikes occpied detected. No user is logged in.");
return new Result<BikeCollection>(
typeof(CopriCallsMonkeyStore),
await Task.Run(() => new BikeCollection(new Dictionary<int, BikeInfo>())),
await Task.Run(() => new BikeCollection(new Dictionary<string, BikeInfo>())),
new System.Exception("Abfrage der reservierten/ gebuchten Räder fehlgeschlagen. Kein Benutzer angemeldet."));
}

View file

@ -2,7 +2,7 @@
using System.Threading.Tasks;
using TINK.Model.Bike;
using TINK.Model.Services.CopriApi;
using TINK.Model.Repository;
using TINK.Repository;
namespace TINK.Model.Connector
{

View file

@ -5,8 +5,8 @@ using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using TINK.Model.Bike;
using TINK.Model.Repository.Exception;
using TINK.Model.Repository.Response;
using TINK.Repository.Exception;
using TINK.Repository.Response;
using TINK.Model.Services.CopriApi.ServerUris;
using TINK.Model.State;
@ -51,14 +51,16 @@ namespace TINK.Model.Connector
/// <summary> Gets the position from StationInfo object. </summary>
/// <param name="p_oAuthorizationResponse">Object to get information from.</param>
/// <returns>Position information.</returns>
public static IEnumerable<string> GetGroup(this string p_oGroup)
public static IEnumerable<string> GetGroup(this string[] group)
{
if (string.IsNullOrEmpty(p_oGroup))
if (group == null || group.Length == 0)
{
throw new ArgumentException("Can not get goup form string. Group text can not be null.");
// If not logged in stations groups are empty form COPRI version v4.1.
Log.Debug("Can not get goup form string. Group text can not be null.");
return new List<string>();
}
return new HashSet<string>(p_oGroup.Split(',')).ToList();
return new HashSet<string>(group).ToList();
}
/// <summary> Gets the position from StationInfo object. </summary>
@ -78,9 +80,9 @@ namespace TINK.Model.Connector
{
return p_oStationInfo.station_group.GetGroup();
}
catch (System.Exception l_oException)
catch (Exception l_oException)
{
throw new System.Exception($"Can not get group of stations from text \"{p_oStationInfo.station_group}\".", l_oException);
throw new Exception($"Can not get group of stations from text \"{p_oStationInfo.station_group}\".", l_oException);
}
}
@ -310,24 +312,19 @@ namespace TINK.Model.Connector
/// </summary>
/// <param name="p_strGps">Text to extract positon from.</param>
/// <returns>Position object.</returns>
public static Station.Position GetPosition(string p_strGps)
public static Station.Position GetPosition(GpsInfo gps)
{
if (p_strGps == null)
if (gps == null)
{
return null;
}
var l_oPosition = p_strGps.Split(',');
if (l_oPosition.Length != 2)
return null;
double l_oLatitude;
if (!double.TryParse(l_oPosition[0], NumberStyles.Float, CultureInfo.InvariantCulture, out l_oLatitude))
if (!double.TryParse(gps.latitude, NumberStyles.Float, CultureInfo.InvariantCulture, out l_oLatitude))
return null;
double l_oLongitude;
if (!double.TryParse(l_oPosition[1], NumberStyles.Float, CultureInfo.InvariantCulture, out l_oLongitude))
if (!double.TryParse(gps.longitude, NumberStyles.Float, CultureInfo.InvariantCulture, out l_oLongitude))
return null;
return new Station.Position(l_oLatitude, l_oLongitude);
@ -354,5 +351,15 @@ namespace TINK.Model.Connector
? new Uri($"{bikeInfo.uri_operator}/{CopriServerUriList.REST_RESOURCE_ROOT}")
: null;
}
/// <summary> Gets the copriversion from.</summary>
/// <param name="response">Response to get version info from.</param>
/// <returns>COPRI version</returns>
public static Version GetCopriVersion(this CopriVersion response)
=> response!= null
&& !string.IsNullOrEmpty(response.copri_version)
&& Version.TryParse(response.copri_version, out Version copriVersion)
? copriVersion
: throw new InvalidResponseException($"Can not get version info from copri response {response?.copri_version}.");
}
}

View file

@ -1,11 +1,11 @@
using System;
using TINK.Model.Bike;
using TINK.Model.Station;
using TINK.Model.Repository.Response;
using TINK.Repository.Response;
using TINK.Model.User.Account;
using System.Collections.Generic;
using TINK.Model.State;
using TINK.Model.Repository.Exception;
using TINK.Repository.Exception;
using Serilog;
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
@ -190,8 +190,8 @@ namespace TINK.Model.Connector
string p_strMail,
Func<DateTime> p_oDateTimeProvider)
{
var l_oBikesDictionary = new Dictionary<int, BikeInfo>();
var l_oDuplicates = new Dictionary<int, BikeInfo>();
var l_oBikesDictionary = new Dictionary<string, BikeInfo>();
var l_oDuplicates = new Dictionary<string, BikeInfo>();
// Get bikes from Copri/ file/ memory, ....
if (p_oBikesAvailableResponse != null
@ -295,7 +295,7 @@ namespace TINK.Model.Connector
return null;
}
if (bikeInfo.station == null)
if (string.IsNullOrEmpty(bikeInfo.station))
{
// Bike available must always have a station id because bikes can only be returned at a station.
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. No station info set.");
@ -485,7 +485,11 @@ namespace TINK.Model.Connector
return new Bikes.Bike.TariffDescription
{
Name = tariffDesciption?.name,
#if USCSHARP9
Number = int.TryParse(tariffDesciption?.number, out int number) ? number : null,
#else
Number = int.TryParse(tariffDesciption?.number, out int number) ? number : (int?) null,
#endif
FreeTimePerSession = double.TryParse(tariffDesciption?.free_hours, NumberStyles.Any, CultureInfo.InvariantCulture, out double freeHours) ? TimeSpan.FromHours(freeHours) : TimeSpan.Zero,
FeeEuroPerHour = double.TryParse(tariffDesciption?.eur_per_hour, NumberStyles.Any, CultureInfo.InvariantCulture, out double euroPerHour) ? euroPerHour : double.NaN,
AboEuroPerMonth = double.TryParse(tariffDesciption?.abo_eur_per_month, NumberStyles.Any, CultureInfo.InvariantCulture, out double aboEuroPerMonth) ? aboEuroPerMonth : double.NaN,