feat(api,dal): в JobGroup добавлено свойство UserTimeZoneOffsetMinutes, доработан контроллер. EsppScheduleTransformService - доработка с учетом offset
This commit is contained in:
4148
PARR.DAL/Migrations/20260209010657_tblJobGroupAddUserTimeZoneOffsetMinutes.Designer.cs
generated
Normal file
4148
PARR.DAL/Migrations/20260209010657_tblJobGroupAddUserTimeZoneOffsetMinutes.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PARR.DAL.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class tblJobGroupAddUserTimeZoneOffsetMinutes : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "UserTimeZoneOffsetMinutes",
|
||||
schema: "job",
|
||||
table: "Groups",
|
||||
type: "integer",
|
||||
nullable: true,
|
||||
comment: "Смещение часового пояса пользователя в минутах относительно UTC на момент ReferenceDate");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "UserTimeZoneOffsetMinutes",
|
||||
schema: "job",
|
||||
table: "Groups");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1702,6 +1702,10 @@ namespace PARR.DAL.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int?>("UserTimeZoneOffsetMinutes")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("Смещение часового пояса пользователя в минутах относительно UTC на момент ReferenceDate");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GroupTypeId");
|
||||
|
||||
@@ -92,6 +92,21 @@ namespace PARR.DAL.Models.Job
|
||||
public DateTimeOffset ReferenceDate { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Смещение часового пояса пользователя в минутах относительно UTC на момент ReferenceDate
|
||||
/// </summary>
|
||||
[Comment("Смещение часового пояса пользователя в минутах относительно UTC на момент ReferenceDate")]
|
||||
public int? UserTimeZoneOffsetMinutes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Смещение часового пояса пользователя относительно UTC на момент ReferenceDate, рассчитывается из поля UserTimeZoneOffsetMinutes
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public TimeSpan? UserTimeZoneOffset =>
|
||||
UserTimeZoneOffsetMinutes.HasValue
|
||||
? TimeSpan.FromMinutes(UserTimeZoneOffsetMinutes.Value)
|
||||
: null;
|
||||
|
||||
/// <summary>
|
||||
/// Использовать таймзону рабочей группы ответственного за ЭК шаблона
|
||||
/// </summary>
|
||||
|
||||
@@ -72,12 +72,12 @@ namespace PARR.DAL.NextRunServices
|
||||
/// <returns></returns>
|
||||
DateTimeOffset GetNextRunWithTimezoneEsppAndResponseArea(DateTimeOffset nextRun, bool? isResponseAreaTimezone = null, string? responseArea = null);
|
||||
|
||||
/// <summary>
|
||||
/// Получить NextRun согласно таймзоны ЗО
|
||||
/// </summary>
|
||||
/// <param name="nextRun"></param>
|
||||
/// <param name="responseArea"></param>
|
||||
/// <returns></returns>
|
||||
DateTimeOffset GetNextRunWithResponseAreaOffset(DateTimeOffset nextRun, string responseArea);
|
||||
///// <summary>
|
||||
///// Получить NextRun согласно таймзоны ЗО
|
||||
///// </summary>
|
||||
///// <param name="nextRun"></param>
|
||||
///// <param name="responseArea"></param>
|
||||
///// <returns></returns>
|
||||
//DateTimeOffset GetNextRunWithResponseAreaOffset(DateTimeOffset nextRun, string responseArea);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,6 +295,7 @@ namespace PARR.DAL.NextRunServices
|
||||
}
|
||||
|
||||
|
||||
#region Ok
|
||||
|
||||
/// <summary>
|
||||
/// Получить продолжительность в днях
|
||||
@@ -331,6 +332,8 @@ namespace PARR.DAL.NextRunServices
|
||||
return periodDays;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Получить дату начала распределения
|
||||
@@ -438,14 +441,15 @@ namespace PARR.DAL.NextRunServices
|
||||
//return resultNextRun;
|
||||
}
|
||||
|
||||
public DateTimeOffset GetNextRunWithResponseAreaOffset(DateTimeOffset nextRun, string responseArea)
|
||||
{
|
||||
var responseAreaOffset = scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea).UtcTimeOffset;
|
||||
var resulstNextRun = nextRun.Add(responseAreaOffset);
|
||||
logger.LogDebug("Смещение nextRun для ЗО {responseArea}, входящий: {nextRun}, смещение: {offset}, результат: {result}", responseArea, nextRun, responseAreaOffset, resulstNextRun);
|
||||
//// был паблик
|
||||
//private DateTimeOffset GetNextRunWithResponseAreaOffset(DateTimeOffset nextRun, string responseArea)
|
||||
//{
|
||||
// var responseAreaOffset = scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea).UtcTimeOffset;
|
||||
// var resulstNextRun = nextRun.Add(responseAreaOffset);
|
||||
// logger.LogDebug("Смещение nextRun для ЗО {responseArea}, входящий: {nextRun}, смещение: {offset}, результат: {result}", responseArea, nextRun, responseAreaOffset, resulstNextRun);
|
||||
|
||||
return resulstNextRun;
|
||||
}
|
||||
// return resulstNextRun;
|
||||
//}
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
using PARR.DAL.Contracts;
|
||||
using PARR.DAL.DomainModels;
|
||||
using PARR.DAL.Services.Interfaces;
|
||||
using PARR.DAL.TransformServices;
|
||||
|
||||
namespace PARR.DAL.NextRunServices.Subservices
|
||||
{
|
||||
internal class EsppScheduleTransformService : IEsppScheduleTransformService
|
||||
{
|
||||
private readonly IEsppSchTypeConfigService esppSchTypeConfigService;
|
||||
//private readonly INextRunModifierService nextRunModifierService;
|
||||
private readonly ILogger<EsppScheduleTransformService> logger;
|
||||
|
||||
private static readonly Dictionary<string, int> monthDict = new Dictionary<string, int>()
|
||||
@@ -52,11 +50,9 @@ namespace PARR.DAL.NextRunServices.Subservices
|
||||
public EsppScheduleTransformService(
|
||||
ILogger<EsppScheduleTransformService> logger,
|
||||
IEsppSchTypeConfigService esppSchTypeConfigService
|
||||
//INextRunModifierService nextRunModifierService
|
||||
)
|
||||
{
|
||||
this.esppSchTypeConfigService = esppSchTypeConfigService;
|
||||
//this.nextRunModifierService = nextRunModifierService;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -69,9 +65,12 @@ namespace PARR.DAL.NextRunServices.Subservices
|
||||
|
||||
private DateTimeOffset GetNextDate(EsppScheduleDto esppSchedule, DateTimeOffset referenceDate, TimeSpan? offset)
|
||||
{
|
||||
//offset - желательно передавать всегда, хотябы МСК, чтобы рассчитывались правильно даты, так как в ЕСПП все в МСК
|
||||
|
||||
var nextRun = referenceDate;
|
||||
|
||||
var finalOffset = new TimeSpan(3, 0, 0) + offset ?? TimeSpan.Zero;
|
||||
//var finalOffset = new TimeSpan(3, 0, 0) + (offset ?? TimeSpan.Zero);
|
||||
var finalOffset = offset ?? TimeSpan.Zero;
|
||||
|
||||
var offsetReferenceDate = referenceDate.ToOffset(finalOffset);
|
||||
|
||||
@@ -115,64 +114,12 @@ namespace PARR.DAL.NextRunServices.Subservices
|
||||
}
|
||||
|
||||
|
||||
//private List<DateTimeOffset> GetNextSchedule(EsppScheduleDto esppSchedule, DateTimeOffset referenceDate)
|
||||
//{
|
||||
// // расписание на оставшееся на сегодня время. его так мало...
|
||||
// referenceDate = referenceDate.UtcDateTime;
|
||||
// var schedules = new List<DateTimeOffset>();
|
||||
// var curTime = referenceDate;
|
||||
|
||||
// //Если ещё не произошло то добавляем расписание, возможно это новое AiW и он ещё ниразу не запускался.
|
||||
// //lastRun это будущее
|
||||
// if (referenceDate >= DateTimeOffset.UtcNow && referenceDate <= DateTimeOffset.UtcNow.EndOfDay())
|
||||
// schedules.Add(referenceDate);
|
||||
|
||||
// while (curTime < DateTimeOffset.UtcNow.EndOfDay())
|
||||
// {
|
||||
// switch (esppSchedule.TypeSchedule.Id)
|
||||
// {
|
||||
// case (int)EsppSchTypeScheduleEnum.Regularly:
|
||||
// curTime = GetNextDateRegularly(esppSchedule.Values, curTime);
|
||||
// break;
|
||||
// case (int)EsppSchTypeScheduleEnum.Weekly:
|
||||
// curTime = GetNextDateWeekly(esppSchedule.Values, curTime);
|
||||
// break;
|
||||
// case (int)EsppSchTypeScheduleEnum.Monthly:
|
||||
// curTime = GetNextDateMonthly(esppSchedule.Values, curTime);
|
||||
// break;
|
||||
// case (int)EsppSchTypeScheduleEnum.Monthly2:
|
||||
// curTime = GetNextDateMonthly2(esppSchedule.Values, curTime);
|
||||
// break;
|
||||
// case (int)EsppSchTypeScheduleEnum.Annually:
|
||||
// curTime = GetNextDateAnnually(esppSchedule.Values, curTime);
|
||||
// break;
|
||||
// case (int)EsppSchTypeScheduleEnum.Annually2:
|
||||
// curTime = GetNextDateAnnually2(esppSchedule.Values, curTime);
|
||||
// break;
|
||||
// }
|
||||
|
||||
// schedules.Add(curTime);
|
||||
// }
|
||||
// schedules.RemoveAll(s => s > DateTimeOffset.UtcNow.EndOfDay());
|
||||
|
||||
// return schedules.OrderBy(t => t).ToList();
|
||||
//}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//public async Task<List<DateTimeOffset>> GetNextScheduleAsync(Guid jobGroupId, DateTimeOffset referenceDate)
|
||||
//{
|
||||
// //TODO: тут не проверен переход через выходные дни!!! Переход не используется, так как тут не рассчитываем NextRun.
|
||||
// //В общем проверить, когда будем тестировать агента, что с датами все ок
|
||||
// var esppSchedule = await GetEsppScheduleAsync(jobGroupId);
|
||||
// var nextSchedule = GetNextSchedule(esppSchedule, referenceDate);
|
||||
|
||||
// return nextSchedule;
|
||||
//}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Получить расписание из БД
|
||||
/// </summary>
|
||||
/// <param name="jobGroupId"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private async Task<EsppScheduleDto> GetEsppScheduleAsync(Guid jobGroupId)
|
||||
{
|
||||
var esppSchedule = await esppSchTypeConfigService.GetEsppScheduleDtoAsync(jobGroupId);
|
||||
@@ -236,7 +183,22 @@ namespace PARR.DAL.NextRunServices.Subservices
|
||||
|
||||
var calcDay = referenceDate;
|
||||
if (calcDay < DateTimeOffset.UtcNow)
|
||||
calcDay = new DateTimeOffset(DateTimeOffset.UtcNow.Year, DateTimeOffset.UtcNow.Month, DateTimeOffset.UtcNow.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
{
|
||||
var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
calcDay = new DateTimeOffset(
|
||||
nowInTargetTz.Year,
|
||||
//DateTimeOffset.UtcNow.Year,
|
||||
nowInTargetTz.Month,
|
||||
//DateTimeOffset.UtcNow.Month,
|
||||
nowInTargetTz.Day,
|
||||
//DateTimeOffset.UtcNow.Day,
|
||||
referenceDate.Hour,
|
||||
referenceDate.Minute,
|
||||
referenceDate.Second,
|
||||
referenceDate.Offset
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (((int)calcDay.DayOfWeek) != dwNum)
|
||||
calcDay = OffsetToDayOfWeek(dwNum, calcDay);
|
||||
@@ -260,7 +222,20 @@ namespace PARR.DAL.NextRunServices.Subservices
|
||||
|
||||
var calcDay = new DateTimeOffset(referenceDate.Year, referenceDate.Month, dayNum, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
if (referenceDate < DateTimeOffset.UtcNow)
|
||||
calcDay = new DateTimeOffset(DateTimeOffset.UtcNow.Year, DateTimeOffset.UtcNow.Month, dayNum, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
{
|
||||
var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
calcDay = new DateTimeOffset(
|
||||
nowInTargetTz.Year,
|
||||
nowInTargetTz.Month,
|
||||
dayNum,
|
||||
referenceDate.Hour,
|
||||
referenceDate.Minute,
|
||||
referenceDate.Second,
|
||||
referenceDate.Offset
|
||||
);
|
||||
//calcDay = new DateTimeOffset(DateTimeOffset.UtcNow.Year, DateTimeOffset.UtcNow.Month, dayNum, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
}
|
||||
|
||||
|
||||
//TODO: вот это повторяется от метода к методу
|
||||
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
|
||||
@@ -285,9 +260,29 @@ namespace PARR.DAL.NextRunServices.Subservices
|
||||
throw new ArgumentException($"Неизвестное значение: {values[1].Value.Value}");
|
||||
}
|
||||
|
||||
//var startDay = referenceDate < DateTimeOffset.UtcNow
|
||||
// ? new DateTimeOffset(DateTimeOffset.UtcNow.Year, DateTimeOffset.UtcNow.Month, 1, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset)
|
||||
// : new DateTimeOffset(referenceDate.Year, referenceDate.Month, 1, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
|
||||
var startDay = referenceDate < DateTimeOffset.UtcNow
|
||||
? new DateTimeOffset(DateTimeOffset.UtcNow.Year, DateTimeOffset.UtcNow.Month, 1, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset)
|
||||
: new DateTimeOffset(referenceDate.Year, referenceDate.Month, 1, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
? new DateTimeOffset(
|
||||
DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset).Year,
|
||||
DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset).Month,
|
||||
1,
|
||||
referenceDate.Hour,
|
||||
referenceDate.Minute,
|
||||
referenceDate.Second,
|
||||
referenceDate.Offset
|
||||
)
|
||||
: new DateTimeOffset(
|
||||
referenceDate.Year,
|
||||
referenceDate.Month,
|
||||
1,
|
||||
referenceDate.Hour,
|
||||
referenceDate.Minute,
|
||||
referenceDate.Second,
|
||||
referenceDate.Offset
|
||||
);
|
||||
|
||||
var daysList = FindAllDaysInMonth(dwNum, startDay);
|
||||
|
||||
@@ -330,7 +325,12 @@ namespace PARR.DAL.NextRunServices.Subservices
|
||||
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
|
||||
}
|
||||
|
||||
var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year;
|
||||
//var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year;
|
||||
//сравниваем года в одном часовом поясе
|
||||
var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
var year = (referenceDate.Year < nowInTargetTz.Year)
|
||||
? nowInTargetTz.Year
|
||||
: referenceDate.Year;
|
||||
|
||||
var calcDay = new DateTimeOffset(
|
||||
year, monthNum, dayNum,
|
||||
@@ -379,9 +379,15 @@ namespace PARR.DAL.NextRunServices.Subservices
|
||||
throw new ArgumentException($"Неизвестное значение: {values[1].Value.Value}");
|
||||
}
|
||||
|
||||
var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year;
|
||||
//var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year;
|
||||
// сравниваем года в одном часовом поясе
|
||||
var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
var year = (referenceDate.Year < nowInTargetTz.Year)
|
||||
? nowInTargetTz.Year
|
||||
: referenceDate.Year;
|
||||
|
||||
if (year == DateTimeOffset.UtcNow.Year && referenceDate.Month < DateTimeOffset.UtcNow.Month)
|
||||
//if (year == DateTimeOffset.UtcNow.Year && referenceDate.Month < DateTimeOffset.UtcNow.Month)
|
||||
if (year == nowInTargetTz.Year && referenceDate.Month < nowInTargetTz.Month)
|
||||
year += 1;
|
||||
|
||||
var calcDay = new DateTimeOffset(
|
||||
@@ -432,91 +438,5 @@ namespace PARR.DAL.NextRunServices.Subservices
|
||||
return calcDay.AddDays(daysOffset);
|
||||
}
|
||||
|
||||
|
||||
//public DateTimeOffset GetNextDateForDistributionRun(DateTimeOffset lastRun, DistributionPeriodTypeEnum periodType, string distributionPeriod)
|
||||
//{
|
||||
// var nextRun = lastRun;
|
||||
|
||||
// switch (periodType)
|
||||
// {
|
||||
// case (DistributionPeriodTypeEnum.Day):
|
||||
// nextRun = lastRun.AddDays(ParseInt(distributionPeriod));
|
||||
// break;
|
||||
// case (DistributionPeriodTypeEnum.Month):
|
||||
// nextRun = lastRun.AddMonths(ParseInt(distributionPeriod));
|
||||
// break;
|
||||
// case (DistributionPeriodTypeEnum.Year):
|
||||
// nextRun = lastRun.AddYears(ParseInt(distributionPeriod));
|
||||
// break;
|
||||
// }
|
||||
|
||||
// return nextRunModifierService.GetWorkDayAsync(nextRun).GetAwaiter().GetResult();
|
||||
//}
|
||||
|
||||
|
||||
//private DateTimeOffset GetPrevDateForDistributionRun(DateTimeOffset lastRun, DistributionPeriodTypeEnum periodType, string distributionPeriod)
|
||||
//{
|
||||
// switch (periodType)
|
||||
// {
|
||||
// case (DistributionPeriodTypeEnum.Day):
|
||||
// return lastRun.AddDays(-ParseInt(distributionPeriod));
|
||||
|
||||
// case (DistributionPeriodTypeEnum.Month):
|
||||
// return lastRun.AddMonths(-ParseInt(distributionPeriod));
|
||||
|
||||
// case (DistributionPeriodTypeEnum.Year):
|
||||
// return lastRun.AddYears(-ParseInt(distributionPeriod));
|
||||
// }
|
||||
|
||||
// return lastRun;
|
||||
//}
|
||||
|
||||
|
||||
//public async Task<DateOnly> GetStartPeriodForDateAsync(Guid jobGroupId, DateTimeOffset date, DateTimeOffset referenceDate, DistributionPeriodTypeEnum periodType, string distributionPeriod)
|
||||
//{
|
||||
// var esppSchedule = await GetEsppScheduleAsync(jobGroupId);
|
||||
|
||||
// var currentStartPeriod = referenceDate;
|
||||
// var currentEndPeriod = GetNextDateForDistributionRun(currentStartPeriod, periodType, distributionPeriod);
|
||||
|
||||
// while (!(date >= currentStartPeriod && date < currentEndPeriod))
|
||||
// {
|
||||
// if (date > currentEndPeriod)
|
||||
// {
|
||||
// currentStartPeriod = currentEndPeriod;
|
||||
// currentEndPeriod = GetNextDateForDistributionRun(currentStartPeriod, periodType, distributionPeriod);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// currentEndPeriod = currentStartPeriod;
|
||||
// currentStartPeriod = GetPrevDateForDistributionRun(currentStartPeriod, periodType, distributionPeriod);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return DateOnly.FromDateTime(currentStartPeriod.DateTime);
|
||||
//}
|
||||
|
||||
|
||||
//private int ParseInt(string value)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// return int.Parse(value);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// logger.LogError(ex, $"{nameof(this.GetType)}, получение конца периода. Не смог распарсить string в int для значения {value}.");
|
||||
// return 0;
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
//private DistributionPeriodTypeEnum ParseDistributionPeriodType(string value)
|
||||
//{
|
||||
// var result = (DistributionPeriodTypeEnum)Enum.Parse(typeof(DistributionPeriodTypeEnum), value);
|
||||
|
||||
// return result;
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,9 @@
|
||||
/// </summary>
|
||||
/// <param name="jobGroupId"></param>
|
||||
/// <param name="lastRun"></param>
|
||||
/// <param name="offset">offset - желательно передавать всегда, хотябы МСК, чтобы рассчитывались правильно даты, так как в ЕСПП все в МСК</param>
|
||||
/// <returns></returns>
|
||||
Task<DateTimeOffset> GetNextDateAsync(Guid jobGroupId, DateTimeOffset lastRun, TimeSpan? offset = null);
|
||||
Task<DateTimeOffset> GetNextDateAsync(Guid jobGroupId, DateTimeOffset referenceDate, TimeSpan? offset = null);
|
||||
|
||||
///// <summary>
|
||||
///// Получить расписание по jobGroupId
|
||||
|
||||
Reference in New Issue
Block a user