Esempio n. 1
0
        /// <summary>
        /// Imports excluded match dates from an Excel workbook. The first worksheet of the workbook will be used.
        /// 3 expected columns: <see cref="DateTime"/> DateFrom, <see cref="DateTime"/> DateTo, <see cref="string"/> Reason.
        /// </summary>
        /// <remarks>
        /// Excel date values are considered as local time and will be converted to UTC.
        /// A maximum of 1,000 rows will be imported.
        /// </remarks>
        /// <param name="xlPathAndFileName">The path to the Excel file.</param>
        /// <param name="dateLimits">The limits for dates, which will be imported.</param>
        /// <returns>
        /// Returns an <see cref="IEnumerable{T}" />of <see cref="ExcludeMatchDateEntity"/>.
        /// <see cref="ExcludeMatchDateEntity.TournamentId"/>, <see cref="ExcludeMatchDateEntity.RoundId"/> and <see cref="ExcludeMatchDateEntity.TeamId"/> will not be set.
        /// </returns>
        public IEnumerable <ExcludeMatchDateEntity> Import(string xlPathAndFileName, DateTimePeriod dateLimits)
        {
            var xlFile = new FileInfo(xlPathAndFileName);

            _logger.LogTrace("Opening Excel file '{0}'", xlPathAndFileName);
            using var package = new ExcelPackage(xlFile);

            var worksheet = package.Workbook.Worksheets.First();

            _logger.LogTrace("Using the first worksheet, '{0}'", worksheet.Name);
            _logger.LogTrace("Date limits are {0} - {1}", dateLimits.Start, dateLimits.End);
            var row = 0;

            while (true)
            {
                row++;
                if (row == 1 && !(worksheet.Cells[row, 1].Value is DateTime))
                {
                    _logger.LogTrace("First cell is not a date, assume existing headline row");
                    continue; // may contain a headline row
                }


                if (!(worksheet.Cells[row, 1].Value is DateTime from && worksheet.Cells[row, 2].Value is DateTime to) ||
                    row > 1000)
                {
                    _logger.LogTrace("Import finished with worksheet row {0}", row - 1);
                    yield break;
                }


                from = _timeZoneConverter.ToUtc(from.Date);
                to   = _timeZoneConverter.ToUtc(to.Date);

                if (!dateLimits.Overlaps(new DateTimePeriod(from, to)))
                {
                    _logger.LogTrace("UTC Dates {0} - {1} are out of limits", from, to);
                    continue;
                }

                var reason = worksheet.Cells[row, 3].Value as string ?? string.Empty;
                yield return(CreateEntity((from, to, reason)));

                _logger.LogTrace("Imported UTC {0} - {1} ({2})", from, to, reason);
            }
        }
Esempio n. 2
0
        private IEnumerable <ExcludeMatchDateEntity> Map(List <Axuno.Tools.GermanHoliday> holidays, DateTimePeriod dateLimits)
        {
            // sort short date ranges before big ranges
            var holidayGroups = holidays.ConsecutiveRanges()
                                .OrderBy(tuple => tuple.From.Date).ThenBy(tuple => (tuple.To - tuple.From).Days);

            foreach (var holidayGroup in holidayGroups)
            {
                var entity = CreateEntity(holidayGroup);
                if (!dateLimits.Contains(entity.DateFrom) && !dateLimits.Contains(entity.DateTo))
                {
                    continue;
                }

                // convert from import time zone to UTC
                entity.DateFrom = _timeZoneConverter.ToUtc(entity.DateFrom.Date);
                entity.DateTo   = _timeZoneConverter.ToUtc(entity.DateTo.AddDays(1).AddSeconds(-1));

                yield return(entity);
            }
        }
