feat(dal,api): IMatchingStatusService - управление статусами matcher`a. Отображение статусов в АПИ, нельзя повторно из апи отправить задание мэтчеру если сейчас идет matching

This commit is contained in:
Mikhail Trubnikov
2026-01-16 11:04:26 +10:00
parent 3205c1db8e
commit 99a491ede8
14 changed files with 303 additions and 91 deletions

View File

@@ -40,8 +40,11 @@
{ {
//public List<JobResponse>? Jobs { get; set; } //public List<JobResponse>? Jobs { get; set; }
public int JobsCount { get; set; } public int JobsCount { get; set; }
public JobGroupScheduleResponse? Schedule { get; set; } public JobGroupScheduleResponse? Schedule { get; set; }
public MatchingStatusResponse? MatchingStatus { get; set; }
/// <summary> /// <summary>
/// Расписание /// Расписание
/// </summary> /// </summary>

View File

@@ -34,6 +34,8 @@
public JobAutoControlResponse? AutoControl { get; set; } public JobAutoControlResponse? AutoControl { get; set; }
public MatchingStatusResponse? MatchingStatus { get; set; }
#region Статистика Old #region Статистика Old
//public TemplateStats? TemplateStatistics { get; set; } //public TemplateStats? TemplateStatistics { get; set; }

View File

@@ -0,0 +1,32 @@
using PARR.DAL.Cache.Models;
namespace PARR.API.Contracts.V1.Responses
{
public class MatchingStatusResponse
{
/// <summary>
/// В целом по искомому объекту, идет ли matching
/// </summary>
public bool IsMatchingObject { get; set; }
/// <summary>
/// Идет ли мэтчинг по Jobs
/// </summary>
public bool IsMatchingJobs { get; set; }
/// <summary>
/// Идет ли мэтчинг по JobGroups
/// </summary>
public bool IsMatchingJobGroups { get; set; }
/// <summary>
/// Детально по Jobs
/// </summary>
public List<MatchingStatusItemDto>? DetailsJobs { get; set; }
/// <summary>
/// Детально по JobGroups
/// </summary>
public List<MatchingStatusItemDto>? DetailsJobGroups { get; set; }
}
}

View File

@@ -16,6 +16,7 @@ using PARR.BLL.Domain.Mq;
using PARR.BLL.Services.Interfaces; using PARR.BLL.Services.Interfaces;
using PARR.Common.Domain; using PARR.Common.Domain;
using PARR.Constants; using PARR.Constants;
using PARR.DAL.Cache.Services;
using PARR.DAL.Contracts; using PARR.DAL.Contracts;
using PARR.DAL.DomainModels; using PARR.DAL.DomainModels;
using PARR.DAL.DomainServices.Interfaces; using PARR.DAL.DomainServices.Interfaces;
@@ -24,7 +25,6 @@ using PARR.DAL.Models.Job;
using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces;
using PARR.DAL.Services.Interfaces.Job; using PARR.DAL.Services.Interfaces.Job;
using PARR.DAL.Services.Interfaces.Unit; using PARR.DAL.Services.Interfaces.Unit;
using System.Net;
using System.Text.Json; using System.Text.Json;
namespace PARR.API.Controllers.V1 namespace PARR.API.Controllers.V1
@@ -45,6 +45,7 @@ namespace PARR.API.Controllers.V1
private readonly MqSettings mqSettings; private readonly MqSettings mqSettings;
private readonly IClientService clientService; private readonly IClientService clientService;
private readonly IJobAutoControlService jobAutoControlService; private readonly IJobAutoControlService jobAutoControlService;
private readonly IMatchingStatusService matchingStatusService;
public JobController( public JobController(
ILogger<JobController> logger, ILogger<JobController> logger,
@@ -59,7 +60,8 @@ namespace PARR.API.Controllers.V1
IMqService mqService, IMqService mqService,
MqSettings mqSettings, MqSettings mqSettings,
IClientService clientService, IClientService clientService,
IJobAutoControlService jobAutoControlService IJobAutoControlService jobAutoControlService,
IMatchingStatusService matchingStatusService
) )
{ {
this.logger = logger; this.logger = logger;
@@ -72,6 +74,7 @@ namespace PARR.API.Controllers.V1
this.mqSettings = mqSettings; this.mqSettings = mqSettings;
this.clientService = clientService; this.clientService = clientService;
this.jobAutoControlService = jobAutoControlService; this.jobAutoControlService = jobAutoControlService;
this.matchingStatusService = matchingStatusService;
} }
/// <summary> /// <summary>
@@ -124,6 +127,11 @@ namespace PARR.API.Controllers.V1
{ {
//jobResponse.TemplatesCount = await templateService.Get().CountAsync(t => t.JobId == jobResponse.Id); //jobResponse.TemplatesCount = await templateService.Get().CountAsync(t => t.JobId == jobResponse.Id);
jobResponse.TemplatesCount = await GetCountTemplatesAsync(jobResponse.Id); jobResponse.TemplatesCount = await GetCountTemplatesAsync(jobResponse.Id);
if (filter.IsFull)
{
jobResponse.MatchingStatus = await GetMatchingStatusAsync(jobResponse.Id);
}
} }
var paginationResponse = new PagedResponse<JobResponse>(response, true).GetPaginatedProps(paginationFilter, query); var paginationResponse = new PagedResponse<JobResponse>(response, true).GetPaginatedProps(paginationFilter, query);
@@ -159,6 +167,7 @@ namespace PARR.API.Controllers.V1
var response = mapper.Map<JobResponse>(job); var response = mapper.Map<JobResponse>(job);
response.TemplatesCount = await GetCountTemplatesAsync(id); //await templateService.Get().CountAsync(t => t.JobId == id); response.TemplatesCount = await GetCountTemplatesAsync(id); //await templateService.Get().CountAsync(t => t.JobId == id);
response.MatchingStatus = await GetMatchingStatusAsync(id);
//var statistics = await GetStatisticsAsync(response.Id); //var statistics = await GetStatisticsAsync(response.Id);
//BindStatistics(response, statistics); //BindStatistics(response, statistics);
@@ -234,6 +243,7 @@ namespace PARR.API.Controllers.V1
var response = mapper.Map<JobResponse>(createdJob); var response = mapper.Map<JobResponse>(createdJob);
// так как мы только что создали Job, то у него нет шаблонов, смело ставим = 0 (ускоряем запрос) // так как мы только что создали Job, то у него нет шаблонов, смело ставим = 0 (ускоряем запрос)
response.TemplatesCount = 0; response.TemplatesCount = 0;
response.MatchingStatus = await GetMatchingStatusAsync(response.Id);
return Created(locationUri, new Response<JobResponse>(response, true)); return Created(locationUri, new Response<JobResponse>(response, true));
} }
@@ -355,6 +365,7 @@ namespace PARR.API.Controllers.V1
var response = mapper.Map<JobResponse>(updatedJob); var response = mapper.Map<JobResponse>(updatedJob);
response.TemplatesCount = await GetCountTemplatesAsync(id); //await templateService.Get().CountAsync(t => t.JobId == id); response.TemplatesCount = await GetCountTemplatesAsync(id); //await templateService.Get().CountAsync(t => t.JobId == id);
response.MatchingStatus = await GetMatchingStatusAsync(id);
//не используется //не используется
//var statistics = await GetStatisticsAsync(response.Id); //var statistics = await GetStatisticsAsync(response.Id);
@@ -657,6 +668,18 @@ namespace PARR.API.Controllers.V1
return await templateService.Get().CountAsync(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used); return await templateService.Get().CountAsync(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used);
} }
/// <summary>
/// Получить статус matching`a
/// </summary>
/// <param name="jobId"></param>
/// <returns></returns>
private async Task<MatchingStatusResponse?> GetMatchingStatusAsync(Guid jobId)
{
var statusMatching = await matchingStatusService.GetStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
return mapper.Map<MatchingStatusResponse>(statusMatching);
}
} }

