diff --git a/BO4E.Extensions/BusinessObjects/Benachrichtigung/BenachrichtigungExtension.cs b/BO4E.Extensions/BusinessObjects/Benachrichtigung/BenachrichtigungExtension.cs index d147b902..c35959c9 100644 --- a/BO4E.Extensions/BusinessObjects/Benachrichtigung/BenachrichtigungExtension.cs +++ b/BO4E.Extensions/BusinessObjects/Benachrichtigung/BenachrichtigungExtension.cs @@ -34,7 +34,11 @@ public static bool Has(this BO.Benachrichtigung b, string key, string value) /// public static bool Has(this BO.Benachrichtigung b, GenericStringStringInfo gssi) { - if (b.Infos == null || b.Infos.Count == 0) return false; + if (b.Infos == null || b.Infos.Count == 0) + { + return false; + } + // ToDo für Hamid: Bitte prüfen, warum Contains false zurückliefert. return b.Infos.Any(m => m.KeyColumn == gssi.KeyColumn && m.Value == gssi.Value); } @@ -47,7 +51,11 @@ public static bool Has(this BO.Benachrichtigung b, GenericStringStringInfo gssi) /// true if key is in public static bool Has(this BO.Benachrichtigung b, string key) { - if (b.Infos == null || b.Infos.Count == 0) return false; + if (b.Infos == null || b.Infos.Count == 0) + { + return false; + } + return b.Infos.Any(gssi => gssi.KeyColumn == key); } @@ -68,11 +76,18 @@ public static bool Has(this BO.Benachrichtigung b, string key) public static bool Has(this BO.Benachrichtigung b, string keyName, Predicate predicate, bool passByDefault = true, TypeConverter typeConverter = null) where T : IComparable { - if (!b.Has(keyName)) return passByDefault; + if (!b.Has(keyName)) + { + return passByDefault; + } + foreach (var info in b.Infos.Where(gssi => gssi.KeyColumn == keyName)) try { - if (typeConverter == null) typeConverter = TypeDescriptor.GetConverter(typeof(T)); + if (typeConverter == null) + { + typeConverter = TypeDescriptor.GetConverter(typeof(T)); + } { var value = (T)typeConverter.ConvertFromString(info.Value); @@ -97,11 +112,18 @@ public static void MoveInfosToUserProperties(this BO.Benachrichtigung b, bool ov { if (b.Infos != null && b.Infos.Count > 0) { - if (b.UserProperties == null) b.UserProperties = new Dictionary(); + if (b.UserProperties == null) + { + b.UserProperties = new Dictionary(); + } + foreach (var info in b.Infos) { if (b.UserProperties.ContainsKey(info.KeyColumn) && overwriteExistingKeys) + { b.UserProperties.Remove(info.KeyColumn); + } + b.UserProperties.Add(info.KeyColumn, info.Value); // might throw exception if key exists and !overwriteExistingKeys. That's ok. } diff --git a/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtension.cs b/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtension.cs index f4f35217..2c256b02 100644 --- a/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtension.cs +++ b/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtension.cs @@ -115,12 +115,23 @@ public static decimal GetTotalConsumption(this BO.Energiemenge em, /// Tuple of consumption value and automatically determined unit of measurement public static Tuple GetConsumption(this BO.Energiemenge em, ITimeRange reference) { - if (!IsPure(em)) throw new ArgumentException("The Energiemenge is not pure."); - if (em.Energieverbrauch.Count == 0) return Tuple.Create(0.0M, Mengeneinheit.ANZAHL); + if (!IsPure(em)) + { + throw new ArgumentException("The Energiemenge is not pure."); + } + + if (em.Energieverbrauch.Count == 0) + { + return Tuple.Create(0.0M, Mengeneinheit.ANZAHL); + } + ISet einheiten = new HashSet(em.Energieverbrauch.Select(x => x.Einheit)); if (einheiten.Count > 1) - // z.B. kWh und Wh oder Monat und Jahr... Die liefern IsPure==true. + // z.B. kWh und Wh oder Monat und Jahr... Die liefern IsPure==true. + { throw new NotImplementedException("Converting different units of same type is not supported yet."); + } + var v = em.Energieverbrauch.First(); var consumption = em.GetConsumption(reference, v.Wertermittlungsverfahren, v.Obiskennzahl, v.Einheit); return Tuple.Create(consumption, v.Einheit); @@ -143,8 +154,11 @@ public static decimal GetConsumption(this BO.Energiemenge em, ITimeRange referen Wertermittlungsverfahren? wev, string obiskennzahl, Mengeneinheit me) { if (!me.IsExtensive()) + { throw new ArgumentException( $"The Mengeneinheit {me} isn't extensive. Calculating a consumption doesn't make sense."); + } + return em.Energieverbrauch .Where(v => v.Wertermittlungsverfahren == wev && v.Obiskennzahl == obiskennzahl && v.Einheit == me) .Sum(v => GetOverlapFactor(new TimeRange((v.Startdatum ?? DateTimeOffset.MinValue).DateTime, (v.Enddatum ?? DateTimeOffset.MinValue).DateTime), reference, false) * v.Wert); @@ -166,9 +180,13 @@ public static BO.Energiemenge Normalise(this BO.Energiemenge em, decimal target totalConsumption = em.GetTotalConsumption(); result = em.DeepClone(); if (totalConsumption.Item1 != 0.0M) + { scalingFactor = target / totalConsumption.Item1; + } else + { scalingFactor = 0.0M; + } Parallel.ForEach(result.Energieverbrauch.Where(v => v.Einheit == totalConsumption.Item2), v => { v.Wert = scalingFactor * v.Wert; }); @@ -188,14 +206,22 @@ public static BO.Energiemenge Normalise(this BO.Energiemenge em, decimal target public static decimal? GetLoad(this BO.Energiemenge em, Mengeneinheit me, DateTime dt) { if (!me.IsIntensive()) + { throw new ArgumentException( $"The Mengeneinheit {me} isn't intensive. Calculating the value for a specific point in time doesn't make sense."); + } + decimal? result = null; foreach (var v in em.Energieverbrauch.Where(v => v.Startdatum <= dt && dt < v.Enddatum)) if (result.HasValue) + { result += v.Wert; + } else + { result = v.Wert; + } + return result; } @@ -209,9 +235,16 @@ public static BO.Energiemenge Normalise(this BO.Energiemenge em, decimal target /// Tuple of average value and unit of measurement public static Tuple GetAverage(this BO.Energiemenge em) { - if (!IsPure(em)) throw new ArgumentException("Energiemenge is not pure."); + if (!IsPure(em)) + { + throw new ArgumentException("Energiemenge is not pure."); + } + + if (em.Energieverbrauch.Count == 0) + { + return Tuple.Create(null, Mengeneinheit.KW); + } - if (em.Energieverbrauch.Count == 0) return Tuple.Create(null, Mengeneinheit.KW); var v = em.Energieverbrauch.First(); return Tuple.Create(em.GetAverage(v.Wertermittlungsverfahren, v.Obiskennzahl, v.Einheit), v.Einheit); } @@ -251,13 +284,21 @@ public static BO.Energiemenge Normalise(this BO.Energiemenge em, decimal target { var overlapFactor = GetOverlapFactor(new TimeRange((v.Startdatum ?? DateTimeOffset.MinValue).DateTime, (v.Enddatum ?? DateTimeOffset.MinValue).DateTime), reference, true); if (result.HasValue) + { result += overlapFactor * v.Wert; + } else + { result = v.Wert; + } + overallDenominator += overlapFactor; } - if (result.HasValue) return result / overallDenominator; + if (result.HasValue) + { + return result / overallDenominator; + } return null; } @@ -293,16 +334,25 @@ public static List GetMissingTimeRanges(this BO.Energiemenge em, ITim if (filteredVerbrauch.Count < 2) + { throw new ArgumentException("Not enough entries in energieverbrauch to determine periodicity."); + } + if (!IsEvenlySpaced(em, reference, wev, obis, me, true)) + { throw new ArgumentException( "The provided Energiemenge is not evenly spaced although gaps are allowed."); + } + var periodicity = GetTimeSpans(em, wev, obis, me).Min(); if ( Math.Abs((reference.Start - em.GetMinDate()).TotalMilliseconds % periodicity.TotalMilliseconds) != 0) + { throw new ArgumentException( $"The absolute difference between reference.start ({reference.Start}) and the minimal date time in the Energiemenge ({em.GetMinDate()}) has to be an integer multiple of the periodicity {periodicity.TotalMilliseconds} but was {(reference.Start - em.GetMinDate()).TotalMilliseconds}."); + } + // since it's assured, that the energieverbrauch entries are evenly spaced it doesn't matter which entry we use to determine the duration. var duration = filteredVerbrauch.Values.Min(v => v.Enddatum) - filteredVerbrauch.Values.Min(v => v.Startdatum); @@ -327,7 +377,9 @@ public static List GetMissingTimeRanges(this BO.Energiemenge em, ITim if (!filteredVerbrauch.ContainsKey( new Tuple(dt, dt + duration))) // Where(v => v.startdatum == dt && v.enddatum == dt + duration).Any()) + { result.Add(new TimeRange(dt, (dt + duration).Value)); + } //} } @@ -345,8 +397,11 @@ public static List GetMissingTimeRanges(this BO.Energiemenge em, ITim public static List GetMissingTimeRanges(this BO.Energiemenge em, TimeRange reference) { if (!em.IsPure()) + { throw new ArgumentException( "The Energiemenge you provided is not pure. Consider using the overloaded method."); + } + var v = em.Energieverbrauch.FirstOrDefault(); return GetMissingTimeRanges(em, reference, v.Wertermittlungsverfahren, v.Obiskennzahl, v.Einheit); } @@ -372,7 +427,11 @@ public static bool IsEvenlySpaced(this BO.Energiemenge em, ITimeRange reference, startEndDatumPeriods = GetTimeSpans(em, wev, obis, me); - if (startEndDatumPeriods.Count < 2) return true; + if (startEndDatumPeriods.Count < 2) + { + return true; + } + if (allowGaps) { // each time difference must be a multiple of the smallest difference. @@ -380,9 +439,11 @@ public static bool IsEvenlySpaced(this BO.Energiemenge em, ITimeRange reference, var minDiff = startEndDatumPeriods.Min().TotalSeconds; foreach (var ts in startEndDatumPeriods) if (Math.Abs(ts.TotalSeconds % minDiff) != 0) - // use profiler as logger: + // use profiler as logger: + { return false; + } return true; @@ -408,7 +469,9 @@ public static bool IsEvenlySpaced(this BO.Energiemenge em, bool allowGaps = fals var combinations = GetWevObisMeCombinations(em); foreach (var combo in combinations) if (!em.IsEvenlySpaced(em.GetTimeRange(), combo.Item1, combo.Item2, combo.Item3, allowGaps)) + { return false; + } return true; @@ -490,8 +553,15 @@ public static decimal GetCoverage(this BO.Energiemenge em, ITimeRange reference) { if (!IsPure(em)) + { throw new ArgumentException("The Energiemenge is not pure. Cannot determine parameters."); - if (em.Energieverbrauch.Count == 0) return 0.0M; + } + + if (em.Energieverbrauch.Count == 0) + { + return 0.0M; + } + var v = em.Energieverbrauch.First(); return em.GetCoverage(reference, v.Wertermittlungsverfahren, v.Obiskennzahl, v.Einheit); @@ -571,8 +641,10 @@ private static decimal GetOverlapFactor(TimeRange period, ITimeRange reference, try { if (toReference) + { return (decimal)intersectedPeriods.TotalDuration.TotalSeconds / (decimal)reference.Duration.TotalSeconds; + } return (decimal)intersectedPeriods.TotalDuration.TotalSeconds / (decimal)period.Duration.TotalSeconds; } @@ -653,8 +725,14 @@ public static bool IsPureUserProperties(this BO.Energiemenge em) if (values.TryGetValue(key, out var onlyValue)) { if (rawValue == null && onlyValue != null) + { return false; - if (rawValue != null && !rawValue.Equals(onlyValue)) return false; + } + + if (rawValue != null && !rawValue.Equals(onlyValue)) + { + return false; + } } else { @@ -674,7 +752,10 @@ public static bool IsPureUserProperties(this BO.Energiemenge em) public static bool IsPureMengeneinheit(this BO.Energiemenge em) { - if (em.Energieverbrauch.Select(v => v.Einheit).Distinct().Count() <= 1) return true; + if (em.Energieverbrauch.Select(v => v.Einheit).Distinct().Count() <= 1) + { + return true; + } var me1 = em.Energieverbrauch.Select(v => v.Einheit).First(); return em.Energieverbrauch.Select(v => v.Einheit).All(me2 => me1.IsConvertibleTo(me2)); @@ -712,7 +793,10 @@ public static bool IsExtensive(this BO.Energiemenge em) /// a list of pure energiemengen () public static List SplitInPureGroups(this BO.Energiemenge em) { - if (em.Energieverbrauch == null) return new List { em }; + if (em.Energieverbrauch == null) + { + return new List { em }; + } var result = new List(); foreach (var group in em.Energieverbrauch.GroupBy(PurityGrouper)) @@ -732,7 +816,10 @@ public static bool IsExtensive(this BO.Energiemenge em) /// public static void Detangle(this BO.Energiemenge em) { - if (em.Energieverbrauch != null) em.Energieverbrauch = VerbrauchExtension.Detangle(em.Energieverbrauch); + if (em.Energieverbrauch != null) + { + em.Energieverbrauch = VerbrauchExtension.Detangle(em.Energieverbrauch); + } } private class BasicVerbrauchDateTimeComparer : IComparer diff --git a/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtensionCompleteness.cs b/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtensionCompleteness.cs index 63d3268d..95c5310d 100644 --- a/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtensionCompleteness.cs +++ b/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtensionCompleteness.cs @@ -135,10 +135,15 @@ public static CompletenessReport GetCompletenessReport(this BO.Energiemenge em, em.Energieverbrauch.Select(v => new TimeRange((v.Startdatum ?? DateTimeOffset.MinValue).DateTime, (v.Enddatum ?? DateTimeOffset.MinValue).DateTime))); ITimeRange limits; if (result.ReferenceTimeFrame != null && result.ReferenceTimeFrame.Startdatum.HasValue) + { limits = new TimeRange(result.ReferenceTimeFrame.Startdatum.Value.UtcDateTime, result.ReferenceTimeFrame.Enddatum.Value.UtcDateTime); + } else + { limits = null; + } + var gaps = new TimeGapCalculator().GetGaps(nonNullValues, limits); result.Gaps = gaps.Select(gap => new CompletenessReport.BasicVerbrauch { @@ -153,20 +158,28 @@ public static CompletenessReport GetCompletenessReport(this BO.Energiemenge em, result.values.Sort(new BasicVerbrauchDateTimeComparer()); }*/ if (em.IsPure(true)) + { try { foreach (var kvp in em.Energieverbrauch.Where(v => v.UserProperties != null) - .SelectMany(v => v.UserProperties)) + .SelectMany(v => v.UserProperties)) { - if (result.UserProperties == null) result.UserProperties = new Dictionary(); + if (result.UserProperties == null) + { + result.UserProperties = new Dictionary(); + } + if (!result.UserProperties.ContainsKey(kvp.Key)) + { result.UserProperties.Add(kvp.Key, kvp.Value); + } } } catch (InvalidOperationException) { // ok, there's no Verbrauch with user properties. } + } } /*else @@ -190,8 +203,11 @@ public static CompletenessReport GetCompletenessReport(this BO.Energiemenge em, public static CompletenessReport GetCompletenessReport(this BO.Energiemenge em) { if (!em.IsPure()) + { throw new ArgumentException( "The provided Energiemenge is not pure. Please use overloaded method GetCompletenessReport(... , wertermittlungsverfahren, obiskennzahl, mengeneinheit)."); + } + Verbrauch v; try { @@ -220,11 +236,17 @@ public static CompletenessReport GetCompletenessReport(this BO.Energiemenge em) public static IDictionary GetSlicedCompletenessReports(this BO.Energiemenge em, IEnumerable ranges, bool useParallelExecution = false) { - if (ranges == null) throw new ArgumentNullException(nameof(ranges), "list of time ranges must not be null"); + if (ranges == null) + { + throw new ArgumentNullException(nameof(ranges), "list of time ranges must not be null"); + } + if (ranges.Any()) { if (useParallelExecution) + { return ranges.AsParallel().ToDictionary(r => r, r => GetCompletenessReport(em, r)); + } return ranges.ToDictionary(r => r, r => GetCompletenessReport(em, r)); } diff --git a/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtensionPlausibility.cs b/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtensionPlausibility.cs index c8568c41..de575bc6 100644 --- a/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtensionPlausibility.cs +++ b/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtensionPlausibility.cs @@ -42,10 +42,15 @@ public static PlausibilityReport GetPlausibilityReport(this BO.Energiemenge emRe { var overlap = trReference.GetIntersection(trOther); if (!ignoreLocation) + { if (!(emReference.LokationsId == emOther.LokationsId && emReference.LokationsTyp == emOther.LokationsTyp)) + { throw new ArgumentException( $"locations do not match! '{emReference.LokationsId}' ({emReference.LokationsTyp}) != '{emOther.LokationsId}' ({emOther.LokationsTyp})"); + } + } + timeframe = overlap; } @@ -59,13 +64,17 @@ public static PlausibilityReport GetPlausibilityReport(this BO.Energiemenge emRe { // unit mismatch if (consumptionReference.Item2.IsConvertibleTo(consumptionOtherRaw.Item2)) + { consumptionOther = new Tuple( consumptionOtherRaw.Item1 * consumptionOtherRaw.Item2.GetConversionFactor(consumptionReference.Item2), consumptionReference.Item2); + } else + { throw new ArgumentException( $"The unit {consumptionOtherRaw.Item2} is not comparable to {consumptionReference.Item2}!"); + } } else { @@ -111,9 +120,14 @@ public static PlausibilityReport GetPlausibilityReport(this BO.Energiemenge emRe AbsoluteDeviationEinheit = consumptionReference.Item2 }; if (relativeDeviation.HasValue) + { pr.RelativeDeviation = Math.Round(relativeDeviation.Value, 4); + } else + { pr.RelativeDeviation = null; + } + return pr; } @@ -144,7 +158,11 @@ public static PlausibilityReport GetPlausibilityReport(this BO.Energiemenge ener public static IDictionary GetSlicedPlausibilityReports(this BO.Energiemenge em, PlausibilityReportConfiguration config, IEnumerable ranges) { - if (ranges == null) throw new ArgumentNullException(nameof(ranges), "list of time ranges must not be null"); + if (ranges == null) + { + throw new ArgumentNullException(nameof(ranges), "list of time ranges must not be null"); + } + var result = new Dictionary(); foreach (var range in ranges) { @@ -175,8 +193,16 @@ public static IDictionary GetSlicedPlausibilityR public static IDictionary GetDailyPlausibilityReports(this BO.Energiemenge em, PlausibilityReportConfiguration config) { - if (config == null) throw new ArgumentNullException(nameof(config)); - if (config.Timeframe == null) throw new ArgumentNullException(nameof(config.Timeframe)); + if (config == null) + { + throw new ArgumentNullException(nameof(config)); + } + + if (config.Timeframe == null) + { + throw new ArgumentNullException(nameof(config.Timeframe)); + } + var slices = GetLocalDailySlices(new TimeRange { Start = config.Timeframe.Startdatum.Value.UtcDateTime, @@ -197,8 +223,16 @@ public static IDictionary GetDailyPlausibilityRe public static IDictionary GetMonthlyPlausibilityReports(this BO.Energiemenge em, PlausibilityReportConfiguration config) { - if (config == null) throw new ArgumentNullException(nameof(config)); - if (config.Timeframe == null) throw new ArgumentNullException(nameof(config.Timeframe)); + if (config == null) + { + throw new ArgumentNullException(nameof(config)); + } + + if (config.Timeframe == null) + { + throw new ArgumentNullException(nameof(config.Timeframe)); + } + var slices = GetLocalMonthlySlices(new TimeRange { Start = config.Timeframe.Startdatum.Value.UtcDateTime, diff --git a/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtensionSlicingHelper.cs b/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtensionSlicingHelper.cs index a09ed57c..9128389f 100644 --- a/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtensionSlicingHelper.cs +++ b/BO4E.Extensions/BusinessObjects/Energiemenge/EnergiemengeExtensionSlicingHelper.cs @@ -12,14 +12,26 @@ public static partial class EnergiemengeExtension internal static IList GetLocalDailySlices(ITimeRange overallTimeRange, TimeZoneInfo tz = null) { if (overallTimeRange == null) + { throw new ArgumentNullException(nameof(overallTimeRange), "overall time range must not be null"); - if (tz == null) tz = CentralEuropeStandardTime.CentralEuropeStandardTimezoneInfo; + } + + if (tz == null) + { + tz = CentralEuropeStandardTime.CentralEuropeStandardTimezoneInfo; + } + if (overallTimeRange.Start.Kind == DateTimeKind.Unspecified) + { throw new ArgumentException("TimeRange start must not have DateTimeKind.Unspecified", nameof(overallTimeRange)); + } + if (overallTimeRange.End.Kind == DateTimeKind.Unspecified) + { throw new ArgumentException("TimeRange end must not have DateTimeKind.Unspecified", nameof(overallTimeRange)); + } IList result = new List(); if (!overallTimeRange.IsMoment) @@ -43,7 +55,10 @@ internal static IList GetLocalDailySlices(ITimeRange overallTimeRang internal static IList GetLocalMonthlySlices(ITimeRange overallTimeRange, TimeZoneInfo tz = null) { if (overallTimeRange == null) + { throw new ArgumentNullException(nameof(overallTimeRange), "overall time range must not be null"); + } + DateTime localStart; DateTime localEnd; if (tz == null) @@ -51,13 +66,19 @@ internal static IList GetLocalMonthlySlices(ITimeRange overallTimeRa tz = CentralEuropeStandardTime.CentralEuropeStandardTimezoneInfo; if (overallTimeRange.Start.Kind != DateTimeKind.Utc) + { throw new ArgumentException( $"TimeRange start must have DateTimeKind.Utc if no timezone is given in parameter {nameof(tz)}", nameof(overallTimeRange)); + } + if (overallTimeRange.End.Kind != DateTimeKind.Utc) + { throw new ArgumentException( $"TimeRange end must have DateTimeKind.Utc if no timezone is given in parameter {nameof(tz)}", nameof(overallTimeRange)); + } + localStart = TimeZoneInfo.ConvertTimeFromUtc(overallTimeRange.Start, tz); localEnd = TimeZoneInfo.ConvertTimeFromUtc(overallTimeRange.End, tz); } @@ -142,7 +163,11 @@ internal static IList GetLocalMonthlySlices(ITimeRange overallTimeRa /// public static DateTime AddDaysDST(this DateTime dt, double value, TimeZoneInfo tz = null) { - if (tz == null) tz = CentralEuropeStandardTime.CentralEuropeStandardTimezoneInfo; + if (tz == null) + { + tz = CentralEuropeStandardTime.CentralEuropeStandardTimezoneInfo; + } + switch (dt.Kind) { case DateTimeKind.Local: diff --git a/BO4E.Extensions/COM/VerbrauchExtension.cs b/BO4E.Extensions/COM/VerbrauchExtension.cs index 57de3079..9100e8c8 100644 --- a/BO4E.Extensions/COM/VerbrauchExtension.cs +++ b/BO4E.Extensions/COM/VerbrauchExtension.cs @@ -88,9 +88,13 @@ public static HashSet Merge(this Verbrauch v1, Verbrauch v2, bool red vmerge.Enddatum = v1.Enddatum; if (exclusiveV1Wert == 0.0M && exclusiveV2Wert == 0.0M && overlapV1Wert == overlapV2Wert) + { vmerge.Wert = overlapV1Wert; + } else + { vmerge.Wert = v1.Wert - overlapV2Wert; // overlapV1Wert; + } } else { @@ -130,8 +134,11 @@ public static HashSet Merge(this Verbrauch v1, Verbrauch v2, bool red if (redundant) { if (v1.Wert != v2.Wert) + { throw new ArgumentException( $"Data cannot be redundant if values ({v1.Wert}{v1.Einheit} vs. {v2.Wert}{v2.Einheit}) don't match for interval [{vmerge2.Startdatum}, {vmerge2.Enddatum})."); + } + vmerge2.Wert = v1.Wert; } else @@ -269,8 +276,11 @@ public static List Detangle(IEnumerable input) foreach (var frv in fullyRedundantVerbrauchs) { if (frv.av.x.Wert + frv.av.y.Wert != frv.z.Wert) + { throw new ArgumentException( $"Inconsistent data detected: {JsonConvert.SerializeObject(frv.av.x)} + {JsonConvert.SerializeObject(frv.av.y)} ≠ {JsonConvert.SerializeObject(frv.z)}"); + } + subResult.Remove(frv.z); } @@ -406,9 +416,16 @@ public class VerbrauchDateTimeComparer : IComparer { int IComparer.Compare(Verbrauch x, Verbrauch y) { - if (x.Startdatum != y.Startdatum) return DateTimeOffset.Compare(x.Startdatum ?? DateTimeOffset.MinValue, y.Startdatum ?? DateTimeOffset.MinValue); + if (x.Startdatum != y.Startdatum) + { + return DateTimeOffset.Compare(x.Startdatum ?? DateTimeOffset.MinValue, y.Startdatum ?? DateTimeOffset.MinValue); + } + + if (x.Enddatum != y.Enddatum) + { + return DateTimeOffset.Compare(x.Enddatum ?? DateTimeOffset.MinValue, y.Enddatum ?? DateTimeOffset.MinValue); + } - if (x.Enddatum != y.Enddatum) return DateTimeOffset.Compare(x.Enddatum ?? DateTimeOffset.MinValue, y.Enddatum ?? DateTimeOffset.MinValue); return 0; } } diff --git a/BO4E.Extensions/ENUM/MengeneinheitExtension.cs b/BO4E.Extensions/ENUM/MengeneinheitExtension.cs index 0b0ba32c..e620ba97 100644 --- a/BO4E.Extensions/ENUM/MengeneinheitExtension.cs +++ b/BO4E.Extensions/ENUM/MengeneinheitExtension.cs @@ -36,7 +36,10 @@ public static class MengeneinheitExtenion public static bool AreConvertible(Mengeneinheit me1, Mengeneinheit me2) { #pragma warning disable 618 - if (me1 == Mengeneinheit.ZERO || me2 == Mengeneinheit.ZERO) return false; + if (me1 == Mengeneinheit.ZERO || me2 == Mengeneinheit.ZERO) + { + return false; + } #pragma warning restore 618 return DimensionSets.Any(einheitengroup => einheitengroup.Contains(me1) && einheitengroup.Contains(me2)); } @@ -109,13 +112,25 @@ public static decimal GetConversionFactor(this Mengeneinheit me1, Mengeneinheit #pragma warning disable 618 if (me1 == Mengeneinheit.ZERO || me2 == Mengeneinheit.ZERO) #pragma warning restore 618 + { throw new InvalidOperationException("You must not use the artificial 'ZERO' value."); + } + + if (me1 == me2) + { + return 1.0M; + } - if (me1 == me2) return 1.0M; if (!me1.IsConvertibleTo(me2)) + { throw new InvalidOperationException( $"{me1} and {me2} are not convertible into each other because they don't share the same dimension."); - if ((int)me1 % (int)me2 == 0 || (int)me2 % (int)me2 == 0) return (decimal)me1 / (decimal)me2; + } + + if ((int)me1 % (int)me2 == 0 || (int)me2 % (int)me2 == 0) + { + return (decimal)me1 / (decimal)me2; + } throw new InvalidOperationException($"{me1} and {me2} are not (trivially) convertible into each other."); } diff --git a/BO4E.Reporting/CompletenessReport.cs b/BO4E.Reporting/CompletenessReport.cs index 39d64416..1fd1ec7a 100644 --- a/BO4E.Reporting/CompletenessReport.cs +++ b/BO4E.Reporting/CompletenessReport.cs @@ -92,15 +92,33 @@ public class CompletenessReport : Report, IComparable /// public int CompareTo(CompletenessReport other) { - if (ReferenceTimeFrame == null && other.ReferenceTimeFrame == null) return 0; - if (ReferenceTimeFrame != null && other.ReferenceTimeFrame == null) return 1; - if (ReferenceTimeFrame == null && other.ReferenceTimeFrame != null) return -1; + if (ReferenceTimeFrame == null && other.ReferenceTimeFrame == null) + { + return 0; + } + + if (ReferenceTimeFrame != null && other.ReferenceTimeFrame == null) + { + return 1; + } + + if (ReferenceTimeFrame == null && other.ReferenceTimeFrame != null) + { + return -1; + } + if (ReferenceTimeFrame != null && other.ReferenceTimeFrame != null) { if (ReferenceTimeFrame.Startdatum.HasValue && other.ReferenceTimeFrame.Startdatum.HasValue) + { return Comparer.Default.Compare(ReferenceTimeFrame.Startdatum.Value, other.ReferenceTimeFrame.Startdatum.Value); - if (ReferenceTimeFrame.Startdatum.HasValue) return 1; + } + + if (ReferenceTimeFrame.Startdatum.HasValue) + { + return 1; + } return -1; } @@ -166,14 +184,23 @@ public int CompareTo(CompletenessReport other) columns.Add("MSB"); // MSB if (UserProperties.TryGetValue("profil", out var profil)) + { columns.Add(profil.ToString()); + } else + { columns.Add(string.Empty); + } if (UserProperties.TryGetValue("profilRolle", out var profilRolle)) + { columns.Add(profilRolle.ToString()); + } else + { columns.Add(string.Empty); + } + if (Gaps != null && Gaps.Any()) { var minGap = Gaps.Min(x => x.Startdatum); // OrderBy(x => x.Startdatum).First().Startdatum; @@ -191,9 +218,14 @@ public int CompareTo(CompletenessReport other) } if (Coverage.HasValue) + { columns.Add((Coverage.Value * 100).ToString("0.####") + " %"); + } else + { columns.Add(string.Empty); + } + columns.Add("Status"); builder.Append(string.Join(separator, columns) + lineTerminator); diff --git a/BO4E.Reporting/Report.cs b/BO4E.Reporting/Report.cs index c98d563f..16eadf94 100644 --- a/BO4E.Reporting/Report.cs +++ b/BO4E.Reporting/Report.cs @@ -110,8 +110,11 @@ public abstract class Report : BusinessObject } if (i == 0 && headerLine) + { resultBuilder = new StringBuilder(string.Join(separator.ToString(), sortedHeaderNamesList) + lineTerminator); + } + resultBuilder.Append(string.Join(separator.ToString(), sortedResults) + lineTerminator); } } @@ -156,8 +159,11 @@ public abstract class Report : BusinessObject } if (headerLine) + { resultBuilder = new StringBuilder(string.Join(separator.ToString(), sortedHeaderNamesList) + lineTerminator); + } + resultBuilder.Append(string.Join(separator.ToString(), sortedResults)); } @@ -226,17 +232,25 @@ private Dictionary, List> Detect(Type type, char separator, if (nestedValue != null) { var muterType = ""; - if (field.DeclaringType.BaseType == typeof(COM.COM)) muterType = field.DeclaringType.Name + "."; + if (field.DeclaringType.BaseType == typeof(COM.COM)) + { + muterType = field.DeclaringType.Name + "."; + } + var val = nestedValue.ToString(); if (field.PropertyType == typeof(DateTime?)) { if (((DateTime?)nestedValue).HasValue) + { val = ((DateTime?)nestedValue).Value.ToString("yyyy-MM-ddTHH:mm:ssZ"); + } } else if (field.PropertyType == typeof(DateTimeOffset?)) { if (((DateTimeOffset?)nestedValue).HasValue) + { val = ((DateTimeOffset?)nestedValue).Value.ToString("yyyy-MM-ddTHH:mm:ssZ"); + } } else if (field.PropertyType == typeof(DateTime)) { @@ -263,7 +277,10 @@ private Dictionary, List> DetectGaps(Type type, char separa var h = returnData.Keys.First(); foreach (var field in fields) { - if (field.FieldType.IsSubclassOf(typeof(COM.COM))) continue; + if (field.FieldType.IsSubclassOf(typeof(COM.COM))) + { + continue; + } if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(List<>)) { @@ -280,7 +297,11 @@ private Dictionary, List> DetectGaps(Type type, char separa var nestedValue = field.GetValue(value); if (nestedValue != null) { - if (field.DeclaringType.BaseType == typeof(COM.COM)) continue; + if (field.DeclaringType.BaseType == typeof(COM.COM)) + { + continue; + } + if (field.DeclaringType == typeof(BasicVerbrauch)) { var muterType = "gap."; @@ -288,7 +309,10 @@ private Dictionary, List> DetectGaps(Type type, char separa if (field.FieldType == typeof(DateTime?)) { if (((DateTime?)nestedValue).HasValue) + { val = ((DateTime?)nestedValue).Value.ToString("yyyy-MM-ddTHH:mm:ssZ"); + } + h.Add(muterType + field.Name); d.Add(val.Contains(separator) ? "\"" + val + "\"" : val); } diff --git a/BO4E/BO/Avis.cs b/BO4E/BO/Avis.cs index 41d33e58..da866e8e 100644 --- a/BO4E/BO/Avis.cs +++ b/BO4E/BO/Avis.cs @@ -1,3 +1,4 @@ +#nullable enable using BO4E.COM; using BO4E.ENUM; using BO4E.meta; @@ -20,37 +21,37 @@ public class Avis : BusinessObject /// /// Eine im Verwendungskontext eindeutige Nummer für das Avis. /// - [JsonProperty(PropertyName = "avisNummer", Required = Required.Always)] + [JsonProperty(PropertyName = "avisNummer", Required = Required.Default)] [JsonPropertyName("avisNummer")] [NonOfficial(NonOfficialCategory.MISSING)] [ProtoMember(1000)] - public string AvisNummer { get; set; } + public string? AvisNummer { get; set; } /// /// Gibt den Typ des Avis an. /// /// - [JsonProperty(PropertyName = "avisTyp", Required = Required.Always)] + [JsonProperty(PropertyName = "avisTyp", Required = Required.Default)] [JsonPropertyName("avisTyp")] [ProtoMember(1001)] - public AvisTyp AvisTyp { get; set; } + public AvisTyp? AvisTyp { get; set; } /// /// Avispositionen /// - [JsonProperty(PropertyName = "positionen", Required = Required.Always)] + [JsonProperty(PropertyName = "positionen", Required = Required.Default)] [JsonPropertyName("positionen")] [NonOfficial(NonOfficialCategory.MISSING)] [ProtoMember(1002)] [BoKey] - public List Positionen { get; set; } + public List? Positionen { get; set; } /// /// Summenbetrag /// - [JsonProperty(PropertyName = "zuZahlen", Required = Required.Always)] + [JsonProperty(PropertyName = "zuZahlen", Required = Required.Default)] [JsonPropertyName("zuZahlen")] [NonOfficial(NonOfficialCategory.MISSING)] [ProtoMember(1003)] - public Betrag ZuZahlen { get; set; } -} \ No newline at end of file + public Betrag? ZuZahlen { get; set; } +} diff --git a/BO4E/BO/BusinessObject.cs b/BO4E/BO/BusinessObject.cs index 9b7b3988..e837dc06 100644 --- a/BO4E/BO/BusinessObject.cs +++ b/BO4E/BO/BusinessObject.cs @@ -301,8 +301,11 @@ public JSchema GetJsonScheme() public static JSchema GetJsonSchema(Type boType) { if (!boType.IsSubclassOf(typeof(BusinessObject))) + { throw new ArgumentException( $"You must only request JSON schemes for Business Objects. {boType} is not a valid Business Object type."); + } + var generator = new JSchemaGenerator(); generator.GenerationProviders.Add(new StringEnumGenerationProvider()); var schema = generator.Generate(boType); @@ -415,16 +418,24 @@ public static Dictionary GetExpandableFieldNames(string boTypeName protected static Dictionary GetExpandablePropertyNames(Type type, bool rootLevel = true) { if (rootLevel && !type.IsSubclassOf(typeof(BusinessObject))) + { throw new ArgumentException("Only allowed for BusinessObjects"); + } + var result = new Dictionary(); foreach (var prop in type.GetProperties()) { string fieldName; var jpa = prop.GetCustomAttribute(); if (jpa?.PropertyName != null) + { fieldName = jpa.PropertyName; + } else + { fieldName = prop.Name; + } + if (prop.PropertyType.IsSubclassOf(typeof(BusinessObject))) { foreach (var subResult in GetExpandablePropertyNames(prop.PropertyType, false)) @@ -477,8 +488,11 @@ public Dictionary GetBoKeys() public static List GetBoKeyProps(Type boType) { if (!boType.IsSubclassOf(typeof(BusinessObject))) + { throw new ArgumentException( $"Business Object keys are only defined on Business Object types but {boType} is not a Business Object."); + } + return boType.GetProperties() .Where(p => p.GetCustomAttributes(typeof(BoKey), false).Length > 0) .OrderBy(ap => ap.GetCustomAttribute()?.Order) @@ -511,7 +525,10 @@ internal class BaseSpecifiedConcreteClassConverter : DefaultContractResolver protected override JsonConverter ResolveContractConverter(Type objectType) { if (typeof(BusinessObject).IsAssignableFrom(objectType) && !objectType.IsAbstract) + { return null; // pretend TableSortRuleConvert is not specified (thus avoiding a stack overflow) + } + return base.ResolveContractConverter(objectType); } } @@ -529,7 +546,11 @@ public override bool CanConvert(Type objectType) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - if (reader.TokenType == JsonToken.Null) return null; + if (reader.TokenType == JsonToken.Null) + { + return null; + } + if (objectType.IsAbstract) { var jo = JObject.Load(reader); @@ -567,12 +588,17 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist continue; } - if (boType != null) break; + if (boType != null) + { + break; + } } if (boType == null) + { throw new NotImplementedException( $"The type '{jo["boTyp"].Value()}' does not exist in the BO4E standard."); + } } var deserializationMethod = serializer.GetType() // https://stackoverflow.com/a/5218492/10009545 @@ -624,12 +650,19 @@ public override bool CanConvert(Type objectType) public override BusinessObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType == JsonTokenType.Null) return null; + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + if (typeToConvert.IsAbstract) { var jdoc = JsonDocument.ParseValue(ref reader); if (!jdoc.RootElement.TryGetProperty("BoTyp", out var boTypeProp)) + { boTypeProp = jdoc.RootElement.GetProperty("boTyp"); + } + var boTypeString = boTypeProp.GetString(); #pragma warning disable CS0618 // Type or member is obsolete var boType = BoMapper.GetTypeForBoName(boTypeString); @@ -648,12 +681,17 @@ public override BusinessObject Read(ref Utf8JsonReader reader, Type typeToConver continue; } - if (boType != null) break; + if (boType != null) + { + break; + } } if (boType == null) + { throw new NotImplementedException( $"The type '{boTypeString}' does not exist in the BO4E standard."); + } } return System.Text.Json.JsonSerializer.Deserialize(jdoc.RootElement.GetRawText(), boType, options) diff --git a/BO4E/BO/Energiemenge.cs b/BO4E/BO/Energiemenge.cs index 3949bf02..3e97c048 100644 --- a/BO4E/BO/Energiemenge.cs +++ b/BO4E/BO/Energiemenge.cs @@ -82,8 +82,11 @@ static Energiemenge() { if (em1.LokationsId != em2.LokationsId || em1.LokationsTyp != em2.LokationsTyp || em1.VersionStruktur != em2.VersionStruktur) + { throw new InvalidOperationException( $"You must not add the Energiemengen with different locations {em1.LokationsId} ({em1.LokationsTyp}) (v{em1.VersionStruktur}) vs. {em2.LokationsId} ({em2.LokationsTyp}) (v{em2.VersionStruktur})"); + } + var result = new Energiemenge { LokationsId = em1.LokationsId, @@ -105,7 +108,9 @@ static Energiemenge() foreach (var kvp1 in em1.UserProperties) result.UserProperties.Add(kvp1.Key, kvp1.Value); foreach (var kvp2 in em2.UserProperties) if (!result.UserProperties.ContainsKey(kvp2.Key)) + { result.UserProperties.Add(kvp2.Key, kvp2.Value); + } } if (em1.Energieverbrauch == null || em1.Energieverbrauch.Count == 0) diff --git a/BO4E/BO/Lokationszuordnung.cs b/BO4E/BO/Lokationszuordnung.cs index af0e202e..17d48460 100644 --- a/BO4E/BO/Lokationszuordnung.cs +++ b/BO4E/BO/Lokationszuordnung.cs @@ -1,8 +1,6 @@ -using System; using System.Collections.Generic; using System.Text.Json.Serialization; using BO4E.COM; -using BO4E.ENUM; using BO4E.meta; using Newtonsoft.Json; using ProtoBuf; diff --git a/BO4E/BO/Marktlokation.cs b/BO4E/BO/Marktlokation.cs index 6af09851..717121ba 100644 --- a/BO4E/BO/Marktlokation.cs +++ b/BO4E/BO/Marktlokation.cs @@ -430,6 +430,20 @@ public class Marktlokation : BusinessObject [JsonPropertyOrder(42)] public string? LokationsbuendelObjektcode { get; set; } + /// + /// Enthält die ID der vorgelagerten Lokation. Kann IDs unterschiedlicher Lokationen enthalten, also zum Beispiel + /// einer Messlokation oder Netzlokation. + /// + [JsonProperty( + Required = Required.Default, + Order = 43, + PropertyName = "vorgelagerteLokationsId" + )] + [JsonPropertyName("vorgelagerteLokationsId")] + [ProtoMember(43)] + [JsonPropertyOrder(43)] + public string? VorgelagerteLokationsId { get; set; } + /// /// Test if a is a valid Marktlokations ID. /// @@ -438,9 +452,15 @@ public class Marktlokation : BusinessObject public static bool ValidateId(string id) { if (string.IsNullOrWhiteSpace(id)) + { return false; + } + if (!RegexValidate.IsMatch(id)) + { return false; + } + var expectedChecksum = GetChecksum(id); var actualChecksum = id.Substring(10, 1); return actualChecksum == expectedChecksum; @@ -459,17 +479,26 @@ public static bool ValidateId(string id) public static string GetChecksum(string input) { if (string.IsNullOrWhiteSpace(input)) + { throw new ArgumentException( $"Input '{nameof(input)}' must not be empty but was '{input}'" ); + } + if (input.Length is < 10 or > 11) + { throw new ArgumentException( $"Input '{nameof(input)}' must be a string with length 10 (to generate the checksum) or 11 (to validate the checksum)." ); + } + if (!RegexNumericString.IsMatch(input)) + { throw new ArgumentException( $"Input '{nameof(input)}' must be numeric was '{input}'" ); + } + var oddChecksum = 0; var evenChecksum = 0; @@ -478,9 +507,13 @@ public static string GetChecksum(string input) { var s = input.Substring(i - 1, 1); if (i % 2 == 0) + { evenChecksum += 2 * int.Parse(s); + } else + { oddChecksum += int.Parse(s); + } } var result = (10 - (evenChecksum + oddChecksum) % 10) % 10; diff --git a/BO4E/BO/Messlokation.cs b/BO4E/BO/Messlokation.cs index e01c2b0d..753c651b 100644 --- a/BO4E/BO/Messlokation.cs +++ b/BO4E/BO/Messlokation.cs @@ -282,6 +282,7 @@ public class Messlokation : BusinessObject [NonOfficial(NonOfficialCategory.CUSTOMER_REQUIREMENTS)] public List? Messprodukte { get; set; } + // /// // /// Lokationszuordnung, um bspw. die zugehörigen Marktlokationen anzugeben // /// @@ -308,6 +309,20 @@ public class Messlokation : BusinessObject [JsonPropertyOrder(32)] public string? LokationsbuendelObjektcode { get; set; } + /// + /// Enthält die ID der vorgelagerten Lokation. Kann Ids unterschiedlicher Lokationen enthalten, also zum Beispiel + /// einer Messlokation oder Marktlokation. + /// + [JsonProperty( + Required = Required.Default, + Order = 33, + PropertyName = "vorgelagerteLokationsId" + )] + [JsonPropertyName("vorgelagerteLokationsId")] + [ProtoMember(1027)] + [JsonPropertyOrder(33)] + public string? VorgelagerteLokationsId { get; set; } + /// /// Test if a is a valid messlokations ID. /// diff --git a/BO4E/BO/Netzlokation.cs b/BO4E/BO/Netzlokation.cs index 9773cf67..7bedf332 100644 --- a/BO4E/BO/Netzlokation.cs +++ b/BO4E/BO/Netzlokation.cs @@ -144,4 +144,18 @@ public class Netzlokation : BusinessObject [JsonPropertyOrder(20)] [NonOfficial(NonOfficialCategory.CUSTOMER_REQUIREMENTS)] public string? LokationsbuendelObjektcode { get; set; } + + /// + /// Enthält die ID der vorgelagerten Lokation. Kann Ids unterschiedlicher Lokationen enthalten, also zum Beispiel + /// einer Messlokation oder Marktlokation. + /// + [JsonProperty( + Required = Required.Default, + Order = 21, + PropertyName = "vorgelagerteLokationsId" + )] + [JsonPropertyName("vorgelagerteLokationsId")] + [ProtoMember(15)] + [JsonPropertyOrder(21)] + public string? VorgelagerteLokationsId { get; set; } } diff --git a/BO4E/BO/Rechnung.cs b/BO4E/BO/Rechnung.cs index 68e5bc57..001bbe86 100644 --- a/BO4E/BO/Rechnung.cs +++ b/BO4E/BO/Rechnung.cs @@ -43,8 +43,10 @@ public Rechnung(JObject sapPrintDocument) : this() var infoToken = sapPrintDocument.SelectToken("erdk") ?? sapPrintDocument.SelectToken("ERDK"); var tErdzToken = sapPrintDocument.SelectToken("tErdz") ?? sapPrintDocument.SelectToken("T_ERDZ"); if (tErdzToken == null) + { throw new ArgumentException( "The SAP print document did not contain a 'tErdz' token. Did you serialize using the right naming convention?"); + } Rechnungsnummer = (infoToken["opbel"] ?? infoToken["OPBEL"]).Value(); Rechnungsdatum = new DateTimeOffset(TimeZoneInfo.ConvertTime( @@ -79,7 +81,10 @@ public Rechnung(JObject sapPrintDocument) : this() foreach (var jrp in tErdzToken) { var belzart = (jrp["belzart"] ?? jrp["BELZART"]).ToString(); - if (belzart == "IQUANT" || belzart == "ROUND" || belzart == "ROUNDO") continue; + if (belzart == "IQUANT" || belzart == "ROUND" || belzart == "ROUNDO") + { + continue; + } var rp = new Rechnungsposition(); decimal zeitbezogeneMengeWert = 0; @@ -139,30 +144,40 @@ public Rechnung(JObject sapPrintDocument) : this() if (rp.Einzelpreis == null) { if ((jrp["preisbtr"] ?? jrp["PREISBTR"]) != null) + { rp.Einzelpreis = new Preis { Wert = decimal.Parse((jrp["preisbtr"] ?? jrp["PREISBTR"]).ToString()), Einheit = waehrungseinheit, Bezugswert = mengeneinheit }; + } else + { rp.Einzelpreis = new Preis { Wert = 0, Einheit = waehrungseinheit, Bezugswert = mengeneinheit }; + } } rp.Positionsnummer = (jrp["belzeile"] ?? jrp["BELZEILE"]).Value(); if ((jrp["bis"] ?? jrp["BIS"]) != null && (jrp["bis"] ?? jrp["BIS"]).Value() != "0000-00-00") + { rp.LieferungBis = new DateTimeOffset(TimeZoneInfo.ConvertTime( (jrp["bis"] ?? jrp["BIS"]).Value(), CentralEuropeStandardTime.CentralEuropeStandardTimezoneInfo, TimeZoneInfo.Utc)); + } + if ((jrp["ab"] ?? jrp["AB"]) != null && (jrp["ab"] ?? jrp["AB"]).Value() != "0000-00-00") + { rp.LieferungVon = new DateTimeOffset(TimeZoneInfo.ConvertTime( (jrp["ab"] ?? jrp["AB"]).Value(), CentralEuropeStandardTime.CentralEuropeStandardTimezoneInfo, TimeZoneInfo.Utc)); + } + if ((jrp["vertrag"] ?? jrp["VERTRAG"]) != null) { #pragma warning disable CS0618 // Type or member is obsolete @@ -171,11 +186,13 @@ public Rechnung(JObject sapPrintDocument) : this() } if ((jrp["iAbrmenge"] ?? jrp["I_ABRMENGE"]) != null) + { rp.PositionsMenge = new Menge { Wert = (jrp["iAbrmenge"] ?? jrp["I_ABRMENGE"]).Value(), Einheit = mengeneinheit }; + } if ((jrp["nettobtr"] ?? jrp["NETTOBTR"]) != null) { @@ -204,11 +221,15 @@ public Rechnung(JObject sapPrintDocument) : this() decimal steuerProzent; if ((jrp["stprz"] ?? jrp["STPRZ"]) != null && !string.IsNullOrWhiteSpace((jrp["stprz"] ?? jrp["STPRZ"]).Value())) + { steuerProzent = decimal.Parse((jrp["stprz"] ?? jrp["STPRZ"]).Value().Replace(",", ".").Trim(), CultureInfo.InvariantCulture); + } else + { steuerProzent = steuerbetrag.Steuerwert / steuerbetrag.Basiswert * 100.0M; + } steuerbetrag.Steuerkennzeichen = (int)steuerProzent switch { @@ -221,8 +242,10 @@ public Rechnung(JObject sapPrintDocument) : this() } if ((jrp["nettobtr"] ?? jrp["NETTOBTR"]).Value() <= 0) + { Vorausgezahlt = new Betrag { Waehrung = waehrungscode, Wert = (jrp["nettobtr"] ?? jrp["NETTOBTR"]).Value() }; + } } rp.Zeiteinheit = mengeneinheit; @@ -247,11 +270,15 @@ public Rechnung(JObject sapPrintDocument) : this() decimal steuerProzent; if ((jrp["stprz"] ?? jrp["STPRZ"]) != null && !string.IsNullOrWhiteSpace((jrp["stprz"] ?? jrp["STPRZ"]).Value())) + { steuerProzent = decimal.Parse((jrp["stprz"] ?? jrp["STPRZ"]).Value().Replace(",", ".").Trim(), CultureInfo.InvariantCulture); + } else + { steuerProzent = Math.Round(steuerbetrag.Steuerwert / steuerbetrag.Basiswert * 100.0M); + } steuerbetrag.Steuerkennzeichen = steuerProzent switch { diff --git a/BO4E/BO/TechnischeRessource.cs b/BO4E/BO/TechnischeRessource.cs index cd1e62a1..42e7cd81 100644 --- a/BO4E/BO/TechnischeRessource.cs +++ b/BO4E/BO/TechnischeRessource.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.ComponentModel; using System.Text.Json.Serialization; using BO4E.COM; @@ -239,4 +238,18 @@ public class TechnischeRessource : BusinessObject [JsonPropertyOrder(24)] [NonOfficial(NonOfficialCategory.CUSTOMER_REQUIREMENTS)] public string? LokationsbuendelObjektcode { get; set; } -} \ No newline at end of file + + /// + /// Enthält die ID der vorgelagerten Lokation. Kann Ids unterschiedlicher Lokationen enthalten, also zum Beispiel + /// einer Messlokation oder Marktlokation. + /// + [JsonProperty( + Required = Required.Default, + Order = 25, + PropertyName = "vorgelagerteLokationsId" + )] + [JsonPropertyName("vorgelagerteLokationsId")] + [ProtoMember(25)] + [JsonPropertyOrder(25)] + public string? VorgelagerteLokationsId { get; set; } +} diff --git a/BO4E/BO/Vertrag.cs b/BO4E/BO/Vertrag.cs index fb56cb5d..26cdf40f 100644 --- a/BO4E/BO/Vertrag.cs +++ b/BO4E/BO/Vertrag.cs @@ -215,6 +215,7 @@ protected void OnDeserialized(StreamingContext context) { if ((Vertragsteile == null || Vertragsteile.Count == 0) && UserProperties != null && UserProperties.ContainsKey("lokationsId")) + { Vertragsteile = new List { new Vertragsteil @@ -224,6 +225,7 @@ protected void OnDeserialized(StreamingContext context) Lokation = UserProperties["lokationsId"] as string } }; + } } } @@ -253,6 +255,7 @@ public override Vertrag Read(ref Utf8JsonReader reader, Type typeToConvert, Json var v = JsonSerializer.Deserialize(ref reader, Vertrag.VertragsSerializerOptions); if ((v.Vertragsteile == null || v.Vertragsteile.Count == 0) && v.UserProperties != null && v.UserProperties.ContainsKey("lokationsId")) + { v.Vertragsteile = new List { new() @@ -262,6 +265,8 @@ public override Vertrag Read(ref Utf8JsonReader reader, Type typeToConvert, Json Lokation = ((JsonElement) v.UserProperties["lokationsId"]).GetString() } }; + } + return v; } diff --git a/BO4E/BO4E.csproj b/BO4E/BO4E.csproj index cf1739ab..a4860b0b 100644 --- a/BO4E/BO4E.csproj +++ b/BO4E/BO4E.csproj @@ -1,98 +1,98 @@ - - - netstandard2.0 - BO4E - true - Hochfrequenz.BO4Enet - Hochfrequenz Untenehmensberatung GmbH - BO4E .net core bindings - https://github.com/Hochfrequenz/BO4E-dotnet/ - - 0.2.50 - BO4Enet - false - true - - - true - - - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - https://github.com/Hochfrequenz/BO4E-dotnet/ - false - LICENSE.txt - - git - true - Embedded - True - latest - BO4Enet.xml - annotations - - - - false - true - - - DEBUG - true - - - - - - - - - - - True - - - - - - - - - - - - - - - - - - - - - - - - Always - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - + + + netstandard2.0 + BO4E + true + Hochfrequenz.BO4Enet + Hochfrequenz Untenehmensberatung GmbH + BO4E .net core bindings + https://github.com/Hochfrequenz/BO4E-dotnet/ + + 0.2.50 + BO4Enet + false + true + + + true + + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + https://github.com/Hochfrequenz/BO4E-dotnet/ + false + LICENSE.txt + + git + true + Embedded + True + latest + BO4Enet.xml + annotations + + + + false + true + + + DEBUG + true + + + + + + + + + + + True + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/BO4E/BoMapper.cs b/BO4E/BoMapper.cs index 3cd264f1..12b21932 100644 --- a/BO4E/BoMapper.cs +++ b/BO4E/BoMapper.cs @@ -34,7 +34,10 @@ public static HashSet GetValidBoNames() foreach (var t in types) { var m = BoRegex.Match(t.ToString()); - if (m.Success) result.Add(m.Groups["boName"].Value); + if (m.Success) + { + result.Add(m.Groups["boName"].Value); + } } return result; @@ -55,7 +58,10 @@ public static HashSet GetValidBoNames() /// public static Type GetTypeForBoName(string businessObjectName) { - if (businessObjectName == null) throw new ArgumentNullException(nameof(businessObjectName)); + if (businessObjectName == null) + { + throw new ArgumentNullException(nameof(businessObjectName)); + } //Type[] types = Assembly.GetExecutingAssembly().GetTypes(); var clazz = Assembly.GetExecutingAssembly().GetType(PackagePrefix + "." + businessObjectName); @@ -67,18 +73,4 @@ where string.Equals(boName, businessObjectName, StringComparison.CurrentCultureI //throw new ArgumentException($"No implemented BusinessObject type matches the name '{businessObjectName}'."); } - - /// - /// Get JSON Scheme for given Business Object type - /// - /// Business Object type (e.g. typeof(BO4E.BO.Messlokation) - /// A JSON scheme to be used for validation purposes. - /// if given type is not derived from BusinessObject - public static JSchema GetJsonSchemeFor(Type businessObjectType) - { - if (!businessObjectType.IsSubclassOf(typeof(BusinessObject))) - throw new ArgumentException($"The given type {businessObjectType} is not derived from BusinessObject."); - var bo = Activator.CreateInstance(businessObjectType) as BusinessObject; - return bo.GetJsonScheme(); - } } diff --git a/BO4E/COM/Bankverbindung.cs b/BO4E/COM/Bankverbindung.cs index 35aa17be..74bd89f1 100644 --- a/BO4E/COM/Bankverbindung.cs +++ b/BO4E/COM/Bankverbindung.cs @@ -1,5 +1,4 @@ using System.Text.Json.Serialization; -using BO4E.ENUM; using BO4E.meta; using Newtonsoft.Json; using ProtoBuf; diff --git a/BO4E/COM/COM.cs b/BO4E/COM/COM.cs index 4572cab9..2cfd5001 100644 --- a/BO4E/COM/COM.cs +++ b/BO4E/COM/COM.cs @@ -119,7 +119,11 @@ protected DateTime _TimeStamp /// true iff all elements of this COM and COM b are equal; false otherwise public bool Equals(COM b) { - if (b == null || b.GetType() != GetType()) return false; + if (b == null || b.GetType() != GetType()) + { + return false; + } + return JsonConvert.SerializeObject(this) == JsonConvert.SerializeObject(b); } diff --git a/BO4E/COM/ExterneReferenz.cs b/BO4E/COM/ExterneReferenz.cs index 9e7b2693..27ca31f0 100644 --- a/BO4E/COM/ExterneReferenz.cs +++ b/BO4E/COM/ExterneReferenz.cs @@ -62,7 +62,11 @@ internal static class ExterneReferenzExtensions public static bool TryGetExterneReferenz(this ICollection? extReferences, string? extRefName, out string? extRefWert) { - if (extRefName == null) throw new ArgumentNullException(nameof(extRefName)); + if (extRefName == null) + { + throw new ArgumentNullException(nameof(extRefName)); + } + if (extReferences == null) { extRefWert = null; @@ -93,13 +97,21 @@ public static bool TryGetExterneReferenz(this ICollection? extR public static List SetExterneReferenz(this List? extReferences, ExterneReferenz extRef, bool overwriteExisting = false) { - if (extRef == null) throw new ArgumentNullException(nameof(extRef)); + if (extRef == null) + { + throw new ArgumentNullException(nameof(extRef)); + } + if (!extRef.IsValid()) + { throw new ArgumentException( $"The external reference with {nameof(extRef.ExRefName)}='{extRef.ExRefName}' and {nameof(extRef.ExRefWert)}='{extRef.ExRefWert}' you tried to add is invalid.", nameof(extRef)); - if (extReferences == null) return [extRef]; - + } + if (extReferences == null) + { + return new List { extRef }; + } if (extReferences.Any() && extReferences.TryGetExterneReferenz(extRef.ExRefName, out var existingRefWert)) { if (overwriteExisting) @@ -109,8 +121,10 @@ public static List SetExterneReferenz(this List(einheitString, true, out var einheit)) + { throw new ArgumentException($"'{einheitString}' is not a valid Mengeneinheit"); + } Einheit = einheit; } diff --git a/BO4E/UserPropertiesDataContractResolver.cs b/BO4E/UserPropertiesDataContractResolver.cs index 07cbc032..2b2fb7c4 100644 --- a/BO4E/UserPropertiesDataContractResolver.cs +++ b/BO4E/UserPropertiesDataContractResolver.cs @@ -32,12 +32,23 @@ public UserPropertiesDataContractResolver(HashSet userPropertiesWhiteLis public override JsonContract ResolveContract(Type type) { var contract = base.ResolveContract(type); - if (!(contract is JsonObjectContract objContract)) return contract; - if (objContract.ExtensionDataSetter == null) return contract; + if (!(contract is JsonObjectContract objContract)) + { + return contract; + } + + if (objContract.ExtensionDataSetter == null) + { + return contract; + } + var oldSetter = objContract.ExtensionDataSetter; objContract.ExtensionDataSetter = (o, key, value) => { - if (_allowList.Contains(key)) oldSetter(o, key, value); + if (_allowList.Contains(key)) + { + oldSetter(o, key, value); + } }; return contract; } diff --git a/BO4E/UserPropertiesExtensions.cs b/BO4E/UserPropertiesExtensions.cs index 5a0c98ac..23bc78cc 100644 --- a/BO4E/UserPropertiesExtensions.cs +++ b/BO4E/UserPropertiesExtensions.cs @@ -34,7 +34,11 @@ public static bool TryGetUserProperty(this TParent paren out TUserProperty value) where TParent : IUserProperties { var up = parent.UserProperties; - if (string.IsNullOrWhiteSpace(userPropertyKey)) throw new ArgumentNullException(nameof(userPropertyKey)); + if (string.IsNullOrWhiteSpace(userPropertyKey)) + { + throw new ArgumentNullException(nameof(userPropertyKey)); + } + if (up != null && up.TryGetValue(userPropertyKey, out var upToken)) { switch (upToken) @@ -106,7 +110,9 @@ public static TUserProperty GetUserProperty(this TParent TUserProperty defaultValue) where TParent : IUserProperties { if (parent != null && parent.TryGetUserProperty(userPropertyKey, out TUserProperty actualValue)) + { return actualValue; + } return defaultValue; } @@ -128,9 +134,16 @@ public static void SetUserProperty(this TParent parent, TUserProperty value) where TParent : IUserProperties { // if user properties don't exist already, create them - if (parent.UserProperties == null) parent.UserProperties = new Dictionary(); + if (parent.UserProperties == null) + { + parent.UserProperties = new Dictionary(); + } + // if there is already an value for the property, delete ist first - if (parent.UserProperties.ContainsKey(userPropertyKey)) parent.UserProperties.Remove(userPropertyKey); + if (parent.UserProperties.ContainsKey(userPropertyKey)) + { + parent.UserProperties.Remove(userPropertyKey); + } // set the value parent.UserProperties.Add(userPropertyKey, value); @@ -147,9 +160,16 @@ public static void RemoveUserProperty(this TParent parent, string userP where TParent : IUserProperties { // if user properties don't exist we cannot remove anything - if (parent.UserProperties == null) return; + if (parent.UserProperties == null) + { + return; + } + // if there is already an value for the property, delete it - if (parent.UserProperties.ContainsKey(userPropertyKey)) parent.UserProperties.Remove(userPropertyKey); + if (parent.UserProperties.ContainsKey(userPropertyKey)) + { + parent.UserProperties.Remove(userPropertyKey); + } } /// @@ -176,13 +196,19 @@ public static void RemoveUserProperty(this TParent parent, string userP public static bool UserPropertyEquals(this TParent parent, string userPropertyKey, TUserProperty other, bool ignoreWrongType = true) where TParent : IUserProperties { - if (parent.UserProperties == null) return false; + if (parent.UserProperties == null) + { + return false; + } try { return parent.EvaluateUserProperty(userPropertyKey, value => { - if (value == null && other != null) return false; + if (value == null && other != null) + { + return false; + } return value == null || value.Equals(other); } @@ -210,7 +236,11 @@ public static TEvaluationResult EvaluateUserProperty(userPropertyKey, out var value) ? evaluation(value) : default; @@ -234,12 +264,18 @@ public static TEvaluationResult EvaluateUserProperty(this TParent parent, string flagKey, bool? flagValue = true) where TParent : class, IUserProperties { - if (string.IsNullOrWhiteSpace(flagKey)) throw new ArgumentNullException(nameof(flagKey)); + if (string.IsNullOrWhiteSpace(flagKey)) + { + throw new ArgumentNullException(nameof(flagKey)); + } if (parent.UserProperties == null) { parent.UserProperties = new Dictionary(); - if (!flagValue.HasValue) return false; + if (!flagValue.HasValue) + { + return false; + } } else if (flagValue.HasValue && flagValue.Value == parent.HasFlagSet(flagKey)) { @@ -248,7 +284,10 @@ public static bool SetFlag(this TParent parent, string flagKey, bool? f if (!flagValue.HasValue) { - if (!parent.UserProperties.ContainsKey(flagKey)) return false; + if (!parent.UserProperties.ContainsKey(flagKey)) + { + return false; + } parent.UserProperties.Remove(flagKey); return true; @@ -285,7 +324,10 @@ public static bool SetFlag(this TParent parent, string flagKey, bool? f public static bool HasFlagSet(this TParent parent, string flagKey) where TParent : class, IUserProperties { - if (string.IsNullOrWhiteSpace(flagKey)) throw new ArgumentNullException(nameof(flagKey)); + if (string.IsNullOrWhiteSpace(flagKey)) + { + throw new ArgumentNullException(nameof(flagKey)); + } try { diff --git a/BO4E/meta/Bo4eUri.cs b/BO4E/meta/Bo4eUri.cs index 4f4f9602..f373f714 100644 --- a/BO4E/meta/Bo4eUri.cs +++ b/BO4E/meta/Bo4eUri.cs @@ -34,14 +34,24 @@ public class Bo4eUri : Uri /// URI string to be processed public Bo4eUri(string uri) : base(uri) { - if (uri == null) throw new ArgumentNullException(nameof(uri), "URI string must not be null."); + if (uri == null) + { + throw new ArgumentNullException(nameof(uri), "URI string must not be null."); + } + /*if (!base.IsWellFormedOriginalString()) { throw new ArgumentException($"The URI {uri} is not well formed."); }*/ if (Scheme + "://" != Bo4EScheme) + { throw new ArgumentException($"The scheme '{Scheme}' in {uri} is not valid. Expected '{Bo4EScheme}://'"); - if (GetBoName() == null) throw new ArgumentException($"There is no Business Object of type '{Host}'."); + } + + if (GetBoName() == null) + { + throw new ArgumentException($"There is no Business Object of type '{Host}'."); + } } /// @@ -124,7 +134,11 @@ public static bool IsValid(string uri) /// on the returned object. public static Bo4eUri GetUri(BusinessObject bo, bool includeUserProperties = false) { - if (bo == null) throw new ArgumentNullException(nameof(bo), "Business Object must not be null."); + if (bo == null) + { + throw new ArgumentNullException(nameof(bo), "Business Object must not be null."); + } + var baseUriString = Bo4EScheme + bo.GetType().Name + "/"; var baseUri = new Bo4eUri(baseUriString); var relativeUriBuilder = new StringBuilder(); @@ -136,9 +150,13 @@ public static Bo4eUri GetUri(BusinessObject bo, bool includeUserProperties = fal if (keyProp.GetValue(bo) is string) { if (keyProp.GetValue(bo).ToString() == string.Empty) + { relativeUriBuilder.Append(NullKeyPlaceholder + "/"); + } else + { relativeUriBuilder.Append(keyProp.GetValue(bo) + "/"); + } } else if (keyProp.GetValue(bo) is int) { @@ -188,7 +206,11 @@ public static Bo4eUri GetUri(BusinessObject bo, bool includeUserProperties = fal private static IList GetKeyFields(BusinessObject bo) { - if (bo == null) throw new ArgumentNullException(nameof(bo), "Business Object must not be null."); + if (bo == null) + { + throw new ArgumentNullException(nameof(bo), "Business Object must not be null."); + } + return GetKeyProperties(bo.GetType()); } @@ -199,8 +221,11 @@ private static IList GetKeyProperties(Type boType) .OrderBy(af => af.GetCustomAttribute()?.Order) .ToArray(); if (allKeyProperties.Length == 0) + { throw new NotImplementedException( $"Business Object {boType.Name} has no [BoKey] defined => can't create URI."); + } + IList ownKeyProps = new List(); var ignoreInheritedFields = false; // default foreach (var keyProp in allKeyProperties) @@ -211,7 +236,10 @@ private static IList GetKeyProperties(Type boType) ownKeyProps.Add(keyProp); } - if (ignoreInheritedFields) return ownKeyProps; + if (ignoreInheritedFields) + { + return ownKeyProps; + } return allKeyProperties.ToList(); } @@ -236,7 +264,10 @@ public JObject GetQueryObject(Type boType = null, int i = 0) // Method is called recursively if sub-BOs are part of the key. To distinguish between // top level and recursive calls, check the value of boType and i. if (boType == null) // top level call + { boType = GetBoType(); + } + result.Add("boTyp", boType.Name.ToUpper()); // The order of the FieldInfos is the same as the JsonProperty.Order attribute of the @@ -247,7 +278,11 @@ public JObject GetQueryObject(Type boType = null, int i = 0) { var keyPropName = keyProp.Name; var jpa = keyProp.GetCustomAttribute(); - if (jpa?.PropertyName != null) keyPropName = jpa.PropertyName; + if (jpa?.PropertyName != null) + { + keyPropName = jpa.PropertyName; + } + string keyValue; try { @@ -258,7 +293,11 @@ public JObject GetQueryObject(Type boType = null, int i = 0) break; // no arguments at all. } - if (keyValue.EndsWith("/")) keyValue = keyValue.Substring(0, keyValue.Length - 1); + if (keyValue.EndsWith("/")) + { + keyValue = keyValue.Substring(0, keyValue.Length - 1); + } + if (keyProp.PropertyType == typeof(string)) { result.Add(keyPropName, keyValue == NullKeyPlaceholder ? null : keyValue); @@ -272,10 +311,14 @@ public JObject GetQueryObject(Type boType = null, int i = 0) } else*/ if (int.TryParse(keyValue, out var keyValueInt)) + { result.Add(keyPropName, keyValueInt); + } else + { throw new ArgumentException( $"Key segment {keyPropName} could not be parsed as int although an integer type was expected!"); + } } else if (keyProp.PropertyType.IsSubclassOf(typeof(BusinessObject))) { @@ -297,7 +340,9 @@ public JObject GetQueryObject(Type boType = null, int i = 0) var filter = query.Get("filter"); foreach (Match match in FilterAndPattern.Matches(filter)) if (boProps.Contains(match.Groups["key"].Value, StringComparer.OrdinalIgnoreCase)) + { result[match.Groups["key"].Value] = match.Groups["value"].Value; + } } return result; @@ -316,7 +361,11 @@ public Bo4eUri AddFilter(IDictionary filterObject) filterString = filterObject .Where(kvp => kvp.Value != null && boFields.Contains(kvp.Key, StringComparer.OrdinalIgnoreCase)) .Aggregate(filterString, (current, kvp) => current + $"{andString}{kvp.Key} eq '{kvp.Value}'"); - if (filterString.StartsWith(andString)) filterString = filterString.Substring(andString.Length); + if (filterString.StartsWith(andString)) + { + filterString = filterString.Substring(andString.Length); + } + query.Add("filter", filterString); var ub = new UriBuilder(this) { @@ -341,7 +390,11 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - if (value is string @string) return new Bo4eUri(@string); + if (value is string @string) + { + return new Bo4eUri(@string); + } + return base.ConvertFrom(context, culture, value); } } diff --git a/BO4E/meta/BusinessObjectSerializationBinder.cs b/BO4E/meta/BusinessObjectSerializationBinder.cs index 59268034..22a81a61 100644 --- a/BO4E/meta/BusinessObjectSerializationBinder.cs +++ b/BO4E/meta/BusinessObjectSerializationBinder.cs @@ -27,13 +27,13 @@ static BusinessObjectSerializationBinder() public static IList BusinessObjectAndCOMTypes { get; } /// - public Type BindToType(string assemblyName, string typeName) + public Type BindToType(string? assemblyName, string typeName) { return BusinessObjectAndCOMTypes.SingleOrDefault(t => t.Name == typeName); } /// - public void BindToName(Type serializedType, out string assemblyName, out string typeName) + public void BindToName(Type serializedType, out string? assemblyName, out string typeName) { assemblyName = null; typeName = serializedType.Name; diff --git a/BO4E/meta/CentralEuropeStandardTime.cs b/BO4E/meta/CentralEuropeStandardTime.cs index b98a01f0..f07c21ea 100644 --- a/BO4E/meta/CentralEuropeStandardTime.cs +++ b/BO4E/meta/CentralEuropeStandardTime.cs @@ -10,10 +10,15 @@ namespace BO4E.meta /// public abstract class CentralEuropeStandardTime { + private static readonly TimeZoneInfo? _CentralEuropeStandardTimezoneInfo; + /// /// Central Europe Standard Time as hard coded default time. Public to be used elsewhere ;) /// - public static readonly TimeZoneInfo CentralEuropeStandardTimezoneInfo; + public static TimeZoneInfo CentralEuropeStandardTimezoneInfo + { + get => _CentralEuropeStandardTimezoneInfo!; + } static CentralEuropeStandardTime() { @@ -22,14 +27,14 @@ static CentralEuropeStandardTime() const string resourceFileName = "BO4E.meta.CentralEuropeStandardTime.json"; using var stream = assembly.GetManifestResourceStream(resourceFileName); if (stream == null) - // this should never ever happen - throw new FileNotFoundException($"The file resource {resourceFileName} was not found."); - using (var jsonReader = new StreamReader(stream)) + // this should never ever happen { - var jsonString = jsonReader.ReadToEnd(); - //Console.WriteLine(jsonString); - CentralEuropeStandardTimezoneInfo = JsonConvert.DeserializeObject(jsonString); + throw new FileNotFoundException($"The file resource {resourceFileName} was not found."); } + using var jsonReader = new StreamReader(stream); + var jsonString = jsonReader.ReadToEnd(); + //Console.WriteLine(jsonString); + _CentralEuropeStandardTimezoneInfo = JsonConvert.DeserializeObject(jsonString); } /// @@ -39,4 +44,4 @@ static CentralEuropeStandardTime() // ReSharper disable once InconsistentNaming public static TimeZoneInfo CENTRAL_EUROPE_STANDARD_TIME => CentralEuropeStandardTimezoneInfo; } -} \ No newline at end of file +} diff --git a/BO4E/meta/DataCategoryAttribute.cs b/BO4E/meta/DataCategoryAttribute.cs index 88c83e4f..b9f88cfb 100644 --- a/BO4E/meta/DataCategoryAttribute.cs +++ b/BO4E/meta/DataCategoryAttribute.cs @@ -30,7 +30,11 @@ public class DataCategoryAttribute : Attribute /// public DataCategoryAttribute(params object[] enums) { - if (enums.Any(r => r.GetType().BaseType != typeof(Enum))) throw new ArgumentException("enums"); + if (enums.Any(r => r.GetType().BaseType != typeof(Enum))) + { + throw new ArgumentException("enums"); + } + Mapping = new HashSet(); foreach (Enum e in enums) Mapping.Add(e); } diff --git a/BO4E/meta/LenientConverters/LenientBo4eUriConverter.cs b/BO4E/meta/LenientConverters/LenientBo4eUriConverter.cs index 767b2309..bd569d53 100644 --- a/BO4E/meta/LenientConverters/LenientBo4eUriConverter.cs +++ b/BO4E/meta/LenientConverters/LenientBo4eUriConverter.cs @@ -22,9 +22,17 @@ public override bool CanConvert(Type objectType) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - if (reader.Value == null) return null; + if (reader.Value == null) + { + return null; + } + var rawString = (string)reader.Value; - if (rawString.Trim() == string.Empty) return null; + if (rawString.Trim() == string.Empty) + { + return null; + } + return new Bo4eUri(rawString); } diff --git a/BO4E/meta/LenientConverters/LenientDictionaryConverter.cs b/BO4E/meta/LenientConverters/LenientDictionaryConverter.cs index a6cbd69e..25364910 100644 --- a/BO4E/meta/LenientConverters/LenientDictionaryConverter.cs +++ b/BO4E/meta/LenientConverters/LenientDictionaryConverter.cs @@ -32,19 +32,29 @@ public override Dictionary Read(ref Utf8JsonReader reader, Type JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) + { throw new JsonException($"JsonTokenType was of type {reader.TokenType}, only objects are supported"); + } var dictionary = new Dictionary(); while (reader.Read()) { - if (reader.TokenType == JsonTokenType.EndObject) return dictionary; + if (reader.TokenType == JsonTokenType.EndObject) + { + return dictionary; + } if (reader.TokenType != JsonTokenType.PropertyName) + { throw new JsonException("JsonTokenType was not PropertyName"); + } var propertyName = reader.GetString(); - if (string.IsNullOrWhiteSpace(propertyName)) throw new JsonException("Failed to get property name"); + if (string.IsNullOrWhiteSpace(propertyName)) + { + throw new JsonException("Failed to get property name"); + } reader.Read(); @@ -72,7 +82,10 @@ public override void Write(Utf8JsonWriter writer, Dictionary val private static void HandleValue(Utf8JsonWriter writer, string key, object objectValue) { - if (key != null) writer.WritePropertyName(key); + if (key != null) + { + writer.WritePropertyName(key); + } switch (objectValue) { @@ -126,7 +139,11 @@ private object ExtractValue(ref Utf8JsonReader reader, JsonSerializerOptions opt switch (reader.TokenType) { case JsonTokenType.String: - if (reader.TryGetDateTime(out var date)) return date; + if (reader.TryGetDateTime(out var date)) + { + return date; + } + return reader.GetString(); case JsonTokenType.False: return false; @@ -135,7 +152,11 @@ private object ExtractValue(ref Utf8JsonReader reader, JsonSerializerOptions opt case JsonTokenType.Null: return null; case JsonTokenType.Number: - if (reader.TryGetInt64(out var result)) return result; + if (reader.TryGetInt64(out var result)) + { + return result; + } + return reader.GetDecimal(); case JsonTokenType.StartObject: return Read(ref reader, null, options); diff --git a/BO4E/meta/LenientConverters/LenientEnumListConverter.cs b/BO4E/meta/LenientConverters/LenientEnumListConverter.cs index 136c7c3e..afea618d 100644 --- a/BO4E/meta/LenientConverters/LenientEnumListConverter.cs +++ b/BO4E/meta/LenientConverters/LenientEnumListConverter.cs @@ -18,8 +18,16 @@ public class LenientEnumListConverter : JsonConverter /// public override bool CanConvert(Type objectType) { - if (!objectType.IsGenericType) return false; - if (objectType.GetGenericTypeDefinition() != typeof(List<>)) return false; + if (!objectType.IsGenericType) + { + return false; + } + + if (objectType.GetGenericTypeDefinition() != typeof(List<>)) + { + return false; + } + var expectedListElementType = objectType.GetGenericArguments()[0]; return expectedListElementType.ToString().StartsWith("BO4E.ENUM"); } @@ -33,7 +41,11 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist var expectedListElementType = objectType.GetGenericArguments()[0]; var expectedListType = typeof(List<>).MakeGenericType(expectedListElementType); var result = Activator.CreateInstance(expectedListType); - if (rawList == null || rawList.Count == 0) return result; + if (rawList == null || rawList.Count == 0) + { + return result; + } + // First try to parse the List normally, in case it's formatted as expected foreach (var rawItem in rawList) if (rawItem is string && Enum.IsDefined(expectedListElementType, rawItem.ToString())) diff --git a/BO4E/meta/LenientConverters/LenientJsonSerializerOptionsGenerator.cs b/BO4E/meta/LenientConverters/LenientJsonSerializerOptionsGenerator.cs index 2fb3e8d1..125bd266 100644 --- a/BO4E/meta/LenientConverters/LenientJsonSerializerOptionsGenerator.cs +++ b/BO4E/meta/LenientConverters/LenientJsonSerializerOptionsGenerator.cs @@ -47,7 +47,8 @@ public static JsonSerializerOptions GetJsonSerializerOptions(this LenientParsing foreach (LenientParsing lp in Enum.GetValues(typeof(LenientParsing))) if (lenient.HasFlag(lp)) - // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + { switch (lp) { case LenientParsing.DATE_TIME: @@ -85,6 +86,7 @@ public static JsonSerializerOptions GetJsonSerializerOptions(this LenientParsing // no default case because NONE and MOST_LENIENT do not come up with more converters } + } //IContractResolver contractResolver; //if (userPropertiesWhiteList.Count > 0) //{ diff --git a/BO4E/meta/LenientConverters/LenientJsonSerializerSettingsGenerator.cs b/BO4E/meta/LenientConverters/LenientJsonSerializerSettingsGenerator.cs index cc6865fd..75b4a705 100644 --- a/BO4E/meta/LenientConverters/LenientJsonSerializerSettingsGenerator.cs +++ b/BO4E/meta/LenientConverters/LenientJsonSerializerSettingsGenerator.cs @@ -34,7 +34,8 @@ public static JsonSerializerSettings GetJsonSerializerSettings(this LenientParsi var converters = new List(); foreach (LenientParsing lp in Enum.GetValues(typeof(LenientParsing))) if (lenient.HasFlag(lp)) - // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + { switch (lp) { case LenientParsing.DATE_TIME: @@ -60,6 +61,7 @@ public static JsonSerializerSettings GetJsonSerializerSettings(this LenientParsi // no default case because NONE and MOST_LENIENT do not come up with more converters } + } IContractResolver contractResolver = userPropertiesWhiteList.Count > 0 ? new UserPropertiesDataContractResolver(userPropertiesWhiteList) diff --git a/BO4E/meta/LenientConverters/LenientStringToIntConverter.cs b/BO4E/meta/LenientConverters/LenientStringToIntConverter.cs index 1a03df2f..2d7a0385 100644 --- a/BO4E/meta/LenientConverters/LenientStringToIntConverter.cs +++ b/BO4E/meta/LenientConverters/LenientStringToIntConverter.cs @@ -24,11 +24,21 @@ public override bool CanConvert(Type objectType) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - if (reader.Value == null) return null; + if (reader.Value == null) + { + return null; + } + var numeric = new string(reader.Value.ToString().Where(char.IsDigit).ToArray()); - if (int.TryParse(numeric, out var intValue)) return intValue; + if (int.TryParse(numeric, out var intValue)) + { + return intValue; + } - if (objectType == typeof(int?)) return null; + if (objectType == typeof(int?)) + { + return null; + } return 0; } diff --git a/BO4E/meta/LenientConverters/LenientSystemTextJsonEnumListConverter.cs b/BO4E/meta/LenientConverters/LenientSystemTextJsonEnumListConverter.cs index c7c76c15..d25d70d9 100644 --- a/BO4E/meta/LenientConverters/LenientSystemTextJsonEnumListConverter.cs +++ b/BO4E/meta/LenientConverters/LenientSystemTextJsonEnumListConverter.cs @@ -19,9 +19,15 @@ public class LenientSystemTextJsonEnumListConverter : JsonConverterFactory /// public override bool CanConvert(Type typeToConvert) { - if (!typeToConvert.IsGenericType) return false; + if (!typeToConvert.IsGenericType) + { + return false; + } - if (typeToConvert.GetGenericTypeDefinition() != typeof(List<>)) return false; + if (typeToConvert.GetGenericTypeDefinition() != typeof(List<>)) + { + return false; + } var expectedListElementType = typeToConvert.GetGenericArguments()[0]; return expectedListElementType.ToString().StartsWith("BO4E.ENUM"); @@ -55,9 +61,15 @@ public class LenientSystemTextJsonEnumListConverter : JsonConverter /// public override bool CanConvert(Type objectType) { - if (!objectType.IsGenericType) return false; + if (!objectType.IsGenericType) + { + return false; + } - if (objectType.GetGenericTypeDefinition() != typeof(List<>)) return false; + if (objectType.GetGenericTypeDefinition() != typeof(List<>)) + { + return false; + } var expectedListElementType = objectType.GetGenericArguments()[0]; return expectedListElementType.ToString().StartsWith("BO4E.ENUM"); @@ -75,7 +87,10 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial var expectedListElementType = typeToConvert.GetGenericArguments()[0]; var expectedListType = typeof(List<>).MakeGenericType(expectedListElementType); var result = Activator.CreateInstance(expectedListType); - if (rawList == null || rawList.Count == 0) return result as T; + if (rawList == null || rawList.Count == 0) + { + return result as T; + } // First try to parse the List normally, in case it's formatted as expected foreach (var rawItem in rawList) diff --git a/BO4E/meta/LenientConverters/LenientSystemTextJsonStringToIntConverter.cs b/BO4E/meta/LenientConverters/LenientSystemTextJsonStringToIntConverter.cs index bf0f2d90..db8f2408 100644 --- a/BO4E/meta/LenientConverters/LenientSystemTextJsonStringToIntConverter.cs +++ b/BO4E/meta/LenientConverters/LenientSystemTextJsonStringToIntConverter.cs @@ -31,7 +31,10 @@ public class LenientSystemTextJsonStringToIntConverter : JsonConverter case JsonTokenType.String: { var numeric = new string(reader.GetString().Where(char.IsDigit).ToArray()); - if (int.TryParse(numeric, out var intValue)) return intValue; + if (int.TryParse(numeric, out var intValue)) + { + return intValue; + } break; } diff --git a/BO4E/meta/LenientConverters/NullableEnumConverter.cs b/BO4E/meta/LenientConverters/NullableEnumConverter.cs index 9549053a..fcd7e2b7 100644 --- a/BO4E/meta/LenientConverters/NullableEnumConverter.cs +++ b/BO4E/meta/LenientConverters/NullableEnumConverter.cs @@ -17,7 +17,10 @@ public class StringNullableEnumConverter : JsonConverterFactory public override bool CanConvert(Type typeToConvert) { if (Nullable.GetUnderlyingType(typeToConvert) == null) + { return typeToConvert.ToString().StartsWith("BO4E.ENUM"); + } + return Nullable.GetUnderlyingType(typeToConvert).ToString().StartsWith("BO4E.ENUM"); } @@ -62,12 +65,17 @@ public StringNullableEnumConverter() : this(null) public StringNullableEnumConverter(JsonSerializerOptions options) { // for performance, use the existing converter if available - if (options != null) _converter = (JsonConverter)options.GetConverter(typeof(T)); + if (options != null) + { + _converter = (JsonConverter)options.GetConverter(typeof(T)); + } // cache the underlying type _underlyingType = Nullable.GetUnderlyingType(typeof(T)); if (_underlyingType == null) + { _underlyingType = typeof(T); + } } /// @@ -96,13 +104,24 @@ public override bool CanConvert(Type typeToConvert) public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (_converter != null) return _converter.Read(ref reader, _underlyingType, options); + if (_converter != null) + { + return _converter.Read(ref reader, _underlyingType, options); + } + if (reader.TokenType == JsonTokenType.Null) + { return default; + } + if (reader.TokenType == JsonTokenType.String) { var value = reader.GetString(); - if (string.IsNullOrEmpty(value)) return default; + if (string.IsNullOrEmpty(value)) + { + return default; + } + // for performance, parse with ignoreCase:false first. try { diff --git a/BO4E/meta/MultiLangResolver.cs b/BO4E/meta/MultiLangResolver.cs index 9b811eda..ef17ea91 100644 --- a/BO4E/meta/MultiLangResolver.cs +++ b/BO4E/meta/MultiLangResolver.cs @@ -39,13 +39,17 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ // See if there is a [FieldName] attribute applied to the property // for the requested language - var att = prop.AttributeProvider.GetAttributes(true) + var att = prop.AttributeProvider?.GetAttributes(true) .OfType() .FirstOrDefault(a => a.Language == _language); // if so, change the property name to the one from the attribute - if (att != null) prop.PropertyName = att.Text; + if (att != null) + { + prop.PropertyName = att.Text; + } + return prop; } } -} \ No newline at end of file +} diff --git a/BO4E/meta/NonOfficialAttribute.cs b/BO4E/meta/NonOfficialAttribute.cs index 2552dc30..2fbfe74d 100644 --- a/BO4E/meta/NonOfficialAttribute.cs +++ b/BO4E/meta/NonOfficialAttribute.cs @@ -31,8 +31,11 @@ public class NonOfficialAttribute : Attribute public NonOfficialAttribute(params object[] enums) { if (enums.Any(r => r.GetType().BaseType != typeof(Enum) || r.GetType() != typeof(NonOfficialCategory))) + { throw new ArgumentException($"You must only pass enums of type {nameof(NonOfficialCategory)}", nameof(enums)); + } + Mapping = new HashSet(); foreach (Enum e in enums) Mapping.Add(e); } diff --git a/BO4ETestProject/TestBO4E.csproj b/BO4ETestProject/TestBO4E.csproj index 8941eee1..150d73c0 100644 --- a/BO4ETestProject/TestBO4E.csproj +++ b/BO4ETestProject/TestBO4E.csproj @@ -22,8 +22,9 @@ - - + + + diff --git a/BO4ETestProject/TestBo4eUri.cs b/BO4ETestProject/TestBo4eUri.cs index 75e22e36..2687b99f 100644 --- a/BO4ETestProject/TestBo4eUri.cs +++ b/BO4ETestProject/TestBo4eUri.cs @@ -85,7 +85,9 @@ public void TestUriConstructionAndKeyDeconstruction() .Replace("\n", "") .Replace("\r", "") .Replace(" ", "") == "{\"vorname\":[null,null]}") + { continue; + } Assert.IsNull(patch, patch.ToString()); } diff --git a/BO4ETestProject/TestBoMapperSystemText.cs b/BO4ETestProject/TestBoMapperSystemText.cs index 2705abf6..41e818cc 100644 --- a/BO4ETestProject/TestBoMapperSystemText.cs +++ b/BO4ETestProject/TestBoMapperSystemText.cs @@ -37,16 +37,26 @@ public void TestBoMapping() $"You have to specify the object name in test file {file}"); var lenients = LenientParsing.STRICT; // default if (json.RootElement.TryGetProperty("lenientDateTime", out var boolElement) && boolElement.GetBoolean()) + { lenients |= LenientParsing.DATE_TIME; + } if (json.RootElement.TryGetProperty("lenientEnumList", out var listElement) && listElement.GetBoolean()) + { lenients |= LenientParsing.ENUM_LIST; + } if (json.RootElement.TryGetProperty("lenientBo4eUri", out var urlElement) && urlElement.GetBoolean()) + { lenients |= LenientParsing.BO4_E_URI; + } if (json.RootElement.TryGetProperty("lenientStringToInt", out var intElement) && - intElement.GetBoolean()) lenients |= LenientParsing.STRING_TO_INT; + intElement.GetBoolean()) + { + lenients |= LenientParsing.STRING_TO_INT; + } + BusinessObject bo = null; try { @@ -61,7 +71,11 @@ public void TestBoMapping() } var regularOutputString = JsonSerializer.Serialize(bo, bo.GetType()); - if (bo.GetType() == typeof(Rechnung)) continue; // todo: fix this! + if (bo.GetType() == typeof(Rechnung)) + { + continue; // todo: fix this! + } + /*if (json["input"]["boTyp"] != null) { //BusinessObject bo2 = BoMapper.MapObject((JObject)json["input"], lenients); @@ -83,10 +97,16 @@ public void TestBoMapping() }*/ HashSet whitelist; if (json.RootElement.TryGetProperty("userPropWhiteList", out var whiteList)) + { whitelist = new HashSet(JsonSerializer.Deserialize>(whiteList.GetRawText())); + } else + { whitelist = new HashSet(); + } + if (lenients == LenientParsing.STRICT) + { foreach (LenientParsing lenient in Enum.GetValues(typeof(LenientParsing))) { // strict mappings must also work with lenient mapping @@ -112,6 +132,7 @@ public void TestBoMapping() // Assert.AreEqual(regularOutputString, dateLenietOutputString); //} } + } } } @@ -260,9 +281,13 @@ public DateTime EventOccured set { if (value == DateTime.MinValue) + { eventOccured = new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Utc); + } else + { eventOccured = value; + } } } } diff --git a/BO4ETestProject/TestHelper/JsonHelper.cs b/BO4ETestProject/TestHelper/JsonHelper.cs index 02c7368d..0d87a605 100644 --- a/BO4ETestProject/TestHelper/JsonHelper.cs +++ b/BO4ETestProject/TestHelper/JsonHelper.cs @@ -13,8 +13,15 @@ public static JToken RemoveEmptyChildren(JToken token) foreach (var prop in token.Children()) { var child = prop.Value; - if (child.HasValues) child = RemoveEmptyChildren(child); - if (!IsEmpty(child)) copy.Add(prop.Name, child); + if (child.HasValues) + { + child = RemoveEmptyChildren(child); + } + + if (!IsEmpty(child)) + { + copy.Add(prop.Name, child); + } } return copy; @@ -25,8 +32,15 @@ public static JToken RemoveEmptyChildren(JToken token) foreach (var item in token.Children()) { var child = item; - if (child.HasValues) child = RemoveEmptyChildren(child); - if (!IsEmpty(child)) copy.Add(child); + if (child.HasValues) + { + child = RemoveEmptyChildren(child); + } + + if (!IsEmpty(child)) + { + copy.Add(child); + } } return copy; diff --git a/BO4ETestProject/TestJsonOrder.cs b/BO4ETestProject/TestJsonOrder.cs index a5c91748..45ab713b 100644 --- a/BO4ETestProject/TestJsonOrder.cs +++ b/BO4ETestProject/TestJsonOrder.cs @@ -125,7 +125,10 @@ public void TestJsonOrderAttributesOfBO() protected static void TestOrderFromAbstract(Type abstractBaseType) { if (!abstractBaseType.IsAbstract) + { throw new ArgumentException($"The type {abstractBaseType} is not abstract", nameof(abstractBaseType)); + } + var relevantTypes = typeof(BusinessObject).Assembly.GetTypes().Where(abstractBaseType.IsAssignableFrom); foreach (var relevantType in relevantTypes.Where(t => !IgnoreOrderTypes.Contains(t) && !t.Name.Contains("Marktrolle"))) { diff --git a/BO4ETestProject/TestNullable.cs b/BO4ETestProject/TestNullable.cs index 7415cf13..fc8a30a7 100644 --- a/BO4ETestProject/TestNullable.cs +++ b/BO4ETestProject/TestNullable.cs @@ -34,7 +34,10 @@ public void TestNullableAttributesFromBO() protected void TestNullableAttributesFromAbstract(Type abstractBaseType) { if (!abstractBaseType.IsAbstract) + { throw new ArgumentException($"The type {abstractBaseType} is not abstract", nameof(abstractBaseType)); + } + var relevantTypes = typeof(BusinessObject).Assembly.GetTypes() .Where(t => abstractBaseType.IsAssignableFrom(t)); foreach (var relevantType in relevantTypes) diff --git a/BO4ETestProject/TestProtobufAttributes.cs b/BO4ETestProject/TestProtobufAttributes.cs index d6c2002f..dd2c7589 100644 --- a/BO4ETestProject/TestProtobufAttributes.cs +++ b/BO4ETestProject/TestProtobufAttributes.cs @@ -54,9 +54,12 @@ public void TestUniqueProtoIncludeTagsBO() protected void TestUniqueProtobufMemberIdAbstract(Type abstractType) { if (!abstractType.IsAbstract) + { throw new ArgumentException($"The type {abstractType} is not abstract", nameof(abstractType)); + } + foreach (var type in - typeof(BusinessObject).Assembly.GetTypes().Where(t => abstractType.IsAssignableFrom(t))) + typeof(BusinessObject).Assembly.GetTypes().Where(t => abstractType.IsAssignableFrom(t))) TestProtobufType(type, type.BaseType == abstractType || type == abstractType); } @@ -99,9 +102,11 @@ protected void TestProtobufType(Type type, bool isDirectBase) try { if (isDirectBase) + { Assert.AreEqual(allFields.Length, fieldsWithProtoMemberAttribute.Count(), $"Missing protobuf attributes for {type} for: " + string.Join(", ", allFields.Except(fieldsWithProtoMemberAttribute))); + } } catch (ArgumentOutOfRangeException aoore) when (aoore.ParamName == "tag") { @@ -130,7 +135,10 @@ protected void TestProtobufType(Type type, bool isDirectBase) protected void TestProtobufDateTimeWorkaround(Type abstractBaseType) { if (!abstractBaseType.IsAbstract) + { throw new ArgumentException($"The type {abstractBaseType} is not abstract", nameof(abstractBaseType)); + } + var relevantTypes = typeof(BusinessObject).Assembly.GetTypes() .Where(t => abstractBaseType.IsAssignableFrom(t)); foreach (var relevantType in relevantTypes) @@ -171,7 +179,10 @@ protected void TestProtobufDateTimeWorkaround(Type abstractBaseType) protected void TestUniqueProtoIncludeTagAbstract(Type abstractBaseType) // ToDo: test this with preisblatt { if (!abstractBaseType.IsAbstract) + { throw new ArgumentException($"The type {abstractBaseType} is not abstract", nameof(abstractBaseType)); + } + var duplicateIncludeTags = typeof(BusinessObject).Assembly.GetTypes() .Where(t => abstractBaseType.IsAssignableFrom(t)) .SelectMany(t => t.GetCustomAttributes(typeof(ProtoIncludeAttribute), false)) @@ -236,13 +247,18 @@ where baseType.IsAssignableFrom(inheritingType) && baseType != inheritingType // Assert.IsTrue(typePair.baseType.GetCustomAttributes(typeof(ProtoContractAttribute), false).Any(), $"The (base) type {typePair.baseType} has not [ProtoContract] attribute."); // ToDo: re-add this line because fields on BO / COM level are not properly proto-serialized as of now! if (typePair.inheritingType.BaseType == typeof(BusinessObject)) //because protobuf-net doesn't support mutliple levels of inheritance + { Assert.IsTrue( typePair.inheritingType.GetCustomAttributes(typeof(ProtoContractAttribute), false).Any(), $"The (inheriting) type {typePair.inheritingType} has not [ProtoContract] attribute."); + } else + { Assert.IsFalse( typePair.inheritingType.GetCustomAttributes(typeof(ProtoContractAttribute), false).Any(), $"The (INDIRECTLY inheriting) type {typePair.inheritingType} has the [ProtoContract] attribute."); // bug in protobuf-net + } + if (typePair.inheritingType.BaseType == typePair.baseType && typePair.baseType != typeof(BusinessObject)) { diff --git a/BO4ETestProject/TestZeitraumDeserialization.cs b/BO4ETestProject/TestZeitraumDeserialization.cs index 47a7b9f4..0f2bba5c 100644 --- a/BO4ETestProject/TestZeitraumDeserialization.cs +++ b/BO4ETestProject/TestZeitraumDeserialization.cs @@ -2,9 +2,7 @@ using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -using BO4E.BO; using BO4E.COM; -using BO4E.ENUM; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; diff --git a/TestBO4E.Extensions/TestBO4E.Extensions.csproj b/TestBO4E.Extensions/TestBO4E.Extensions.csproj index f827d644..e4fb5671 100644 --- a/TestBO4E.Extensions/TestBO4E.Extensions.csproj +++ b/TestBO4E.Extensions/TestBO4E.Extensions.csproj @@ -19,8 +19,9 @@ - - + + + diff --git a/TestBO4E.Extensions/TestEnergiemengeExtensionCompleteness.cs b/TestBO4E.Extensions/TestEnergiemengeExtensionCompleteness.cs index e94e274a..d2236fc5 100644 --- a/TestBO4E.Extensions/TestEnergiemengeExtensionCompleteness.cs +++ b/TestBO4E.Extensions/TestEnergiemengeExtensionCompleteness.cs @@ -1,4 +1,3 @@ -using BO4E; using BO4E.BO; using BO4E.COM; using BO4E.ENUM; @@ -11,9 +10,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - - using System; using System.Collections.Generic; using System.IO; @@ -146,7 +142,9 @@ internal void TestMonthlySlices(bool testFirstOnly = true, bool useParallelExecu Assert.AreEqual(12, result.Count); // don't care about values of coverage, just the start/end and count of reports generated. if (testFirstOnly) + { break; // one test is enough. the rest is covered by the individual completeness report tests + } } } diff --git a/TestBO4E.Extensions/TestEnergiemengeExtensionPlausibility.cs b/TestBO4E.Extensions/TestEnergiemengeExtensionPlausibility.cs index df3032a8..437880bc 100644 --- a/TestBO4E.Extensions/TestEnergiemengeExtensionPlausibility.cs +++ b/TestBO4E.Extensions/TestEnergiemengeExtensionPlausibility.cs @@ -27,7 +27,10 @@ public void TestPlausibilityReportGenerationSomeCustomer() foreach (var key in new HashSet { "reference", "other", "expectedResult" }) if (!json.ContainsKey(key)) + { throw new ArgumentException($"Test file {boFile} has no key '{key}'."); + } + var emReference = JsonConvert.DeserializeObject(json["reference"].ToString()); var emOther = JsonConvert.DeserializeObject(json["other"].ToString()); diff --git a/TestBO4E.Extensions/TestMengeneinheitExtension.cs b/TestBO4E.Extensions/TestMengeneinheitExtension.cs index 0c721ccd..e513d9b6 100644 --- a/TestBO4E.Extensions/TestMengeneinheitExtension.cs +++ b/TestBO4E.Extensions/TestMengeneinheitExtension.cs @@ -22,7 +22,11 @@ public void TestConversionFactor() { foreach (Mengeneinheit me in Enum.GetValues(typeof(Mengeneinheit))) { - if ((int)me == 0) continue; + if ((int)me == 0) + { + continue; + } + Assert.AreEqual(1.0M, me.GetConversionFactor(me)); Assert.IsTrue(me.IsConvertibleTo(me)); } @@ -36,8 +40,10 @@ public void TestConversionFactor() foreach (Mengeneinheit me1 in Enum.GetValues(typeof(Mengeneinheit))) foreach (Mengeneinheit me2 in Enum.GetValues(typeof(Mengeneinheit))) if (!me1.IsConvertibleTo(me2)) + { Assert.ThrowsException(() => me1.GetConversionFactor(me2), $"Conversion {me1}-->{me2} should throw an exception!"); + } } } } \ No newline at end of file diff --git a/TestBO4E.Reporting/TestBO4E.Reporting.csproj b/TestBO4E.Reporting/TestBO4E.Reporting.csproj index a6f263fa..f1ebc15a 100644 --- a/TestBO4E.Reporting/TestBO4E.Reporting.csproj +++ b/TestBO4E.Reporting/TestBO4E.Reporting.csproj @@ -15,8 +15,9 @@ - - + + + diff --git a/TestBO4E.Reporting/TestReportToCsv.cs b/TestBO4E.Reporting/TestReportToCsv.cs index d81557da..723f7c72 100644 --- a/TestBO4E.Reporting/TestReportToCsv.cs +++ b/TestBO4E.Reporting/TestReportToCsv.cs @@ -53,7 +53,10 @@ public void TestCompletenessReportToCsv() var commaResult = cr.ToCsv(',', lineTerminator: Environment.NewLine, reihenfolge: reihenfolge); var separator = ""; if (decimalSeparator == ",") + { separator = "\""; + } + Assert.AreEqual( $"DE12345,{separator}0" + decimalSeparator + $"87{separator},2019-01-01T00:00:00Z,2019-03-01T00:00:00Z,", commaResult.Split(Environment.NewLine)[1]);