diff --git a/Axuno.Tools/GermanFederalStates.cs b/Axuno.Tools/GermanFederalStates.cs
index 751e5cb3..bf65beb1 100644
--- a/Axuno.Tools/GermanFederalStates.cs
+++ b/Axuno.Tools/GermanFederalStates.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@@ -68,11 +67,11 @@ public GermanFederalStates()
{
try
{
- Contains(this[stateId]);
+ _ = Contains(this[stateId]);
}
catch
{
- throw new Exception(string.Format("GermanFederalStateId \"{0}\" wird in \"{1}\" nicht abgebildet.", stateId, GetType()));
+ throw new Exception($"GermanFederalStateId \"{stateId}\" is not included in \"{GetType()}\".");
}
}
#endif
@@ -88,8 +87,8 @@ public GermanFederalStates()
///
/// Get a GermanFederalState object.
///
- /// State abbriviation or state name.
- /// Returns the GermanFederalState for the state abbriviation or state name.
+ /// State abbreviation or state name.
+ /// Returns the GermanFederalState for the state abbreviation or state name.
public GermanFederalState this[string nameOrAbbreviation]
{
get
@@ -100,4 +99,4 @@ public GermanFederalState this[string nameOrAbbreviation]
return this.First(fs => fs.Name == nameOrAbbreviation);
}
}
-}
\ No newline at end of file
+}
diff --git a/Axuno.Tools/GermanHolidays.cs b/Axuno.Tools/GermanHolidays.cs
index e79c5c72..2157c07a 100644
--- a/Axuno.Tools/GermanHolidays.cs
+++ b/Axuno.Tools/GermanHolidays.cs
@@ -100,7 +100,7 @@ public bool IsPublicHoliday(GermanFederalStates.Id stateId)
public static bool operator ==(GermanHoliday? h1, GermanHoliday? h2)
{
- if (h1 == null || h2 == null) return false;
+ if (h1 is null || h2 is null) return false;
return h1.Equals(h2);
}
@@ -162,7 +162,7 @@ public GermanHolidays(int year)
{
// Plausibility check
if (year < 1583 || year > 4099)
- throw new Exception("Year must be between 1583 and 4099.");
+ throw new ArgumentException("Year must be between 1583 and 4099.", nameof(year));
Year = year;
_easterSunday = GetEasterSunday();
diff --git a/TournamentManager/TournamentManager/Assets/Holidays.A-Indoor.config b/TournamentManager/TournamentManager/Assets/Holidays.A-Indoor.config
deleted file mode 100644
index c63c7414..00000000
--- a/TournamentManager/TournamentManager/Assets/Holidays.A-Indoor.config
+++ /dev/null
@@ -1,159 +0,0 @@
-
-
-
-
-
-
-
-
- Public
- Augsburger Friedensfest´
-
- Bayern
-
-
-
- Custom
- Heiliger Abend
-
-
- Custom
- Rosenmontag
-
-
- Custom
- Faschingsdienstag
-
-
- Custom
- Silvester
-
-
-
- School
- 2019-10-28
- 2019-10-31
- Herbstferien
-
- Bayern
-
-
-
- School
- 2019-11-20
- 2019-11-20
- Herbstferien
-
- Bayern
-
-
-
- School
- 2019-12-23
- 2020-01-04
- Weihnachtsferien
-
- Bayern
-
-
-
- School
- 2020-02-24
- 2020-02-28
- Winterferien
-
- Bayern
-
-
-
- School
- 2020-04-06
- 2020-04-18
- Osterferien
-
- Bayern
-
-
-
- School
- 2020-06-02
- 2020-06-13
- Pfingstferien
-
- Bayern
-
-
-
- School
- 2020-07-27
- 2020-09-07
- Sommerferien
-
- Bayern
-
-
-
-
- School
- 2020-10-31
- 2020-11-06
- Herbstferien
-
- Bayern
-
-
-
- School
- 2020-11-18
- 2020-11-18
- Herbstferien
-
- Bayern
-
-
-
- School
- 2020-12-23
- 2021-01-09
- Weihnachtsferien
-
- Bayern
-
-
-
- School
- 2021-02-15
- 2021-02-19
- Winterferien
-
- Bayern
-
-
-
- School
- 2021-03-29
- 2021-04-10
- Osterferien
-
- Bayern
-
-
-
- School
- 2021-05-25
- 2021-06-04
- Pfingstferien
-
- Bayern
-
-
-
- School
- 2021-07-30
- 2021-09-13
- Sommerferien
-
- Bayern
-
-
-
diff --git a/TournamentManager/TournamentManager/Assets/PublicHolidaysGermany.xlsx b/TournamentManager/TournamentManager/Assets/PublicHolidaysGermany.xlsx
deleted file mode 100644
index 330b10b5..00000000
Binary files a/TournamentManager/TournamentManager/Assets/PublicHolidaysGermany.xlsx and /dev/null differ
diff --git a/TournamentManager/TournamentManager/Data/AvailableMatchDateRepository.cs b/TournamentManager/TournamentManager/Data/AvailableMatchDateRepository.cs
index f2cd0d7e..33f1a94d 100644
--- a/TournamentManager/TournamentManager/Data/AvailableMatchDateRepository.cs
+++ b/TournamentManager/TournamentManager/Data/AvailableMatchDateRepository.cs
@@ -18,7 +18,7 @@
namespace TournamentManager.Data;
///
-/// Class for ExcludedMatchDate related data selections
+/// Class for database operations for available match dates.
///
public class AvailableMatchDateRepository
{
@@ -47,8 +47,6 @@ public async Task> GetAvailableMatchD
await da.FetchEntityCollectionAsync(qp, cancellationToken);
da.CloseConnection();
- _logger.LogDebug("{count} available match dates found", available.Count);
-
return available;
}
-}
\ No newline at end of file
+}
diff --git a/TournamentManager/TournamentManager/Data/ExcludedMatchDateRepository.cs b/TournamentManager/TournamentManager/Data/ExcludedMatchDateRepository.cs
index 645ed6b8..b25b29d3 100644
--- a/TournamentManager/TournamentManager/Data/ExcludedMatchDateRepository.cs
+++ b/TournamentManager/TournamentManager/Data/ExcludedMatchDateRepository.cs
@@ -27,6 +27,7 @@ public class ExcludedMatchDateRepository
public ExcludedMatchDateRepository(MultiTenancy.IDbContext dbContext)
{
_dbContext = dbContext;
+ _logger.LogDebug("Repository created. {Repository} {Identifier}", nameof(TournamentRepository), dbContext.Tenant?.Identifier);
}
///
@@ -47,8 +48,6 @@ public async Task> GetExcludedMatchDate
await da.FetchEntityCollectionAsync(qp, cancellationToken);
da.CloseConnection();
- _logger.LogDebug("{count} excluded match dates found", excluded.Count);
-
return excluded;
}
@@ -98,4 +97,4 @@ public async Task> GetExcludedMatchDate
.AddWithOr(roundFilter).AddWithOr(teamFilter)).Limit(1),
cancellationToken)).Cast().FirstOrDefault();
}
-}
\ No newline at end of file
+}
diff --git a/TournamentManager/TournamentManager/Data/TournamentRepository.cs b/TournamentManager/TournamentManager/Data/TournamentRepository.cs
index 2613cda2..17205868 100644
--- a/TournamentManager/TournamentManager/Data/TournamentRepository.cs
+++ b/TournamentManager/TournamentManager/Data/TournamentRepository.cs
@@ -28,7 +28,7 @@ public class TournamentRepository
public TournamentRepository(MultiTenancy.IDbContext dbContext)
{
_dbContext = dbContext;
- _logger.LogDebug("{repository} created.", nameof(TournamentRepository));
+ _logger.LogDebug("Repository created. {Repository} {Identifier}", nameof(TournamentRepository), dbContext.Tenant?.Identifier);
}
[Obsolete("Use GetTournamentAsync instead", false)]
@@ -95,4 +95,4 @@ public virtual EntityCollection GetTournamentRounds(long tournament
await da.FetchEntityCollectionAsync(qp, cancellationToken);
return t.FirstOrDefault();
}
-}
\ No newline at end of file
+}
diff --git a/TournamentManager/TournamentManager/Plan/AvailableMatchDates.cs b/TournamentManager/TournamentManager/Plan/AvailableMatchDates.cs
index f4bf8843..dcd33e0b 100644
--- a/TournamentManager/TournamentManager/Plan/AvailableMatchDates.cs
+++ b/TournamentManager/TournamentManager/Plan/AvailableMatchDates.cs
@@ -38,18 +38,27 @@ internal AvailableMatchDates(ITenantContext tenantContext,
_logger = logger;
}
+ ///
+ /// Clears and loads excluded match dates and available match dates
+ /// from storage.
+ ///
private async Task Initialize(CancellationToken cancellationToken)
{
+ _logger.LogDebug($"Initializing {nameof(AvailableMatchDates)}");
_excludedMatchDateEntities.Clear();
_excludedMatchDateEntities.AddRange(
await _appDb.ExcludedMatchDateRepository.GetExcludedMatchDatesAsync(
_tenantContext.TournamentContext.MatchPlanTournamentId, cancellationToken));
+ _logger.LogDebug("{count} excluded match dates loaded from storage", _excludedMatchDateEntities.Count);
+
_availableMatchDateEntities.Clear();
_availableMatchDateEntities.AddRange(
await _appDb.AvailableMatchDateRepository.GetAvailableMatchDatesAsync(
_tenantContext.TournamentContext.MatchPlanTournamentId, cancellationToken));
+ _logger.LogDebug("{count} available match dates loaded from storage", _availableMatchDateEntities.Count);
+
_generatedAvailableMatchDateEntities.Clear();
}
@@ -92,6 +101,32 @@ internal async Task ClearAsync(ClearMatchDates clear, CancellationToken can
return deleted;
}
+ ///
+ /// Checks the for ,
+ /// and for not .
+ ///
+ private bool IsVenueAndDateDefined(TeamEntity team)
+ {
+ return team is { MatchDayOfWeek: not null, MatchTime: not null, VenueId: not null };
+ }
+
+ ///
+ /// Verifies, that the given is within the date
+ /// bounderies, and it is not excluded, and the venue is not occupied by another match.
+ ///
+ private async Task IsDateUsable(DateTime matchDateTimeUtc, RoundLegEntity roundLeg, TeamEntity team, CancellationToken cancellationToken)
+ {
+ var plannedDuration = _tenantContext.TournamentContext.FixtureRuleSet.PlannedDurationOfMatch;
+
+ // Todo: This code creates heavy load on the database
+ return IsDateWithinRoundLegDateTime(roundLeg, matchDateTimeUtc)
+ && !IsExcludedDate(matchDateTimeUtc, roundLeg.RoundId, team.Id)
+ && !await IsVenueOccupiedByMatchAsync(
+ new DateTimePeriod(matchDateTimeUtc, matchDateTimeUtc.Add(plannedDuration)),
+ team.VenueId!.Value, cancellationToken);
+ }
+
+
///
/// Generate available match dates for teams where
/// , ,
@@ -103,23 +138,10 @@ internal async Task ClearAsync(ClearMatchDates clear, CancellationToken can
internal async Task GenerateNewAsync(RoundEntity round, CancellationToken cancellationToken)
{
await Initialize(cancellationToken);
- var teamIdProcessed = new List();
- var listTeamsWithSameVenue = new List>();
-
- // Make a list of teams of the same round and with the same venue AND weekday AND match time
- // Venues will later be assigned to these teams alternately
- foreach (var team in round.TeamCollectionViaTeamInRound)
- {
- // the collection will contain at least one team
- var teams = GetTeamsWithSameVenueAndMatchTime(team, round);
- if (teamIdProcessed.Contains(teams[0].Id)) continue;
-
- listTeamsWithSameVenue.Add(teams);
- foreach (var t in teams)
- if (!teamIdProcessed.Contains(t.Id))
- teamIdProcessed.Add(t.Id);
- }
+ // Venues will later be assigned to these teams on a rotating basis
+ var listTeamsWithSameVenue = GetListOfTeamsWithSameVenue(round);
+
foreach (var roundLeg in round.RoundLegs)
{
var startDate = DateTime.SpecifyKind(roundLeg.StartDateTime, DateTimeKind.Utc);
@@ -127,54 +149,44 @@ internal async Task GenerateNewAsync(RoundEntity round, CancellationToken cancel
foreach (var teamsWithSameVenue in listTeamsWithSameVenue)
{
- var teamIndex = 0;
+ var team = teamsWithSameVenue[0];
- // Make sure these values are not null
- if (!teamsWithSameVenue[teamIndex].MatchDayOfWeek.HasValue ||
- !teamsWithSameVenue[teamIndex].MatchTime.HasValue ||
- !teamsWithSameVenue[teamIndex].VenueId.HasValue)
- continue;
-
- // Create Tuple for non-nullable context
-#pragma warning disable IDE0042 // Deconstruct variable declaration
- var team = (teamsWithSameVenue[teamIndex].Id,
- MatchDayOfWeek: (DayOfWeek) teamsWithSameVenue[teamIndex].MatchDayOfWeek!.Value,
- MatchTime: teamsWithSameVenue[teamIndex].MatchTime!.Value,
- VenueId: teamsWithSameVenue[teamIndex].VenueId!.Value);
-#pragma warning restore IDE0042 // Deconstruct variable declaration
-
// get the first possible match date equal or after the leg's starting date
- var matchDate = IncrementDateUntilDayOfWeek(startDate, team.MatchDayOfWeek);
+ var matchDate = IncrementDateUntilDayOfWeek(startDate, (DayOfWeek) team.MatchDayOfWeek!);
// process the period of a leg
+ var teamIndex = 0;
while (matchDate <= endDate)
{
+ team = teamsWithSameVenue[teamIndex];
+
// if there is more than one team per venue with same weekday and match time,
// match dates will be assigned alternately
- var matchDateAndTimeUtc = _timeZoneConverter.ToUtc(matchDate.Date.Add(team.MatchTime));
+ var matchDateTimeUtc = _timeZoneConverter.ToUtc(matchDate.Date.Add(team.MatchTime!.Value));
// check whether the calculated date
// is within the borders of round legs (if any) and is not marked as excluded
- if (IsDateWithinRoundLegDateTime(roundLeg, matchDateAndTimeUtc)
- && !IsExcludedDate(matchDateAndTimeUtc, round.Id, team.Id)
- && !await IsVenueOccupiedByMatchAsync(
- new DateTimePeriod(matchDateAndTimeUtc,
- matchDateAndTimeUtc.Add(_tenantContext.TournamentContext.FixtureRuleSet
- .PlannedDurationOfMatch)), team.VenueId, cancellationToken))
+
+ if (await IsDateUsable(matchDateTimeUtc, roundLeg, team, cancellationToken))
{
var av = new AvailableMatchDateEntity
{
TournamentId = _tenantContext.TournamentContext.MatchPlanTournamentId,
HomeTeamId = team.Id,
- VenueId = team.VenueId,
- MatchStartTime = matchDateAndTimeUtc,
+ VenueId = team.VenueId!.Value,
+ MatchStartTime = matchDateTimeUtc,
MatchEndTime =
- matchDateAndTimeUtc.Add(_tenantContext.TournamentContext.FixtureRuleSet.PlannedDurationOfMatch),
+ matchDateTimeUtc.Add(_tenantContext.TournamentContext.FixtureRuleSet.PlannedDurationOfMatch),
IsGenerated = true
};
_generatedAvailableMatchDateEntities.Add(av);
- teamIndex = ++teamIndex >= teamsWithSameVenue.Count ? 0 : teamIndex;
+ }
+
+ if (teamsWithSameVenue.Count > 1)
+ {
+ teamIndex++;
+ if (teamIndex >= teamsWithSameVenue.Count) teamIndex = 0;
}
matchDate = matchDate.Date.AddDays(7);
@@ -189,6 +201,29 @@ internal async Task GenerateNewAsync(RoundEntity round, CancellationToken cancel
// await _appDb.GenericRepository.SaveEntitiesAsync(_generatedAvailableMatchDateEntities, true, false, cancellationToken);
}
+ ///
+ /// Make a list of teams of the same round and with the same venue AND weekday AND overlapping match time
+ ///
+ private List> GetListOfTeamsWithSameVenue(RoundEntity round)
+ {
+ var listTeamsWithSameVenue = new List>();
+
+ var teamIdProcessed = new List();
+ foreach (var team in round.TeamCollectionViaTeamInRound)
+ {
+ // the collection will contain at least one team
+ var teams = GetTeamsWithSameVenueAndMatchTime(team, round);
+ if (!IsVenueAndDateDefined(teams[0]) || teamIdProcessed.Contains(teams[0].Id)) continue;
+
+ listTeamsWithSameVenue.Add(teams);
+ foreach (var t in teams)
+ if (!teamIdProcessed.Contains(t.Id))
+ teamIdProcessed.Add(t.Id);
+ }
+
+ return listTeamsWithSameVenue;
+ }
+
private async Task IsVenueOccupiedByMatchAsync(DateTimePeriod matchTime, long venueId,
CancellationToken cancellationToken)
{
@@ -277,6 +312,10 @@ internal List GetGeneratedAndManualAvailableMatchDates
return result.ToList();
}
+ ///
+ /// Gets the teams in a round with the same ,
+ /// and overlapping .
+ ///
private EntityCollection GetTeamsWithSameVenueAndMatchTime(TeamEntity team, RoundEntity round)
{
var resultTeams = new EntityCollection();
@@ -305,4 +344,4 @@ private EntityCollection GetTeamsWithSameVenueAndMatchTime(TeamEntit
return resultTeams;
}
-}
\ No newline at end of file
+}
diff --git a/TournamentManager/TournamentManager/Plan/MatchPlanner.cs b/TournamentManager/TournamentManager/Plan/MatchPlanner.cs
index 3a9d1e01..49af46f3 100644
--- a/TournamentManager/TournamentManager/Plan/MatchPlanner.cs
+++ b/TournamentManager/TournamentManager/Plan/MatchPlanner.cs
@@ -190,7 +190,7 @@ await _appDb.GenericRepository.DeleteEntitiesDirectlyAsync(typeof(MatchEntity),
// matchDates contains calculated dates in the same order as combinations,
// so the index can be used for both.
var availableDates = GetMatchDates(roundLeg, teamCombinationGroup, roundMatches);
- _logger.LogDebug("Selected dates: {dates}", string.Join(", ", availableDates.OrderBy(bd => bd?.MatchStartTime).Select(bd => bd?.MatchStartTime.ToShortDateString())).TrimEnd(',', ' '));
+ _logger.LogDebug("Available dates for combination: {dates}", string.Join(", ", availableDates.OrderBy(bd => bd?.MatchStartTime).Select(bd => bd?.MatchStartTime.ToShortDateString())).TrimEnd(',', ' '));
for (var index = 0; index < teamCombinationGroup.Count; index++)
{
@@ -222,6 +222,9 @@ await _appDb.GenericRepository.DeleteEntitiesDirectlyAsync(typeof(MatchEntity),
ChangeSerial = 0,
Remarks = string.Empty
};
+
+ _logger.LogDebug("Fixture: {HomeTeam} - {GuestTeam}: {PlannedStart}", match.HomeTeamId, match.GuestTeamId, match.PlannedStart);
+
roundMatches.Add(match);
}
}
@@ -268,7 +271,7 @@ private static List GetOccupiedMatchDates(TeamCombination combin
new DateTimePeriod(roundLeg.StartDateTime, roundLeg.EndDateTime),
GetOccupiedMatchDates(combination, groupMatches));
}
-
+
matchDates.Add(availableDates);
#if DEBUG
@@ -286,7 +289,7 @@ private static List GetOccupiedMatchDates(TeamCombination combin
#endif
}
- // we can't proceed without and match dates found
+ // we can't proceed without any match dates found
if (matchDates.Count == 0) return matchDatePerCombination;
// only 1 match date found, so optimization is not possible