View File

@@ -14,6 +14,7 @@ using PARR.API.Services.Interfaces;
using PARR.Constants; using PARR.Constants;
using PARR.DAL.Contracts; using PARR.DAL.Contracts;
using PARR.DAL.DomainModels; using PARR.DAL.DomainModels;
using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.Models; using PARR.DAL.Models;
using PARR.DAL.Models.Job; using PARR.DAL.Models.Job;
using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces;
@@ -36,6 +37,7 @@ namespace PARR.API.Controllers.V1
private readonly IValidator<JobGroupRequest> validator; private readonly IValidator<JobGroupRequest> validator;
private readonly IJobGroupTypeService jobGroupTypeService; private readonly IJobGroupTypeService jobGroupTypeService;
private readonly SettingsFromDb settingsFromDb; private readonly SettingsFromDb settingsFromDb;
private readonly IMatchingStatusService matchingStatusService;
public JobGroupController( public JobGroupController(
ILogger<JobController> logger, ILogger<JobController> logger,
@@ -46,7 +48,8 @@ namespace PARR.API.Controllers.V1
IEsppSchTypeConfigService esppConfigService, IEsppSchTypeConfigService esppConfigService,
IValidator<JobGroupRequest> validator, IValidator<JobGroupRequest> validator,
IJobGroupTypeService jobGroupTypeService, IJobGroupTypeService jobGroupTypeService,
SettingsFromDb settingsFromDb SettingsFromDb settingsFromDb,
IMatchingStatusService matchingStatusService
) )
{ {
this.logger = logger; this.logger = logger;
@@ -58,6 +61,7 @@ namespace PARR.API.Controllers.V1
this.validator = validator; this.validator = validator;
this.jobGroupTypeService = jobGroupTypeService; this.jobGroupTypeService = jobGroupTypeService;
this.settingsFromDb = settingsFromDb; this.settingsFromDb = settingsFromDb;
this.matchingStatusService = matchingStatusService;
} }
@@ -366,7 +370,7 @@ namespace PARR.API.Controllers.V1
/// <summary> /// <summary>
/// Заполнить поля планировщика /// Заполнить обязательные поля
/// </summary> /// </summary>
/// <param name="jobGroupResponse"></param> /// <param name="jobGroupResponse"></param>
/// <returns></returns> /// <returns></returns>
@@ -390,6 +394,8 @@ namespace PARR.API.Controllers.V1
jobGroupResponse.Schedule = scheduleResponse; jobGroupResponse.Schedule = scheduleResponse;
jobGroupResponse.JobsCount = await jobService.Get().CountAsync(t => t.GroupId == jobGroupResponse.Id); jobGroupResponse.JobsCount = await jobService.Get().CountAsync(t => t.GroupId == jobGroupResponse.Id);
jobGroupResponse.MatchingStatus = await GetMatchingStatusAsync(jobGroupResponse.Id);
} }
@@ -419,5 +425,18 @@ namespace PARR.API.Controllers.V1
return null; return null;
} }
} }
/// <summary>
/// Получить статус matching`a
/// </summary>
/// <param name="jobGroupId"></param>
/// <returns></returns>
private async Task<MatchingStatusResponse?> GetMatchingStatusAsync(Guid jobGroupId)
{
var statusMatching = await matchingStatusService.GetStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup);
return mapper.Map<MatchingStatusResponse>(statusMatching);
}
} }
} }