Esempio n. 3
0
        public async Task <IActionResult> EditFixture([FromForm] EditFixtureViewModel model, CancellationToken cancellationToken)
        {
            // [FromBody] => 'content-type': 'application/json'
            // [FromForm] => 'content-type': 'application/x-www-form-urlencoded'

            model = new EditFixtureViewModel(await GetPlannedMatchFromDatabase(model.Id, cancellationToken), _timeZoneConverter)
            {
                Tournament = await GetPlanTournament(cancellationToken)
            };

            if (model.PlannedMatch == null || model.Tournament == null)
            {
                var msg = $"No data for fixture id '{model.Id}'. User ID '{GetCurrentUserId()}'";
                _logger.LogInformation(msg);
                return(NotFound(msg));
            }

            if (!(await _authorizationService.AuthorizeAsync(User,
                                                             new MatchEntity
            {
                HomeTeamId = model.PlannedMatch.HomeTeamId,
                GuestTeamId = model.PlannedMatch.GuestTeamId,
                VenueId = model.PlannedMatch.VenueId,
                OrigVenueId = model.PlannedMatch.OrigVenueId
            }, Authorization.MatchOperations.ChangeFixture)).Succeeded)
            {
                return(Forbid());
            }

            // sync input with new model instance
            if (!await TryUpdateModelAsync(model))
            {
                return(View(ViewNames.Match.EditFixture, await AddDisplayDataToEditFixtureViewModel(model, cancellationToken)));
            }

            // create a new MatchEntity for validation
            var match = FillMatchEntity(model.PlannedMatch);

            match.SetPlannedStart(model.MatchDate.HasValue && model.MatchTime.HasValue
                ? _timeZoneConverter.ToUtc(model.MatchDate.Value.Add(model.MatchTime.Value))
                : null, _siteContext.FixtureRuleSet.PlannedDurationOfMatch);
            match.SetVenueId(model.VenueId);
            if (match.IsDirty)
            {
                match.ChangeSerial += 1;
            }

            ModelState.Clear();

            // Todo: This business logic should rather go into settings
            _siteContext.FixtureRuleSet.PlannedMatchTimeMustStayInCurrentLegBoundaries = model.Tournament.IsPlanningMode;

            if (!await model.ValidateAsync(
                    new FixtureValidator(match, (_siteContext, _timeZoneConverter, model.PlannedMatch), DateTime.UtcNow),
                    ModelState))
            {
                return(View(ViewNames.Match.EditFixture, await AddDisplayDataToEditFixtureViewModel(model, cancellationToken)));
            }

            var fixtureIsChanged = match.IsDirty;

            var fixtureMessage = new EditFixtureViewModel.FixtureMessage {
                MatchId = model.Id, ChangeSuccess = false
            };

            // save the match entity
            try
            {
                fixtureMessage.ChangeSuccess = await _appDb.GenericRepository.SaveEntityAsync(match, false, false, cancellationToken);

                _logger.LogInformation($"Fixture for match id {match.Id} updated successfully for user ID '{0}'", GetCurrentUserId());
                if (fixtureIsChanged)
                {
                    SendFixtureNotification(match.Id);
                }
            }
            catch (Exception e)
            {
                fixtureMessage.ChangeSuccess = false;
                _logger.LogCritical(e, "Fixture update for match id {0} failed for user ID '{1}'", match.Id, GetCurrentUserId());
            }

            // redirect to fixture overview, where success message is shown
            TempData.Put <EditFixtureViewModel.FixtureMessage>(nameof(EditFixtureViewModel.FixtureMessage), fixtureMessage);
            return(RedirectToAction(nameof(Fixtures), nameof(Match), new { Organization = _siteContext.UrlSegmentValue }));
        }
Esempio n. 4
0
        /// <summary>
        /// Generate available match dates for teams where
        /// <see cref="TeamEntity.MatchDayOfWeek"/>, <see cref="TeamEntity.MatchTime"/>, <see cref="TeamEntity.VenueId"/>
        /// are not <see langword="null"/>.
        /// </summary>
        /// <param name="round"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        internal async Task GenerateNewAsync(RoundEntity round, CancellationToken cancellationToken)
        {
            await Initialize(cancellationToken);

            var teamIdProcessed        = new List <long>();
            var listTeamsWithSameVenue = new List <EntityCollection <TeamEntity> >();

            // 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);
                    }
                }
            }

            foreach (var roundLeg in round.RoundLegs)
            {
                var startDate = DateTime.SpecifyKind(roundLeg.StartDateTime, DateTimeKind.Utc);
                var endDate   = DateTime.SpecifyKind(roundLeg.EndDateTime, DateTimeKind.Utc);

                foreach (var teamsWithSameVenue in listTeamsWithSameVenue)
                {
                    var teamIndex = 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
                    var team = (Id : teamsWithSameVenue[teamIndex].Id,
                                MatchDayOfWeek : (DayOfWeek)teamsWithSameVenue[teamIndex].MatchDayOfWeek !.Value,
                                MatchTime : teamsWithSameVenue[teamIndex].MatchTime !.Value,
                                VenueId : teamsWithSameVenue[teamIndex].VenueId !.Value);

                    // get the first possible match date equal or after the leg's starting date
                    var matchDate = IncrementDateUntilDayOfWeek(startDate, team.MatchDayOfWeek);

                    // process the period of a leg
                    while (matchDate <= endDate)
                    {
                        // 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));

                        // 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))
                        {
                            var av = new AvailableMatchDateEntity
                            {
                                TournamentId   = _tenantContext.TournamentContext.MatchPlanTournamentId,
                                HomeTeamId     = team.Id,
                                VenueId        = team.VenueId,
                                MatchStartTime = matchDateAndTimeUtc,
                                MatchEndTime   =
                                    matchDateAndTimeUtc.Add(_tenantContext.TournamentContext.FixtureRuleSet.PlannedDurationOfMatch),
                                IsGenerated = true
                            };

                            _generatedAvailableMatchDateEntities.Add(av);
                            teamIndex = ++teamIndex >= teamsWithSameVenue.Count ? 0 : teamIndex;
                        }

                        matchDate = matchDate.Date.AddDays(7);
                    }
                }
            }

            _logger.LogTrace("Generated {Count} UTC dates for HomeTeams:", _generatedAvailableMatchDateEntities.Count);
            _logger.LogTrace("{Generated}\n", _generatedAvailableMatchDateEntities.Select(gen => (gen.HomeTeamId, gen.MatchStartTime)));

            // save to the persistent storage
            // await _appDb.GenericRepository.SaveEntitiesAsync(_generatedAvailableMatchDateEntities, true, false, cancellationToken);
        }