View File

@@ -9,6 +9,8 @@ using PARR.BLL.Domain.Mq;
using PARR.BLL.Services.Interfaces; using PARR.BLL.Services.Interfaces;
using PARR.Common.Domain; using PARR.Common.Domain;
using PARR.Constants; using PARR.Constants;
using PARR.DAL.Cache.Services;
using PARR.DAL.DomainServices.Interfaces;
using System.Text.Json; using System.Text.Json;
namespace PARR.API.Controllers.V1 namespace PARR.API.Controllers.V1
@@ -20,13 +22,15 @@ namespace PARR.API.Controllers.V1
private readonly IUriService uriService; private readonly IUriService uriService;
private readonly MqSettings mqSettings; private readonly MqSettings mqSettings;
private readonly IClientService clientService; private readonly IClientService clientService;
private readonly IMatchingStatusService matchingStatusService;
public SyncTaskController( public SyncTaskController(
ILogger<SyncTaskController> logger, ILogger<SyncTaskController> logger,
IMqService mqService, IMqService mqService,
IUriService uriService, IUriService uriService,
MqSettings mqSettings, MqSettings mqSettings,
IClientService clientService IClientService clientService,
IMatchingStatusService matchingStatusService
) )
{ {
this.logger = logger; this.logger = logger;
@@ -34,6 +38,7 @@ namespace PARR.API.Controllers.V1
this.uriService = uriService; this.uriService = uriService;
this.mqSettings = mqSettings; this.mqSettings = mqSettings;
this.clientService = clientService; this.clientService = clientService;
this.matchingStatusService = matchingStatusService;
} }
/// <summary> /// <summary>
@@ -45,6 +50,14 @@ namespace PARR.API.Controllers.V1
//todo: Валидатор! Валидатор то забыли!!! //todo: Валидатор! Валидатор то забыли!!!
// проверяем, если уже идет синхронизация по этому объекту, то ахтунг, ошибка
var matchingStatus = await matchingStatusService.GetStatusAsync(request.ObjectId, request.EntityType);
if (matchingStatus.IsMatchingObject)
{
logger.LogWarning("Прекращена попытка повторного формирования задания для matcher. objectId: {objectId}, entityType: {entityType}, action: {action}", request.ObjectId, request.EntityType, request.Action);
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = $"Уже выполняется задание синхронизации этого объекта" } }));
}
//24b3529a-dd43-444a-b10e-ac54fefd046a //24b3529a-dd43-444a-b10e-ac54fefd046a
var matchTemplateTask = new TemplateMatcherMq var matchTemplateTask = new TemplateMatcherMq
{ {

View File

@@ -437,6 +437,14 @@ namespace PARR.API.MappingProfiles
#endregion #endregion
CreateMap<ShortcodeInfoDto, ShortcodeResponse>(); CreateMap<ShortcodeInfoDto, ShortcodeResponse>();
#region MatchingStatus -> MatchingStatusResponse
CreateMap<MatchingStatus, MatchingStatusResponse>()
.ForMember(d => d.DetailsJobGroups, o => o.MapFrom(s => s.DetailsJobGroups != null ? s.DetailsJobGroups.Select(t => t.Data) : null))
.ForMember(d => d.DetailsJobs, o => o.MapFrom(s => s.DetailsJobs != null ? s.DetailsJobs.Select(t => t.Data) : null));
#endregion
} }
} }

View File

@@ -6,9 +6,9 @@ namespace PARR.DAL.Cache.Models
/// <summary> /// <summary>
/// КЭШ модель, состояние matching`a /// КЭШ модель, состояние matching`a
/// </summary> /// </summary>
public class MatchingStatus : IBaseCache<MatchingStatusDto> public class MatchingStatusItem : IBaseCache<MatchingStatusItemDto>
{ {
public required MatchingStatusDto Data { get; set; } public required MatchingStatusItemDto Data { get; set; }
public DateTimeOffset Timestamp { get; set; } public DateTimeOffset Timestamp { get; set; }
@@ -16,7 +16,7 @@ namespace PARR.DAL.Cache.Models
} }
public class MatchingStatusDto public class MatchingStatusItemDto
{ {
public DateTimeOffset DateStart { get; set; } public DateTimeOffset DateStart { get; set; }

View File

@@ -1,63 +0,0 @@
using PARR.Constants;
using PARR.DAL.Cache.Models;
using PARR.DAL.Cache.Services.Base;
namespace PARR.DAL.Cache.Services
{
internal class CacheMatchingStatusService : ICacheMatchingStatusService
{
private readonly IRedisCacheService redisCacheService;
public CacheMatchingStatusService(IRedisCacheService redisCacheService)
{
this.redisCacheService = redisCacheService;
}
public async Task DeleteCacheAsync(Guid id, SyncTaskEntityTypeEnum type)
{
var key = GetKey(id, type);
await redisCacheService.DeleteCachedDataAsync(key);
}
public async Task<MatchingStatus?> GetDataAsync(Guid id, SyncTaskEntityTypeEnum type)
{
var key = GetKey(id, type);
return await redisCacheService.GetCachedDataAsync<MatchingStatus>(key);
}
public async Task SetCacheAsync(Guid id, SyncTaskEntityTypeEnum type, MatchingStatusDto data, TimeSpan cacheDuration)
{
var key = GetKey(id, type);
await redisCacheService.SetCachedDataAsync(key, data, cacheDuration);
}
private string GetKey(Guid id, SyncTaskEntityTypeEnum type)
{
var typeStr = "";
switch (type)
{
case SyncTaskEntityTypeEnum.Job:
typeStr = "j";
break;
case SyncTaskEntityTypeEnum.JobGroup:
typeStr = "g";
break;
case SyncTaskEntityTypeEnum.Template:
typeStr = "t";
break;
default:
break;
}
return redisCacheService.GetKey(new string[] { "ms", typeStr, id.ToString() });
}
}
}

View File

@@ -1,18 +0,0 @@
using PARR.Constants;
using PARR.DAL.Cache.Models;
namespace PARR.DAL.Cache.Services
{
/// <summary>
/// Сервис - статус matching`a
/// </summary>
public interface ICacheMatchingStatusService
{
Task<MatchingStatus?> GetDataAsync(Guid id, SyncTaskEntityTypeEnum type);
Task SetCacheAsync(Guid id, SyncTaskEntityTypeEnum type, MatchingStatusDto data, TimeSpan cacheDuration);
Task DeleteCacheAsync(Guid id, SyncTaskEntityTypeEnum type);
}
}

View File

@@ -0,0 +1,36 @@
using PARR.DAL.Cache.Models;
namespace PARR.DAL.DomainModels
{
/// <summary>
/// Статус matching`a
/// </summary>
public class MatchingStatus
{
/// <summary>
/// В целом по искомому объекту, идет ли matching
/// </summary>
public bool IsMatchingObject => IsMatchingJobs || IsMatchingJobGroups;
/// <summary>
/// Идет ли мэтчинг по Jobs
/// </summary>
public bool IsMatchingJobs => DetailsJobs?.Any() ?? false;
/// <summary>
/// Идет ли мэтчинг по JobGroups
/// </summary>
public bool IsMatchingJobGroups => DetailsJobGroups?.Any() ?? false;
/// <summary>
/// Детально из КЭШ по Jobs
/// </summary>
public List<MatchingStatusItem>? DetailsJobs { get; set; }
/// <summary>
/// Детально из КЭШ по JobGroups
/// </summary>
public List<MatchingStatusItem>? DetailsJobGroups { get; set; }
}
}

View File

@@ -0,0 +1,120 @@
using Microsoft.EntityFrameworkCore;
using PARR.Constants;
using PARR.DAL.Cache.Models;
using PARR.DAL.Cache.Services.Base;
using PARR.DAL.DomainModels;
using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.Services.Interfaces.Job;
namespace PARR.DAL.DomainServices.Implementations
{
internal class MatchingStatusService : IMatchingStatusService
{
private readonly IRedisCacheService redisCacheService;
private readonly IJobService jobService;
private readonly IJobGroupService jobGroupService;
public MatchingStatusService(
IRedisCacheService redisCacheService,
IJobService jobService,
IJobGroupService jobGroupService
)
{
this.redisCacheService = redisCacheService;
this.jobService = jobService;
this.jobGroupService = jobGroupService;
}
public async Task DeleteMatchingStatusAsync(Guid objectId, SyncTaskEntityTypeEnum type)
{
var key = GetKey(objectId, type);
await redisCacheService.DeleteCachedDataAsync(key);
}
public async Task<MatchingStatus> GetStatusAsync(Guid objectId, SyncTaskEntityTypeEnum type)
{
var jobGroupIdList = new List<Guid>();
var jobIdList = new List<Guid>();
//формируем список jobId и jobGroupId для поиска в КЭШ, происходит ли у кого-то из них сейчас matching
switch (type)
{
case SyncTaskEntityTypeEnum.Job:
var job = await jobService.Get().FirstOrDefaultAsync(t => t.Id == objectId);
if (job != null)
{
jobIdList.Add(job.Id);
jobGroupIdList = await jobGroupService.Get().Where(t => t.Id == job.GroupId).Select(t => t.Id).ToListAsync();
}
break;
case SyncTaskEntityTypeEnum.JobGroup:
var jobGroup = await jobGroupService.Get().FirstOrDefaultAsync(t => t.Id == objectId);
if (jobGroup != null)
{
jobGroupIdList.Add(jobGroup.Id);
jobIdList = await jobService.Get().Where(t => t.GroupId == jobGroup.Id).Select(t => t.Id).ToListAsync();
}
break;
case SyncTaskEntityTypeEnum.Template:
throw new Exception("Нет реализации получения кэша статуса matching для template");
//todo: У нас вообще нет matching для Template, позже надо удалить это вообще из Enum SyncTaskEntityTypeEnum
break;
}
// Если списки пустые, мало ли, говорим что нет matching`a
if (!jobGroupIdList.Any() && !jobIdList.Any())
return new MatchingStatus { DetailsJobGroups = null, DetailsJobs = null };
// Ищем в КЭШ
var jobGroupDetails = new List<MatchingStatusItem>();
foreach (var jobGroupId in jobGroupIdList)
{
var cacheResult = await redisCacheService.GetCachedDataAsync<MatchingStatusItem>(GetKey(jobGroupId, SyncTaskEntityTypeEnum.JobGroup));
if (cacheResult != null)
jobGroupDetails.Add(cacheResult);
}
var jobDetails = new List<MatchingStatusItem>();
foreach (var jobId in jobIdList)
{
var cacheResult = await redisCacheService.GetCachedDataAsync<MatchingStatusItem>(GetKey(jobId, SyncTaskEntityTypeEnum.Job));
if (cacheResult != null)
jobDetails.Add(cacheResult);
}
return new MatchingStatus
{
DetailsJobGroups = jobGroupDetails.Any() ? jobGroupDetails : null,
DetailsJobs = jobDetails.Any() ? jobDetails : null
};
}
public async Task SetMatchingStatusAsync(Guid objectId, SyncTaskEntityTypeEnum type, MatchingStatusItem data, TimeSpan cacheDuration)
{
var key = GetKey(objectId, type);
await redisCacheService.SetCachedDataAsync(key, data, cacheDuration);
}
private string GetKey(Guid id, SyncTaskEntityTypeEnum type)
{
var typeStr = "";
switch (type)
{
case SyncTaskEntityTypeEnum.Job:
typeStr = "j";
break;
case SyncTaskEntityTypeEnum.JobGroup:
typeStr = "g";
break;
case SyncTaskEntityTypeEnum.Template:
typeStr = "t";
break;
default:
break;
}
return redisCacheService.GetKey(new string[] { "ms", typeStr, id.ToString() });
}
}
}

View File

@@ -0,0 +1,38 @@
using PARR.Constants;
using PARR.DAL.Cache.Models;
using PARR.DAL.DomainModels;
namespace PARR.DAL.DomainServices.Interfaces
{
/// <summary>
/// Сервис по управлению статусом Matching`a
/// </summary>
public interface IMatchingStatusService
{
/// <summary>
/// Получить статус matching`a
/// </summary>
/// <param name="objectId"></param>
/// <param name="type"></param>
/// <returns></returns>
Task<MatchingStatus> GetStatusAsync(Guid objectId, SyncTaskEntityTypeEnum type);
/// <summary>
/// Установить/обновить статус matching`a
/// </summary>
/// <param name="objectId"></param>
/// <param name="type"></param>
/// <param name="data"></param>
/// <param name="cacheDuration"></param>
/// <returns></returns>
Task SetMatchingStatusAsync(Guid objectId, SyncTaskEntityTypeEnum type, MatchingStatusItem data, TimeSpan cacheDuration);
/// <summary>
/// Удалить статус matching`a
/// </summary>
/// <param name="objectId"></param>
/// <param name="type"></param>
/// <returns></returns>
Task DeleteMatchingStatusAsync(Guid objectId, SyncTaskEntityTypeEnum type);
}
}

View File

@@ -53,8 +53,6 @@ namespace PARR.DAL
services.AddTransient<IRedisCacheService, RedisCacheService>(); services.AddTransient<IRedisCacheService, RedisCacheService>();
services.AddTransient<ICacheMatchingStatusService, CacheMatchingStatusService>();
#endregion #endregion
#region InfluxDb #region InfluxDb
@@ -141,6 +139,7 @@ namespace PARR.DAL
#region DomainServces #region DomainServces
services.AddTransient<IShortcodesService, ShortcodesService>(); services.AddTransient<IShortcodesService, ShortcodesService>();
services.AddTransient<IUnitFilterService, UnitFilterService>(); services.AddTransient<IUnitFilterService, UnitFilterService>();
services.AddTransient<IMatchingStatusService, MatchingStatusService>();
#endregion #endregion