Compare commits
52 Commits
3b6df1f617
...
min-relati
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a087958fd5 | ||
|
|
4ee2880a8f | ||
|
|
497d241caf | ||
|
|
4c80e247f5 | ||
|
|
83017c6bee | ||
|
|
323231f907 | ||
|
|
b1c3d90b40 | ||
|
|
511721b798 | ||
|
|
9f42a733e7 | ||
|
|
50556ef605 | ||
|
|
9194d127d2 | ||
|
|
33b0576ccc | ||
|
|
33cdc29e80 | ||
|
|
d7d4713953 | ||
|
|
4af552b29e | ||
|
|
9d3273191b | ||
|
|
246e8e69ae | ||
|
|
e9c9e9f03e | ||
|
|
fb9578fec0 | ||
|
|
9c27f81fe8 | ||
|
|
36e866f637 | ||
|
|
1affa34b07 | ||
|
|
3c9e03adbb | ||
|
|
143ae10190 | ||
|
|
f8e16e9496 | ||
|
|
55ac424f1a | ||
|
|
cb3c98ed8c | ||
|
|
16f97904eb | ||
|
|
52bd0d8ce1 | ||
|
|
65ef0dec92 | ||
|
|
7574cd31c2 | ||
|
|
8a44f43a34 | ||
|
|
16ba4f21a2 | ||
|
|
c1ff4d0258 | ||
|
|
5c2d1c31b5 | ||
|
|
806633aaf7 | ||
|
|
0d7969da44 | ||
|
|
4f2b2c204e | ||
|
|
df6ed47bd9 | ||
|
|
8dfb2587b2 | ||
|
|
4a8a67247d | ||
|
|
8151ea89b9 | ||
|
|
fe4461ee4e | ||
|
|
108eb05853 | ||
|
|
1fb5cbd2e8 | ||
|
|
c0720b38de | ||
|
|
db270dbb1c | ||
|
|
dd206b44e9 | ||
|
|
21e00ce42e | ||
|
|
1b21ee4a79 | ||
|
|
a438f18480 | ||
|
|
19cb6b50bf |
@@ -20,6 +20,7 @@ variables:
|
||||
PROD_TEMPLATE_UPDATER: "parr/parr-template-updater"
|
||||
PROD_WORKLOAD_BUILDER: "parr/parr-workload-builder"
|
||||
PROD_TASK_RECONCILIATION: "parr/parr-task-reconciliation"
|
||||
PROD_SNAPSHOTS: "parr/parr-snapshots"
|
||||
|
||||
|
||||
stages:
|
||||
@@ -1009,4 +1010,62 @@ prod_task_reconciliation_deploy:
|
||||
matrix:
|
||||
- RUNNER: shell-api-swarm-01
|
||||
tags:
|
||||
- ${RUNNER}
|
||||
- ${RUNNER}
|
||||
|
||||
|
||||
### SNAPSHOTS PROD ###
|
||||
prod_snapshots_build:
|
||||
stage: build
|
||||
only:
|
||||
- /^sn[0-9]+\.[0-9]+\.[0-9]+$/
|
||||
except:
|
||||
- branches
|
||||
services:
|
||||
- name: docker:20.10.21-dind
|
||||
command: [
|
||||
"--insecure-registry=10.99.253.167:8090",
|
||||
"--registry-mirror=http://10.99.253.167:8090",
|
||||
"--insecure-registry=harbor.dvgd.rzd",
|
||||
"--tls=false"
|
||||
]
|
||||
variables:
|
||||
DOCKER_HOST: tcp://docker:2375
|
||||
DOCKER_DRIVER: overlay2
|
||||
DOCKER_TLS_CERTDIR: ""
|
||||
script:
|
||||
- APP_VERSION=$(echo $CI_COMMIT_TAG | tr -d sn)
|
||||
- IMAGE_VERSION=$(echo $CI_COMMIT_TAG | sed 's/sn/v/g')
|
||||
- AUTHOR=$CI_COMMIT_AUTHOR
|
||||
- |
|
||||
docker build \
|
||||
-t $REPO/$PROD_SNAPSHOTS:$IMAGE_VERSION \
|
||||
-t $REPO/$PROD_SNAPSHOTS:latest \
|
||||
-t $PROD_SNAPSHOTS:$IMAGE_VERSION \
|
||||
-t $PROD_SNAPSHOTS:latest \
|
||||
--build-arg app_version=$APP_VERSION \
|
||||
--build-arg commit_author="$AUTHOR" \
|
||||
-f PARR.SnapshotWorker/Dockerfile .
|
||||
- docker login -u $HARBOR_PUSH_USER -p $HARBOR_PUSH_PASS $REPO
|
||||
- docker push --all-tags $REPO/$PROD_SNAPSHOTS
|
||||
tags:
|
||||
- docker
|
||||
|
||||
|
||||
prod_snapshots_deploy:
|
||||
stage: deploy
|
||||
environment:
|
||||
name: parr-snapshots
|
||||
only:
|
||||
- /^sn[0-9]+\.[0-9]+\.[0-9]+$/
|
||||
except:
|
||||
- branches
|
||||
script:
|
||||
- IMAGE_VERSION=$(echo $CI_COMMIT_TAG | sed 's/sn/v/g')
|
||||
- docker login -u $HARBOR_PULL_USER -p $HARBOR_PULL_PASS $REPO
|
||||
#- tag=$CI_COMMIT_TAG docker compose up -d
|
||||
- tag=$IMAGE_VERSION docker stack deploy -c docker-compose.snapshots.yml parr-snapshots --with-registry-auth
|
||||
parallel:
|
||||
matrix:
|
||||
- RUNNER: shell-api-swarm-01
|
||||
tags:
|
||||
- ${RUNNER}
|
||||
|
||||
21
.gitlab/issue_templates/Bug.md
Normal file
21
.gitlab/issue_templates/Bug.md
Normal file
@@ -0,0 +1,21 @@
|
||||
### 📋 Описание бага
|
||||
Кратко и понятно опишите, что идет не так.
|
||||
|
||||
### 🔄 Шаги для воспроизведения
|
||||
1. Перейти на страницу...
|
||||
2. Нажать кнопку...
|
||||
3. Ввести в поле поиска...
|
||||
|
||||
### ❌ Фактический результат
|
||||
Что произошло на самом деле? (Например: Приложение завершилось с ошибкой 500).
|
||||
|
||||
### ✅ Ожидаемый результат
|
||||
Что должно было произойти? (Например: Появился список найденных товаров).
|
||||
|
||||
### 🖥️ Окружение
|
||||
* **ОС:** Windows 11 / macOS Sequoia
|
||||
* **Браузер / Версия:** Chrome 124 / iOS App v2.1
|
||||
* **Стенд:** Staging / Production
|
||||
|
||||
### 📎 Вложения
|
||||
Скриншоты, гифки или логи, которые помогают понять проблему.
|
||||
23
NuGet.config
23
NuGet.config
@@ -1,13 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||
<add key="Nexus" value="http://10.99.253.167:8081/repository/nuget-group/" allowInsecureConnections="true"/>
|
||||
<add key="Microsoft Visual Studio Offline Packages" value="C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\" />
|
||||
</packageSources>
|
||||
<disabledPackageSources>
|
||||
<add key="Microsoft Visual Studio Offline Packages" value="true" />
|
||||
<add key="nuget.org" value="true" />
|
||||
</disabledPackageSources>
|
||||
</configuration>
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||
<add key="Nexus-SVRW" value="http://nexus.svrw.oao.rzd/repository/nuget-group/" allowInsecureConnections="true"/>
|
||||
<add key="Nexus-DVGD" value="http://10.99.253.167:8081/repository/nuget-group/index.json" protocolVersion="3" allowInsecureConnections="true"/>
|
||||
<add key="Microsoft Visual Studio Offline Packages" value="C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\" />
|
||||
</packageSources>
|
||||
<disabledPackageSources>
|
||||
<add key="Microsoft Visual Studio Offline Packages" value="true" />
|
||||
<add key="nuget.org" value="true" />
|
||||
<add key="Nexus-SVRW" value="true" />
|
||||
</disabledPackageSources>
|
||||
</configuration>
|
||||
@@ -27,7 +27,14 @@ FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
|
||||
# Fixes an old version TLS (AIH IT GVC)
|
||||
RUN sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /etc/ssl/openssl.cnf
|
||||
# 1. Принудительно настраиваем OpenSSL 3 на игнорирование непредвиденных EOF (UnsafeLegacyRenegotiation и IgnoreUnexpectedEOF)
|
||||
RUN sed -i 's/providers = provider_sect/providers = provider_sect\nssl_conf = ssl_sect/' /etc/ssl/openssl.cnf \
|
||||
&& printf "\n[ssl_sect]\nsystem_default = system_default_sect\n" >> /etc/ssl/openssl.cnf \
|
||||
&& printf "\n[system_default_sect]\nOptions = UnsafeLegacyRenegotiation,IgnoreUnexpectedEOF\nMinProtocol = TLSv1\nCipherString = DEFAULT@SECLEVEL=0\n" >> /etc/ssl/openssl.cnf
|
||||
|
||||
# 2. Оставляем глобальные переменные совместимости для самого рантайма .NET 9
|
||||
ENV DOTNET_AppContext_Switch_System_Net_Security_UseUnsafeOldTlsRuntimeBehavior=true
|
||||
ENV DOTNET_AppContext_Switch_Microsoft_Data_SqlClient_UseOldTlsValue=true
|
||||
ENV DOTNET_SYSTEM_NET_SECURITY_ALLOWUNSECUREDEFAULTS=true
|
||||
|
||||
ENTRYPOINT ["dotnet", "PARR.AIHITLoaderWorker.dll"]
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"AihitConnection": "Data Source=10.248.19.97; Initial Catalog=mao2;User ID=awhit-ipp-parr;pwd=ET3h$9y1LH#D;TrustServerCertificate=true;"
|
||||
"AihitConnection": "Data Source=10.248.19.97; Initial Catalog=mao2;User ID=awhit-ipp-parr;pwd=ET3h$9y1LH#D;Encrypt=Optional;TrustServerCertificate=true;MultiSubnetFailover=True;"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.17" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -258,7 +258,7 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
}
|
||||
|
||||
|
||||
private async Task SyncValuesAsync(List<string?> values)
|
||||
private async Task SyncValuesAsync(List<string?> values)
|
||||
{
|
||||
bool hasNull = values.Any(v => v is null);
|
||||
var normalizedNoneNullValues = values
|
||||
@@ -288,6 +288,7 @@ private async Task SyncValuesAsync(List<string?> values)
|
||||
if (hasNull && !existingValuesInDb.Contains(null))
|
||||
newValuesToInsert.Add(null);
|
||||
|
||||
// Если нет новых значений, выходим БЕЗ вызова CommitAsync
|
||||
if (newValuesToInsert.Count == 0)
|
||||
{
|
||||
logger.LogDebug("Нет новых значений для добавления в базу данных");
|
||||
@@ -302,15 +303,19 @@ private async Task SyncValuesAsync(List<string?> values)
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// Только добавляем в контекст, НЕ коммитим
|
||||
if (!await unitFieldValueRepository.AddRangeAsync(newFieldValues))
|
||||
{
|
||||
logger.LogError("Не удалось добавить {Count} новых значений FieldValues в контекст", newValuesToInsert.Count);
|
||||
return;
|
||||
}
|
||||
else
|
||||
// Коммит выполняется ТОЛЬКО если были добавлены новые сущности
|
||||
if (!await unitFieldValueRepository.CommitAsync())
|
||||
{
|
||||
logger.LogDebug("Подготовлено {Count} новых значений FieldValues для сохранения", newValuesToInsert.Count);
|
||||
logger.LogError("Не удалось сохранить {Count} новых значений FieldValues в базу данных", newValuesToInsert.Count);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogDebug("Успешно сохранено {Count} новых значений FieldValues", newValuesToInsert.Count);
|
||||
}
|
||||
|
||||
|
||||
@@ -318,6 +323,7 @@ private async Task SyncValuesAsync(List<string?> values)
|
||||
{
|
||||
var newFields = fieldsFromAihit.Where(t => !fieldsFromDB.Any(f => IsStringEqual(f.AihitName, t))).ToList();
|
||||
|
||||
// Если нет новых полей, выходим БЕЗ вызова CommitAsync
|
||||
if (!newFields.Any())
|
||||
{
|
||||
logger.LogDebug("Нет новых полей для добавления в базу данных");
|
||||
@@ -335,19 +341,24 @@ private async Task SyncValuesAsync(List<string?> values)
|
||||
EsppName = null,
|
||||
};
|
||||
|
||||
// Только добавляем в контекст, НЕ коммитим
|
||||
if (!await unitFieldRepository.CreateAsync(field))
|
||||
{
|
||||
logger.LogError("Не удалось добавить поле в контекст: {FieldName}", item);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Коммит выполняется ТОЛЬКО после успешного добавления конкретного поля
|
||||
if (!await unitFieldRepository.CommitAsync())
|
||||
{
|
||||
logger.LogError("Не удалось сохранить поле в базу данных: {FieldName}", item);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("Подготовлено поле для сохранения: {FieldName}", item);
|
||||
logger.LogDebug("Успешно сохранено поле: {FieldName}", item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool IsStringEqual(string? value1, string? value2)
|
||||
{
|
||||
return string.Equals(
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.17" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,236 +1,261 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.AIHITRelationshipsSyncer.Models;
|
||||
using PARR.AIHITRelationshipsSyncer.Services.Interfaces;
|
||||
using PARR.AIHITRelationshipsSyncer.Settings;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
using PARR.Domain.Entities.Unit;
|
||||
using PARR.Domain.Settings;
|
||||
|
||||
namespace PARR.AIHITRelationshipsSyncer.Services.Implementations
|
||||
namespace PARR.AIHITRelationshipsSyncer.Services.Implementations;
|
||||
|
||||
internal class RelationshipsSyncService : IRelationshipsSyncService
|
||||
{
|
||||
internal class RelationshipsSyncService : IRelationshipsSyncService
|
||||
private readonly ILogger<RelationshipsSyncService> _logger;
|
||||
private readonly IUnitRepository _unitRepository;
|
||||
private readonly SettingsFromDb _settingsFromDb;
|
||||
|
||||
public RelationshipsSyncService(
|
||||
ILogger<RelationshipsSyncService> logger,
|
||||
IUnitRepository unitRepository,
|
||||
SettingsFromDb settingsFromDb)
|
||||
{
|
||||
private readonly ILogger<RelationshipsSyncService> logger;
|
||||
private readonly IUnitRepository unitService;
|
||||
private readonly WorkerSettings workerSettings;
|
||||
|
||||
public RelationshipsSyncService(
|
||||
ILogger<RelationshipsSyncService> logger,
|
||||
IUnitRepository unitService,
|
||||
WorkerSettings workerSettings
|
||||
)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.unitService = unitService;
|
||||
this.workerSettings = workerSettings;
|
||||
}
|
||||
|
||||
|
||||
public async Task SyncAsync(List<AihitData> aihitdata)
|
||||
{
|
||||
var validPairs = ValidateInput(aihitdata);
|
||||
if (!validPairs.Any()) return;
|
||||
|
||||
var sourceUnitNames = ExtractUnitNames(validPairs);
|
||||
var existingUnits = await GetOrCreateUnitsAsync(sourceUnitNames);
|
||||
|
||||
var (toRemove, toAdd) = await GetDeltaAsync(validPairs);
|
||||
|
||||
await RemoveRelationshipsAsync(toRemove);
|
||||
await AddRelationshipsAsync(toAdd, existingUnits);
|
||||
|
||||
await FinalCommitAsync(toRemove.Count, toAdd.Count);
|
||||
}
|
||||
|
||||
private List<(string Parent, string Child)> ValidateInput(List<AihitData> data)
|
||||
{
|
||||
if (data == null || data.Count == 0)
|
||||
{
|
||||
logger.LogWarning("Получены пустые или null данные из АИХ ИТ. Пропускаю синхронизацию");
|
||||
return null!;
|
||||
}
|
||||
|
||||
var validPairs = data
|
||||
.Where(t => !string.IsNullOrWhiteSpace(t.ParentName) && !string.IsNullOrWhiteSpace(t.ChildName))
|
||||
.Where(t => t.ParentName != t.ChildName)
|
||||
.Select(t => (
|
||||
Parent: t.ParentName!.Trim().ToUpperInvariant(),
|
||||
Child: t.ChildName!.Trim().ToUpperInvariant()
|
||||
))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
if (validPairs.Count == 0)
|
||||
{
|
||||
logger.LogWarning("После нормализации не осталось валидных связей. Пропускаю синхронизацию");
|
||||
return new();
|
||||
}
|
||||
|
||||
logger.LogInformation("Получено {Count} валидных связей", validPairs.Count);
|
||||
return validPairs;
|
||||
}
|
||||
|
||||
|
||||
private static List<string> ExtractUnitNames(List<(string Parent, string Child)> pairs)
|
||||
{
|
||||
return pairs
|
||||
.SelectMany(x => new[] { x.Parent, x.Child })
|
||||
.Distinct()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
private async Task<Dictionary<string, Unit>> GetOrCreateUnitsAsync(List<string> names)
|
||||
{
|
||||
var existing = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.ToDictionaryAsync(u => u.Name, StringComparer.Ordinal);
|
||||
|
||||
var missing = names.Except(existing.Keys).ToList();
|
||||
if (!missing.Any()) return existing;
|
||||
|
||||
logger.LogInformation("Создание отсутствующих Unit:{Count}", missing.Count);
|
||||
foreach (var name in missing)
|
||||
{
|
||||
var unit = new Unit
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = name,
|
||||
DateCreated = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
if (await unitService.CreateAsync(unit))
|
||||
{
|
||||
logger.LogInformation("Создан Unit: {Name}", name);
|
||||
existing[name] = unit;
|
||||
}
|
||||
else
|
||||
logger.LogError("Не удалось создать Unit: {Name}", name);
|
||||
}
|
||||
|
||||
await unitService.CommitAsync();
|
||||
return existing;
|
||||
}
|
||||
|
||||
|
||||
private async Task<(List<ExistingRelationship> ToRemove, List<(string, string)> ToAdd)> GetDeltaAsync(List<(string Parent, string Child)> sourcePairs)
|
||||
{
|
||||
var allExistings = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Include(u => u.ChildUnits).ThenInclude(r => r.ChildUnit)
|
||||
.Where(u => u.ChildUnits.Any())
|
||||
.SelectMany(u => u.ChildUnits, (parent, relationship) => new ExistingRelationship
|
||||
{
|
||||
ParentName = parent.Name,
|
||||
ChildName = relationship.ChildUnit!.Name,
|
||||
Relationship = relationship
|
||||
}
|
||||
)
|
||||
.ToListAsync();
|
||||
|
||||
var sourceSet = new HashSet<(string, string)>(sourcePairs);
|
||||
var existingSet = new HashSet<(string, string)>(allExistings.Select(x => (x.ParentName, x.ChildName)));
|
||||
|
||||
var toRemove = allExistings.Where(r => !sourceSet.Contains((r.ParentName, r.ChildName))).ToList();
|
||||
var toAdd = sourcePairs.Where(p => !existingSet.Contains(p)).ToList();
|
||||
|
||||
logger.LogInformation("Связей для удаления: {ToRemove}, для добавления: {ToAdd}", toRemove.Count, toAdd.Count);
|
||||
return (toRemove, toAdd);
|
||||
}
|
||||
|
||||
|
||||
private async Task RemoveRelationshipsAsync(List<ExistingRelationship> toRemove)
|
||||
{
|
||||
if (!toRemove.Any()) return;
|
||||
|
||||
var parentNames = toRemove.Select(r => r.ParentName).Distinct().ToList();
|
||||
|
||||
var parents = await unitService.Get()
|
||||
.Include(u => u.ChildUnits)
|
||||
.Where(u => parentNames.Contains(u.Name))
|
||||
.ToDictionaryAsync(u => u.Name, StringComparer.Ordinal);
|
||||
|
||||
foreach (var rel in toRemove)
|
||||
{
|
||||
if (!parents.TryGetValue(rel.ParentName, out var parent))
|
||||
{
|
||||
logger.LogWarning("Родитель '{Parent}' не найден при попытке удаления связи - '{Child}'", rel.ParentName, rel.ChildName);
|
||||
continue;
|
||||
}
|
||||
|
||||
var relationshipToRemove = parent.ChildUnits
|
||||
.FirstOrDefault(r => r.ChildUnitId == rel.Relationship.ChildUnitId);
|
||||
|
||||
if (relationshipToRemove != null)
|
||||
{
|
||||
logger.LogInformation("Удаление связи: '{Parent}' - '{Child}'", rel.ParentName, rel.ChildName);
|
||||
parent.ChildUnits.Remove(relationshipToRemove);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("Связь '{Parent}'-'{Child}' не найдена в коллекции ChildUnits для удаления", rel.ParentName, rel.ChildName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddRelationshipsAsync(List<(string Parent, string Child)> toAdd, Dictionary<string, Unit> allUnits)
|
||||
{
|
||||
if (!toAdd.Any()) return;
|
||||
|
||||
var parentNames = toAdd.Select(x => x.Parent).Distinct().ToList();
|
||||
var parents = await unitService.Get()
|
||||
.Include(u => u.ChildUnits)
|
||||
.Where(u => parentNames.Contains(u.Name))
|
||||
.ToDictionaryAsync(u => u.Name, StringComparer.Ordinal);
|
||||
|
||||
foreach (var (Parent, Child) in toAdd)
|
||||
{
|
||||
if (!parents.TryGetValue(Parent, out var parent) ||
|
||||
!allUnits.TryGetValue(Child, out var child))
|
||||
{
|
||||
logger.LogWarning("Пропущена связь '{Parent}' - '{Child}': Unit не найден", Parent, Child);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parent!.ChildUnits.Any(r => r.ChildUnitId == child.Id))
|
||||
{
|
||||
logger.LogWarning("Связь уже существует: '{Parent}' - '{Child}'", Parent, Child);
|
||||
continue;
|
||||
}
|
||||
|
||||
parent.ChildUnits.Add(new UnitInUnit
|
||||
{
|
||||
ParentUnitId = parent.Id,
|
||||
ChildUnitId = child.Id,
|
||||
DateCreated = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
logger.LogInformation("Добавлена связь: '{Parent}' - '{Child}'", Parent, Child);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task FinalCommitAsync(int removedCount, int addedCount)
|
||||
{
|
||||
if (removedCount > 0 || addedCount > 0)
|
||||
{
|
||||
if (await unitService.CommitAsync())
|
||||
{
|
||||
logger.LogInformation("Синхронизация завершена. Удалено: {Removed}, добавлено: {Added}", removedCount, addedCount);
|
||||
}
|
||||
else
|
||||
logger.LogError("Не удалось сохранить изменения в БД");
|
||||
}
|
||||
else
|
||||
logger.LogInformation("Изменений не обнаружено");
|
||||
}
|
||||
|
||||
private sealed class ExistingRelationship
|
||||
{
|
||||
public string ParentName { get; init; }
|
||||
public string ChildName { get; init; }
|
||||
public UnitInUnit Relationship { get; init; }
|
||||
}
|
||||
|
||||
_logger = logger;
|
||||
_unitRepository = unitRepository;
|
||||
_settingsFromDb = settingsFromDb;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SyncAsync(List<AihitData> aihitData)
|
||||
{
|
||||
var validPairs = ValidateInput(aihitData);
|
||||
|
||||
if (validPairs.Count == 0)
|
||||
return;
|
||||
|
||||
if (!await PassSafeguardCheckAsync(validPairs.Count))
|
||||
return;
|
||||
|
||||
var unitNames = ExtractUnitNames(validPairs);
|
||||
var unitsByName = await GetOrCreateUnitsAsync(unitNames);
|
||||
|
||||
var (toRemove, toAdd) = await GetDeltaAsync(validPairs);
|
||||
|
||||
// Удаление и добавление накапливаются в одном ChangeTracker.
|
||||
// CommitAsync сохраняет всё атомарно через SaveChangesAsync.
|
||||
await RemoveRelationshipsAsync(toRemove);
|
||||
await AddRelationshipsAsync(toAdd, unitsByName);
|
||||
await CommitChangesAsync(toRemove.Count, toAdd.Count);
|
||||
}
|
||||
|
||||
private List<(string ParentName, string ChildName)> ValidateInput(List<AihitData> data)
|
||||
{
|
||||
if (data == null || data.Count == 0)
|
||||
{
|
||||
_logger.LogWarning("Получены пустые данные из АИХ ИТ. Синхронизация пропущена");
|
||||
return new List<(string, string)>();
|
||||
}
|
||||
|
||||
var validPairs = data
|
||||
.Where(item => !string.IsNullOrWhiteSpace(item.ParentName) && !string.IsNullOrWhiteSpace(item.ChildName))
|
||||
.Where(item => item.ParentName != item.ChildName)
|
||||
.Select(item => (
|
||||
ParentName: item.ParentName!.Trim().ToUpperInvariant(),
|
||||
ChildName: item.ChildName!.Trim().ToUpperInvariant()
|
||||
))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (validPairs.Count == 0)
|
||||
{
|
||||
_logger.LogWarning("После нормализации не осталось валидных связей. Синхронизация пропущена");
|
||||
return new List<(string, string)>();
|
||||
}
|
||||
|
||||
_logger.LogInformation("Получено {Count} валидных связей", validPairs.Count);
|
||||
return validPairs;
|
||||
}
|
||||
|
||||
private async Task<bool> PassSafeguardCheckAsync(int incomingCount)
|
||||
{
|
||||
var previousCount = await _unitRepository.Get()
|
||||
.SelectMany(u => u.ChildUnits)
|
||||
.CountAsync();
|
||||
|
||||
if (previousCount == 0)
|
||||
return true;
|
||||
|
||||
var currentPercentage = Math.Round((decimal)incomingCount / previousCount * 100, 1, MidpointRounding.AwayFromZero);
|
||||
|
||||
if (currentPercentage < _settingsFromDb.MinRelationshipsThresholdPct)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Синхронизация отменена: количество полученных связей ниже порогового значения. " +
|
||||
"Получено: {CurrentCount} ({CurrentPercentage:F1}%), порог: {Threshold}% от прошлого объема ({PreviousCount})",
|
||||
incomingCount, currentPercentage, _settingsFromDb.MinRelationshipsThresholdPct, previousCount);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<string> ExtractUnitNames(List<(string ParentName, string ChildName)> pairs)
|
||||
{
|
||||
return pairs
|
||||
.SelectMany(pair => new[] { pair.ParentName, pair.ChildName })
|
||||
.Distinct()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, Unit>> GetOrCreateUnitsAsync(List<string> names)
|
||||
{
|
||||
var existingUnits = await _unitRepository.Get()
|
||||
.AsNoTracking()
|
||||
.ToDictionaryAsync(u => u.Name, StringComparer.Ordinal);
|
||||
|
||||
var missingNames = names.Except(existingUnits.Keys).ToList();
|
||||
|
||||
if (missingNames.Count == 0)
|
||||
return existingUnits;
|
||||
|
||||
_logger.LogInformation("Создание отсутствующих юнитов: {Count}", missingNames.Count);
|
||||
|
||||
foreach (var name in missingNames)
|
||||
{
|
||||
var unit = new Unit
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = name,
|
||||
DateCreated = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
if (!await _unitRepository.CreateAsync(unit))
|
||||
{
|
||||
_logger.LogError("Не удалось создать юнит: {Name}", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
existingUnits[name] = unit;
|
||||
_logger.LogDebug("Создан юнит: {Name}", name);
|
||||
}
|
||||
|
||||
await _unitRepository.CommitAsync();
|
||||
return existingUnits;
|
||||
}
|
||||
|
||||
private async Task<(List<ExistingRelationship> ToRemove, List<(string ParentName, string ChildName)> ToAdd)> GetDeltaAsync(
|
||||
List<(string ParentName, string ChildName)> sourcePairs)
|
||||
{
|
||||
var relevantParentNames = sourcePairs.Select(p => p.ParentName).Distinct().ToList();
|
||||
|
||||
var existingForParents = await _unitRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Include(u => u.ChildUnits).ThenInclude(r => r.ChildUnit)
|
||||
.Where(u => relevantParentNames.Contains(u.Name))
|
||||
.SelectMany(u => u.ChildUnits, (parent, relationship) => new ExistingRelationship
|
||||
{
|
||||
ParentName = parent.Name,
|
||||
ChildName = relationship.ChildUnit!.Name,
|
||||
Relationship = relationship
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
var sourceSet = new HashSet<(string, string)>(sourcePairs);
|
||||
var existingSet = new HashSet<(string, string)>(
|
||||
existingForParents.Select(x => (x.ParentName, x.ChildName)));
|
||||
|
||||
var toRemove = existingForParents
|
||||
.Where(r => !sourceSet.Contains((r.ParentName, r.ChildName)))
|
||||
.ToList();
|
||||
|
||||
var toAdd = sourcePairs
|
||||
.Where(p => !existingSet.Contains(p))
|
||||
.ToList();
|
||||
|
||||
_logger.LogInformation("Дельта: удаление {ToRemove}, добавление {ToAdd}", toRemove.Count, toAdd.Count);
|
||||
return (toRemove, toAdd);
|
||||
}
|
||||
|
||||
private async Task RemoveRelationshipsAsync(List<ExistingRelationship> toRemove)
|
||||
{
|
||||
if (toRemove.Count == 0)
|
||||
return;
|
||||
|
||||
var parentNames = toRemove.Select(r => r.ParentName).Distinct().ToList();
|
||||
|
||||
var parents = await _unitRepository.Get()
|
||||
.Include(u => u.ChildUnits)
|
||||
.Where(u => parentNames.Contains(u.Name))
|
||||
.ToDictionaryAsync(u => u.Name, StringComparer.Ordinal);
|
||||
|
||||
foreach (var relationship in toRemove)
|
||||
{
|
||||
if (!parents.TryGetValue(relationship.ParentName, out var parent))
|
||||
{
|
||||
_logger.LogWarning("Родитель '{Parent}' не найден при удалении связи с '{Child}'",
|
||||
relationship.ParentName, relationship.ChildName);
|
||||
continue;
|
||||
}
|
||||
|
||||
var entityToRemove = parent.ChildUnits
|
||||
.FirstOrDefault(r => r.ChildUnitId == relationship.Relationship.ChildUnitId);
|
||||
|
||||
if (entityToRemove != null)
|
||||
parent.ChildUnits.Remove(entityToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddRelationshipsAsync(
|
||||
List<(string ParentName, string ChildName)> toAdd,
|
||||
Dictionary<string, Unit> unitsByName)
|
||||
{
|
||||
if (toAdd.Count == 0)
|
||||
return;
|
||||
|
||||
var parentNames = toAdd.Select(x => x.ParentName).Distinct().ToList();
|
||||
|
||||
var parents = await _unitRepository.Get()
|
||||
.Include(u => u.ChildUnits)
|
||||
.Where(u => parentNames.Contains(u.Name))
|
||||
.ToDictionaryAsync(u => u.Name, StringComparer.Ordinal);
|
||||
|
||||
foreach (var (parentName, childName) in toAdd)
|
||||
{
|
||||
if (!parents.TryGetValue(parentName, out var parent) ||
|
||||
!unitsByName.TryGetValue(childName, out var child))
|
||||
{
|
||||
_logger.LogWarning("Пропущена связь '{Parent}' - '{Child}': юнит не найден", parentName, childName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parent.ChildUnits.Any(r => r.ChildUnitId == child.Id))
|
||||
continue;
|
||||
|
||||
parent.ChildUnits.Add(new UnitInUnit
|
||||
{
|
||||
ParentUnitId = parent.Id,
|
||||
ChildUnitId = child.Id,
|
||||
DateCreated = DateTimeOffset.UtcNow
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CommitChangesAsync(int removedCount, int addedCount)
|
||||
{
|
||||
if (removedCount == 0 && addedCount == 0)
|
||||
{
|
||||
_logger.LogInformation("Изменений не обнаружено");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await _unitRepository.CommitAsync())
|
||||
throw new InvalidOperationException("Не удалось сохранить изменения связей в БД");
|
||||
|
||||
_logger.LogInformation("Синхронизация завершена. Удалено: {Removed}, добавлено: {Added}", removedCount, addedCount);
|
||||
}
|
||||
|
||||
private sealed class ExistingRelationship
|
||||
{
|
||||
public string ParentName { get; init; } = null!;
|
||||
public string ChildName { get; init; } = null!;
|
||||
public UnitInUnit Relationship { get; init; } = null!;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
FROM 10.99.253.167:8090/dotnet/runtime:9.0 AS base
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
# This stage is used to build the service project
|
||||
FROM 10.99.253.167:8090/dotnet/sdk:9.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
@@ -32,8 +31,15 @@ RUN dotnet publish "./PARR.AIHITRelationshipsSyncerWorker.csproj" -c $BUILD_CONF
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
|
||||
# 1. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> OpenSSL 3 <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> EOF (UnsafeLegacyRenegotiation <20> IgnoreUnexpectedEOF)
|
||||
RUN sed -i 's/providers = provider_sect/providers = provider_sect\nssl_conf = ssl_sect/' /etc/ssl/openssl.cnf \
|
||||
&& printf "\n[ssl_sect]\nsystem_default = system_default_sect\n" >> /etc/ssl/openssl.cnf \
|
||||
&& printf "\n[system_default_sect]\nOptions = UnsafeLegacyRenegotiation,IgnoreUnexpectedEOF\nMinProtocol = TLSv1\nCipherString = DEFAULT@SECLEVEL=0\n" >> /etc/ssl/openssl.cnf
|
||||
|
||||
# Fixes an old version TLS (AIH IT GVC)
|
||||
RUN sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /etc/ssl/openssl.cnf
|
||||
# 2. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> .NET 9
|
||||
ENV DOTNET_AppContext_Switch_System_Net_Security_UseUnsafeOldTlsRuntimeBehavior=true
|
||||
ENV DOTNET_AppContext_Switch_Microsoft_Data_SqlClient_UseOldTlsValue=true
|
||||
ENV DOTNET_SYSTEM_NET_SECURITY_ALLOWUNSECUREDEFAULTS=true
|
||||
|
||||
ENTRYPOINT ["dotnet", "PARR.AIHITRelationshipsSyncerWorker.dll"]
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"AihitConnection": "Data Source=10.248.19.97; Initial Catalog=mao2;User ID=awhit-ipp-parr;pwd=ET3h$9y1LH#D;TrustServerCertificate=true;",
|
||||
"AihitConnection": "Data Source=10.248.19.97; Initial Catalog=mao2;User ID=awhit-ipp-parr;pwd=ET3h$9y1LH#D;Encrypt=Optional;TrustServerCertificate=true;MultiSubnetFailover=True;",
|
||||
"DefaultConnection": "Server=10.99.253.184;Database=parr;User Id=app_parr; Password=PosdfkhT&)%sdfligL&%5546;"
|
||||
},
|
||||
"Logging": {
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"Microsoft.EntityFrameworkCore": "Debug",
|
||||
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Debug",
|
||||
"Override": {
|
||||
//"Microsoft": "Information",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
"ConnectionStrings": {
|
||||
"RedisConnection": "10.99.253.216:6379,password=ParrP@ssPtk202MMdevDvs"
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "File",
|
||||
"Args": {
|
||||
"path": "log/log-.txt",
|
||||
"rollingInterval": "Day"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"MqSettings": {
|
||||
"HostName": "10.99.253.216"
|
||||
}
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Debug",
|
||||
"Override": {
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Debug",
|
||||
"PARR.DAL": "Information"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "File",
|
||||
"Args": {
|
||||
"path": "log/log-.txt",
|
||||
"rollingInterval": "Day"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"MqSettings": {
|
||||
"HostName": "10.99.253.216"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=10.99.253.184;Database=parr;User Id=app_parr; Password=PosdfkhT&)%sdfligL&%5546;"
|
||||
"DefaultConnection": "Server=10.99.253.184;Database=parr;User Id=app_parr; Password=PosdfkhT&)%sdfligL&%5546;",
|
||||
"RedisConnection": "parr-redis:6379,password=ParrP@ssPtk202MMdevDvs"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
|
||||
@@ -99,6 +99,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.WorkloadBuilderWorker"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.TaskReconciliationWorker", "PARR.TaskReconciliationWorker\PARR.TaskReconciliationWorker.csproj", "{DB701295-1696-4E1A-9088-D3AECF823BA6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PARR.SnapshotWorker", "PARR.SnapshotWorker\PARR.SnapshotWorker.csproj", "{F5318808-942F-4EFB-9BC9-00B9F4704EB5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -275,6 +277,10 @@ Global
|
||||
{DB701295-1696-4E1A-9088-D3AECF823BA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DB701295-1696-4E1A-9088-D3AECF823BA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DB701295-1696-4E1A-9088-D3AECF823BA6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F5318808-942F-4EFB-9BC9-00B9F4704EB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F5318808-942F-4EFB-9BC9-00B9F4704EB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F5318808-942F-4EFB-9BC9-00B9F4704EB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F5318808-942F-4EFB-9BC9-00B9F4704EB5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
public const string GetNextRun = Base + "/tests/next-run";
|
||||
|
||||
public const string CreateCache = Base + "/tests/cache/";
|
||||
|
||||
public const string TestHandler = Base + "/tests/test/";
|
||||
}
|
||||
|
||||
public static class Template
|
||||
|
||||
@@ -1,52 +1,14 @@
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.API.Contracts.V1.Requests
|
||||
namespace PARR.API.Contracts.V1.Requests
|
||||
{
|
||||
public class JobAutoControlRequest
|
||||
/// <summary>
|
||||
/// Часть реквеста по управлению авто контролем для Job, JobGroup
|
||||
/// </summary>
|
||||
public record JobAutoControlRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Создавать новые РР (из настроке РР)
|
||||
/// </summary>
|
||||
public bool CreateNew { get; set; }
|
||||
public bool IsEnable { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Деактивировать не актуальные согласно ЭК (обратные статусы ЭК)
|
||||
/// </summary>
|
||||
public bool Deactivate { get; set; }
|
||||
public bool InitUsedTemplateState { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Активировать РР после смены ЭК (согласно настройкам статусов ЭК)
|
||||
/// </summary>
|
||||
public bool Activate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Состояние для активированных РР, шаблоны
|
||||
/// </summary>
|
||||
public bool OnTemplateIsActive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Состояние для активированных РР, расписания
|
||||
/// </summary>
|
||||
public bool OnScheduleIsActive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Состояние для деактивированных РР, шаблоны
|
||||
/// </summary>
|
||||
public bool OffTemplateIsActive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Состояние для деактивированных РР, расписания
|
||||
/// </summary>
|
||||
public bool OffScheduleIsActive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Маски ЭК
|
||||
/// </summary>
|
||||
public required List<string> EkMasks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Статусы ЭК для которых включена генерация РР и активация
|
||||
/// </summary>
|
||||
public required List<EkStatusEnum> EnabledEkStatuses { get; set; }
|
||||
public bool InitUsedScheduleState { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
namespace PARR.API.Contracts.V1.Requests
|
||||
{
|
||||
public class JobGroupRequest
|
||||
public record JobGroupRequest
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public required string Name { get; init; }
|
||||
|
||||
//public bool? IsUmbrella { get; set; }
|
||||
|
||||
public Guid GroupTypeId { get; set; }
|
||||
public Guid GroupTypeId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Поле по которому групиируем, если тип - ГРУППА
|
||||
/// </summary>
|
||||
public Guid? GroupingUnitFieldId { get; set; }
|
||||
public Guid? GroupingUnitFieldId { get; init; }
|
||||
|
||||
public bool? IsGroupByResponsible { get; set; }
|
||||
public bool? IsGroupByResponsible { get; init; }
|
||||
|
||||
public required string ShortDescription { get; set; }
|
||||
public required string ShortDescription { get; init; }
|
||||
|
||||
public required string FullDescription { get; set; }
|
||||
public required string FullDescription { get; init; }
|
||||
|
||||
public required string Solution { get; set; }
|
||||
public required string Solution { get; init; }
|
||||
|
||||
public required string TemplateDuration { get; set; }
|
||||
public required string TemplateDuration { get; init; }
|
||||
|
||||
public DateTimeOffset ReferenceDate { get; set; }
|
||||
public DateTimeOffset ReferenceDate { get; init; }
|
||||
|
||||
public int? UserTimeZoneOffsetMinutes { get; set; }
|
||||
public int? UserTimeZoneOffsetMinutes { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Использовать таймзону рабочей группы ответственного за ЭК шаблона
|
||||
/// </summary>
|
||||
public bool IsWorkGroupTimezone { get; set; }
|
||||
public bool IsWorkGroupTimezone { get; init; }
|
||||
|
||||
public Guid ScheduleExcludeTypeId { get; set; }
|
||||
public Guid ScheduleExcludeTypeId { get; init; }
|
||||
|
||||
public Guid? ScheduleExcludeTypeCalendarId { get; set; }
|
||||
public Guid? ScheduleExcludeTypeCalendarId { get; init; }
|
||||
|
||||
public bool IsAutoDistributionEnabled { get; set; }
|
||||
public bool IsAutoDistributionEnabled { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Настройки автораспределения
|
||||
/// </summary>
|
||||
public DistributionConfigRequest? DistributionConfig { get; set; }
|
||||
public DistributionConfigRequest? DistributionConfig { get; init; }
|
||||
|
||||
//public bool IsAgent { get; set; }
|
||||
|
||||
@@ -51,28 +51,35 @@
|
||||
|
||||
//public string? AgentScript { get; set; }
|
||||
|
||||
public List<EsppSchValueRequest> Schedule { get; set; } = new List<EsppSchValueRequest>();
|
||||
public List<EsppSchValueRequest> Schedule { get; init; } = new List<EsppSchValueRequest>();
|
||||
|
||||
/// <summary>
|
||||
/// Настройки автоконтроля (возможны только в случае, если JobGroupType.IsJobGroupAutocontrol==true)
|
||||
/// </summary>
|
||||
public JobAutoControlRequest? AutoControl { get; init; }
|
||||
}
|
||||
|
||||
public class EsppSchValueRequest
|
||||
public record EsppSchValueRequest
|
||||
{
|
||||
public Guid TypeValueId { get; set; }
|
||||
public Guid TypeValueId { get; init; }
|
||||
|
||||
public Guid TypeConfigId { get; set; }
|
||||
public Guid TypeConfigId { get; init; }
|
||||
}
|
||||
|
||||
public class DistributionConfigRequest
|
||||
public record DistributionConfigRequest
|
||||
{
|
||||
public Guid DistributionPeriodId { get; set; }
|
||||
public Guid DistributionPeriodId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Исключать выходные и праздники
|
||||
/// </summary>
|
||||
public bool IsExcludeWeekends { get; set; }
|
||||
public bool IsExcludeWeekends { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Группировать по рабочей группе
|
||||
/// </summary>
|
||||
public bool IsGroupingByWorkGroup { get; set; }
|
||||
public bool IsGroupingByWorkGroup { get; init; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,79 +1,91 @@
|
||||
namespace PARR.API.Contracts.V1.Requests
|
||||
{
|
||||
public class JobRequest
|
||||
public record JobRequest
|
||||
{
|
||||
|
||||
public required Guid TnkId { get; set; }
|
||||
public required Guid TnkId { get; init; }
|
||||
|
||||
public required Guid GroupId { get; set; }
|
||||
public required Guid GroupId { get; init; }
|
||||
|
||||
public required string Name { get; set; }
|
||||
public required string Name { get; init; }
|
||||
|
||||
public int? MinValueRelationships { get; set; }
|
||||
//public int? MinValueRelationships { get; init; }
|
||||
|
||||
public int? MaxValueRelationships { get; set; }
|
||||
//public int? MaxValueRelationships { get; init; }
|
||||
|
||||
public bool? IsParentRelationships { get; set; }
|
||||
//public bool? IsParentRelationships { get; init; }
|
||||
|
||||
public required string TemplateNameMask { get; set; }
|
||||
public JobRelationships? Relationships { get; init; }
|
||||
|
||||
public required string WorkGroupMask { get; set; }
|
||||
public required string TemplateNameMask { get; init; }
|
||||
|
||||
public required string WorkName { get; set; }
|
||||
public required string WorkGroupMask { get; init; }
|
||||
|
||||
public required string ResponseAreaMask { get; set; }
|
||||
public required string WorkName { get; init; }
|
||||
|
||||
#region AutoControl
|
||||
public required string ResponseAreaMask { get; init; }
|
||||
|
||||
public bool IsEnableAutoControl { get; set; }
|
||||
//#region AutoControl
|
||||
|
||||
public bool InitUsedTemplateState { get; set; }
|
||||
//public bool IsEnableAutoControl { get; set; }
|
||||
|
||||
public bool InitUsedScheduleState { get; set; }
|
||||
//public bool InitUsedTemplateState { get; set; }
|
||||
|
||||
#endregion
|
||||
//public bool InitUsedScheduleState { get; set; }
|
||||
|
||||
public required List<UnitFilterRequest> UnitFilters { get; set; }
|
||||
//#endregion
|
||||
|
||||
public JobAutoControlRequest? AutoControl { get; init; }
|
||||
|
||||
public required List<UnitFilterRequest> UnitFilters { get; init; }
|
||||
}
|
||||
|
||||
public record JobRelationships
|
||||
{
|
||||
public int MinValueRelationships { get; init; }
|
||||
|
||||
public class UnitFilterRequest
|
||||
public int MaxValueRelationships { get; init; }
|
||||
|
||||
public bool IsParentRelationships { get; init; }
|
||||
}
|
||||
|
||||
public record UnitFilterRequest
|
||||
{
|
||||
///// <summary>
|
||||
///// Id = null в методе Create, в Update обязателен
|
||||
///// </summary>
|
||||
//public Guid? Id { get; set; }
|
||||
|
||||
public required string UnitFilterMask { get; set; }
|
||||
public required string UnitFilterMask { get; init; }
|
||||
|
||||
public List<FieldFilterRequest>? FieldFilters { get; set; }
|
||||
public List<FieldFilterRequest>? FieldFilters { get; init; }
|
||||
|
||||
public List<RelationshipFilterRequest>? RelationshipFilters { get; set; }
|
||||
public List<RelationshipFilterRequest>? RelationshipFilters { get; init; }
|
||||
}
|
||||
|
||||
|
||||
public class FieldFilterRequest
|
||||
public record FieldFilterRequest
|
||||
{
|
||||
|
||||
public Guid FieldId { get; set; }
|
||||
public Guid FieldId { get; init; }
|
||||
|
||||
public string? ValueMask { get; set; }
|
||||
public string? ValueMask { get; init; }
|
||||
|
||||
public bool IsInverse { get; set; } = false;
|
||||
public bool IsInverse { get; init; } = false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class RelationshipFilterRequest
|
||||
public record RelationshipFilterRequest
|
||||
{
|
||||
public Guid FieldId { get; set; }
|
||||
public Guid FieldId { get; init; }
|
||||
|
||||
public bool? IsParent { get; set; }
|
||||
public bool? IsParent { get; init; }
|
||||
|
||||
public string? ValueMask { get; set; }
|
||||
public string? ValueMask { get; init; }
|
||||
|
||||
public bool? IsFullMatch { get; set; }
|
||||
public bool? IsFullMatch { get; init; }
|
||||
|
||||
public bool? IsInverse { get; set; }
|
||||
public bool? IsInverse { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -2,22 +2,31 @@
|
||||
|
||||
namespace PARR.API.Contracts.V1.Requests.Queries
|
||||
{
|
||||
public class RobotHistoryQuery
|
||||
public record RobotHistoryQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// Фильтр по ИД шаблона
|
||||
/// </summary>
|
||||
public Guid? TemplateId { get; set; }
|
||||
public Guid? TemplateId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Фильтр по ИД робота
|
||||
/// </summary>
|
||||
public RobotsEnum? RobotCode { get; set; }
|
||||
public RobotsEnum? RobotCode { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Фильтр по уровню истории
|
||||
/// </summary>
|
||||
public RobotHistoryLevelEnum? HistoryLevel { get; set; }
|
||||
public RobotHistoryLevelEnum? HistoryLevel { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Дата начала
|
||||
/// </summary>
|
||||
public DateTimeOffset? DateFrom { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Дата окончания
|
||||
/// </summary>
|
||||
public DateTimeOffset? DateTo { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
/// </summary>
|
||||
public bool IsAutoDistributionEnabled { get; set; }
|
||||
|
||||
public JobGroupAutoControlResponse? AutoControl { get; set; }
|
||||
|
||||
// public bool IsAgent { get; set; }
|
||||
|
||||
// public string? AgentName { get; set; }
|
||||
@@ -74,6 +76,15 @@
|
||||
//public JobGroupDistributionConfigResponse? DistributionConfig { get; set; }
|
||||
}
|
||||
|
||||
public class JobGroupAutoControlResponse
|
||||
{
|
||||
public bool IsEnable { get; set; }
|
||||
|
||||
public bool InitUsedTemplateState { get; set; }
|
||||
|
||||
public bool InitUsedScheduleState { get; set; }
|
||||
}
|
||||
|
||||
public class JobGroupScheduleResponse
|
||||
{
|
||||
public string Timezone { get; set; } = string.Empty;
|
||||
|
||||
@@ -2,14 +2,20 @@
|
||||
|
||||
namespace PARR.API.Contracts.V1.Responses
|
||||
{
|
||||
public class JobGroupTypeResponse
|
||||
public record JobGroupTypeResponse
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid Id { get; init; }
|
||||
|
||||
public required JobGroupTypesEnum Code { get; set; }
|
||||
public required JobGroupTypesEnum Code { get; init; }
|
||||
|
||||
public required string Name { get; set; }
|
||||
public required string Name { get; init; }
|
||||
|
||||
public required string Description { get; set; }
|
||||
public required string Description { get; init; }
|
||||
|
||||
public bool IsAllowJobUnitFilter { get; init; }
|
||||
|
||||
public bool IsJobGroupAutoControl { get; init; }
|
||||
|
||||
public bool IsRelationshipsAllowed { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,5 +23,10 @@
|
||||
/// Ошибок
|
||||
/// </summary>
|
||||
public int Errors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Среднее кол-во роботов работающих в течении часа
|
||||
/// </summary>
|
||||
public double AvgRobots { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class TemplateResponse : TemplateBaseResponse
|
||||
{
|
||||
public required string Category { get; set; }
|
||||
@@ -72,6 +71,42 @@
|
||||
|
||||
public required string TemplateDuration { get; set; }
|
||||
|
||||
|
||||
#region Поля с примененнымм шорткодами
|
||||
|
||||
/// <summary>
|
||||
/// Работа в ЕСПП (с примененными шорткодами)
|
||||
/// </summary>
|
||||
public string? WorkNameRendered { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Рабочая группа (с примененными шорткодами)
|
||||
/// </summary>
|
||||
public string? WorkGroupRendered { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ЗО (с примененными шорткодами)
|
||||
/// </summary>
|
||||
public string? ResponseAreaRendered { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Краткое описание (с примененными шорткодами)
|
||||
/// </summary>
|
||||
public string? ShortDescriptionRendered { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Подробное описание (с примененными шорткодами)
|
||||
/// </summary>
|
||||
public string? FullDescriptionRendered { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Решение (с примененными шорткодами)
|
||||
/// </summary>
|
||||
public string? SolutionRendered { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public JobResponse? Job { get; set; }
|
||||
|
||||
public ProcessResponse? Process { get; set; }
|
||||
|
||||
@@ -16,7 +16,8 @@ using PARR.Core.Common.Helpers;
|
||||
using PARR.Core.Common.Interfaces.RabbitServices;
|
||||
using PARR.Core.Extensions;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.JobRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
using PARR.Core.Services.MatchingStatusService;
|
||||
using PARR.Core.Services.UnitFilterService;
|
||||
@@ -24,7 +25,7 @@ using PARR.Domain.Common.Pagination;
|
||||
using PARR.Domain.Common.Rabbit.Messages;
|
||||
using PARR.Domain.Common.Roles;
|
||||
using PARR.Domain.Entities.Base.History;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.API.Controllers.V1
|
||||
@@ -35,46 +36,40 @@ namespace PARR.API.Controllers.V1
|
||||
[Authorize(Roles = ParrRoles.Administrator.Role)]
|
||||
public class JobController : BaseApiController
|
||||
{
|
||||
private readonly ILogger<JobController> logger;
|
||||
private readonly IMapper mapper;
|
||||
private readonly IUriService uriService;
|
||||
private readonly IJobRepository jobService;
|
||||
private readonly ITemplateRepository templateService;
|
||||
//private readonly IValidator<JobRequest> jobValidator;
|
||||
private readonly IRabbitService mqService;
|
||||
private readonly MqSettings mqSettings;
|
||||
private readonly IClientService clientService;
|
||||
private readonly IJobAutoControlRepository jobAutoControlService;
|
||||
private readonly IMatchingStatusService matchingStatusService;
|
||||
private readonly ILogger<JobController> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IUriService _uriService;
|
||||
private readonly IJobRepository _jobRepository;
|
||||
private readonly ITemplateRepository _templateRepository;
|
||||
private readonly IRabbitService _mqService;
|
||||
private readonly MqSettings _mqSettings;
|
||||
private readonly IClientService _clientService;
|
||||
private readonly IMatchingStatusService _matchingStatusService;
|
||||
|
||||
public JobController(
|
||||
ILogger<JobController> logger,
|
||||
IMapper mapper,
|
||||
IUriService uriService,
|
||||
IJobRepository jobService,
|
||||
ITemplateRepository templateService,
|
||||
IJobGroupRepository jobGroupService,
|
||||
//IValidator<JobRequest> jobValidator,
|
||||
IUnitFilterService unitFilterService,
|
||||
IUnitRepository unitService,
|
||||
IJobRepository jobRepository,
|
||||
ITemplateRepository templateRepository,
|
||||
IJobGroupRepository jobGroupRepository,
|
||||
IUnitFilterService unitFilterRepository,
|
||||
IUnitRepository unitRepository,
|
||||
IRabbitService mqService,
|
||||
MqSettings mqSettings,
|
||||
IClientService clientService,
|
||||
IJobAutoControlRepository jobAutoControlService,
|
||||
IMatchingStatusService matchingStatusService
|
||||
)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.mapper = mapper;
|
||||
this.uriService = uriService;
|
||||
this.jobService = jobService;
|
||||
this.templateService = templateService;
|
||||
//this.jobValidator = jobValidator;
|
||||
this.mqService = mqService;
|
||||
this.mqSettings = mqSettings;
|
||||
this.clientService = clientService;
|
||||
this.jobAutoControlService = jobAutoControlService;
|
||||
this.matchingStatusService = matchingStatusService;
|
||||
_logger = logger;
|
||||
_mapper = mapper;
|
||||
_uriService = uriService;
|
||||
_jobRepository = jobRepository;
|
||||
_templateRepository = templateRepository;
|
||||
_mqService = mqService;
|
||||
_mqSettings = mqSettings;
|
||||
_clientService = clientService;
|
||||
_matchingStatusService = matchingStatusService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -84,18 +79,20 @@ namespace PARR.API.Controllers.V1
|
||||
[HttpGet(ApiRoutes.Job.GetAll)]
|
||||
public async Task<IActionResult> GetAll([FromQuery] PaginationQuery paginationQuery, [FromQuery] JobQuery filter)
|
||||
{
|
||||
var paginationFilter = mapper.Map<PaginationFilter>(paginationQuery);
|
||||
var paginationFilter = _mapper.Map<PaginationFilter>(paginationQuery);
|
||||
|
||||
IQueryable<Job> query = _jobRepository.Get()
|
||||
.Include(t => t.AutoControl);
|
||||
|
||||
IQueryable<Job> query = jobService.Get().Include(t => t.AutoControl);
|
||||
|
||||
query = query.OrderBy(t => t.Name);
|
||||
|
||||
if (!string.IsNullOrEmpty(filter.Name))
|
||||
{
|
||||
//query = query.Where(t => t.Name.ToLower().Contains(filter.Name.ToLower()));
|
||||
query = query.Where(t=>EF.Functions.Like(t.Name.ToLower(), SqlHelpers.RegexToLike(filter.Name)));
|
||||
query = query.Where(t => EF.Functions.Like(t.Name.ToLower(), SqlHelpers.RegexToLike(filter.Name)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (filter.GroupId.HasValue)
|
||||
query = query.Where(t => t.GroupId == filter.GroupId.Value);
|
||||
@@ -107,9 +104,10 @@ namespace PARR.API.Controllers.V1
|
||||
if (filter.IsFull)
|
||||
{
|
||||
query = query
|
||||
.Include(t => t.Tnk)
|
||||
.Include(t => t.Group).ThenInclude(t => t.GroupType)
|
||||
.Include(t => t.Group).ThenInclude(t => t.GroupingUnitField);
|
||||
.Include(t => t.Tnk)
|
||||
.Include(t => t.Group).ThenInclude(t => t.GroupType)
|
||||
.Include(t => t.Group).ThenInclude(t => t.GroupingUnitField)
|
||||
.Include(t => t.Group).ThenInclude(t => t.AutoControl);
|
||||
|
||||
query = query
|
||||
.Include(t => t.UnitFilters)
|
||||
@@ -120,12 +118,12 @@ namespace PARR.API.Controllers.V1
|
||||
.ThenInclude(t => t.UnitField);
|
||||
}
|
||||
|
||||
var jobs = await jobService.GetPage(query, paginationFilter).ToListAsync();
|
||||
var jobs = await _jobRepository.GetPage(query, paginationFilter).ToListAsync();
|
||||
|
||||
if (!jobs.Any())
|
||||
return NoContent();
|
||||
|
||||
var response = mapper.Map<List<JobResponse>>(jobs);//TODO Migration to job
|
||||
var response = _mapper.Map<List<JobResponse>>(jobs);//TODO Migration to job
|
||||
|
||||
foreach (var jobResponse in response)
|
||||
{
|
||||
@@ -152,9 +150,10 @@ namespace PARR.API.Controllers.V1
|
||||
[HttpGet(ApiRoutes.Job.Get)]
|
||||
public async Task<IActionResult> GetById([FromRoute] Guid id)
|
||||
{
|
||||
var job = await jobService.Get()
|
||||
var job = await _jobRepository.Get()
|
||||
.Include(t => t.Tnk)
|
||||
.Include(t => t.Group).ThenInclude(t => t.GroupType)
|
||||
.Include(t => t.Group).ThenInclude(t => t.AutoControl)
|
||||
.Include(t => t.Group).ThenInclude(t => t.GroupingUnitField)
|
||||
.Include(t => t.UnitFilters)
|
||||
.ThenInclude(t => t.FieldFilters)
|
||||
@@ -168,7 +167,7 @@ namespace PARR.API.Controllers.V1
|
||||
if (job == null)
|
||||
return NotFound();
|
||||
|
||||
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.MatchingStatus = await GetMatchingStatusAsync(id);
|
||||
@@ -188,51 +187,56 @@ namespace PARR.API.Controllers.V1
|
||||
[HttpPost(ApiRoutes.Job.Create)]
|
||||
public async Task<IActionResult> Create([FromBody] JobRequest request)
|
||||
{
|
||||
//#region Валидация
|
||||
//var jobValidateResult = await jobValidator.ValidateAsync(request);//Валидация параметров самого задания
|
||||
#region Проверка существования работы с такими же настройками связей параметрами
|
||||
|
||||
//if (!jobValidateResult.IsValid)
|
||||
// return BadRequest(new Response(jobValidateResult.Errors));
|
||||
//#endregion
|
||||
if (request.Relationships != null)
|
||||
{
|
||||
//todo: не сильно правильный запрос, в нем проверяем полное совпадение, но не проверяем пересечения
|
||||
var isExistTheSameLinks = await _jobRepository.Get()
|
||||
//.Include(t => t.Group).ThenInclude(t => t.GroupType)
|
||||
.AsNoTracking()
|
||||
.CountAsync(t =>
|
||||
//t.Group!.GroupType!.Code == JobGroupTypesEnum.Umbrella
|
||||
//(t.Group!.GroupType!.Code == JobGroupTypesEnum.Umbrella || t.Group!.GroupType!.Code == JobGroupTypesEnum.Group) &&
|
||||
t.Group!.GroupType!.IsRelationshipsAllowed &&
|
||||
t.GroupId == request.GroupId &&
|
||||
(t.MinValueRelationships == request.Relationships.MinValueRelationships ||
|
||||
t.MaxValueRelationships == request.Relationships.MaxValueRelationships)
|
||||
);
|
||||
|
||||
//TODO: !!!!!!! проверить, если тип ЗОНТИК или ГРУППИРОВКА, то обязательно должны быть заполнены поля min max
|
||||
|
||||
#region Проверка существования работы с такими же параметрами
|
||||
|
||||
var isExistTheSameLinks = await jobService.Get()
|
||||
.Include(t => t.Group).ThenInclude(t => t.GroupType)
|
||||
.CountAsync(t =>
|
||||
//t.Group!.GroupType!.Code == JobGroupTypesEnum.Umbrella
|
||||
(t.Group!.GroupType!.Code == JobGroupTypesEnum.Umbrella || t.Group!.GroupType!.Code == JobGroupTypesEnum.Group) &&
|
||||
t.GroupId == request.GroupId &&
|
||||
(t.MinValueRelationships == request.MinValueRelationships ||
|
||||
t.MaxValueRelationships == request.MaxValueRelationships)
|
||||
);
|
||||
|
||||
if (isExistTheSameLinks > 0)
|
||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { FieldName = nameof(request.Name), Message = $"Работа с указанным диапазоном связей пересекается с уже имеющейся в базе данных({request.MinValueRelationships}-{request.MaxValueRelationships})" } }));
|
||||
if (isExistTheSameLinks > 0)
|
||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel {
|
||||
FieldName = nameof(request.Name),
|
||||
Message = $"Работа с указанным диапазоном связей пересекается с уже имеющейся в базе данных({request.Relationships.MinValueRelationships}-{request.Relationships.MaxValueRelationships})"
|
||||
} }));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
var job = mapper.Map<Job>(request);
|
||||
var job = _mapper.Map<Job>(request);
|
||||
|
||||
job.AutoControl = new JobAutoControl
|
||||
if (request.AutoControl != null)
|
||||
{
|
||||
JobId = job.Id,
|
||||
IsEnable = request.IsEnableAutoControl,
|
||||
InitUsedScheduleState = request.InitUsedScheduleState,
|
||||
InitUsedTemplateState = request.InitUsedTemplateState
|
||||
};
|
||||
// разрешен автоконтроль или нет, проверил в валидаторе
|
||||
job.AutoControl = new JobAutoControl
|
||||
{
|
||||
JobId = job.Id,
|
||||
IsEnable = request.AutoControl.IsEnable,
|
||||
InitUsedScheduleState = request.AutoControl.InitUsedScheduleState,
|
||||
InitUsedTemplateState = request.AutoControl.InitUsedTemplateState
|
||||
};
|
||||
}
|
||||
|
||||
if (!await jobService.CreateAsync(job) || !await jobService.CommitAsync())
|
||||
if (!await _jobRepository.CreateAsync(job) || !await _jobRepository.CommitAsync())
|
||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при созании задания на выполнение работ" } }));
|
||||
|
||||
logger.LogInformation($"Пользователь {User.Identity?.Name} добавил задание на выполнение работ: {job.Id}, {job.Name}, {job.WorkName}");
|
||||
_logger.LogInformation($"Пользователь {User.Identity?.Name} добавил задание на выполнение работ: {job.Id}, {job.Name}, {job.WorkName}");
|
||||
|
||||
var createdJob = await jobService.Get()
|
||||
var createdJob = await _jobRepository.Get()
|
||||
.Include(t => t.Tnk)
|
||||
.Include(t => t.Group).ThenInclude(t => t.GroupType)
|
||||
.Include(t => t.Group).ThenInclude(t => t.AutoControl)
|
||||
.Include(t => t.Group).ThenInclude(t => t.GroupingUnitField)
|
||||
.Include(t => t.UnitFilters)
|
||||
.ThenInclude(t => t.FieldFilters)
|
||||
@@ -242,9 +246,9 @@ namespace PARR.API.Controllers.V1
|
||||
.Include(t => t.AutoControl)
|
||||
.FirstOrDefaultAsync(t => t.Id == job.Id);
|
||||
|
||||
var locationUri = uriService.GetUri(ApiRoutes.Job.Get, ApiRoutes.Job.getParam, createdJob!.Id);
|
||||
var locationUri = _uriService.GetUri(ApiRoutes.Job.Get, ApiRoutes.Job.getParam, createdJob!.Id);
|
||||
|
||||
var response = mapper.Map<JobResponse>(createdJob);
|
||||
var response = _mapper.Map<JobResponse>(createdJob);
|
||||
// так как мы только что создали Job, то у него нет шаблонов, смело ставим = 0 (ускоряем запрос)
|
||||
response.TemplatesCount = 0;
|
||||
response.MatchingStatus = await GetMatchingStatusAsync(response.Id);
|
||||
@@ -262,11 +266,7 @@ namespace PARR.API.Controllers.V1
|
||||
[HttpPut(ApiRoutes.Job.Update)]
|
||||
public async Task<IActionResult> Update([FromRoute] Guid id, [FromBody] JobRequest request)
|
||||
{
|
||||
//var resultValidate = await jobValidator.ValidateAsync(request);
|
||||
//if (!resultValidate.IsValid)
|
||||
// return BadRequest(new Response(resultValidate.Errors));
|
||||
|
||||
var orig = await jobService.Get()
|
||||
var orig = await _jobRepository.Get()
|
||||
.Include(t => t.Tnk)
|
||||
.Include(t => t.Group).ThenInclude(t => t.GroupType)
|
||||
.Include(t => t.Group).ThenInclude(t => t.GroupingUnitField)
|
||||
@@ -287,77 +287,77 @@ namespace PARR.API.Controllers.V1
|
||||
|
||||
//TODO: ВОТ ЭТО ВООБЩЕ МЫ БУДЕМ ПРОВЕРЯТЬ, АААА???? - Проверка существования работы с такими же параметрами
|
||||
|
||||
//TODO: !!!!!!! проверить, если тип ЗОНТИК или ГРУППИРОВКА, то обязательно должны быть заполнены поля min max
|
||||
|
||||
#region обновление полей задания на работу
|
||||
|
||||
orig.Name = request.Name.Trim();
|
||||
orig.WorkName = request.WorkName.Trim();
|
||||
orig.MinValueRelationships = request.MinValueRelationships;
|
||||
orig.MaxValueRelationships = request.MaxValueRelationships;
|
||||
orig.IsParentRelationships = request.IsParentRelationships;
|
||||
orig.MinValueRelationships = request.Relationships?.MinValueRelationships;
|
||||
orig.MaxValueRelationships = request.Relationships?.MaxValueRelationships;
|
||||
orig.IsParentRelationships = request.Relationships?.IsParentRelationships;
|
||||
orig.TemplateNameMask = request.TemplateNameMask.Trim();
|
||||
orig.WorkGroupMask = request.WorkGroupMask.Trim();
|
||||
orig.TnkId = request.TnkId;
|
||||
orig.GroupId = request.GroupId;
|
||||
orig.ResponseAreaMask = request.ResponseAreaMask.Trim();
|
||||
|
||||
if (orig.AutoControl != null)
|
||||
#region Настройки автоконтроля
|
||||
|
||||
// валидатор проверяет корректность
|
||||
if (request.AutoControl != null)
|
||||
{
|
||||
orig.AutoControl.IsEnable = request.IsEnableAutoControl;
|
||||
orig.AutoControl.InitUsedScheduleState = request.InitUsedScheduleState;
|
||||
orig.AutoControl.InitUsedTemplateState = request.InitUsedTemplateState;
|
||||
}
|
||||
else
|
||||
{
|
||||
// автоконтрол не загружен, проверяем есть ли он в бд, если нет, то создадим
|
||||
var existAutoControl = await jobAutoControlService.Get().FirstOrDefaultAsync(t => t.JobId == id);
|
||||
if (existAutoControl != null)
|
||||
// Создаем новую запись или обновляем существующую
|
||||
if (orig.AutoControl != null)
|
||||
{
|
||||
logger.LogError($"При обновлении job {id}, не загрузась связь с JobAutoControl, но она есть. Не стал обновлять Job, вернул ошибку.");
|
||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = $"Ошибка при изменении задания на выполнение работ." } }));
|
||||
orig.AutoControl.IsEnable = request.AutoControl.IsEnable;
|
||||
orig.AutoControl.InitUsedScheduleState = request.AutoControl.InitUsedScheduleState;
|
||||
orig.AutoControl.InitUsedTemplateState = request.AutoControl.InitUsedTemplateState;
|
||||
}
|
||||
else
|
||||
{
|
||||
var autoControl = new JobAutoControl
|
||||
orig.AutoControl = new JobAutoControl
|
||||
{
|
||||
JobId = id,
|
||||
InitUsedScheduleState = request.InitUsedScheduleState,
|
||||
InitUsedTemplateState = request.InitUsedTemplateState,
|
||||
IsEnable = request.IsEnableAutoControl
|
||||
InitUsedScheduleState = request.AutoControl.InitUsedScheduleState,
|
||||
InitUsedTemplateState = request.AutoControl.InitUsedTemplateState,
|
||||
IsEnable = request.AutoControl.IsEnable
|
||||
};
|
||||
orig.AutoControl = autoControl;
|
||||
logger.LogWarning($"При обновлении job {id}, отсутствовала запись в таблице JobAutoControl, создал ее. {autoControl.ToJson()}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Удаляем настройки, скорей всего автоконтролем управляет группа работ
|
||||
orig.AutoControl = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
orig.DateModified = DateTimeOffset.UtcNow;
|
||||
#endregion
|
||||
|
||||
var job = mapper.Map<Job>(request);
|
||||
var job = _mapper.Map<Job>(request);
|
||||
job.Id = id;//На всякий. Пусть будет для чистоты
|
||||
|
||||
UpdateUnitFilters(orig, job);//Обновление вложенных дочерних элементов-фильтров
|
||||
|
||||
if (!await jobService.CommitAsync())
|
||||
if (!await _jobRepository.CommitAsync())
|
||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при изменении задания на выполнение работ." } }));
|
||||
|
||||
// если изменили маску, отправим задание на переименование связанных шаблонов
|
||||
if (isChangedTemplateNameMask)
|
||||
{
|
||||
var mqResult = await SendRequestToUpdateTemplates(id);
|
||||
var mqResult = await SendRequestToUpdateTemplates(orig);
|
||||
//todo: если ошибка. пользователя не предупреждаем... возможно ему это и не нужно знать...ну не переименуются шаблоны, может они переименуются позже...
|
||||
}
|
||||
|
||||
logger.LogInformation($"Пользователь {User.Identity?.Name} обновил задание на выполнение работ: {orig.Id}," +
|
||||
_logger.LogInformation($"Пользователь {User.Identity?.Name} обновил задание на выполнение работ: {orig.Id}," +
|
||||
$" {orig.Name}, {orig.WorkName}, {orig.MinValueRelationships}, {orig.MaxValueRelationships}," +
|
||||
$" {orig.TemplateNameMask}, {orig.TnkId}, {nameof(orig.GroupId)}");
|
||||
|
||||
|
||||
var updatedJob = await jobService.Get()
|
||||
var updatedJob = await _jobRepository.Get()
|
||||
.Include(t => t.Tnk)
|
||||
.Include(t => t.Group).ThenInclude(t => t.GroupType)
|
||||
.Include(t => t.Group).ThenInclude(t => t.AutoControl)
|
||||
.Include(t => t.Group).ThenInclude(t => t.GroupingUnitField)
|
||||
.Include(t => t.UnitFilters)
|
||||
.ThenInclude(t => t.FieldFilters)
|
||||
@@ -367,7 +367,7 @@ namespace PARR.API.Controllers.V1
|
||||
.Include(t => t.AutoControl)
|
||||
.FirstAsync(t => t.Id == orig.Id);
|
||||
|
||||
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.MatchingStatus = await GetMatchingStatusAsync(id);
|
||||
|
||||
@@ -564,7 +564,7 @@ namespace PARR.API.Controllers.V1
|
||||
[HttpDelete(ApiRoutes.Job.Delete)]
|
||||
public async Task<IActionResult> Delete([FromRoute] Guid id)
|
||||
{
|
||||
var job = await jobService.Get().Include(t => t.Tnk)
|
||||
var job = await _jobRepository.Get().Include(t => t.Tnk)
|
||||
.FirstOrDefaultAsync(t => t.Id == id);
|
||||
|
||||
if (job == null)
|
||||
@@ -572,19 +572,19 @@ namespace PARR.API.Controllers.V1
|
||||
Message = $"Ошибка при удалении задания на выполнение работ. Не найдено задание на выполнение работ Id: {id}"
|
||||
} }));
|
||||
|
||||
var templateCount = await templateService.Get().CountAsync(t => t.JobId == id);
|
||||
var templateCount = await _templateRepository.Get().CountAsync(t => t.JobId == id);
|
||||
|
||||
if (templateCount > 0)
|
||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel {
|
||||
Message = $"Ошибка при удалении задания на выполнение работ. С данным заданием связаны шаблоны: {templateCount} шт."
|
||||
} }));
|
||||
|
||||
if (!jobService.Delete(job) || !await jobService.CommitAsync())
|
||||
if (!_jobRepository.Delete(job) || !await _jobRepository.CommitAsync())
|
||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel {
|
||||
Message = $"Ошибка при удалении задания на выполнение работ"
|
||||
} }));
|
||||
|
||||
logger.LogInformation($"Пользователь {User.Identity?.Name} удалил задание на выполнение работ: {job.Id},{job.Name}," +
|
||||
_logger.LogInformation($"Пользователь {User.Identity?.Name} удалил задание на выполнение работ: {job.Id},{job.Name}," +
|
||||
$" {job.WorkName}, {job.MinValueRelationships}, {job.MaxValueRelationships}," +
|
||||
$" {job.TemplateNameMask}, {job.TnkId}, {job.GroupId}");
|
||||
|
||||
@@ -599,7 +599,7 @@ namespace PARR.API.Controllers.V1
|
||||
/// <returns></returns>
|
||||
private async Task<JobStatModel> GetStatisticsAsync(Guid jobId)
|
||||
{
|
||||
var statResult = await jobService.Get()
|
||||
var statResult = await _jobRepository.Get()
|
||||
.Include(t => t.Templates)
|
||||
.ThenInclude(t => t.RobotConfigurations)
|
||||
.Where(x => x.Id == jobId)
|
||||
@@ -629,35 +629,53 @@ namespace PARR.API.Controllers.V1
|
||||
}
|
||||
|
||||
|
||||
private async Task<bool> SendRequestToUpdateTemplates(Guid jobId)
|
||||
private async Task<bool> SendRequestToUpdateTemplates(Job job)
|
||||
{
|
||||
if (job.Group?.GroupType == null)
|
||||
throw new ArgumentException("Не хватает include для job.Group.GroupType", nameof(job.Group.GroupType));
|
||||
|
||||
Guid Id = default;
|
||||
SyncTaskEntityTypeEnum EntityType = default;
|
||||
|
||||
// Смотрим кто управляет автоконтролем, и какой объект можно синхронизировать
|
||||
if (job.Group.GroupType.IsJobGroupAutoControl)
|
||||
{
|
||||
// Управляет JobGroup
|
||||
Id = job.GroupId;
|
||||
EntityType = SyncTaskEntityTypeEnum.JobGroup;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Управляет Job
|
||||
Id = job.Id;
|
||||
EntityType = SyncTaskEntityTypeEnum.Job;
|
||||
}
|
||||
|
||||
var request = new TemplateMatcherMq
|
||||
{
|
||||
Id = jobId,
|
||||
EntityType = SyncTaskEntityTypeEnum.Job,
|
||||
Id = Id,
|
||||
EntityType = EntityType,
|
||||
Action = TemplateMatcherActionEnum.Update,
|
||||
Initiator = new HistoryInitiator
|
||||
{
|
||||
InitiatorIp = clientService.GetClientIp()?.ToString(),
|
||||
InitiatorIp = _clientService.GetClientIp()?.ToString(),
|
||||
InitiatorParrComponentId = ParrComponentsEnum.Api,
|
||||
InitiatorComment = $"В GUI изменено имя шаблона, при сохранении Job отправлен запрос на обновление связанных шаблонов"
|
||||
}
|
||||
};
|
||||
|
||||
//var msg = JsonSerializer.Serialize(request);
|
||||
var result = await _mqService.SendAsync(_mqSettings.TemplatesMatcher, new List<object> { request });
|
||||
|
||||
//var result = await mqService.SendAsync(mqSettings.TemplatesMatcher, new[] { msg });
|
||||
var result = await mqService.SendAsync(mqSettings.TemplatesMatcher, new List<object> { request });
|
||||
|
||||
logger.LogDebug("Получен код отпрвки: {IsSuccess}", result.IsSuccess);
|
||||
_logger.LogDebug("Получен код отпрвки: {IsSuccess}", result.IsSuccess);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
logger.LogError($"Ошибка при отправке запроса в очередь на обновление связанных шаблонов, после обновления маски шаблона. {request.ToJson()}");
|
||||
_logger.LogError($"Ошибка при отправке запроса в очередь на обновление связанных шаблонов, после обновления маски шаблона. {request.ToJson()}");
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.LogInformation($"После изменения маски шаблона в jobId: {jobId}, отправлен запрос в очередь на переименование связанных шаблонов: {request.ToJson()}");
|
||||
_logger.LogInformation($"После изменения маски шаблона в jobId: {job.Id}, отправлен запрос в очередь на переименование связанных шаблонов: {request.ToJson()}");
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -671,7 +689,7 @@ namespace PARR.API.Controllers.V1
|
||||
private async Task<int> GetCountTemplatesAsync(Guid jobId)
|
||||
{
|
||||
// Получаем только шаблоны в статусе used
|
||||
return await templateService.Get().CountAsync(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used);
|
||||
return await _templateRepository.Get().CountAsync(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -681,9 +699,9 @@ namespace PARR.API.Controllers.V1
|
||||
/// <returns></returns>
|
||||
private async Task<MatchingStatusResponse?> GetMatchingStatusAsync(Guid jobId)
|
||||
{
|
||||
var statusMatching = await matchingStatusService.GetStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
|
||||
var statusMatching = await _matchingStatusService.GetStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
|
||||
|
||||
return mapper.Map<MatchingStatusResponse>(statusMatching);
|
||||
return _mapper.Map<MatchingStatusResponse>(statusMatching);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,14 +14,15 @@ using PARR.API.Services.Interfaces;
|
||||
using PARR.API.Settings;
|
||||
using PARR.Core.Common.Helpers;
|
||||
using PARR.Core.Common.Interfaces.RabbitServices;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.JobRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.Schedule;
|
||||
using PARR.Core.Services.MatchingStatusService;
|
||||
using PARR.Domain.Common.Pagination;
|
||||
using PARR.Domain.Common.Rabbit.Messages;
|
||||
using PARR.Domain.Common.Roles;
|
||||
using PARR.Domain.Entities.Base.History;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
using PARR.Domain.Entities.Schedule;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
@@ -33,63 +34,64 @@ namespace PARR.API.Controllers.V1
|
||||
[Authorize(Roles = ParrRoles.Administrator.Role)]
|
||||
public class JobGroupController : BaseApiController
|
||||
{
|
||||
private readonly ILogger<JobController> logger;
|
||||
private readonly IMapper mapper;
|
||||
private readonly IUriService uriService;
|
||||
private readonly IJobGroupRepository groupService;
|
||||
private readonly IJobRepository jobService;
|
||||
private readonly IEsppSchTypeConfigRepository esppConfigService;
|
||||
//private readonly IValidator<JobGroupRequest> validator;
|
||||
private readonly IJobGroupTypeRepository jobGroupTypeService;
|
||||
private readonly IMatchingStatusService matchingStatusService;
|
||||
private readonly IScheduleResponseAreaTimeOffsetRepository scheduleResponseAreaTimeOffsetService;
|
||||
private readonly IRabbitService mqService;
|
||||
private readonly MqSettings mqSettings;
|
||||
private readonly ILogger<JobController> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IUriService _uriService;
|
||||
private readonly IJobGroupRepository _groupRepository;
|
||||
private readonly IJobRepository _jobRepository;
|
||||
private readonly IEsppSchTypeConfigRepository _esppConfigRepository;
|
||||
private readonly IJobGroupTypeRepository _jobGroupTypeRepository;
|
||||
private readonly IMatchingStatusService _matchingStatusRepository;
|
||||
private readonly IScheduleResponseAreaTimeOffsetRepository _scheduleResponseAreaTimeOffsetRepository;
|
||||
private readonly IJobAutoControlRepository _jobAutoControlRepository;
|
||||
private readonly IRabbitService _mqService;
|
||||
private readonly MqSettings _mqSettings;
|
||||
|
||||
public JobGroupController(
|
||||
ILogger<JobController> logger,
|
||||
IMapper mapper,
|
||||
IUriService uriService,
|
||||
IJobGroupRepository groupService,
|
||||
IJobRepository jobService,
|
||||
IEsppSchTypeConfigRepository esppConfigService,
|
||||
//IValidator<JobGroupRequest> validator,
|
||||
IJobGroupTypeRepository jobGroupTypeService,
|
||||
IMatchingStatusService matchingStatusService,
|
||||
IScheduleResponseAreaTimeOffsetRepository scheduleResponseAreaTimeOffsetService,
|
||||
IJobGroupRepository groupRepository,
|
||||
IJobRepository jobRepository,
|
||||
IEsppSchTypeConfigRepository esppConfigRepository,
|
||||
IJobGroupTypeRepository jobGroupTypeRepository,
|
||||
IMatchingStatusService matchingStatusRepository,
|
||||
IScheduleResponseAreaTimeOffsetRepository scheduleResponseAreaTimeOffsetRepository,
|
||||
IJobAutoControlRepository jobAutoControlRepository,
|
||||
IRabbitService mqService,
|
||||
MqSettings mqSettings
|
||||
)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.mapper = mapper;
|
||||
this.uriService = uriService;
|
||||
this.groupService = groupService;
|
||||
this.jobService = jobService;
|
||||
this.esppConfigService = esppConfigService;
|
||||
//this.validator = validator;
|
||||
this.jobGroupTypeService = jobGroupTypeService;
|
||||
this.matchingStatusService = matchingStatusService;
|
||||
this.scheduleResponseAreaTimeOffsetService = scheduleResponseAreaTimeOffsetService;
|
||||
this.mqService = mqService;
|
||||
this.mqSettings = mqSettings;
|
||||
this._logger = logger;
|
||||
this._mapper = mapper;
|
||||
this._uriService = uriService;
|
||||
_groupRepository = groupRepository;
|
||||
_jobRepository = jobRepository;
|
||||
_esppConfigRepository = esppConfigRepository;
|
||||
_jobGroupTypeRepository = jobGroupTypeRepository;
|
||||
_matchingStatusRepository = matchingStatusRepository;
|
||||
_scheduleResponseAreaTimeOffsetRepository = scheduleResponseAreaTimeOffsetRepository;
|
||||
_jobAutoControlRepository = jobAutoControlRepository;
|
||||
this._mqService = mqService;
|
||||
this._mqSettings = mqSettings;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Получить список групп заданий на выполнение работ постранично
|
||||
/// Получить список групп работ постранично
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet(ApiRoutes.JobGroup.GetAll)]
|
||||
public async Task<IActionResult> GetAll([FromQuery] PaginationQuery paginationQuery, [FromQuery] JobGroupQuery filter)
|
||||
{
|
||||
var paginationFilter = mapper.Map<PaginationFilter>(paginationQuery);
|
||||
var paginationFilter = _mapper.Map<PaginationFilter>(paginationQuery);
|
||||
|
||||
IQueryable<JobGroup> query = groupService.Get()
|
||||
IQueryable<JobGroup> query = _groupRepository.Get()
|
||||
.Include(t => t.GroupType)
|
||||
.Include(t => t.GroupingUnitField)
|
||||
.Include(t => t.ScheduleExcludeType)
|
||||
.Include(t => t.ScheduleExcludeTypeCalendar);
|
||||
.Include(t => t.ScheduleExcludeTypeCalendar)
|
||||
.Include(t => t.AutoControl);
|
||||
|
||||
query = query.OrderBy(t => t.GroupName);
|
||||
|
||||
@@ -104,12 +106,12 @@ namespace PARR.API.Controllers.V1
|
||||
.Include(t => t.Jobs).ThenInclude(t => t.Tnk)
|
||||
.Include(t => t.DistributionConfig).ThenInclude(t => t.DistributionPeriod);
|
||||
|
||||
var jobGroups = await groupService.GetPage(query, paginationFilter).ToListAsync();
|
||||
var jobGroups = await _groupRepository.GetPage(query, paginationFilter).ToListAsync();
|
||||
|
||||
if (!jobGroups.Any())
|
||||
return NoContent();
|
||||
|
||||
var response = mapper.Map<List<JobGroupResponse>>(jobGroups);
|
||||
var response = _mapper.Map<List<JobGroupResponse>>(jobGroups);
|
||||
|
||||
if (filter.IsFull)
|
||||
foreach (var jobGroupResponse in response)
|
||||
@@ -122,26 +124,27 @@ namespace PARR.API.Controllers.V1
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Получить группу заданий на выполнение работ по id
|
||||
/// Получить группу работ по id
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet(ApiRoutes.JobGroup.Get)]
|
||||
public async Task<IActionResult> GetById([FromRoute] Guid id)
|
||||
{
|
||||
var jobGroup = await groupService.Get()
|
||||
var jobGroup = await _groupRepository.Get()
|
||||
.Include(t => t.Jobs).ThenInclude(t => t.Tnk)
|
||||
.Include(t => t.GroupType)
|
||||
.Include(t => t.GroupingUnitField)
|
||||
.Include(t => t.ScheduleExcludeType)
|
||||
.Include(t => t.ScheduleExcludeTypeCalendar)
|
||||
.Include(t => t.DistributionConfig).ThenInclude(t => t.DistributionPeriod)
|
||||
.Include(t => t.AutoControl)
|
||||
.FirstOrDefaultAsync(t => t.Id == id);
|
||||
|
||||
if (jobGroup == null)
|
||||
return NotFound();
|
||||
|
||||
var response = mapper.Map<JobGroupResponse>(jobGroup);
|
||||
var response = _mapper.Map<JobGroupResponse>(jobGroup);
|
||||
await AppendMissingDataAsync(response);
|
||||
|
||||
return Ok(new Response<JobGroupResponse>(response, true));
|
||||
@@ -149,18 +152,13 @@ namespace PARR.API.Controllers.V1
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Создать группу заданий на выполнение работ (JobGroup)
|
||||
/// Создать группу работ (JobGroup)
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost(ApiRoutes.JobGroup.Create)]
|
||||
public async Task<IActionResult> Create([FromBody] JobGroupRequest request)
|
||||
{
|
||||
//var resultValidate = await validator.ValidateAsync(request);
|
||||
|
||||
//if (!resultValidate.IsValid)
|
||||
// return BadRequest(new Response(resultValidate.Errors));
|
||||
|
||||
var jobGroup = new JobGroup
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
@@ -191,7 +189,7 @@ namespace PARR.API.Controllers.V1
|
||||
// на всякий проверим, но вообще это проверяется в валидаторе
|
||||
if (request.DistributionConfig == null)
|
||||
{
|
||||
logger.LogError("Ошибка при создании группы работ '{name}', отсутствуют настройки автораспределения", request.Name);
|
||||
_logger.LogError("Ошибка при создании группы работ '{name}', отсутствуют настройки автораспределения", request.Name);
|
||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при создании группы заданий на выполнение работ" } }));
|
||||
}
|
||||
|
||||
@@ -199,6 +197,22 @@ namespace PARR.API.Controllers.V1
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Настройки автоконтроля
|
||||
|
||||
// Корректность проверяется в валидаторе
|
||||
if (request.AutoControl != null)
|
||||
{
|
||||
jobGroup.AutoControl = new JobGroupAutoControl
|
||||
{
|
||||
InitUsedScheduleState = request.AutoControl.InitUsedScheduleState,
|
||||
InitUsedTemplateState = request.AutoControl.InitUsedTemplateState,
|
||||
IsEnable = request.AutoControl.IsEnable,
|
||||
JobGroupId = jobGroup.Id
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
//Добавляем настройки планировщика
|
||||
request.Schedule.ForEach(item =>
|
||||
{
|
||||
@@ -210,23 +224,24 @@ namespace PARR.API.Controllers.V1
|
||||
});
|
||||
});
|
||||
|
||||
if (!await groupService.CreateAsync(jobGroup) || !await groupService.CommitAsync())
|
||||
if (!await _groupRepository.CreateAsync(jobGroup) || !await _groupRepository.CommitAsync())
|
||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при создании группы заданий на выполнение работ" } }));
|
||||
|
||||
logger.LogInformation($"Пользователь {User.Identity?.Name} добавил группу заданий на выполнение работ: {jobGroup.Id}, {jobGroup.GroupName}, {jobGroup.ShortDescription}");
|
||||
_logger.LogInformation($"Пользователь {User.Identity?.Name} добавил группу заданий на выполнение работ: {jobGroup.Id}, {jobGroup.GroupName}, {jobGroup.ShortDescription}");
|
||||
|
||||
|
||||
var createdJobGroup = await groupService.Get().Include(t => t.Jobs).ThenInclude(t => t.Tnk)
|
||||
var createdJobGroup = await _groupRepository.Get().Include(t => t.Jobs).ThenInclude(t => t.Tnk)
|
||||
.Include(t => t.GroupType)
|
||||
.Include(t => t.GroupingUnitField)
|
||||
.Include(t => t.ScheduleExcludeType)
|
||||
.Include(t => t.ScheduleExcludeTypeCalendar)
|
||||
.Include(t => t.DistributionConfig).ThenInclude(t => t.DistributionPeriod)
|
||||
.Include(t => t.AutoControl)
|
||||
.FirstAsync(t => t.Id == jobGroup.Id);
|
||||
|
||||
var locationUri = uriService.GetUri(ApiRoutes.JobGroup.Get, ApiRoutes.JobGroup.getParam, createdJobGroup.Id);
|
||||
var locationUri = _uriService.GetUri(ApiRoutes.JobGroup.Get, ApiRoutes.JobGroup.getParam, createdJobGroup.Id);
|
||||
|
||||
var response = mapper.Map<JobGroupResponse>(createdJobGroup);
|
||||
var response = _mapper.Map<JobGroupResponse>(createdJobGroup);
|
||||
await AppendMissingDataAsync(response);
|
||||
|
||||
return Created(locationUri, new Response<JobGroupResponse>(response, true));
|
||||
@@ -234,7 +249,7 @@ namespace PARR.API.Controllers.V1
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Обновить группу заданий на выполнение работ (JobGroup)
|
||||
/// Обновить группу работ (JobGroup)
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="request"></param>
|
||||
@@ -242,15 +257,11 @@ namespace PARR.API.Controllers.V1
|
||||
[HttpPut(ApiRoutes.JobGroup.Update)]
|
||||
public async Task<IActionResult> Update([FromRoute] Guid id, [FromBody] JobGroupRequest request)
|
||||
{
|
||||
//var resultValidate = await validator.ValidateAsync(request);
|
||||
|
||||
//if (!resultValidate.IsValid)
|
||||
// return BadRequest(new Response(resultValidate.Errors));
|
||||
|
||||
var orig = await groupService.Get()
|
||||
var orig = await _groupRepository.Get()
|
||||
.Include(t => t.Jobs)
|
||||
.ThenInclude(t => t.Tnk)
|
||||
.Include(t => t.EsppSchValues)
|
||||
.Include(t => t.AutoControl)
|
||||
.Include(t => t.DistributionConfig).ThenInclude(t => t.DistributionPeriod)
|
||||
.FirstOrDefaultAsync(t => t.Id == id);
|
||||
|
||||
@@ -287,7 +298,7 @@ namespace PARR.API.Controllers.V1
|
||||
{
|
||||
if (request.DistributionConfig == null)
|
||||
{
|
||||
logger.LogError("Ошибка при изменении группы работ '{name}', отсутствуют настройки автораспределения", request.Name);
|
||||
_logger.LogError("Ошибка при изменении группы работ '{name}', отсутствуют настройки автораспределения", request.Name);
|
||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при изменении группы заданий на выполнение работ" } }));
|
||||
}
|
||||
|
||||
@@ -309,12 +320,50 @@ namespace PARR.API.Controllers.V1
|
||||
// удаляем настройки распределения если они были
|
||||
if (orig.DistributionConfig != null)
|
||||
{
|
||||
groupService.DeleteDistributionConfig(orig.DistributionConfig);
|
||||
_groupRepository.DeleteDistributionConfig(orig.DistributionConfig);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Настройки автоконтроля
|
||||
|
||||
// валидатор проверяет корректность
|
||||
if (request.AutoControl != null)
|
||||
{
|
||||
// автоконтролем управляет JobGroup
|
||||
if (orig.AutoControl != null)
|
||||
{
|
||||
// обновляем
|
||||
orig.AutoControl.IsEnable = request.AutoControl.IsEnable;
|
||||
orig.AutoControl.InitUsedScheduleState = request.AutoControl.InitUsedScheduleState;
|
||||
orig.AutoControl.InitUsedTemplateState = request.AutoControl.InitUsedTemplateState;
|
||||
// удалить настройки автоконтроля для job
|
||||
await RemoveJobAutoControlSettingsAsync(orig.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
// создаем новую запись
|
||||
orig.AutoControl = new JobGroupAutoControl
|
||||
{
|
||||
InitUsedScheduleState = request.AutoControl.InitUsedScheduleState,
|
||||
InitUsedTemplateState = request.AutoControl.InitUsedTemplateState,
|
||||
IsEnable = request.AutoControl.IsEnable,
|
||||
JobGroupId = orig.Id
|
||||
};
|
||||
// удалить настройки автоконтроля для job
|
||||
await RemoveJobAutoControlSettingsAsync(orig.Id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// автоконтролем управляет каждый job отдельно
|
||||
// удаляем настройки, если они были
|
||||
orig.AutoControl = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
//обновляем планировщик
|
||||
orig.EsppSchValues.Clear();
|
||||
request.Schedule.ForEach(item =>
|
||||
@@ -327,10 +376,10 @@ namespace PARR.API.Controllers.V1
|
||||
});
|
||||
});
|
||||
|
||||
if (!await groupService.CommitAsync())
|
||||
if (!await _groupRepository.CommitAsync())
|
||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при изменении группы заданий на выполнение работ." } }));
|
||||
|
||||
logger.LogInformation($"Пользователь {User.Identity?.Name} обновил группу заданий на выполнение работ: {orig.Id}," +
|
||||
_logger.LogInformation($"Пользователь {User.Identity?.Name} обновил группу заданий на выполнение работ: {orig.Id}," +
|
||||
$" {orig.GroupName}, {orig.ShortDescription}, {orig.FullDescription}," +
|
||||
$" {orig.Solution}, {orig.TemplateDuration}, {orig.ReferenceDate}, {orig.IsAutoDistributionEnabled}" +
|
||||
$", {orig.IsAgent}, {orig.AgentName}, {orig.AgentTimeOutSec}, {orig.AgentScript}");
|
||||
@@ -345,17 +394,17 @@ namespace PARR.API.Controllers.V1
|
||||
JobGroupId = id,
|
||||
Initiator = new HistoryInitiator { InitiatorComment = "Изменилось расписание группы работ в ГУИ, отправлен запрос на перерасчет nextRun", InitiatorParrComponentId = ParrComponentsEnum.Api }
|
||||
};
|
||||
var sendResult = await mqService.SendAsync(mqSettings.NextRun, new List<object> { requestToMq });
|
||||
var sendResult = await _mqService.SendAsync(_mqSettings.NextRun, new List<object> { requestToMq });
|
||||
|
||||
if (sendResult.IsSuccess)
|
||||
logger.LogInformation("Задание на перерасчет NextRun успешно отправлено в очередь MQ {queueName}", mqSettings.NextRun.QueueName);
|
||||
_logger.LogInformation("Задание на перерасчет NextRun успешно отправлено в очередь MQ {queueName}", _mqSettings.NextRun.QueueName);
|
||||
else
|
||||
logger.LogError("Ошибка при отправке задания на перерасчет NextRun в очередь MQ {queueName}", mqSettings.NextRun.QueueName);
|
||||
_logger.LogError("Ошибка при отправке задания на перерасчет NextRun в очередь MQ {queueName}", _mqSettings.NextRun.QueueName);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
var updatedJobGroup = await groupService.Get()
|
||||
var updatedJobGroup = await _groupRepository.Get()
|
||||
.Include(t => t.Jobs)
|
||||
.ThenInclude(t => t.Tnk)
|
||||
.Include(t => t.GroupType)
|
||||
@@ -363,9 +412,10 @@ namespace PARR.API.Controllers.V1
|
||||
.Include(t => t.ScheduleExcludeType)
|
||||
.Include(t => t.ScheduleExcludeTypeCalendar)
|
||||
.Include(t => t.DistributionConfig).ThenInclude(t => t.DistributionPeriod)
|
||||
.Include(t => t.AutoControl)
|
||||
.FirstAsync(t => t.Id == orig.Id);
|
||||
|
||||
var response = mapper.Map<JobGroupResponse>(updatedJobGroup);
|
||||
var response = _mapper.Map<JobGroupResponse>(updatedJobGroup);
|
||||
await AppendMissingDataAsync(response);
|
||||
|
||||
return Ok(new Response<JobGroupResponse>(response, true));
|
||||
@@ -373,14 +423,14 @@ namespace PARR.API.Controllers.V1
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Удалить группу заданий на выполнение работ (только если нет связанных заданий)
|
||||
/// Удалить группу работ (только если нет связанных работ)
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpDelete(ApiRoutes.JobGroup.Delete)]
|
||||
public async Task<IActionResult> Delete([FromRoute] Guid id)
|
||||
{
|
||||
var jobGroup = await groupService.Get()
|
||||
var jobGroup = await _groupRepository.Get()
|
||||
.FirstOrDefaultAsync(t => t.Id == id);
|
||||
|
||||
if (jobGroup == null)
|
||||
@@ -388,18 +438,18 @@ namespace PARR.API.Controllers.V1
|
||||
Message = $"Ошибка при удалении группы заданий на выполнение работ. Не найдена группа заданий на выполнение работ Id: {id}"
|
||||
} }));
|
||||
|
||||
var jobCount = await jobService.Get().CountAsync(t => t.GroupId == id);
|
||||
var jobCount = await _jobRepository.Get().CountAsync(t => t.GroupId == id);
|
||||
if (jobCount > 0)
|
||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel {
|
||||
Message = $"Ошибка при удалении группы заданий на выполнение работ. С данным группой связаны задания: {jobCount} шт."
|
||||
} }));
|
||||
|
||||
if (!groupService.Delete(jobGroup) || !await groupService.CommitAsync())
|
||||
if (!_groupRepository.Delete(jobGroup) || !await _groupRepository.CommitAsync())
|
||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel {
|
||||
Message = $"Ошибка при удалении группы заданий на выполнение работ"
|
||||
} }));
|
||||
|
||||
logger.LogInformation($"Пользователь {User.Identity?.Name} удалил группу заданий на выполнение работ: {jobGroup.Id},{jobGroup.GroupName}," +
|
||||
_logger.LogInformation($"Пользователь {User.Identity?.Name} удалил группу заданий на выполнение работ: {jobGroup.Id},{jobGroup.GroupName}," +
|
||||
$" {jobGroup.ShortDescription}, {jobGroup.FullDescription}," +
|
||||
$" {jobGroup.Solution}, {jobGroup.TemplateDuration}, {jobGroup.ReferenceDate}," +
|
||||
$" {jobGroup.IsAutoDistributionEnabled}, {jobGroup.IsAgent}, {jobGroup.AgentName}," +
|
||||
@@ -408,6 +458,24 @@ namespace PARR.API.Controllers.V1
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удалить настройки автоконтроля для связанных Job
|
||||
/// </summary>
|
||||
/// <param name="jobGroupId"></param>
|
||||
/// <returns></returns>
|
||||
private async Task RemoveJobAutoControlSettingsAsync(Guid jobGroupId)
|
||||
{
|
||||
var jobAutoControlsToRemove = await _jobAutoControlRepository.Get()
|
||||
.Where(t => t.Job!.GroupId == jobGroupId)
|
||||
.ToListAsync();
|
||||
|
||||
if (!jobAutoControlsToRemove.Any())
|
||||
return;
|
||||
|
||||
_logger.LogInformation("У группы работ {JobGroupId}, у связанных работ {JobsCount} шт. удалены настройки автоконтроля, так как автоконтролем управляем группа работ.", jobGroupId, jobAutoControlsToRemove.Count);
|
||||
|
||||
_jobAutoControlRepository.RemoveRange(jobAutoControlsToRemove);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверка, были ли изменения в расписании
|
||||
@@ -471,25 +539,25 @@ namespace PARR.API.Controllers.V1
|
||||
/// <returns></returns>
|
||||
private async Task AppendMissingDataAsync(JobGroupResponse jobGroupResponse)
|
||||
{
|
||||
var schedule = await esppConfigService.GetEsppScheduleDtoAsync(jobGroupResponse.Id);
|
||||
var schedule = await _esppConfigRepository.GetEsppScheduleDtoAsync(jobGroupResponse.Id);
|
||||
|
||||
if (schedule == null)
|
||||
{
|
||||
logger.LogError($"Не смог замапить расписание, так как оно null. JobGroupId: {jobGroupResponse.Id}");
|
||||
_logger.LogError($"Не смог замапить расписание, так как оно null. JobGroupId: {jobGroupResponse.Id}");
|
||||
return;
|
||||
}
|
||||
|
||||
var scheduleResponse = new JobGroupScheduleResponse
|
||||
{
|
||||
//Timezone = settingsFromDb.ScheduleTimezone,
|
||||
Timezone = scheduleResponseAreaTimeOffsetService.GetDefault.EsppValue,
|
||||
TypeSchedule = mapper.Map<EsppScheduleTypeScheduleResponse>(schedule.TypeSchedule),
|
||||
Values = mapper.Map<List<EsppScheduleValResponse>>(schedule.Values).OrderBy(t => t.Order).ToList()
|
||||
Timezone = _scheduleResponseAreaTimeOffsetRepository.GetDefault.EsppValue,
|
||||
TypeSchedule = _mapper.Map<EsppScheduleTypeScheduleResponse>(schedule.TypeSchedule),
|
||||
Values = _mapper.Map<List<EsppScheduleValResponse>>(schedule.Values).OrderBy(t => t.Order).ToList()
|
||||
};
|
||||
|
||||
jobGroupResponse.Schedule = scheduleResponse;
|
||||
|
||||
jobGroupResponse.JobsCount = await jobService.Get().CountAsync(t => t.GroupId == jobGroupResponse.Id);
|
||||
jobGroupResponse.JobsCount = await _jobRepository.Get().CountAsync(t => t.GroupId == jobGroupResponse.Id);
|
||||
|
||||
jobGroupResponse.MatchingStatus = await GetMatchingStatusAsync(jobGroupResponse.Id);
|
||||
}
|
||||
@@ -506,7 +574,7 @@ namespace PARR.API.Controllers.V1
|
||||
return null;
|
||||
|
||||
// Если есть значение, смотрим, групповой ли тип работ, и если нет, то вернем null
|
||||
var groupingType = await jobGroupTypeService.Get().FirstAsync(t => t.Code == JobGroupTypesEnum.Group);
|
||||
var groupingType = await _jobGroupTypeRepository.Get().FirstAsync(t => t.Code == JobGroupTypesEnum.Group);
|
||||
if (request.GroupTypeId == groupingType.Id)
|
||||
{
|
||||
// это групповой тип работ, все ок
|
||||
@@ -515,7 +583,7 @@ namespace PARR.API.Controllers.V1
|
||||
else
|
||||
{
|
||||
// Это не сгруппированный тип, обнуляем IsGroupByResponsible
|
||||
logger.LogInformation("При сохраненни JobGroup, был передан IsGroupByResponsible: {IsGroupByResponsible}, но при этом, тип группы не сгруппированный, а GroupTypeId: {GroupTypeId}, обнулил IsGroupByResponsible",
|
||||
_logger.LogInformation("При сохраненни JobGroup, был передан IsGroupByResponsible: {IsGroupByResponsible}, но при этом, тип группы не сгруппированный, а GroupTypeId: {GroupTypeId}, обнулил IsGroupByResponsible",
|
||||
request.IsGroupByResponsible, request.GroupTypeId);
|
||||
return null;
|
||||
}
|
||||
@@ -534,7 +602,7 @@ namespace PARR.API.Controllers.V1
|
||||
return request.GroupingUnitFieldId;
|
||||
|
||||
|
||||
var groupingType = await jobGroupTypeService.Get().FirstAsync(t => t.Code == JobGroupTypesEnum.Group);
|
||||
var groupingType = await _jobGroupTypeRepository.Get().FirstAsync(t => t.Code == JobGroupTypesEnum.Group);
|
||||
|
||||
if (request.GroupTypeId == groupingType.Id)
|
||||
{
|
||||
@@ -544,7 +612,7 @@ namespace PARR.API.Controllers.V1
|
||||
else
|
||||
{
|
||||
// Это не сгруппированный тип, обнуляем GroupingUnitFieldId
|
||||
logger.LogInformation("При сохраненни JobGroup, был передан GroupingUnitFieldId: {GroupingUnitFieldId}, но при этом, тип группы не сгруппированный, а GroupTypeId: {GroupTypeId}, обнулил GroupingUnitFieldId",
|
||||
_logger.LogInformation("При сохраненни JobGroup, был передан GroupingUnitFieldId: {GroupingUnitFieldId}, но при этом, тип группы не сгруппированный, а GroupTypeId: {GroupTypeId}, обнулил GroupingUnitFieldId",
|
||||
request.GroupingUnitFieldId, request.GroupTypeId);
|
||||
return null;
|
||||
}
|
||||
@@ -558,9 +626,9 @@ namespace PARR.API.Controllers.V1
|
||||
/// <returns></returns>
|
||||
private async Task<MatchingStatusResponse?> GetMatchingStatusAsync(Guid jobGroupId)
|
||||
{
|
||||
var statusMatching = await matchingStatusService.GetStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup);
|
||||
var statusMatching = await _matchingStatusRepository.GetStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup);
|
||||
|
||||
return mapper.Map<MatchingStatusResponse>(statusMatching);
|
||||
return _mapper.Map<MatchingStatusResponse>(statusMatching);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ using PARR.API.Contracts.V1;
|
||||
using PARR.API.Contracts.V1.Responses;
|
||||
using PARR.API.Contracts.V1.Responses.Base;
|
||||
using PARR.API.Controllers.V1.Base;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
using PARR.Domain.Common.Roles;
|
||||
|
||||
namespace PARR.API.Controllers.V1
|
||||
@@ -17,23 +17,29 @@ namespace PARR.API.Controllers.V1
|
||||
[Authorize(Roles = ParrRoles.Administrator.Role)]
|
||||
public class JobGroupTypeController : BaseApiController
|
||||
{
|
||||
private readonly IJobGroupTypeRepository jobGroupTypeService;
|
||||
private readonly IJobGroupTypeRepository jobGroupTypeRepository;
|
||||
private readonly IMapper mapper;
|
||||
|
||||
public JobGroupTypeController(
|
||||
IJobGroupTypeRepository jobGroupTypeService,
|
||||
IJobGroupTypeRepository jobGroupTypeRepository,
|
||||
IMapper mapper
|
||||
)
|
||||
{
|
||||
this.jobGroupTypeService = jobGroupTypeService;
|
||||
this.jobGroupTypeRepository = jobGroupTypeRepository;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Получить список типов групп работ
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet(ApiRoutes.JobGroupType.GetAll)]
|
||||
public async Task<IActionResult> GetAll()
|
||||
{
|
||||
var query = jobGroupTypeService.Get().OrderBy(t => t.Description);
|
||||
var query = jobGroupTypeRepository.Get()
|
||||
.AsNoTracking()
|
||||
.OrderBy(t => t.Description);
|
||||
|
||||
var types = await query.ToListAsync();
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ using PARR.API.Contracts.V1.Responses.Base;
|
||||
using PARR.API.Controllers.V1.Base;
|
||||
using PARR.API.Settings;
|
||||
using PARR.Core.Common.Interfaces.RabbitServices;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
using PARR.Domain.Common.Rabbit.Messages;
|
||||
using PARR.Domain.Common.Roles;
|
||||
using PARR.Domain.Entities.Base.History;
|
||||
|
||||
@@ -8,11 +8,11 @@ using PARR.API.Contracts.V1.Requests;
|
||||
using PARR.API.Contracts.V1.Responses;
|
||||
using PARR.API.Contracts.V1.Responses.Base;
|
||||
using PARR.API.Controllers.V1.Base;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
using PARR.Core.Services.UnitFilterService;
|
||||
using PARR.Domain.Common.Roles;
|
||||
using Job = PARR.Domain.Entities.Job.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
|
||||
namespace PARR.API.Controllers.V1
|
||||
{
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace PARR.API.Controllers.V1
|
||||
var paginationFilter = mapper.Map<PaginationFilter>(paginationQuery);
|
||||
|
||||
IQueryable<RobotHistory> query = robotHistoryService.Get()
|
||||
.AsNoTracking()
|
||||
.Include(t => t.RobotConfiguration)
|
||||
.ThenInclude(t => t!.Template)
|
||||
.Include(t => t.RobotConfiguration)
|
||||
@@ -79,19 +80,38 @@ namespace PARR.API.Controllers.V1
|
||||
if (request.HistoryLevel.HasValue)
|
||||
query = query.Where(t => t.HistoryLevel == (int)request.HistoryLevel);
|
||||
|
||||
if (request.DateFrom.HasValue)
|
||||
{
|
||||
var startDateUtc = request.DateFrom.Value.ToUniversalTime();
|
||||
query = query.Where(t => t.DateCreated >= startDateUtc);
|
||||
}
|
||||
|
||||
if (request.DateTo.HasValue)
|
||||
{
|
||||
var endDateUtc = request.DateTo.Value.ToUniversalTime();
|
||||
query = query.Where(t => t.DateCreated < endDateUtc);
|
||||
}
|
||||
|
||||
var history = await robotHistoryService.GetPage(query, paginationFilter).ToListAsync();
|
||||
|
||||
if (!history.Any())
|
||||
if (history.Count == 0)
|
||||
return NoContent();
|
||||
|
||||
var robotsIp = history.Where(t => !string.IsNullOrEmpty(t.RobotIp)).Select(t => t.RobotIp).Distinct().ToList();
|
||||
var users = await userService.Get().Where(t => robotsIp.Any(x => x == t.Ip)).Distinct().ToListAsync();
|
||||
var usersDictionary = await userService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(t => robotsIp.Contains(t.Ip))
|
||||
.ToDictionaryAsync(t => t.Ip, t => t);
|
||||
|
||||
var response = mapper.Map<List<RobotHistoryResponse>>(history);
|
||||
response.ForEach(item =>
|
||||
foreach (var item in response)
|
||||
{
|
||||
item.User = mapper.Map<UserBaseResponse>(users.FirstOrDefault(t => t.Ip == item.RobotIp));
|
||||
});
|
||||
if (!string.IsNullOrWhiteSpace(item.RobotIp) && usersDictionary.TryGetValue(item.RobotIp, out var user))
|
||||
{
|
||||
item.User = mapper.Map<UserBaseResponse>(user);
|
||||
}
|
||||
}
|
||||
|
||||
var paginationResponse = new PagedResponse<RobotHistoryResponse>(response, true).GetPaginatedProps(paginationFilter, query);
|
||||
|
||||
return Ok(paginationResponse);
|
||||
|
||||
@@ -7,6 +7,7 @@ using PARR.API.Contracts.V1.Responses;
|
||||
using PARR.API.Contracts.V1.Responses.Base;
|
||||
using PARR.API.Controllers.V1.Base;
|
||||
using PARR.API.Services.Interfaces;
|
||||
using PARR.API.Settings;
|
||||
using PARR.Core.Services.RobotTask.Interfaces;
|
||||
using PARR.Domain.Common.Roles;
|
||||
using PARR.Domain.Entities.Base.History;
|
||||
@@ -18,38 +19,22 @@ namespace PARR.API.Controllers.V1
|
||||
[Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
|
||||
public class RobotTaskController : BaseApiController
|
||||
{
|
||||
private readonly IRobotTaskService robotTaskService;
|
||||
|
||||
private readonly IMapper mapper;
|
||||
//private readonly SettingsFromDb settingsFromDb;
|
||||
//private readonly IRobotConfigurationRepository robotConfigurationService;
|
||||
//private readonly ILogger<RobotTaskController> logger;
|
||||
private readonly IClientService clientService;
|
||||
//private readonly IRobotHistoryRepository robotHistoryService;
|
||||
//private readonly IShortcodesService shortcodesService;
|
||||
//private readonly INextRunService nextRunService;
|
||||
private readonly IRobotTaskService _robotTaskService;
|
||||
private readonly CommonSettings _commonSettings;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IClientService _clientService;
|
||||
|
||||
public RobotTaskController(
|
||||
IMapper mapper,
|
||||
//SettingsFromDb settingsFromDb,
|
||||
//IRobotConfigurationRepository robotConfigurationService,
|
||||
//ILogger<RobotTaskController> logger,
|
||||
IClientService clientService,
|
||||
//IRobotHistoryRepository robotHistoryService,
|
||||
//IShortcodesService shortcodesService,
|
||||
//INextRunService nextRunService
|
||||
IRobotTaskService robotTaskService
|
||||
IRobotTaskService robotTaskService,
|
||||
CommonSettings commonSettings
|
||||
)
|
||||
{
|
||||
this.robotTaskService = robotTaskService;
|
||||
this.mapper = mapper;
|
||||
//this.settingsFromDb = settingsFromDb;
|
||||
//this.robotConfigurationService = robotConfigurationService;
|
||||
//this.logger = logger;
|
||||
this.clientService = clientService;
|
||||
//this.robotHistoryService = robotHistoryService;
|
||||
//this.shortcodesService = shortcodesService;
|
||||
//this.nextRunService = nextRunService;
|
||||
_robotTaskService = robotTaskService;
|
||||
_commonSettings = commonSettings;
|
||||
_mapper = mapper;
|
||||
_clientService = clientService;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,314 +51,37 @@ namespace PARR.API.Controllers.V1
|
||||
switch (robotCode)
|
||||
{
|
||||
case RobotsEnum.TemplateOrder:
|
||||
var templateTask = await robotTaskService.GetTemplateTaskAsync(
|
||||
var templateTask = await _robotTaskService.GetTemplateTaskAsync(
|
||||
taskStatusCode,
|
||||
requestQuery.SetInProgressStatus ?? false,
|
||||
clientService.GetClientIp()?.ToString(),
|
||||
_clientService.GetClientIp()?.ToString(),
|
||||
requestQuery.RobotId
|
||||
);
|
||||
var templateResponse = mapper.Map<RobotTaskTemplateResponse>(templateTask);
|
||||
var templateResponse = _mapper.Map<RobotTaskTemplateResponse>(templateTask);
|
||||
|
||||
return Ok(new Response<RobotTaskTemplateResponse>(templateResponse, true));
|
||||
case RobotsEnum.ScheduleOrder:
|
||||
var historyIniciator = new HistoryInitiator
|
||||
{
|
||||
InitiatorComment="Задание роботу, расписание.",
|
||||
InitiatorIp=clientService.GetClientIp()?.ToString(),
|
||||
InitiatorParrComponentId= ParrComponentsEnum.Api
|
||||
InitiatorComment = "Задание роботу, расписание.",
|
||||
InitiatorIp = _clientService.GetClientIp()?.ToString(),
|
||||
InitiatorParrComponentId = ParrComponentsEnum.Api
|
||||
};
|
||||
var scheduleTask = await robotTaskService.GetScheduleTaskAsync(
|
||||
var scheduleTask = await _robotTaskService.GetScheduleTaskAsync(
|
||||
taskStatusCode,
|
||||
requestQuery.SetInProgressStatus ?? false,
|
||||
historyIniciator.InitiatorIp,
|
||||
requestQuery.RobotId,
|
||||
historyIniciator
|
||||
historyIniciator,
|
||||
_commonSettings.ScheduleCooldownDuration
|
||||
);
|
||||
|
||||
var scheduleResponse = mapper.Map<RobotTaskScheduleResponse>(scheduleTask);
|
||||
var scheduleResponse = _mapper.Map<RobotTaskScheduleResponse>(scheduleTask);
|
||||
|
||||
return Ok(new Response<RobotTaskScheduleResponse>(scheduleResponse, true));
|
||||
default:
|
||||
throw new AppValidationException("Некорректное значение robotCode");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Old
|
||||
|
||||
// /// <summary>
|
||||
// /// Получить задание для робота по коду робота и по статусу задания
|
||||
// /// </summary>
|
||||
// /// <param name="robotCode"></param>
|
||||
// /// <param name="taskStatusCode"></param>
|
||||
// /// <returns></returns>
|
||||
// [HttpGet(ApiRoutes.RobotTask.GetByRobotAndStatusTask)]
|
||||
// public async Task<IActionResult> GetByRobotAndStatusTask([FromRoute] RobotsEnum robotCode, [FromRoute] TaskStatusEnum taskStatusCode, [FromQuery] RobotTaskQuery requestQuery)
|
||||
// {
|
||||
// //Ищем все задания с превышенным кол-вом попыток и с просроченным временем и ставим им статус ошибки
|
||||
// await robotConfigurationService.MarkExpiredTasksAsFailedAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime);
|
||||
|
||||
|
||||
// var query = robotConfigurationService.Get()
|
||||
// .AsSingleQuery()
|
||||
// .Where(t => t.RobotCode == (int)robotCode && t.TaskStatusCode == (int)taskStatusCode);
|
||||
|
||||
// switch (robotCode)
|
||||
// {
|
||||
// case RobotsEnum.TemplateOrder:
|
||||
// // шаблоны
|
||||
// query = query
|
||||
// .Include(t => t.Template)
|
||||
// .ThenInclude(t => t!.Unit)
|
||||
// .ThenInclude(t => t!.UnitValues)
|
||||
// .ThenInclude(t => t.Field)
|
||||
// .Include(t => t.Template)
|
||||
// .ThenInclude(t => t!.Unit)
|
||||
// .ThenInclude(t => t!.UnitValues)
|
||||
// .ThenInclude(t => t.Value)
|
||||
// .Include(t => t.Template)
|
||||
// .ThenInclude(a => a!.Job)
|
||||
// .ThenInclude(t => t!.Group)
|
||||
// .ThenInclude(g => g.GroupType)
|
||||
// .Include(t => t.Template)
|
||||
// .ThenInclude(w => w!.Job)
|
||||
// .ThenInclude(t => t!.Tnk)
|
||||
// .ThenInclude(s => s!.Subprocess)
|
||||
// .ThenInclude(p => p!.Process);
|
||||
|
||||
// query = query
|
||||
// .Include(t => t.Template)
|
||||
// .ThenInclude(t => t!.UnitsInTemplate);
|
||||
|
||||
// break;
|
||||
|
||||
// case RobotsEnum.ScheduleOrder:
|
||||
// //расписание
|
||||
// query = query
|
||||
// .Include(t => t.Template)
|
||||
// .ThenInclude(t => t!.Unit)
|
||||
// .ThenInclude(t => t!.UnitValues)
|
||||
// .ThenInclude(t => t.Field)
|
||||
// .Include(t => t.Template)
|
||||
// .ThenInclude(t => t!.Unit)
|
||||
// .ThenInclude(t => t!.UnitValues)
|
||||
// .ThenInclude(t => t.Value)
|
||||
// .Include(t => t.Template)
|
||||
// .ThenInclude(a => a!.Job)
|
||||
// .ThenInclude(t => t!.Group)
|
||||
// .ThenInclude(g => g.GroupType)
|
||||
// .Include(t => t.Template)
|
||||
// .ThenInclude(a => a!.Job)
|
||||
// .ThenInclude(t => t!.Group)
|
||||
// .ThenInclude(t => t!.EsppSchValues)
|
||||
// .ThenInclude(t => t!.EsppSchTypeConfig)
|
||||
// .ThenInclude(t => t!.EsppSchTypeSchedule)
|
||||
// .Include(t => t.Template)
|
||||
// .ThenInclude(t => t!.Job)
|
||||
// .ThenInclude(t => t!.Group)
|
||||
// .ThenInclude(t => t!.ScheduleExcludeType)
|
||||
// .Include(t => t.Template)
|
||||
// .ThenInclude(t => t!.Job)
|
||||
// .ThenInclude(t => t!.Group)
|
||||
// .ThenInclude(t => t.ScheduleExcludeTypeCalendar)
|
||||
// .Include(t => t.Template)
|
||||
// .ThenInclude(t => t!.Job)
|
||||
// .ThenInclude(t => t!.Tnk)
|
||||
// .Include(t => t.Template)
|
||||
// .ThenInclude(t => t!.UnitsInTemplate);
|
||||
|
||||
// //выбираем только записи с созданными шаблонами (у которых статус 20 или 30), а только потом у них ищем расписания
|
||||
// var createdTemplates = robotConfigurationService.Get()
|
||||
// .Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder && (t.TaskStatusCode == (int)TaskStatusEnum.Ok))
|
||||
// .Select(t => t.TemplateId);
|
||||
// query = query.Where(t => t.RobotCode == (int)RobotsEnum.ScheduleOrder && createdTemplates.Contains(t.TemplateId));
|
||||
// // query = query.Where(t => t.RobotCode == (int)RobotsEnum.ScheduleOrder && t.TemplateId==Guid.Parse("7cb7c3be-506d-40e0-a63f-4554edb52459"));
|
||||
// break;
|
||||
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
|
||||
|
||||
// // сортируем по NextRun, чтобы те у которых дата след срабатывания ближе к текущей, выполнились скорее
|
||||
// query = query.OrderBy(t => t.Template!.NextRun).ThenBy(t => t.Template!.IsActiveSchedule).ThenBy(t => t.Template.IsActiveTemplate);
|
||||
|
||||
// RobotConfiguration? task = null;
|
||||
|
||||
// //ищем задание в ожидании, если нашли, выбираем его
|
||||
// task = await query.FirstOrDefaultAsync(t => t.RobotStatusCode == (int)RobotStatusEnum.Wait);
|
||||
|
||||
// if (task == null)
|
||||
// {
|
||||
// //ищем задания в работе, которые можно перезапустить
|
||||
// //Поиск по `RobotStatusCode` = 22.
|
||||
// //Далее проверяется `LastStatusUpdated`, что время последнего смены статуса не превышает допустимого(берется из настроек, поле `RobotWaitTime`)
|
||||
// //и что текущая попытка не больше разрешенной(берется из настроек, поле `RobotAttemptsNumber`) - если это так, берется эта запись.
|
||||
|
||||
// var endDate = DateTimeOffset.UtcNow.Add(-settingsFromDb.RobotWaitTime);
|
||||
// task = await query
|
||||
// .FirstOrDefaultAsync(t =>
|
||||
// t.RobotStatusCode == (int)RobotStatusEnum.InProgress
|
||||
// && t.AttemptsNumber < settingsFromDb.RobotAttemptsNumber
|
||||
// && t.LastRobotStatusUpdated < endDate
|
||||
// );
|
||||
// }
|
||||
|
||||
// if (task == null)
|
||||
// return NotFound();
|
||||
|
||||
// if (requestQuery?.SetInProgressStatus == true)
|
||||
// {
|
||||
// var resultSetStatus = await SetInProgressStatusAsync(task.Id);
|
||||
// if (resultSetStatus == false)
|
||||
// {
|
||||
// logger.LogError($"Ошибка при установке статуса {RobotStatusEnum.InProgress.ToString()} для задания RobotConfigutationId {task.Id} (при выдаче задания роботу)");
|
||||
// return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при выдаче задания." } }));
|
||||
// }
|
||||
// }
|
||||
|
||||
// switch (robotCode)
|
||||
// {
|
||||
// case RobotsEnum.TemplateOrder:
|
||||
// { //RobotTaskTemplateResponse
|
||||
// var robotTaskTemplateResponse = mapper.Map<RobotTaskTemplateResponse>(task);
|
||||
|
||||
// robotTaskTemplateResponse.FullDescription = NormalizeLineEndingsToCrlf(await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.FullDescription, task.Template!));
|
||||
// robotTaskTemplateResponse.ShortDescription = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.ShortDescription, task.Template!);
|
||||
// robotTaskTemplateResponse.Solution = NormalizeLineEndingsToCrlf(await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.Solution, task.Template!));
|
||||
// robotTaskTemplateResponse.TnkName = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.TnkName, task.Template!);
|
||||
// robotTaskTemplateResponse.WorkName = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.WorkName, task.Template!);
|
||||
// robotTaskTemplateResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.WorkGroup, task.Template!);
|
||||
// robotTaskTemplateResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.ResponseArea, task.Template!);
|
||||
|
||||
// return Ok(new Response<RobotTaskTemplateResponse>(robotTaskTemplateResponse, true));
|
||||
// }
|
||||
// case RobotsEnum.ScheduleOrder:
|
||||
// { // если был запрос на расписание, проверяем у него nextRun, lastRun, обновляем их
|
||||
// var resultUpdateNextRun = await UpdateNextRunAsync(task);
|
||||
// if (!resultUpdateNextRun)
|
||||
// {
|
||||
// logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}", task.TemplateId);
|
||||
// return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
|
||||
// }
|
||||
|
||||
// //RobotTaskScheduleResponse
|
||||
// var robotTaskScheduleResponse = mapper.Map<RobotTaskScheduleResponse>(task);
|
||||
|
||||
// robotTaskScheduleResponse.Timezone = settingsFromDb.EsppScheduleTimezone;
|
||||
|
||||
// robotTaskScheduleResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.WorkGroup, task.Template!);
|
||||
// robotTaskScheduleResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.ResponseArea, task.Template!);
|
||||
|
||||
// //var nextRunWithRobotTz = nextRunService.GetNextRunWithTimezoneEsppAndResponseArea(task.Template!.NextRun, task.Template!.Job?.Group?.IsResponseAreaTimezone, robotTaskScheduleResponse.ResponseArea);
|
||||
// //nextRun в часовой зоне УЗ Робота ЕСПП
|
||||
// var nextRunWithRobotTz = task.Template!.NextRun.Add(nextRunService.GetEsppAccountOffset());
|
||||
|
||||
// //на всякий случай еще раз проверяем, что дата не устарела и отправляем задание
|
||||
// if (nextRunWithRobotTz < DateTimeOffset.UtcNow)
|
||||
// {
|
||||
// logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}, итоговое значение для робота, меньше чем сейчас {nextRunWithRobotTz}<{now}",
|
||||
// task.TemplateId, nextRunWithRobotTz, DateTimeOffset.UtcNow);
|
||||
// return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
|
||||
// }
|
||||
|
||||
// robotTaskScheduleResponse.NextStart = EsppScheduleHelpers.GetNextRun(nextRunWithRobotTz);
|
||||
// robotTaskScheduleResponse.GenerationTime = EsppScheduleHelpers.GetGenerationTime(nextRunWithRobotTz);
|
||||
|
||||
// return Ok(new Response<RobotTaskScheduleResponse>(robotTaskScheduleResponse, true));
|
||||
// }
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
|
||||
// return BadRequest();
|
||||
// }
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// Обоновить NextRun если он устарел
|
||||
// /// </summary>
|
||||
// /// <param name="task"></param>
|
||||
// /// <returns></returns>
|
||||
// private async Task<bool> UpdateNextRunAsync(RobotConfiguration task)
|
||||
// {
|
||||
// var template = task.Template!;
|
||||
|
||||
// //var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.Job!.GroupId, template!.Job!.Group!.ReferenceDate);
|
||||
// var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false);
|
||||
|
||||
// if (!nextRun.HasValue)
|
||||
// {
|
||||
// logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun=null, ошибка в расчетах.", template.Id);
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (nextRun.Value < DateTimeOffset.UtcNow)
|
||||
// {
|
||||
// logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun<Now [{nextRun}<{now}], ошибка в расчетах.", template.Id, nextRun.Value, DateTimeOffset.UtcNow);
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (nextRun != template.NextRun)
|
||||
// {
|
||||
// logger.LogDebug($"Для шаблона id {template.Id} обновляю nextRun, новое значение {nextRun}, старое значение {template.NextRun}");
|
||||
|
||||
// template.LastRun = template.NextRun;
|
||||
// template.NextRun = nextRun.Value;
|
||||
|
||||
// await robotConfigurationService.CommitAsync(new HistoryInitiator { InitiatorComment = "При получении задания роботом, обновил NextRun", InitiatorIp = clientService.GetClientIp()?.ToString(), InitiatorParrComponentId = ParrComponentsEnum.Api });
|
||||
// }
|
||||
|
||||
// return true;
|
||||
// }
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// Устанавливаем статус "Робот взял в работу", пишем в историю работы роботов инф о начале работ
|
||||
// /// </summary>
|
||||
// /// <param name="taskId"></param>
|
||||
// /// <returns></returns>
|
||||
// private async Task<bool> SetInProgressStatusAsync(Guid taskId)
|
||||
// {
|
||||
// var config = await robotConfigurationService.GetAsync(taskId);
|
||||
|
||||
// //изменение статуса робота
|
||||
// robotConfigurationService.ChangeRobotStatus(RobotStatusEnum.InProgress, config!);
|
||||
|
||||
// if (!await robotConfigurationService.CommitAsync())
|
||||
// return false;
|
||||
|
||||
// //записываем в лог робота
|
||||
// var history = new RobotHistory
|
||||
// {
|
||||
// Id = Guid.NewGuid(),
|
||||
// HistoryLevel = (int)RobotHistoryLevelEnum.Start,
|
||||
// TaskStatusCode = config.TaskStatusCode,
|
||||
// RobotConfigurationId = config.Id,
|
||||
// RobotIp = clientService.GetClientIp()?.ToString()
|
||||
// };
|
||||
|
||||
// if (!await robotHistoryService.CreateAsync(history) || !await robotHistoryService.CommitAsync())
|
||||
// return false;
|
||||
|
||||
// return true;
|
||||
// }
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// Приводит переносы строк в тексте к формату CRLF (\r\n)
|
||||
// /// </summary>
|
||||
// /// <param name="text">Исходный текст</param>
|
||||
// /// <returns>Текст с унифицированными переносами строк</returns>
|
||||
// private string NormalizeLineEndingsToCrlf(string? text)
|
||||
// {
|
||||
// if (string.IsNullOrEmpty(text))
|
||||
// return string.Empty;
|
||||
|
||||
// // Заменяем любые варианты переносов (\r\n, \r, \n) на единый \r\n
|
||||
// return System.Text.RegularExpressions.Regex.Replace(text, @"\r\n|\r|\n", "\r\n");
|
||||
// }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace PARR.API.Controllers.V1
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Получить список всех переменных составляющих
|
||||
/// Применить шорткод
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost(ApiRoutes.ShortcodeApply.Apply)]
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace PARR.API.Controllers.V1
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Получить список всех переменных составляющих
|
||||
/// Получить список всех переменных составляющих (список шорткодов)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet(ApiRoutes.Shortcode.GetAll)]
|
||||
|
||||
@@ -8,7 +8,9 @@ using PARR.API.Contracts.V1.Responses.Base;
|
||||
using PARR.API.Contracts.V1.Responses.Statistics;
|
||||
using PARR.API.Controllers.V1.Base;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Services.RobotSnapshotServices;
|
||||
using PARR.Domain.Common.Roles;
|
||||
using PARR.Domain.DTOs.RobotSnapshotDTO;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.API.Controllers.V1.Statistics
|
||||
@@ -24,13 +26,15 @@ namespace PARR.API.Controllers.V1.Statistics
|
||||
private readonly ITaskStatusRepository taskStatusService;
|
||||
private readonly IRobotHistoryRepository robotHistoryService;
|
||||
private readonly IMapper mapper;
|
||||
private readonly IRobotSnapshotService _robotSnapshotService;
|
||||
|
||||
public StatRobotTaskController(
|
||||
IRobotRepository robotService,
|
||||
IRobotConfigurationRepository robotConfigurationService,
|
||||
ITaskStatusRepository taskStatusService,
|
||||
IRobotHistoryRepository robotHistoryService,
|
||||
IMapper mapper
|
||||
IMapper mapper,
|
||||
IRobotSnapshotService robotSnapshotService
|
||||
)
|
||||
{
|
||||
this.robotService = robotService;
|
||||
@@ -38,6 +42,7 @@ namespace PARR.API.Controllers.V1.Statistics
|
||||
this.taskStatusService = taskStatusService;
|
||||
this.robotHistoryService = robotHistoryService;
|
||||
this.mapper = mapper;
|
||||
_robotSnapshotService = robotSnapshotService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -87,11 +92,11 @@ namespace PARR.API.Controllers.V1.Statistics
|
||||
[HttpGet(ApiRoutes.StatRobotTask.GetPeriodStatistics)]
|
||||
public async Task<IActionResult> GetPeriodStatistics([FromRoute] RobotsEnum robot)
|
||||
{
|
||||
var offset = TimeSpan.FromHours(0);
|
||||
|
||||
int minusHour = 24;
|
||||
var queryDate = DateTimeOffset.UtcNow.AddHours(-minusHour);
|
||||
queryDate = new DateTimeOffset(queryDate.Year, queryDate.Month, queryDate.Day, queryDate.Hour, 0, 0, new TimeSpan(0));
|
||||
//queryDate = queryDate.Date + new TimeSpan(queryDate.Hour, 0, 0);
|
||||
//queryDate=queryDate.ToOffset(TimeSpan.Zero);
|
||||
queryDate = new DateTimeOffset(queryDate.Year, queryDate.Month, queryDate.Day, queryDate.Hour, 0, 0, offset);
|
||||
|
||||
|
||||
var query = robotHistoryService.Get()
|
||||
@@ -110,16 +115,24 @@ namespace PARR.API.Controllers.V1.Statistics
|
||||
|
||||
var statistics = await query.ToListAsync();
|
||||
|
||||
//if (!statistics.Any())
|
||||
// return NoContent();
|
||||
|
||||
var periodList = GetPeriodList(queryDate, DateTimeOffset.UtcNow);
|
||||
|
||||
if (!periodList.Any())
|
||||
return NoContent();
|
||||
|
||||
// Статистика по роботам (кол-во роботов)
|
||||
var robotStats = await _robotSnapshotService.GetHourlyAnalyticsByRobotTypeAsync(new RobotAnalyticsRobotTypeQuery
|
||||
{
|
||||
DateStart = queryDate,
|
||||
DateEnd = DateTimeOffset.UtcNow,
|
||||
Offset = offset,
|
||||
RobotType = robot
|
||||
});
|
||||
|
||||
periodList.ForEach(item =>
|
||||
{
|
||||
var robotCount = robotStats.FirstOrDefault(t => t.Hour.Date == item.Date.Date && t.Hour.Hour == item.Date.Hour);
|
||||
|
||||
var statItem = statistics.FirstOrDefault(t => t.Date.Date == item.Date.Date && t.Date.Hour == item.Date.Hour);
|
||||
if (statItem != null)
|
||||
{
|
||||
@@ -127,6 +140,7 @@ namespace PARR.API.Controllers.V1.Statistics
|
||||
item.Creating = statItem.Creating;
|
||||
item.Updating = statItem.Updating;
|
||||
item.Ok = statItem.Ok;
|
||||
item.AvgRobots = robotCount?.AvgRobots ?? 0;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -150,6 +164,7 @@ namespace PARR.API.Controllers.V1.Statistics
|
||||
Errors = 0,
|
||||
Ok = 0,
|
||||
Updating = 0,
|
||||
AvgRobots = 0,
|
||||
Date = new DateTimeOffset(periodDate.Date.Year, periodDate.Date.Month, periodDate.Date.Day, periodDate.Hour, 0, 0, new TimeSpan(0)),
|
||||
});
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ using PARR.API.Contracts.V1.Responses.Base;
|
||||
using PARR.API.Contracts.V1.Responses.Statistics;
|
||||
using PARR.API.Controllers.V1.Base;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.Schedule;
|
||||
using PARR.Core.Services.NextRunServices;
|
||||
using PARR.Core.Services.Shortcodes;
|
||||
using PARR.Domain.Common.Roles;
|
||||
using PARR.Domain.Entities;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.API.Controllers.V1.Statistics
|
||||
|
||||
@@ -48,9 +48,6 @@ namespace PARR.API.Controllers.V1
|
||||
[HttpPost(ApiRoutes.SyncTask.MatchTemplates)]
|
||||
public async Task<IActionResult> MatchTemplatesForJob([FromBody] MatchTemplatesRequest request)
|
||||
{
|
||||
//todo: Валидатор! Валидатор то забыли!!!
|
||||
|
||||
|
||||
// проверяем, если уже идет синхронизация по этому объекту, то ахтунг, ошибка
|
||||
var matchingStatus = await matchingStatusService.GetStatusAsync(request.ObjectId, request.EntityType);
|
||||
if (matchingStatus.IsMatchingObject)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PARR.API.Contracts.V1;
|
||||
using PARR.API.Contracts.V1.Requests;
|
||||
@@ -8,7 +7,7 @@ using PARR.API.Controllers.V1.Base;
|
||||
using PARR.API.Services.Interfaces;
|
||||
using PARR.API.Settings;
|
||||
using PARR.Core.Common.Interfaces.RabbitServices;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobRepositories;
|
||||
using PARR.Domain.Common.Rabbit.Messages;
|
||||
using PARR.Domain.Common.Roles;
|
||||
using PARR.Domain.Entities.Base.History;
|
||||
|
||||
@@ -10,7 +10,6 @@ using PARR.API.Contracts.V1.Responses.Base;
|
||||
using PARR.API.Controllers.V1.Base;
|
||||
using PARR.API.Extensions;
|
||||
using PARR.API.Services.Interfaces;
|
||||
using PARR.BLL.Helpers;
|
||||
using PARR.Core.Common.Helpers;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Schedule;
|
||||
@@ -30,37 +29,34 @@ namespace PARR.API.Controllers.V1
|
||||
[Authorize(Roles = ParrRoles.Administrator.Role)]
|
||||
public class TemplateController : BaseApiController
|
||||
{
|
||||
private readonly IMapper mapper;
|
||||
private readonly ITemplateRepository templateService;
|
||||
private readonly IRobotConfigurationRepository robotConfigurationService;
|
||||
private readonly IClientService clientService;
|
||||
private readonly ILogger<TemplateController> logger;
|
||||
private readonly IShortcodesService shortcodesService;
|
||||
private readonly IOrderRepository orderService;
|
||||
private readonly SettingsFromDb settingsFromDb;
|
||||
private readonly IScheduleResponseAreaTimeOffsetRepository scheduleResponseAreaTimeOffsetService;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ITemplateRepository _templateRepository;
|
||||
private readonly IRobotConfigurationRepository _robotConfigurationRepository;
|
||||
private readonly IClientService _clientService;
|
||||
private readonly ILogger<TemplateController> _logger;
|
||||
private readonly IShortcodesService _shortcodesService;
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
private readonly SettingsFromDb _settingsFromDb;
|
||||
|
||||
public TemplateController(
|
||||
IMapper mapper,
|
||||
ITemplateRepository templateService,
|
||||
IRobotConfigurationRepository robotConfigurationService,
|
||||
ITemplateRepository templateRepository,
|
||||
IRobotConfigurationRepository robotConfigurationRepository,
|
||||
IClientService clientService,
|
||||
ILogger<TemplateController> logger,
|
||||
IShortcodesService shortcodesService,
|
||||
IOrderRepository orderService,
|
||||
SettingsFromDb settingsFromDb,
|
||||
IScheduleResponseAreaTimeOffsetRepository scheduleResponseAreaTimeOffsetService
|
||||
IOrderRepository orderRepository,
|
||||
SettingsFromDb settingsFromDb
|
||||
)
|
||||
{
|
||||
this.mapper = mapper;
|
||||
this.templateService = templateService;
|
||||
this.robotConfigurationService = robotConfigurationService;
|
||||
this.clientService = clientService;
|
||||
this.logger = logger;
|
||||
this.shortcodesService = shortcodesService;
|
||||
this.orderService = orderService;
|
||||
this.settingsFromDb = settingsFromDb;
|
||||
this.scheduleResponseAreaTimeOffsetService = scheduleResponseAreaTimeOffsetService;
|
||||
_mapper = mapper;
|
||||
_templateRepository = templateRepository;
|
||||
_robotConfigurationRepository = robotConfigurationRepository;
|
||||
_clientService = clientService;
|
||||
_logger = logger;
|
||||
_shortcodesService = shortcodesService;
|
||||
_orderRepository = orderRepository;
|
||||
_settingsFromDb = settingsFromDb;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,9 +68,10 @@ namespace PARR.API.Controllers.V1
|
||||
[HttpGet(ApiRoutes.Template.GetAll)]
|
||||
public async Task<IActionResult> GetAll([FromQuery] PaginationQuery paginationQuery, [FromQuery] TemplateQuery filter)
|
||||
{
|
||||
var paginationFilter = mapper.Map<PaginationFilter>(paginationQuery);
|
||||
var paginationFilter = _mapper.Map<PaginationFilter>(paginationQuery);
|
||||
|
||||
IQueryable<Template> query = templateService.Get()
|
||||
IQueryable<Template> query = _templateRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Include(t => t.Unit).ThenInclude(t => t!.UnitValues).ThenInclude(t => t.Field)
|
||||
.Include(t => t.Unit).ThenInclude(t => t!.UnitValues).ThenInclude(t => t.Value)
|
||||
.Include(t => t.StatusType)
|
||||
@@ -82,8 +79,7 @@ namespace PARR.API.Controllers.V1
|
||||
.Include(t => t.RobotConfigurations).ThenInclude(t => t.Robot)
|
||||
.Include(t => t.RobotConfigurations).ThenInclude(t => t.TaskStatus)
|
||||
.Include(t => t.RobotConfigurations).ThenInclude(t => t.RobotStatus)
|
||||
.OrderBy(t => t.Name)
|
||||
.AsNoTracking();
|
||||
.OrderBy(t => t.Name);
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(filter.Mask))
|
||||
@@ -114,7 +110,7 @@ namespace PARR.API.Controllers.V1
|
||||
|
||||
#endregion
|
||||
|
||||
var templates = await templateService.GetPage(query, paginationFilter).ToListAsync();
|
||||
var templates = await _templateRepository.GetPage(query, paginationFilter).ToListAsync();
|
||||
|
||||
//logger.LogDebug("Загрузка шаблонов из БД: {ElapsedMs} мс", sw.ElapsedMilliseconds);
|
||||
|
||||
@@ -122,7 +118,7 @@ namespace PARR.API.Controllers.V1
|
||||
if (!templates.Any())
|
||||
return NoContent();
|
||||
|
||||
var templateResponse = mapper.Map<List<TemplateListResponse>>(templates);
|
||||
var templateResponse = _mapper.Map<List<TemplateListResponse>>(templates);
|
||||
|
||||
// словари для быстрого поиска
|
||||
var templatesDict = templates.ToDictionary(t => t.Id);
|
||||
@@ -135,7 +131,7 @@ namespace PARR.API.Controllers.V1
|
||||
foreach (var responseItem in templateResponse)
|
||||
{
|
||||
//await ApplyTemplateShortcodesAsync(responseItem, templates.First(t => t.Id == responseItem.Id));
|
||||
await ApplyTemplateShortcodesAsync(responseItem, templatesDict[responseItem.Id]);
|
||||
await ApplyBaseTemplateShortcodesAsync(responseItem, templatesDict[responseItem.Id]);
|
||||
//FillResponseAreaOffset(responseItem);
|
||||
}
|
||||
//logger.LogDebug("Получение шорткодов: {ElapsedMs} мс", sw.ElapsedMilliseconds);
|
||||
@@ -165,7 +161,8 @@ namespace PARR.API.Controllers.V1
|
||||
[HttpGet(ApiRoutes.Template.Get)]
|
||||
public async Task<IActionResult> GetById([FromRoute] Guid id)
|
||||
{
|
||||
var template = await templateService.GetWithIncludes().AsNoTracking()
|
||||
var template = await _templateRepository.GetWithIncludes()
|
||||
.AsNoTracking()
|
||||
.Include(t => t.RobotConfigurations).ThenInclude(t => t.Robot)
|
||||
.Include(t => t.RobotConfigurations).ThenInclude(t => t.TaskStatus)
|
||||
.Include(t => t.RobotConfigurations).ThenInclude(t => t.RobotStatus)
|
||||
@@ -179,9 +176,9 @@ namespace PARR.API.Controllers.V1
|
||||
if (template == null)
|
||||
return NotFound();
|
||||
|
||||
var response = mapper.Map<TemplateResponse>(template);
|
||||
var response = _mapper.Map<TemplateResponse>(template);
|
||||
await ApplyBaseTemplateShortcodesAsync(response, template);
|
||||
await ApplyTemplateShortcodesAsync(response, template);
|
||||
//FillResponseAreaOffset(response);
|
||||
|
||||
var ordersCountResult = await GetOrdersCountAsync(new List<Guid> { response.Id });
|
||||
response.OrderCount = ordersCountResult.Count > 0 ? ordersCountResult.First().Value : 0;
|
||||
@@ -215,7 +212,7 @@ namespace PARR.API.Controllers.V1
|
||||
// .FirstOrDefaultAsync(t => t.Id == id);
|
||||
#endregion
|
||||
|
||||
var template = await templateService.Get()
|
||||
var template = await _templateRepository.Get()
|
||||
.Include(t => t.RobotConfigurations)
|
||||
.FirstOrDefaultAsync(t => t.Id == id);
|
||||
|
||||
@@ -226,21 +223,21 @@ namespace PARR.API.Controllers.V1
|
||||
{
|
||||
template.IsActiveTemplate = request.IsActiveTemplate;
|
||||
//необходимо обновить шаблон
|
||||
var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.TemplateOrder, template);
|
||||
var config = _robotConfigurationRepository.GetFromTemplateByRobotCode(RobotsEnum.TemplateOrder, template);
|
||||
//robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Updating, config);
|
||||
robotConfigurationService.SetUpdateTaskStatusIfAllow(config);
|
||||
_robotConfigurationRepository.SetUpdateTaskStatusIfAllow(config);
|
||||
}
|
||||
|
||||
if (template.IsActiveSchedule != request.IsActiveSchedule)
|
||||
{
|
||||
template.IsActiveSchedule = request.IsActiveSchedule;
|
||||
//необходимо обновить расписание
|
||||
var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.ScheduleOrder, template);
|
||||
var config = _robotConfigurationRepository.GetFromTemplateByRobotCode(RobotsEnum.ScheduleOrder, template);
|
||||
//robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Updating, config);
|
||||
robotConfigurationService.SetUpdateTaskStatusIfAllow(config);
|
||||
_robotConfigurationRepository.SetUpdateTaskStatusIfAllow(config);
|
||||
}
|
||||
|
||||
if (!await templateService.CommitAsync(new HistoryInitiator { InitiatorComment = "Изменён статус шаблона/расписания", InitiatorIp = clientService.GetClientIp()?.ToString(), InitiatorParrComponentId = ParrComponentsEnum.Api }))
|
||||
if (!await _templateRepository.CommitAsync(new HistoryInitiator { InitiatorComment = "Изменён статус шаблона/расписания", InitiatorIp = _clientService.GetClientIp()?.ToString(), InitiatorParrComponentId = ParrComponentsEnum.Api }))
|
||||
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при изменении шаблона." } }));
|
||||
|
||||
#region old
|
||||
@@ -263,7 +260,7 @@ namespace PARR.API.Controllers.V1
|
||||
//return Ok(new Response<TemplateResponse>(response, true));
|
||||
#endregion
|
||||
|
||||
var templateToResponse = await templateService.Get()
|
||||
var templateToResponse = await _templateRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Include(t => t.Unit).ThenInclude(t => t!.UnitValues).ThenInclude(t => t.Field)
|
||||
.Include(t => t.Unit).ThenInclude(t => t!.UnitValues).ThenInclude(t => t.Value)
|
||||
@@ -274,10 +271,9 @@ namespace PARR.API.Controllers.V1
|
||||
.Include(t => t.RobotConfigurations).ThenInclude(t => t.RobotStatus)
|
||||
.FirstAsync(t => t.Id == id);
|
||||
|
||||
var response = mapper.Map<TemplateListResponse>(templateToResponse);
|
||||
var response = _mapper.Map<TemplateListResponse>(templateToResponse);
|
||||
|
||||
await ApplyTemplateShortcodesAsync(response, templateToResponse);
|
||||
//FillResponseAreaOffset(response);
|
||||
await ApplyBaseTemplateShortcodesAsync(response, templateToResponse);
|
||||
|
||||
var ordersCountResult = await GetOrdersCountAsync(new List<Guid> { response.Id });
|
||||
response.OrderCount = ordersCountResult.Count > 0 ? ordersCountResult.First().Value : 0;
|
||||
@@ -287,18 +283,34 @@ namespace PARR.API.Controllers.V1
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Применить шорткоды
|
||||
/// Применить шорткоды. Для респонса TemplateBaseResponse
|
||||
/// </summary>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="template"></param>
|
||||
/// <returns></returns>
|
||||
private async Task ApplyTemplateShortcodesAsync(TemplateBaseResponse response, Template template)
|
||||
private async Task ApplyBaseTemplateShortcodesAsync(TemplateBaseResponse response, Template template)
|
||||
{
|
||||
response.WorkGroup = await shortcodesService.ApplyShortcodesAsync(template.Job.WorkGroupMask, template);
|
||||
response.ResponseArea = await shortcodesService.ApplyShortcodesAsync(template.Job.ResponseAreaMask, template);
|
||||
response.WorkGroup = await _shortcodesService.ApplyShortcodesAsync(template.Job!.WorkGroupMask, template);
|
||||
response.ResponseArea = await _shortcodesService.ApplyShortcodesAsync(template.Job!.ResponseAreaMask, template);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Применить шорткоды. Для респонса TemplateResponse (добавлены дополнительные поля Rendered)
|
||||
/// </summary>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="template"></param>
|
||||
/// <returns></returns>
|
||||
private async Task ApplyTemplateShortcodesAsync(TemplateResponse response, Template template)
|
||||
{
|
||||
response.WorkNameRendered = await _shortcodesService.ApplyShortcodesAsync(template.Job!.WorkName, template);
|
||||
response.WorkGroupRendered = await _shortcodesService.ApplyShortcodesAsync(template.Job!.WorkGroupMask, template);
|
||||
response.ResponseAreaRendered = await _shortcodesService.ApplyShortcodesAsync(template.Job!.ResponseAreaMask, template);
|
||||
response.ShortDescriptionRendered = await _shortcodesService.ApplyShortcodesAsync(template.Job.Group!.ShortDescription, template);
|
||||
response.FullDescriptionRendered = await _shortcodesService.ApplyShortcodesAsync(template.Job.Group!.FullDescription, template);
|
||||
response.SolutionRendered = await _shortcodesService.ApplyShortcodesAsync(template.Job.Group!.Solution, template);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить кол-во нарядов для шаблонов
|
||||
/// </summary>
|
||||
@@ -309,7 +321,7 @@ namespace PARR.API.Controllers.V1
|
||||
if (!templateIdList.Any())
|
||||
return new Dictionary<Guid, int>();
|
||||
|
||||
var templateWithOrders = await orderService.Get()
|
||||
var templateWithOrders = await _orderRepository.Get()
|
||||
.Where(t => t.TemplateId.HasValue && templateIdList.Contains(t.TemplateId.Value))
|
||||
.GroupBy(t => t.TemplateId)
|
||||
.Select(t => new { TemplateId = t.Key, OrderCount = t.Count() })
|
||||
@@ -323,36 +335,7 @@ namespace PARR.API.Controllers.V1
|
||||
}
|
||||
|
||||
|
||||
///// <summary>
|
||||
///// Заполнить ResponseAreaOffset
|
||||
///// </summary>
|
||||
///// <param name="response"></param>
|
||||
//private void FillResponseAreaOffset(TemplateBaseResponse response)
|
||||
//{
|
||||
// // если стоит галка IsResponseAreaTimezone и есть ЗО, то возвращаем оффсет
|
||||
// if (response.IsResponseAreaTimezone && !string.IsNullOrEmpty(response.ResponseArea))
|
||||
// {
|
||||
// var responseArea = response.ResponseArea;
|
||||
// response.ResponseAreaOffset = mapper.Map<ScheduleResponseAreaTimeOffsetResponse>(scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// response.ResponseAreaOffset = null;
|
||||
// }
|
||||
|
||||
// //var responseArea = response.IsResponseAreaTimezone && !string.IsNullOrEmpty(response.ResponseArea)
|
||||
// // ? response.ResponseArea
|
||||
// // : settingsFromDb.DefaultResponseAreaToTimeOffset;
|
||||
|
||||
// //response.NextRunResponseAreaInLocal = response.IsResponseAreaTimezone && !string.IsNullOrEmpty(response.ResponseArea)
|
||||
// // ? nextRunService.GetNextRunWithResponseAreaOffset(response.NextRun, response.ResponseArea)
|
||||
// // // возвращаем в дефолтной зоне
|
||||
// // : nextRunService.GetNextRunWithResponseAreaOffset(response.NextRun, settingsFromDb.DefaultResponseAreaToTimeOffset);
|
||||
|
||||
// //response.NextRunResponseAreaInLocal = nextRunService.GetNextRunWithResponseAreaOffset(response.NextRun, responseArea);
|
||||
|
||||
// //response.ResponseAreaOffset = mapper.Map<ScheduleResponseAreaTimeOffsetResponse>(scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea));
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@ using PARR.Core.Common.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
using PARR.Core.Services.NextRunServices;
|
||||
using PARR.Core.Services.RobotSnapshotServices;
|
||||
using PARR.Core.Services.UnitService.Interfaces;
|
||||
using PARR.Core.Services.Workload.Implementations;
|
||||
using PARR.Domain.Cache;
|
||||
using PARR.Domain.DTOs.RobotSnapshotDTO;
|
||||
|
||||
namespace PARR.API.Controllers.V1
|
||||
{
|
||||
@@ -27,6 +29,7 @@ namespace PARR.API.Controllers.V1
|
||||
private readonly ILogger<TestController> logger;
|
||||
private readonly IUnitService unitService;
|
||||
private readonly IUnitRepository unitRepository;
|
||||
private readonly IRobotSnapshotService _robotSnapshotService;
|
||||
|
||||
public TestController(
|
||||
IClientService clientService,
|
||||
@@ -37,7 +40,8 @@ namespace PARR.API.Controllers.V1
|
||||
WorkloadCacheService workloadCacheService,
|
||||
ILogger<TestController> logger,
|
||||
IUnitService unitService,
|
||||
IUnitRepository unitRepository
|
||||
IUnitRepository unitRepository,
|
||||
IRobotSnapshotService robotSnapshotService
|
||||
)
|
||||
{
|
||||
this.clientService = clientService;
|
||||
@@ -48,6 +52,7 @@ namespace PARR.API.Controllers.V1
|
||||
this.logger = logger;
|
||||
this.unitService = unitService;
|
||||
this.unitRepository = unitRepository;
|
||||
_robotSnapshotService = robotSnapshotService;
|
||||
}
|
||||
|
||||
|
||||
@@ -161,6 +166,32 @@ namespace PARR.API.Controllers.V1
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Тестовый метод
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet(ApiRoutes.Test.TestHandler)]
|
||||
public async Task<IActionResult> Test()
|
||||
{
|
||||
//var data = await _robotSnapshotService.GetHourlyAnalyticsAsync(new RobotAnalyticsQuery
|
||||
//{
|
||||
// DateStart = DateTimeOffset.UtcNow.AddHours(-1),
|
||||
// DateEnd = DateTimeOffset.UtcNow,
|
||||
// Offset = TimeSpan.FromMinutes(600)
|
||||
//});
|
||||
|
||||
//var data = await _robotSnapshotService.GetHourlyAnalyticsByRobotTypeAsync(new RobotAnalyticsRobotTypeQuery
|
||||
//{
|
||||
// DateStart = DateTimeOffset.UtcNow.AddHours(-12),
|
||||
// DateEnd = DateTimeOffset.UtcNow,
|
||||
// Offset = TimeSpan.FromMinutes(600),
|
||||
// RobotType = PARR.Domain.Enums.RobotsEnum.TemplateOrder
|
||||
//});
|
||||
|
||||
//return Ok(data);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ namespace PARR.API.Installers
|
||||
configuration.GetSection(nameof(MonitoringSettings)).Bind(monitoringSettings);
|
||||
services.AddSingleton(monitoringSettings);
|
||||
|
||||
var commonSettings = new CommonSettings();
|
||||
configuration.GetSection(nameof(CommonSettings)).Bind(commonSettings);
|
||||
services.AddSingleton(commonSettings);
|
||||
|
||||
//TODO: add other
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ using PARR.Domain.DTOs.User;
|
||||
using PARR.Domain.DTOs.Workload;
|
||||
using PARR.Domain.Entities;
|
||||
using PARR.Domain.Entities.Base.History;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
using PARR.Domain.Entities.Schedule;
|
||||
using PARR.Domain.Entities.Unit;
|
||||
|
||||
@@ -367,7 +368,8 @@ namespace PARR.API.MappingProfiles
|
||||
.Include<JobGroup, JobGroupWithDistributionConfigResponse>()
|
||||
.ForMember(d => d.GroupType, o => o.MapFrom(s => s.GroupType))
|
||||
.ForMember(d => d.GroupingUnitField, o => o.MapFrom(s => s.GroupingUnitField))
|
||||
.ForMember(d => d.IsWorkGroupTimezone, o => o.MapFrom(s => s.IsResponseAreaTimezone));
|
||||
.ForMember(d => d.IsWorkGroupTimezone, o => o.MapFrom(s => s.IsResponseAreaTimezone))
|
||||
.ForMember(d => d.AutoControl, o => o.MapFrom(s => s.AutoControl));
|
||||
|
||||
CreateMap<JobGroup, JobGroupBaseResponse>()
|
||||
.ForMember(d => d.Name, o => o.MapFrom(s => s.GroupName))
|
||||
@@ -381,6 +383,7 @@ namespace PARR.API.MappingProfiles
|
||||
.ForMember(d => d.ScheduleExcludeTypeCalendar, o => o.MapFrom(s => s.ScheduleExcludeTypeCalendar));
|
||||
//.ForMember(d => d.DistributionConfig, o => o.MapFrom(s => s.DistributionConfig));
|
||||
|
||||
CreateMap<JobGroupAutoControl, JobGroupAutoControlResponse>();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using PARR.API.Contracts.V1.Requests;
|
||||
using PARR.API.Contracts.V1.Requests.Queries;
|
||||
using PARR.Domain.Common.Pagination;
|
||||
using PARR.Domain.DTOs.RobotSnapshotDTO;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
|
||||
namespace PARR.API.MappingProfiles
|
||||
{
|
||||
@@ -17,7 +17,11 @@ namespace PARR.API.MappingProfiles
|
||||
|
||||
CreateMap<JobRequest, Job>()
|
||||
.ForMember(d => d.Id, o => o.MapFrom(s => Guid.NewGuid()))
|
||||
.ForMember(d => d.IsParentRelationships, o => o.MapFrom(s => s.Relationships != null ? s.Relationships.IsParentRelationships : (bool?)null))
|
||||
.ForMember(d => d.MaxValueRelationships, o => o.MapFrom(s => s.Relationships != null ? s.Relationships.MaxValueRelationships : (int?)null))
|
||||
.ForMember(d => d.MinValueRelationships, o => o.MapFrom(s => s.Relationships != null ? s.Relationships.MinValueRelationships : (int?)null))
|
||||
.ForMember(d => d.DateCreated, o => o.MapFrom(s => DateTimeOffset.UtcNow))
|
||||
.ForMember(d => d.AutoControl, o => o.Ignore())
|
||||
.AfterMap((s, d) =>
|
||||
{
|
||||
if (d.UnitFilters != null)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using AutoMapper;
|
||||
using PARR.API.Contracts.V1.Responses;
|
||||
using PARR.Core.Repositories.Interfaces.Schedule;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
using PARR.Domain.Settings;
|
||||
|
||||
namespace PARR.API.MappingProfiles.Resolvers
|
||||
|
||||
15
PARR.API/Settings/CommonSettings.cs
Normal file
15
PARR.API/Settings/CommonSettings.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace PARR.API.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Общие настройки API
|
||||
/// </summary>
|
||||
public record CommonSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Период охлаждения (кулдаун) для расписаний.
|
||||
/// Запрещает повторно брать активные шаблоны в работу, если с момента их последнего запуска прошло меньше этого времени.
|
||||
/// Применяется только для инициаторов EsppScheduleSync и NextRun.
|
||||
/// </summary>
|
||||
public TimeSpan ScheduleCooldownDuration { get; init; } = TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PARR.API.Contracts.V1.Requests;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
|
||||
namespace PARR.API.Validators
|
||||
{
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
using FluentValidation;
|
||||
using PARR.API.Contracts.V1.Requests;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.API.Validators
|
||||
{
|
||||
public class JobAutoControlRequestValidator : AbstractValidator<JobAutoControlRequest>
|
||||
{
|
||||
public JobAutoControlRequestValidator()
|
||||
{
|
||||
RuleFor(t => t.EkMasks).Must(items =>
|
||||
items.Count(t => t.Trim().Length > 0) > 0
|
||||
).WithMessage("Должна быть указана хотя бы одна маска ЭК");
|
||||
|
||||
RuleFor(t => t.EnabledEkStatuses).Must(
|
||||
items => items.Distinct().Count() == items.Count()
|
||||
).WithMessage("Статусы ЭК не должны повторяться");
|
||||
|
||||
RuleFor(t => t.EnabledEkStatuses).Must(items =>
|
||||
{
|
||||
return !items.Any(item => !Enum.IsDefined(typeof(EkStatusEnum), item)) && !items.Any(t => t == 0);
|
||||
})
|
||||
.WithMessage($"Допустимые значения: {(int)EkStatusEnum.New} ({EkStatusEnum.New}), " +
|
||||
$"{(int)EkStatusEnum.Preapre} ({EkStatusEnum.Preapre}), " +
|
||||
$"{(int)EkStatusEnum.Exploitation} ({EkStatusEnum.Exploitation}), " +
|
||||
$"{(int)EkStatusEnum.Repair} ({EkStatusEnum.Repair}), " +
|
||||
$"{(int)EkStatusEnum.Reserve} ({EkStatusEnum.Reserve}), " +
|
||||
$"{(int)EkStatusEnum.OutOfService} ({EkStatusEnum.OutOfService}), " +
|
||||
$"{(int)EkStatusEnum.Test} ({EkStatusEnum.Test}), " +
|
||||
$"{(int)EkStatusEnum.Development} ({EkStatusEnum.Development})");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PARR.API.Contracts.V1.Requests;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.Schedule;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
using PARR.Domain.Enums;
|
||||
@@ -12,11 +12,11 @@ namespace PARR.API.Validators
|
||||
public class JobGroupValidator : AbstractValidator<JobGroupRequest>
|
||||
{
|
||||
public JobGroupValidator(
|
||||
IJobGroupTypeRepository jobGroupTypeService,
|
||||
IUnitFieldRepository unitFieldService,
|
||||
IScheduleExcludeTypeRepository scheduleExcludeTypeService,
|
||||
IScheduleExcludeTypeCalendarRepository scheduleExcludeTypeCalendarService,
|
||||
IDistributionPeriodRepository distributionPeriodService
|
||||
IJobGroupTypeRepository jobGroupTypeRepository,
|
||||
IUnitFieldRepository unitFieldRepository,
|
||||
IScheduleExcludeTypeRepository scheduleExcludeTypeRepository,
|
||||
IScheduleExcludeTypeCalendarRepository scheduleExcludeTypeCalendarRepository,
|
||||
IDistributionPeriodRepository distributionPeriodRepository
|
||||
)
|
||||
{
|
||||
RuleFor(t => t.Name)
|
||||
@@ -35,7 +35,7 @@ namespace PARR.API.Validators
|
||||
.NotNull().NotEmpty();
|
||||
|
||||
RuleFor(t => t.GroupTypeId)
|
||||
.MustAsync(async (entity, value, c) => await jobGroupTypeService.GetAsync(value) != null)
|
||||
.MustAsync(async (entity, value, c) => await jobGroupTypeRepository.GetAsync(value) != null)
|
||||
.WithMessage("Указан несуществующий Id типа");
|
||||
|
||||
//Проверяем существует ли такой GroupingUnitFieldId в unitField
|
||||
@@ -47,7 +47,7 @@ namespace PARR.API.Validators
|
||||
return true;
|
||||
}
|
||||
|
||||
return await unitFieldService.GetAsync(value.Value) != null;
|
||||
return await unitFieldRepository.GetAsync(value.Value) != null;
|
||||
})
|
||||
.WithMessage("Указано несуществующий Id поля");
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace PARR.API.Validators
|
||||
RuleFor(t => t.GroupingUnitFieldId)
|
||||
.MustAsync(async (entity, value, c) =>
|
||||
{
|
||||
var groupingJobType = await jobGroupTypeService.Get().FirstAsync(t => t.Code == JobGroupTypesEnum.Group);
|
||||
var groupingJobType = await jobGroupTypeRepository.Get().FirstAsync(t => t.Code == JobGroupTypesEnum.Group);
|
||||
|
||||
// Если это сгруппированный тип, то у него обязательно должно быть заполнено поле GroupingUnitFieldId
|
||||
if (entity.GroupTypeId == groupingJobType.Id)
|
||||
@@ -68,14 +68,14 @@ namespace PARR.API.Validators
|
||||
RuleFor(t => t.ScheduleExcludeTypeId)
|
||||
.NotNull()
|
||||
.NotEmpty()
|
||||
.MustAsync(async (entity, value, c) => await scheduleExcludeTypeService.GetAsync(value) != null)
|
||||
.MustAsync(async (entity, value, c) => await scheduleExcludeTypeRepository.GetAsync(value) != null)
|
||||
.WithMessage("Некорректное значение");
|
||||
|
||||
RuleFor(t => t.ScheduleExcludeTypeCalendarId)
|
||||
.MustAsync(async (entity, value, c) =>
|
||||
{
|
||||
// Если выбрано "Нет исключений", то это поле должно быть пустое, иначе, должно быть валидное значение
|
||||
var type = await scheduleExcludeTypeService.GetAsync(entity.ScheduleExcludeTypeId);
|
||||
var type = await scheduleExcludeTypeRepository.GetAsync(entity.ScheduleExcludeTypeId);
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace PARR.API.Validators
|
||||
if (!value.HasValue)
|
||||
return false;
|
||||
|
||||
return await scheduleExcludeTypeCalendarService.GetAsync(value.Value) != null;
|
||||
return await scheduleExcludeTypeCalendarRepository.GetAsync(value.Value) != null;
|
||||
})
|
||||
.WithMessage("Некорректное значение");
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace PARR.API.Validators
|
||||
|
||||
if (periodId.HasValue)
|
||||
{
|
||||
var exist = await distributionPeriodService.GetAsync(periodId.Value);
|
||||
var exist = await distributionPeriodRepository.GetAsync(periodId.Value);
|
||||
|
||||
return exist != null;
|
||||
}
|
||||
@@ -129,7 +129,26 @@ namespace PARR.API.Validators
|
||||
// если IsResponseAreaTimezone == true, то поле UserTimeZoneOffsetMinutes обязательно.
|
||||
// если IsResponseAreaTimezone == false, то UserTimeZoneOffsetMinutes должно быть null
|
||||
return (entity.IsWorkGroupTimezone && value != null) || (!entity.IsWorkGroupTimezone && value == null);
|
||||
}).WithMessage("Некорректное значение."); ;
|
||||
}).WithMessage("Некорректное значение.");
|
||||
|
||||
RuleFor(t => t.AutoControl)
|
||||
.MustAsync(async (entity, value, c) =>
|
||||
{
|
||||
// Автоконтроль разрешен только типам работ у которых включен IsJobGroupAutoControl
|
||||
var isAllowedAutocontrol = await jobGroupTypeRepository.Get()
|
||||
.AnyAsync(t => t.Id == entity.GroupTypeId && t.IsJobGroupAutoControl == true);
|
||||
|
||||
// Разрешен автоконтроль и есть значение
|
||||
if (isAllowedAutocontrol && value != null)
|
||||
return true;
|
||||
|
||||
// Запрещен автоконтроль и нет значения
|
||||
if (!isAllowedAutocontrol && value == null)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
})
|
||||
.WithMessage("Некорректные параметры автоконтроля");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PARR.API.Contracts.V1.Requests;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
using PARR.Domain.Entities.Job;
|
||||
|
||||
namespace PARR.API.Validators
|
||||
{
|
||||
public class JobRequestValidator : AbstractValidator<JobRequest>
|
||||
{
|
||||
private readonly ITnkRepository tnkService;
|
||||
private readonly IJobGroupRepository jobGroupService;
|
||||
private readonly IJobRepository jobRepository;
|
||||
private readonly IUnitFieldRepository unitFieldService;
|
||||
private JobGroup? jobGroup;
|
||||
private readonly ITnkRepository _tnkRepository;
|
||||
private readonly IJobGroupRepository _jobGroupRepository;
|
||||
private readonly IUnitFieldRepository _unitFieldRepository;
|
||||
//private JobGroup? jobGroup;
|
||||
|
||||
public JobRequestValidator(
|
||||
ITnkRepository tnkService,
|
||||
IJobGroupRepository jobGroupService,
|
||||
IJobRepository jobService,
|
||||
IUnitFieldRepository unitFieldService
|
||||
ITnkRepository tnkRepository,
|
||||
IJobGroupRepository jobGroupRepository,
|
||||
IUnitFieldRepository unitFieldRepository
|
||||
)
|
||||
{
|
||||
this.tnkService = tnkService;
|
||||
this.jobGroupService = jobGroupService;
|
||||
this.jobRepository = jobService;
|
||||
this.unitFieldService = unitFieldService;
|
||||
_tnkRepository = tnkRepository;
|
||||
_jobGroupRepository = jobGroupRepository;
|
||||
_unitFieldRepository = unitFieldRepository;
|
||||
|
||||
RuleFor(t => t.Name)
|
||||
.NotNull().NotEmpty();
|
||||
@@ -48,14 +45,54 @@ namespace PARR.API.Validators
|
||||
.MustAsync(async (entity, value, c) => await IsUnitFiltersCorrect(entity))
|
||||
.WithMessage("Неверно заданы параметры фильтров. Внимательнее, пожалуйста!");
|
||||
|
||||
RuleFor(t => t.MinValueRelationships)
|
||||
.GreaterThanOrEqualTo(0);
|
||||
RuleFor(t => t.Relationships)
|
||||
.MustAsync(async (entity, value, c) =>
|
||||
{
|
||||
// Relationships должны быть обязательно заполнены если в типе работ IsRelationshipsAllowed==true
|
||||
var relationshipsIsRequired = await jobGroupRepository.Get()
|
||||
.AnyAsync(t => t.Id == entity.GroupId && t.GroupType!.IsRelationshipsAllowed, c);
|
||||
|
||||
if (relationshipsIsRequired && value != null)
|
||||
return true;
|
||||
|
||||
if (!relationshipsIsRequired && value == null)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
})
|
||||
.WithMessage("Некорректные настройки кол-ва связей");
|
||||
|
||||
When(t => t.Relationships != null, () =>
|
||||
{
|
||||
RuleFor(t => t.Relationships!.MinValueRelationships)
|
||||
.GreaterThanOrEqualTo(0);
|
||||
|
||||
RuleFor(t => t.Relationships!.MaxValueRelationships)
|
||||
.GreaterThanOrEqualTo(t => t.Relationships!.MinValueRelationships);
|
||||
});
|
||||
|
||||
RuleFor(t => t.MaxValueRelationships)
|
||||
.GreaterThanOrEqualTo(t => t.MinValueRelationships);
|
||||
|
||||
RuleFor(t => t.ResponseAreaMask)
|
||||
.NotNull().NotEmpty();
|
||||
|
||||
RuleFor(t => t.AutoControl)
|
||||
.MustAsync(async (entity, value, c) =>
|
||||
{
|
||||
// Автоконтроль разрешен только типам работ, у которых выключен IsJobGroupAutoControl
|
||||
var isAllowedJobAutocontrol = await jobGroupRepository.Get()
|
||||
.AnyAsync(t => t.Id == entity.GroupId && t.GroupType!.IsJobGroupAutoControl == false, c);
|
||||
|
||||
// Разрешен автоконтроль и есть значение
|
||||
if (isAllowedJobAutocontrol && value != null)
|
||||
return true;
|
||||
|
||||
// Запрещен автоконтроль и нет значения
|
||||
if (!isAllowedJobAutocontrol && value == null)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
})
|
||||
.WithMessage("Некорректные параметры автоконтроля");
|
||||
}
|
||||
|
||||
private async Task<bool> IsUnitFiltersCorrect(JobRequest entity)
|
||||
@@ -72,7 +109,7 @@ namespace PARR.API.Validators
|
||||
foreach (var fieldFilter in unitFilter.FieldFilters)
|
||||
{
|
||||
var fieldId = fieldFilter.FieldId;
|
||||
if (await unitFieldService.GetAsync(fieldId) == null)
|
||||
if (await _unitFieldRepository.GetAsync(fieldId) == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -80,7 +117,7 @@ namespace PARR.API.Validators
|
||||
foreach (var relationshipFilter in unitFilter.RelationshipFilters!)
|
||||
{
|
||||
var fieldId = relationshipFilter.FieldId;
|
||||
if (await unitFieldService.GetAsync(fieldId) == null)
|
||||
if (await _unitFieldRepository.GetAsync(fieldId) == null)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -90,7 +127,7 @@ namespace PARR.API.Validators
|
||||
|
||||
private async Task<bool> IsGroupExist(JobRequest entity)
|
||||
{
|
||||
jobGroup = await jobGroupService.GetAsync(entity.GroupId);
|
||||
var jobGroup = await _jobGroupRepository.GetAsync(entity.GroupId);
|
||||
|
||||
return jobGroup != null;
|
||||
}
|
||||
@@ -98,7 +135,7 @@ namespace PARR.API.Validators
|
||||
|
||||
private async Task<bool> IsTnkExist(JobRequest entity)
|
||||
{
|
||||
return await tnkService.GetAsync(entity.TnkId) != null;
|
||||
return await _tnkRepository.GetAsync(entity.TnkId) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
PARR.API/Validators/MatchTemplatesRequestValidator.cs
Normal file
51
PARR.API/Validators/MatchTemplatesRequestValidator.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PARR.API.Contracts.V1.Requests;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.JobRepositories;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.API.Validators
|
||||
{
|
||||
public class MatchTemplatesRequestValidator : AbstractValidator<MatchTemplatesRequest>
|
||||
{
|
||||
public MatchTemplatesRequestValidator(
|
||||
IJobGroupRepository jobGroupRepository,
|
||||
IJobRepository jobRepository,
|
||||
ITemplateRepository templateRepository
|
||||
)
|
||||
{
|
||||
RuleFor(t => t.ObjectId)
|
||||
.NotNull()
|
||||
.NotEmpty()
|
||||
.WithMessage("Идентификатор объекта не может быть пустым.");
|
||||
|
||||
RuleFor(t => t)
|
||||
.MustAsync(async (request, cancellation) =>
|
||||
{
|
||||
return request.EntityType switch
|
||||
{
|
||||
SyncTaskEntityTypeEnum.JobGroup =>
|
||||
await jobGroupRepository.Get().AsNoTracking().AnyAsync(t => t.Id == request.ObjectId, cancellation),
|
||||
|
||||
SyncTaskEntityTypeEnum.Job =>
|
||||
await jobRepository.Get().AsNoTracking().AnyAsync(t => t.Id == request.ObjectId && t.Group!.GroupType!.IsJobGroupAutoControl == false, cancellation),
|
||||
|
||||
SyncTaskEntityTypeEnum.Template =>
|
||||
false, // TODO: Синхронизировать шаблоны нельзя
|
||||
|
||||
_ => false
|
||||
};
|
||||
})
|
||||
.WithMessage(request => request.EntityType switch
|
||||
{
|
||||
SyncTaskEntityTypeEnum.Template => "Синхронизация шаблонов пока не поддерживается.",
|
||||
SyncTaskEntityTypeEnum.Job => "Объект не найден или данные работы нельзя отправить на синхронизацию.",
|
||||
_ => "Не найден объект с переданным ИД."
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using FluentValidation;
|
||||
using PARR.API.Contracts.V1.Requests;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.JobRepositories;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.API.Validators
|
||||
|
||||
@@ -111,7 +111,10 @@
|
||||
}
|
||||
],
|
||||
"RabbitMq": {
|
||||
"ThresholdConnections": 29
|
||||
"ThresholdConnections": 33
|
||||
}
|
||||
},
|
||||
"CommonSettings": {
|
||||
"ScheduleCooldownDuration": "03:00:00"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
using PARR.Domain.Entities.Base.History;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
using PARR.Domain.Entities.Schedule;
|
||||
|
||||
namespace PARR.Core.Common.Helpers;
|
||||
@@ -14,16 +15,13 @@ namespace PARR.Core.Common.Helpers;
|
||||
public class JobGroupCloneHelper
|
||||
{
|
||||
private readonly IJobGroupRepository groupRepository;
|
||||
private readonly IJobRepository jobRepository;
|
||||
private readonly ILogger<JobGroupCloneHelper> logger;
|
||||
|
||||
public JobGroupCloneHelper(
|
||||
IJobGroupRepository groupRepository,
|
||||
IJobRepository jobRepository,
|
||||
ILogger<JobGroupCloneHelper> logger)
|
||||
{
|
||||
this.groupRepository = groupRepository;
|
||||
this.jobRepository = jobRepository;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobRepositories;
|
||||
using PARR.Domain.Entities.Base.History;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
|
||||
namespace PARR.Core.Common.Helpers;
|
||||
|
||||
|
||||
@@ -24,6 +24,6 @@ namespace PARR.Core.Common.Interfaces.RabbitServices
|
||||
/// <param name="mqSettings"></param>
|
||||
/// <param name="msgObjectList"></param>
|
||||
/// <returns></returns>
|
||||
Task<RabbitSendResult> SendAsync(IMqSettings mqSettings, List<object> msgObjectList);
|
||||
Task<RabbitSendResult> SendAsync(IMqSettings mqSettings, IEnumerable<object> msgObjectList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using PARR.Core.Common.Helpers;
|
||||
using PARR.Core.Common.Implementations;
|
||||
using PARR.Core.Common.Interfaces;
|
||||
@@ -12,6 +13,8 @@ using PARR.Core.Services.RobotTask.Implementations;
|
||||
using PARR.Core.Services.RobotTask.Interfaces;
|
||||
using PARR.Core.Services.Shortcodes;
|
||||
using PARR.Core.Services.Shortcodes.Handlers;
|
||||
using PARR.Core.Services.Snapshots.Implementations;
|
||||
using PARR.Core.Services.Snapshots.Interfaces;
|
||||
using PARR.Core.Services.TaskServices.Handlers;
|
||||
using PARR.Core.Services.TaskServices.Handlers.Factory;
|
||||
using PARR.Core.Services.TaskServices.Implementations;
|
||||
@@ -25,6 +28,7 @@ using PARR.Core.Services.UnitService.Implementations;
|
||||
using PARR.Core.Services.UnitService.Interfaces;
|
||||
using PARR.Core.Services.Workload.Implementations;
|
||||
using PARR.Core.Services.Workload.Interfaces;
|
||||
using PARR.Domain.Entities.RobotEntities;
|
||||
using PARR.Domain.Enums;
|
||||
using PARR.Domain.Settings;
|
||||
|
||||
@@ -115,6 +119,16 @@ namespace PARR.Core
|
||||
|
||||
#endregion
|
||||
|
||||
#region Сервисы сбора снапшотов
|
||||
|
||||
services.TryAddSingleton<ISnapshotSettings, DefaultSnapshotSettings>();
|
||||
|
||||
services.AddScoped<ISnapshotProvider, RobotConfigurationSnapshotService>();
|
||||
services.AddScoped<ISnapshotProvider, RobotSnapshotService>();
|
||||
// тут другие сервисы, реализующие ISnapshotProvider
|
||||
|
||||
#endregion
|
||||
|
||||
#region Workload
|
||||
|
||||
services.AddScoped<WorkloadCacheService>();
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using PARR.Domain.Entities.Job;
|
||||
|
||||
namespace PARR.Core.Repositories.Interfaces.Job
|
||||
{
|
||||
public interface IJobAutoControlRepository
|
||||
{
|
||||
IQueryable<JobAutoControl> Get();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using PARR.Core.Repositories.Base;
|
||||
|
||||
namespace PARR.Core.Repositories.Interfaces.Job
|
||||
{
|
||||
public interface IJobRepository : IBaseRepository<Domain.Entities.Job.Job>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
|
||||
namespace PARR.Core.Repositories.Interfaces.JobGroupRepositories
|
||||
{
|
||||
public interface IJobGroupAutoControlRepository
|
||||
{
|
||||
IQueryable<JobGroupAutoControl> Get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using PARR.Core.Repositories.Base;
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
|
||||
namespace PARR.Core.Repositories.Interfaces.JobGroupRepositories
|
||||
{
|
||||
public interface IJobGroupFieldFilterRepository : IBaseRepository<JobGroupFieldFilter>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace PARR.Core.Repositories.Interfaces.JobGroupRepositories
|
||||
{
|
||||
public interface IJobGroupRelationshipFilterRepository
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using PARR.Core.Repositories.Base;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
|
||||
namespace PARR.Core.Repositories.Interfaces.Job
|
||||
namespace PARR.Core.Repositories.Interfaces.JobGroupRepositories
|
||||
{
|
||||
public interface IJobGroupRepository : IBaseRepository<JobGroup>
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using PARR.Core.Repositories.Base;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
|
||||
namespace PARR.Core.Repositories.Interfaces.Job
|
||||
namespace PARR.Core.Repositories.Interfaces.JobGroupRepositories
|
||||
{
|
||||
public interface IJobGroupTypeRepository : IBaseRepository<JobGroupType>
|
||||
{
|
||||
@@ -0,0 +1,9 @@
|
||||
using PARR.Core.Repositories.Base;
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
|
||||
namespace PARR.Core.Repositories.Interfaces.JobGroupRepositories
|
||||
{
|
||||
public interface IJobGroupUnitFilterRepository : IBaseRepository<JobGroupUnitFilter>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using PARR.Core.Repositories.Base;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
|
||||
namespace PARR.Core.Repositories.Interfaces.Job
|
||||
namespace PARR.Core.Repositories.Interfaces.JobRepositories
|
||||
{
|
||||
public interface IFieldFilterRepository : IBaseRepository<JobFieldFilter>
|
||||
{
|
||||
@@ -0,0 +1,10 @@
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
|
||||
namespace PARR.Core.Repositories.Interfaces.JobRepositories
|
||||
{
|
||||
public interface IJobAutoControlRepository
|
||||
{
|
||||
IQueryable<JobAutoControl> Get();
|
||||
void RemoveRange(List<JobAutoControl> autoControlList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using PARR.Core.Repositories.Base;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
|
||||
namespace PARR.Core.Repositories.Interfaces.JobRepositories
|
||||
{
|
||||
public interface IJobRepository : IBaseRepository<Job>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using PARR.Core.Repositories.Base;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
|
||||
namespace PARR.Core.Repositories.Interfaces.Job
|
||||
namespace PARR.Core.Repositories.Interfaces.JobRepositories
|
||||
{
|
||||
public interface IJobUnitFilterRepository : IBaseRepository<JobUnitFilter>
|
||||
{
|
||||
@@ -0,0 +1,9 @@
|
||||
using PARR.Core.Repositories.Base;
|
||||
using PARR.Domain.Entities.RobotEntities;
|
||||
|
||||
namespace PARR.Core.Repositories.Interfaces.RobotRepositories
|
||||
{
|
||||
public interface IRobotConfigurationSnapshotRepository : IBaseRepository<RobotConfigurationSnapshot>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PARR.Core.Common.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.JobRepositories;
|
||||
using PARR.Domain.Cache.Models;
|
||||
using PARR.Domain.DTOs.Matching;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using PARR.Core.Services.NextRunServices.Models;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.Core.Services.NextRunServices
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.Schedule;
|
||||
using PARR.Core.Services.NextRunServices.Models;
|
||||
using PARR.Core.Services.NextRunServices.Subservices;
|
||||
using PARR.Core.Services.Shortcodes;
|
||||
using PARR.Domain.Entities;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
using PARR.Domain.Enums;
|
||||
using PARR.Domain.Settings;
|
||||
|
||||
|
||||
@@ -70,23 +70,12 @@ namespace PARR.Core.Services.NextRunServices.Subservices
|
||||
|
||||
var nextRun = referenceDate;
|
||||
|
||||
//var finalOffset = new TimeSpan(3, 0, 0) + (offset ?? TimeSpan.Zero);
|
||||
var finalOffset = offset ?? TimeSpan.Zero;
|
||||
|
||||
var offsetReferenceDate = referenceDate.ToOffset(finalOffset);
|
||||
|
||||
logger.LogDebug("Дата после добавления offset - {offsetReferenceDate}", offsetReferenceDate);
|
||||
|
||||
////---------
|
||||
//// Для рассчета по МСК времени, потому что в ЮТС может быть еще ВСК, а по МСК это уже ПНД
|
||||
//var offsetReferenceDate = referenceDate.ToOffset(new TimeSpan(3, 0, 0));
|
||||
////---------
|
||||
|
||||
//// может быть offset ,это работает когда смещение для УЗ ЕСПП и применение ЗО РГ
|
||||
//if (offset.HasValue)
|
||||
// offsetReferenceDate = offsetReferenceDate.Off(offsetReferenceDate.Offset + offset);
|
||||
// //offsetReferenceDate = offsetReferenceDate.Add(offset.Value);
|
||||
|
||||
|
||||
switch (esppSchedule.TypeSchedule.Id)
|
||||
{
|
||||
@@ -108,6 +97,9 @@ namespace PARR.Core.Services.NextRunServices.Subservices
|
||||
case (int)EsppSchTypeScheduleEnum.Annually2:
|
||||
nextRun = GetNextDateAnnually2(esppSchedule.Values, offsetReferenceDate);
|
||||
break;
|
||||
default:
|
||||
logger.LogError("Неподдерживаемый тип расписания ЕСПП: {TypeId}", esppSchedule.TypeSchedule.Id);
|
||||
throw new NotSupportedException($"Тип расписания {esppSchedule.TypeSchedule.Id} не поддерживается.");
|
||||
}
|
||||
|
||||
logger.LogDebug("Рассчитанная дата, с учетом offset: {nextRun}", nextRun);
|
||||
@@ -139,6 +131,14 @@ namespace PARR.Core.Services.NextRunServices.Subservices
|
||||
return esppSchedule;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Регулярно. Каждые 1 час, 2 часа, Каждые пол года...
|
||||
/// </summary>
|
||||
/// <param name="values"></param>
|
||||
/// <param name="referenceDate"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
private DateTimeOffset GetNextDateRegularly(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
|
||||
{
|
||||
var regDict = new Dictionary<string, int>
|
||||
@@ -159,27 +159,63 @@ namespace PARR.Core.Services.NextRunServices.Subservices
|
||||
{"Каждые полгода", 4380},
|
||||
{"Каждые 1,5 года", 13140},
|
||||
{"Каждые 3 года", 26280},
|
||||
|
||||
};
|
||||
|
||||
|
||||
if (!regDict.TryGetValue(values[0].Value.Value, out int regNum))
|
||||
var key = values[0].Value.Value;
|
||||
if (!regDict.TryGetValue(key, out int regHours))
|
||||
{
|
||||
logger.LogError("Неизвестное значение: {Value}", values[0].Value.Value);
|
||||
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
|
||||
logger.LogError("Неизвестное значение: {Value}", key);
|
||||
throw new ArgumentException($"Неизвестное значение: {key}");
|
||||
}
|
||||
|
||||
#region Старая логика
|
||||
|
||||
//var calcDay = referenceDate;
|
||||
|
||||
////TODO: вот это повторяется от метода к методу
|
||||
////Если итоговая дата указывает на прошлое, то повторяем расчёт и уходим в рекурсию
|
||||
//if (calcDay < DateTimeOffset.UtcNow /* || calcDay <= referenceDate*/)
|
||||
// calcDay = GetNextDateRegularly(values, calcDay.AddHours(regHours));
|
||||
|
||||
//return calcDay;
|
||||
|
||||
#endregion
|
||||
|
||||
var nowInTargetZone = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
|
||||
var calcDay = referenceDate;
|
||||
|
||||
//TODO: вот это повторяется от метода к методу
|
||||
//Если итоговая дата указывает на прошлое, то повторяем расчёт и уходим в рекурсию
|
||||
if (calcDay < DateTimeOffset.UtcNow)
|
||||
calcDay = GetNextDateRegularly(values, calcDay.AddHours(regNum));
|
||||
// Если referenceDate далеко в прошлом, не шагаем по минутам в цикле.
|
||||
// Сразу вычисляем, сколько целых интервалов нужно прибавить, чтобы догнать текущее время.
|
||||
if (calcDay < nowInTargetZone)
|
||||
{
|
||||
double totalHoursPast = (nowInTargetZone - calcDay).TotalHours;
|
||||
|
||||
return calcDay;
|
||||
// Считаем, сколько полных интервалов помещается в этот отрезок времени
|
||||
long intervalsToSkip = (long)Math.Ceiling(totalHoursPast / regHours);
|
||||
|
||||
calcDay = calcDay.AddHours(intervalsToSkip * regHours);
|
||||
}
|
||||
|
||||
// Проверяем условия и делаем микро-шаги, если необходимо
|
||||
while (true)
|
||||
{
|
||||
if (calcDay >= referenceDate && calcDay >= nowInTargetZone)
|
||||
{
|
||||
return calcDay;
|
||||
}
|
||||
calcDay = calcDay.AddHours(regHours);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Еженедельно, каждый понедельник, вторник, среду...
|
||||
/// </summary>
|
||||
/// <param name="values"></param>
|
||||
/// <param name="referenceDate"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
private DateTimeOffset GetNextDateWeekly(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
|
||||
{
|
||||
if (!dwDict.TryGetValue(values[0].Value.Value, out int dwNum))
|
||||
@@ -188,71 +224,155 @@ namespace PARR.Core.Services.NextRunServices.Subservices
|
||||
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
|
||||
}
|
||||
|
||||
#region Старая логика
|
||||
//var calcDay = referenceDate;
|
||||
//if (calcDay < DateTimeOffset.UtcNow)
|
||||
//{
|
||||
// var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
// calcDay = new DateTimeOffset(
|
||||
// nowInTargetTz.Year,
|
||||
// nowInTargetTz.Month,
|
||||
// nowInTargetTz.Day,
|
||||
// referenceDate.Hour,
|
||||
// referenceDate.Minute,
|
||||
// referenceDate.Second,
|
||||
// referenceDate.Offset
|
||||
// );
|
||||
//}
|
||||
|
||||
|
||||
//if (((int)calcDay.DayOfWeek) != dwNum)
|
||||
// calcDay = OffsetToDayOfWeek(dwNum, calcDay);
|
||||
|
||||
////Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
|
||||
//if (calcDay < DateTimeOffset.UtcNow || calcDay <= referenceDate)
|
||||
// calcDay = GetNextDateWeekly(values, calcDay.AddDays(7));//а вот тут что-то новенькое
|
||||
|
||||
//return calcDay;
|
||||
#endregion
|
||||
|
||||
var nowInTargetZone = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
|
||||
var calcDay = referenceDate;
|
||||
if (calcDay < DateTimeOffset.UtcNow)
|
||||
|
||||
// Сдвигаем дату вперед до нужного дня недели
|
||||
if ((int)calcDay.DayOfWeek != dwNum)
|
||||
{
|
||||
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
|
||||
);
|
||||
calcDay = OffsetToDayOfWeek(dwNum, calcDay);
|
||||
}
|
||||
|
||||
// Если получившаяся дата все еще глубоко в прошлом,
|
||||
// не шагаем неделями в цикле, а мгновенно прыгаем ближе к текущему времени математикой.
|
||||
if (calcDay < nowInTargetZone)
|
||||
{
|
||||
double totalDaysPast = (nowInTargetZone - calcDay).TotalDays;
|
||||
|
||||
if (((int)calcDay.DayOfWeek) != dwNum)
|
||||
calcDay = OffsetToDayOfWeek(dwNum, calcDay);
|
||||
// Вычисляем, сколько полных недель (по 7 дней) нужно прибавить
|
||||
long weeksToSkip = (long)Math.Ceiling(totalDaysPast / 7);
|
||||
|
||||
//TODO: вот это повторяется от метода к методу
|
||||
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
|
||||
if (calcDay < DateTimeOffset.UtcNow)
|
||||
calcDay = GetNextDateWeekly(values, calcDay.AddDays(7));//а вот тут что-то новенькое
|
||||
calcDay = calcDay.AddDays(weeksToSkip * 7);
|
||||
}
|
||||
|
||||
return calcDay;
|
||||
while (true)
|
||||
{
|
||||
// Условие выхода: дата в будущем относительно точки старта И текущего времени
|
||||
if (calcDay >= referenceDate && calcDay >= nowInTargetZone)
|
||||
{
|
||||
return calcDay;
|
||||
}
|
||||
|
||||
// Если условия не выполнены, делаем шаг ровно в одну неделю (7 дней)
|
||||
calcDay = calcDay.AddDays(7);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ежемесячно, 1, 2, 3... числа
|
||||
/// </summary>
|
||||
/// <param name="values"></param>
|
||||
/// <param name="referenceDate"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
private DateTimeOffset GetNextDateMonthly(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
|
||||
{
|
||||
if (!int.TryParse(values[0].Value.Value, out int dayNum))
|
||||
if (!int.TryParse(values[0].Value.Value, out int targetDay))
|
||||
{
|
||||
logger.LogError("Неизвестное значение дня месяца: {Value}", values[0].Value.Value);
|
||||
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
|
||||
}
|
||||
|
||||
var calcDay = new DateTimeOffset(referenceDate.Year, referenceDate.Month, dayNum, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
if (referenceDate < DateTimeOffset.UtcNow)
|
||||
#region старая логика, не учтено попадание например 31 числа на февраль. Нет сравнения что расчитанная дата новее или равна refDate
|
||||
//var calcDay = new DateTimeOffset(referenceDate.Year, referenceDate.Month, targetDay, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
//if (referenceDate < DateTimeOffset.UtcNow)
|
||||
//{
|
||||
// var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
// calcDay = new DateTimeOffset(
|
||||
// nowInTargetTz.Year,
|
||||
// nowInTargetTz.Month,
|
||||
// targetDay,
|
||||
// referenceDate.Hour,
|
||||
// referenceDate.Minute,
|
||||
// referenceDate.Second,
|
||||
// referenceDate.Offset
|
||||
// );
|
||||
//}
|
||||
|
||||
////Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
|
||||
//if (calcDay < DateTimeOffset.UtcNow /*|| calcDay <= referenceDate*/)
|
||||
// calcDay = GetNextDateMonthly(values, calcDay.AddMonths(1));
|
||||
|
||||
//return calcDay;
|
||||
#endregion
|
||||
|
||||
// Начинаем проверку с текущего месяца и года переданной referenceDate
|
||||
var baseDate = referenceDate;
|
||||
int monthsToAdd = 0;
|
||||
|
||||
var nowInTargetZone = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
|
||||
while (true)
|
||||
{
|
||||
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);
|
||||
// Каждую итерацию мы отталкиваемся от исходной точки и прибавляем +1, +2, +3 месяца
|
||||
var nextMonthDate = baseDate.AddMonths(monthsToAdd);
|
||||
int year = nextMonthDate.Year;
|
||||
int month = nextMonthDate.Month;
|
||||
|
||||
// Если в этом конкретном месяце НЕТ нужного нам дня (например, 31 числа в апреле или 30 в феврале)
|
||||
if (targetDay > DateTime.DaysInMonth(year, month))
|
||||
{
|
||||
// Пропускаем этот месяц и идем на следующий круг цикла
|
||||
monthsToAdd++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Если день существует, безопасно собираем дату
|
||||
var calcDay = new DateTimeOffset(
|
||||
year, month, targetDay,
|
||||
referenceDate.Hour, referenceDate.Minute, referenceDate.Second,
|
||||
referenceDate.Offset
|
||||
);
|
||||
|
||||
// Условие выхода: дата строго в будущем относительно referenceDate и текущего UTC-времени
|
||||
if (calcDay >= referenceDate && calcDay >= nowInTargetZone)
|
||||
{
|
||||
return calcDay;
|
||||
}
|
||||
|
||||
// Если дата правильная, но она в прошлом — переходим к следующему месяцу
|
||||
monthsToAdd++;
|
||||
}
|
||||
|
||||
|
||||
//TODO: вот это повторяется от метода к методу
|
||||
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
|
||||
if (calcDay < DateTimeOffset.UtcNow)
|
||||
calcDay = GetNextDateMonthly(values, calcDay.AddMonths(1));
|
||||
|
||||
return calcDay;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ежемесячно-2, Первый понедельник, Третий вторник
|
||||
/// </summary>
|
||||
/// <param name="values"></param>
|
||||
/// <param name="referenceDate"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
private DateTimeOffset GetNextDateMonthly2(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
|
||||
{
|
||||
if (!orderDict.TryGetValue(values[0].Value.Value, out int orderNum))
|
||||
@@ -267,56 +387,113 @@ namespace PARR.Core.Services.NextRunServices.Subservices
|
||||
throw new ArgumentException($"Неизвестное значение: {values[1].Value.Value}");
|
||||
}
|
||||
|
||||
#region Старая логика
|
||||
//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 startDay = referenceDate < DateTimeOffset.UtcNow
|
||||
? 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);
|
||||
|
||||
var daysList = FindAllDaysInMonth(dwNum, startDay);
|
||||
//DateTimeOffset calcDay;
|
||||
//if (orderNum == 5) // Последний
|
||||
//{
|
||||
// calcDay = daysList.Last();
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// if (orderNum > daysList.Count)
|
||||
// {
|
||||
// logger.LogError("Запрошенный порядковый день недели ({OrderNum}) не существует в месяце {Month}/{Year}. Найдено только {Count} дней.", orderNum, startDay.Month, startDay.Year, daysList.Count);
|
||||
// throw new InvalidOperationException($"Запрошенный порядковый день недели ({orderNum}) не существует в месяце {startDay.Month}/{startDay.Year}. Найдено только {daysList.Count} дней.");
|
||||
// }
|
||||
// else
|
||||
// calcDay = daysList[orderNum - 1];
|
||||
//}
|
||||
|
||||
DateTimeOffset calcDay;
|
||||
if (orderNum == 5) // Последний
|
||||
//// Если дата <= текущей, переходим на следующий месяц
|
||||
//if (calcDay <= DateTimeOffset.UtcNow /*|| calcDay <= referenceDate*/)
|
||||
// return GetNextDateMonthly2(values, startDay.AddMonths(1));
|
||||
|
||||
//return calcDay;
|
||||
#endregion
|
||||
|
||||
var nowInTargetZone = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
|
||||
// Начинаем расчет с текущего месяца referenceDate
|
||||
int monthsToAdd = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
calcDay = daysList.Last();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (orderNum > daysList.Count)
|
||||
var currentMonthDate = referenceDate.AddMonths(monthsToAdd);
|
||||
|
||||
// Безопасно формируем первое число для проверяемого месяца
|
||||
var startDayOfMonth = new DateTimeOffset(
|
||||
currentMonthDate.Year,
|
||||
currentMonthDate.Month,
|
||||
1,
|
||||
referenceDate.Hour,
|
||||
referenceDate.Minute,
|
||||
referenceDate.Second,
|
||||
referenceDate.Offset
|
||||
);
|
||||
|
||||
// Ищем все нужные дни недели в этом месяце
|
||||
var daysList = FindAllDaysInMonth(dwNum, startDayOfMonth);
|
||||
|
||||
DateTimeOffset calcDay;
|
||||
|
||||
if (orderNum == 5) // Последний день недели в месяце (например, последняя пятница)
|
||||
{
|
||||
logger.LogError("Запрошенный порядковый день недели ({OrderNum}) не существует в месяце {Month}/{Year}. Найдено только {Count} дней.", orderNum, startDay.Month, startDay.Year, daysList.Count);
|
||||
throw new InvalidOperationException($"Запрошенный порядковый день недели ({orderNum}) не существует в месяце {startDay.Month}/{startDay.Year}. Найдено только {daysList.Count} дней.");
|
||||
calcDay = daysList.Last();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Умная защита: если в текущем месяце нет 5-го вторника,
|
||||
// мы не выкидываем ошибку приложения, а мягко идем искать в следующем месяце
|
||||
if (orderNum > daysList.Count)
|
||||
{
|
||||
monthsToAdd++;
|
||||
continue;
|
||||
}
|
||||
|
||||
calcDay = daysList[orderNum - 1];
|
||||
}
|
||||
|
||||
// 3. Условие выхода (точно такое же, как во всех предыдущих методах)
|
||||
if (calcDay >= referenceDate && calcDay >= nowInTargetZone)
|
||||
{
|
||||
return calcDay;
|
||||
}
|
||||
|
||||
// Если дата правильная, но она в прошлом — переходим к следующему календарному месяцу
|
||||
monthsToAdd++;
|
||||
}
|
||||
|
||||
// Если дата <= текущей, переходим на следующий месяц
|
||||
if (calcDay <= DateTimeOffset.UtcNow)
|
||||
return GetNextDateMonthly2(values, startDay.AddMonths(1));
|
||||
|
||||
return calcDay;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ежегодно, Январь 17, Февраль 20
|
||||
/// </summary>
|
||||
/// <param name="values"></param>
|
||||
/// <param name="referenceDate"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
private DateTimeOffset GetNextDateAnnually(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
|
||||
{
|
||||
|
||||
@@ -332,26 +509,73 @@ namespace PARR.Core.Services.NextRunServices.Subservices
|
||||
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
|
||||
}
|
||||
|
||||
//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;
|
||||
#region Старая логика
|
||||
////сравниваем года в одном часовом поясе
|
||||
//var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
//var year = (referenceDate.Year < nowInTargetTz.Year)
|
||||
// ? nowInTargetTz.Year
|
||||
// : referenceDate.Year;
|
||||
|
||||
var calcDay = new DateTimeOffset(
|
||||
year, monthNum, dayNum,
|
||||
referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
//var calcDay = new DateTimeOffset(
|
||||
// year, monthNum, dayNum,
|
||||
// referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
|
||||
//TODO: вот это повторяется от метода к методу
|
||||
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
|
||||
if (calcDay < DateTimeOffset.UtcNow)
|
||||
calcDay = GetNextDateAnnually(values, calcDay.AddYears(1));
|
||||
////Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
|
||||
//if (calcDay < DateTimeOffset.UtcNow /*|| calcDay <= referenceDate*/)
|
||||
// calcDay = GetNextDateAnnually(values, calcDay.AddYears(1));
|
||||
|
||||
return calcDay;
|
||||
//return calcDay;
|
||||
#endregion
|
||||
|
||||
var nowInTargetZone = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
|
||||
// Начинаем расчет с года переданной referenceDate
|
||||
int yearsToAdd = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Вычисляем текущий проверяемый год относительно исходной referenceDate
|
||||
var currentYearDate = referenceDate.AddYears(yearsToAdd);
|
||||
int year = currentYearDate.Year;
|
||||
|
||||
// ЗАЩИТА: Если в этом году нет такого дня (например, 29 февраля в невисокосном году)
|
||||
if (dayNum > DateTime.DaysInMonth(year, monthNum))
|
||||
{
|
||||
// Просто пропускаем этот год и переходим к следующему
|
||||
yearsToAdd++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Если день существует, безопасно собираем дату
|
||||
var calcDay = new DateTimeOffset(
|
||||
year,
|
||||
monthNum,
|
||||
dayNum,
|
||||
referenceDate.Hour,
|
||||
referenceDate.Minute,
|
||||
referenceDate.Second,
|
||||
referenceDate.Offset
|
||||
);
|
||||
|
||||
// Проверяем условия выхода (абсолютно одинаково для всех методов)
|
||||
if (calcDay >= referenceDate && calcDay >= nowInTargetZone)
|
||||
{
|
||||
return calcDay;
|
||||
}
|
||||
|
||||
// Если дата в прошлом — переходим на следующий год
|
||||
yearsToAdd++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ежегодно-2, Первый понедельник января
|
||||
/// </summary>
|
||||
/// <param name="values"></param>
|
||||
/// <param name="referenceDate"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
private DateTimeOffset GetNextDateAnnually2(List<EsppScheduleValDto> values, DateTimeOffset referenceDate)
|
||||
{
|
||||
var monthADict = new Dictionary<string, int>
|
||||
@@ -386,34 +610,90 @@ namespace PARR.Core.Services.NextRunServices.Subservices
|
||||
throw new ArgumentException($"Неизвестное значение: {values[1].Value.Value}");
|
||||
}
|
||||
|
||||
//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 == nowInTargetTz.Year && referenceDate.Month < nowInTargetTz.Month)
|
||||
year += 1;
|
||||
#region Старая логика
|
||||
|
||||
var calcDay = new DateTimeOffset(
|
||||
year, monthNum, 1,
|
||||
referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
//// сравниваем года в одном часовом поясе
|
||||
//var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
//var year = (referenceDate.Year < nowInTargetTz.Year)
|
||||
// ? nowInTargetTz.Year
|
||||
// : referenceDate.Year;
|
||||
|
||||
List<DateTimeOffset> daysList = FindAllDaysInMonth(dwNum, calcDay);
|
||||
////if (year == DateTimeOffset.UtcNow.Year && referenceDate.Month < DateTimeOffset.UtcNow.Month)
|
||||
//if (year == nowInTargetTz.Year && referenceDate.Month < nowInTargetTz.Month)
|
||||
// year += 1;
|
||||
|
||||
if (orderNum == 5)//5-последний
|
||||
calcDay = daysList.Last();
|
||||
else
|
||||
calcDay = daysList[orderNum - 1];
|
||||
//var calcDay = new DateTimeOffset(
|
||||
// year, monthNum, 1,
|
||||
// referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
|
||||
|
||||
//TODO: вот это повторяется от метода к методу
|
||||
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
|
||||
if (calcDay < DateTimeOffset.UtcNow)
|
||||
calcDay = GetNextDateAnnually2(values, calcDay.AddYears(1));
|
||||
//List<DateTimeOffset> daysList = FindAllDaysInMonth(dwNum, calcDay);
|
||||
|
||||
return calcDay;
|
||||
//if (orderNum == 5)//5-последний
|
||||
// calcDay = daysList.Last();
|
||||
//else
|
||||
// calcDay = daysList[orderNum - 1];
|
||||
|
||||
////Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
|
||||
//if (calcDay < DateTimeOffset.UtcNow /*|| calcDay <= referenceDate*/)
|
||||
// calcDay = GetNextDateAnnually2(values, calcDay.AddYears(1));
|
||||
|
||||
//return calcDay;
|
||||
|
||||
#endregion
|
||||
|
||||
var nowInTargetZone = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
|
||||
|
||||
// Начинаем расчет с года переданной referenceDate
|
||||
int yearsToAdd = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Сдвигаем проверяемый год относительно исходной даты
|
||||
var currentYearDate = referenceDate.AddYears(yearsToAdd);
|
||||
int year = currentYearDate.Year;
|
||||
|
||||
// Формируем первое число целевого месяца для проверяемого года
|
||||
var startDayOfMonth = new DateTimeOffset(
|
||||
year,
|
||||
monthNum,
|
||||
1,
|
||||
referenceDate.Hour,
|
||||
referenceDate.Minute,
|
||||
referenceDate.Second,
|
||||
referenceDate.Offset
|
||||
);
|
||||
|
||||
// Ищем все нужные дни недели в этом месяце
|
||||
var daysList = FindAllDaysInMonth(dwNum, startDayOfMonth);
|
||||
|
||||
DateTimeOffset calcDay;
|
||||
|
||||
if (orderNum == 5) // 5 означает "Последний день недели в месяце"
|
||||
{
|
||||
calcDay = daysList.Last();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Умная защита: если в этом году в этом месяце НЕТ 5-го выбранного дня недели
|
||||
if (orderNum > daysList.Count)
|
||||
{
|
||||
// Мы не падаем с ошибкой, а просто переходим к проверке следующего года
|
||||
yearsToAdd++;
|
||||
continue;
|
||||
}
|
||||
|
||||
calcDay = daysList[orderNum - 1];
|
||||
}
|
||||
|
||||
if (calcDay >= referenceDate && calcDay >= nowInTargetZone)
|
||||
{
|
||||
return calcDay;
|
||||
}
|
||||
|
||||
// Если вычисленная дата в прошлом — переходим на следующий год
|
||||
yearsToAdd++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,8 +4,34 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
{
|
||||
public interface IRobotSnapshotService
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить статистику
|
||||
/// </summary>
|
||||
/// <param name="queryDto"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<RobotSnapshotItemDto>> GetAsync(RobotSnapshotQuery queryDto);
|
||||
|
||||
/// <summary>
|
||||
/// Записать статистику
|
||||
/// </summary>
|
||||
/// <param name="robotSnapshot"></param>
|
||||
/// <returns></returns>
|
||||
Task<RobotSnapshotItemDto> CreateAsync(CreateRobotSnapshot robotSnapshot);
|
||||
|
||||
/// <summary>
|
||||
/// Получить аналитику за период с группировкой по ip серверов. Разбивка по часам.
|
||||
/// Максимальная одновременная работа роботов. Среднее кол-во роботов в час. Максимально возможное кол-во роботов.
|
||||
/// </summary>
|
||||
/// <param name="queryDto"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<ServerHourlyAnalyticsDto>> GetHourlyAnalyticsAsync(RobotAnalyticsQuery queryDto);
|
||||
|
||||
/// <summary>
|
||||
/// Получить аналитику за период по типу робота. Разбивка по часам.
|
||||
/// Среднее кол-во роботов в час.
|
||||
/// </summary>
|
||||
/// <param name="queryDto"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<HourlyRobotTypeAnalyticsDto>> GetHourlyAnalyticsByRobotTypeAsync(RobotAnalyticsRobotTypeQuery queryDto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.RobotRepositories;
|
||||
using PARR.Core.Services.Snapshots.Interfaces;
|
||||
using PARR.Domain.DTOs.RobotSnapshotDTO;
|
||||
using PARR.Domain.DTOs.User;
|
||||
using PARR.Domain.Entities.RobotEntities;
|
||||
@@ -9,21 +11,32 @@ using PARR.Domain.Exceptions;
|
||||
|
||||
namespace PARR.Core.Services.RobotSnapshotServices
|
||||
{
|
||||
internal class RobotSnapshotService : IRobotSnapshotService
|
||||
internal class RobotSnapshotService : IRobotSnapshotService, ISnapshotProvider
|
||||
{
|
||||
private readonly IRobotSnapshotRepository robotSnapshotRepository;
|
||||
private readonly IUserRepository userRepository;
|
||||
private readonly IMapper mapper;
|
||||
private readonly IRobotSnapshotRepository _robotSnapshotRepository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ILogger<RobotSnapshotService> _logger;
|
||||
private readonly ISnapshotSettings _snapshotSettings;
|
||||
|
||||
// Тут любое значение, не используем метод создания снапшота
|
||||
public TimeSpan Interval => TimeSpan.FromHours(1);
|
||||
|
||||
public TimeSpan RetentionPeriod => _snapshotSettings.RobotSnapshotRetentionPeriod;
|
||||
|
||||
public RobotSnapshotService(
|
||||
IRobotSnapshotRepository robotSnapshotRepository,
|
||||
IUserRepository userRepository,
|
||||
IMapper mapper
|
||||
IMapper mapper,
|
||||
ILogger<RobotSnapshotService> logger,
|
||||
ISnapshotSettings snapshotSettings
|
||||
)
|
||||
{
|
||||
this.robotSnapshotRepository = robotSnapshotRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.mapper = mapper;
|
||||
_robotSnapshotRepository = robotSnapshotRepository;
|
||||
_userRepository = userRepository;
|
||||
_mapper = mapper;
|
||||
_logger = logger;
|
||||
_snapshotSettings = snapshotSettings;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +50,7 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
var dateEnd = DateTimeOffset.UtcNow;
|
||||
var dateStart = dateEnd.AddMinutes(-minutes);
|
||||
|
||||
var query = robotSnapshotRepository.Get().AsNoTracking()
|
||||
var query = _robotSnapshotRepository.Get().AsNoTracking()
|
||||
.Where(t => dateStart <= t.DateCreated && t.DateCreated <= dateEnd);
|
||||
|
||||
if (!string.IsNullOrEmpty(queryDto.Ip))
|
||||
@@ -47,7 +60,7 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
|
||||
|
||||
var robotIps = groupingSnapshots.Select(t => t.Key).ToHashSet();
|
||||
var robotList = await userRepository.Get()
|
||||
var robotList = await _userRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Where(t => robotIps.Contains(t.Ip))
|
||||
.ToDictionaryAsync(t => t.Ip);
|
||||
@@ -59,12 +72,262 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
robotList.TryGetValue(item.Key, out var robot);
|
||||
|
||||
var userDto = new UserBaseDto { Ip = item.Key, Description = robot?.Description ?? string.Empty, Name = robot?.Name ?? string.Empty };
|
||||
var snapshots = mapper.Map<List<RobotSnapshotDto>>(item).OrderBy(t => t.DateCreated).ToList();
|
||||
var snapshots = _mapper.Map<List<RobotSnapshotDto>>(item).OrderBy(t => t.DateCreated).ToList();
|
||||
|
||||
result.Add(new RobotSnapshotItemDto { Robot = userDto, Snapshots = snapshots });
|
||||
}
|
||||
|
||||
return result.OrderBy(t => t.Robot.Ip).ThenBy(t => t.Robot.Name).ToList();
|
||||
return result.OrderBy(t => t.Robot.Name).ThenBy(t => t.Robot.Ip).ToList();
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<ServerHourlyAnalyticsDto>> GetHourlyAnalyticsAsync(RobotAnalyticsQuery queryDto)
|
||||
{
|
||||
var snapshotsQuery = _robotSnapshotRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Where(t => queryDto.DateStart <= t.DateCreated && t.DateCreated <= queryDto.DateEnd);
|
||||
|
||||
// Фильтруем по конкретному IP
|
||||
if (!string.IsNullOrEmpty(queryDto.Ip))
|
||||
snapshotsQuery = snapshotsQuery.Where(t => t.Ip == queryDto.Ip);
|
||||
|
||||
|
||||
// Группируем на стороне PostgreSQL с учетом часового пояса пользователя.
|
||||
// Метод .Add(offset) сдвигает UTC-дату в Postgres на интервал времени пользователя,
|
||||
// благодаря чему минуты объединяются в правильные локальные "часовые корзины".
|
||||
var rawGroupedData = await snapshotsQuery
|
||||
.GroupBy(t => new
|
||||
{
|
||||
t.Ip,
|
||||
// Явно сдвигаем дату и достаем только нужные компоненты
|
||||
Year = (t.DateCreated + queryDto.Offset).DateTime.Year,
|
||||
Month = (t.DateCreated + queryDto.Offset).DateTime.Month,
|
||||
Day = (t.DateCreated + queryDto.Offset).DateTime.Day,
|
||||
Hour = (t.DateCreated + queryDto.Offset).DateTime.Hour
|
||||
})
|
||||
.Select(g => new
|
||||
{
|
||||
g.Key.Ip,
|
||||
// Вытаскиваем компоненты даты из уже смещенного локального времени
|
||||
LocalYear = g.Key.Year,
|
||||
LocalMonth = g.Key.Month,
|
||||
LocalDay = g.Key.Day,
|
||||
LocalHour = g.Key.Hour,
|
||||
|
||||
MaxAllowed = g.Max(t => t.MaxRobots),
|
||||
MaxTemplates = g.Max(t => t.TemplateRobotsCount),
|
||||
MaxSchedules = g.Max(t => t.ScheduleRobotsCount),
|
||||
SumTemplates = g.Sum(t => t.TemplateRobotsCount),
|
||||
SumSchedules = g.Sum(t => t.ScheduleRobotsCount)
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
if (!rawGroupedData.Any())
|
||||
return new List<ServerHourlyAnalyticsDto>();
|
||||
|
||||
// Получаем информацию о серверах
|
||||
var serverIps = rawGroupedData.Select(g => g.Ip).ToHashSet();
|
||||
var serverDictionary = await _userRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Where(t => serverIps.Contains(t.Ip))
|
||||
.ToDictionaryAsync(t => t.Ip);
|
||||
|
||||
// Группируем полученные агрегаты по серверам уже в памяти
|
||||
var dataByServers = rawGroupedData.GroupBy(g => g.Ip);
|
||||
var result = new List<ServerHourlyAnalyticsDto>();
|
||||
|
||||
// Принудительно переводим UTC-границы запроса в локальное время пользователя для корректной генерации "дыр"
|
||||
var localStart = queryDto.DateStart.ToOffset(queryDto.Offset).DateTime;
|
||||
var localEnd = queryDto.DateEnd.ToOffset(queryDto.Offset).DateTime;
|
||||
|
||||
// Округляем начальный час до ровного значения (00 минут, 00 секунд)
|
||||
var startHour = new DateTime(localStart.Year, localStart.Month, localStart.Day, localStart.Hour, 0, 0);
|
||||
|
||||
foreach (var serverGroup in dataByServers)
|
||||
{
|
||||
serverDictionary.TryGetValue(serverGroup.Key, out var robotInfo);
|
||||
|
||||
var userDto = new UserBaseDto
|
||||
{
|
||||
Ip = serverGroup.Key,
|
||||
Description = robotInfo?.Description ?? string.Empty,
|
||||
Name = robotInfo?.Name ?? string.Empty
|
||||
};
|
||||
|
||||
// Формируем точки, которые удалось вытащить из базы данных
|
||||
var hourlyPoints = serverGroup.Select(g => new RobotHourlyPointDto
|
||||
{
|
||||
// Возвращаем дату со смещением пользователя, чтобы Angular сразу отображал правильный час
|
||||
Hour = new DateTimeOffset(g.LocalYear, g.LocalMonth, g.LocalDay, g.LocalHour, 0, 0, TimeSpan.Zero),
|
||||
MaxAllowedRobots = g.MaxAllowed,
|
||||
MaxTemplateRobots = g.MaxTemplates,
|
||||
MaxScheduleRobots = g.MaxSchedules,
|
||||
// Считаем среднее количество роботов, деля поминутную сумму строго на 60 минут часа
|
||||
//todo: тут не всегда нужно делить на 60, если час последний, то там может быть 50 минут или 20 и тп
|
||||
AvgTemplateRobots = Math.Round(g.SumTemplates / 60.0, 1),
|
||||
AvgScheduleRobots = Math.Round(g.SumSchedules / 60.0, 1)
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// Алгоритм заполнения пропусков во времени (если сервер был выключен)
|
||||
var filledPoints = new List<RobotHourlyPointDto>();
|
||||
var currentHour = startHour;
|
||||
int lastKnownMax = hourlyPoints.FirstOrDefault()?.MaxAllowedRobots ?? 0;
|
||||
|
||||
while (currentHour <= localEnd)
|
||||
{
|
||||
// Создаем временную точку со смещением для точного сопоставления
|
||||
var targetOffsetDateTime = new DateTimeOffset(currentHour, TimeSpan.Zero);
|
||||
var point = hourlyPoints.FirstOrDefault(p => p.Hour == targetOffsetDateTime);
|
||||
|
||||
if (point != null)
|
||||
{
|
||||
filledPoints.Add(point);
|
||||
lastKnownMax = point.MaxAllowedRobots;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Если за этот час записей в БД нет — значит, сервер был полностью оффлайн.
|
||||
// Выводим нули, чтобы график в Angular не прерывался, а плавно падал.
|
||||
filledPoints.Add(new RobotHourlyPointDto
|
||||
{
|
||||
Hour = targetOffsetDateTime,
|
||||
MaxAllowedRobots = lastKnownMax,
|
||||
MaxTemplateRobots = 0,
|
||||
AvgTemplateRobots = 0,
|
||||
MaxScheduleRobots = 0,
|
||||
AvgScheduleRobots = 0
|
||||
});
|
||||
}
|
||||
// Шагаем строго на +1 час вперед по локальному времени клиента
|
||||
currentHour = currentHour.AddHours(1);
|
||||
}
|
||||
|
||||
result.Add(new ServerHourlyAnalyticsDto
|
||||
{
|
||||
Robot = userDto,
|
||||
Snapshots = filledPoints.OrderBy(p => p.Hour).ToList()
|
||||
});
|
||||
}
|
||||
|
||||
return result
|
||||
.OrderBy(t => t.Robot.Name)
|
||||
.ThenBy(t => t.Robot.Ip)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<HourlyRobotTypeAnalyticsDto>> GetHourlyAnalyticsByRobotTypeAsync(RobotAnalyticsRobotTypeQuery queryDto)
|
||||
{
|
||||
var snapshotsQuery = _robotSnapshotRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Where(t => queryDto.DateStart <= t.DateCreated && t.DateCreated <= queryDto.DateEnd)
|
||||
.GroupBy(t => new
|
||||
{
|
||||
// Явно сдвигаем дату
|
||||
Year = (t.DateCreated + queryDto.Offset).DateTime.Year,
|
||||
Month = (t.DateCreated + queryDto.Offset).DateTime.Month,
|
||||
Day = (t.DateCreated + queryDto.Offset).DateTime.Day,
|
||||
Hour = (t.DateCreated + queryDto.Offset).DateTime.Hour
|
||||
});
|
||||
|
||||
IQueryable<RobotSnapshotRobotTypeDbDto> groupedQuery;
|
||||
|
||||
switch (queryDto.RobotType)
|
||||
{
|
||||
case Domain.Enums.RobotsEnum.TemplateOrder:
|
||||
groupedQuery = snapshotsQuery.Select(g => new RobotSnapshotRobotTypeDbDto
|
||||
{
|
||||
// Вытаскиваем компоненты даты из уже смещенного локального времени
|
||||
LocalYear = g.Key.Year,
|
||||
LocalMonth = g.Key.Month,
|
||||
LocalDay = g.Key.Day,
|
||||
LocalHour = g.Key.Hour,
|
||||
|
||||
// Макс не правильно.
|
||||
//MaxAllowed = g.Max(t => t.MaxRobots),
|
||||
Sum = g.Sum(t => t.TemplateRobotsCount)
|
||||
});
|
||||
break;
|
||||
case Domain.Enums.RobotsEnum.ScheduleOrder:
|
||||
groupedQuery = snapshotsQuery.Select(g => new RobotSnapshotRobotTypeDbDto
|
||||
{
|
||||
// Вытаскиваем компоненты даты из уже смещенного локального времени
|
||||
LocalYear = g.Key.Year,
|
||||
LocalMonth = g.Key.Month,
|
||||
LocalDay = g.Key.Day,
|
||||
LocalHour = g.Key.Hour,
|
||||
|
||||
// Макс не правильно.
|
||||
//MaxAllowed = g.Max(t => t.MaxRobots),
|
||||
Sum = g.Sum(t => t.ScheduleRobotsCount)
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Не обработано значение параметра", nameof(queryDto.RobotType));
|
||||
}
|
||||
|
||||
var data = await groupedQuery.ToListAsync();
|
||||
|
||||
if (!data.Any())
|
||||
return new List<HourlyRobotTypeAnalyticsDto>();
|
||||
|
||||
// Фиксируем текущее время в UTC и «округляем» его до начала часа
|
||||
var nowLocal = DateTimeOffset.UtcNow.ToOffset(queryDto.Offset);
|
||||
var currentHourLocal = new DateTime(nowLocal.Year, nowLocal.Month, nowLocal.Day, nowLocal.Hour, 0, 0);
|
||||
|
||||
var dbDataDict = data.ToDictionary(
|
||||
k => new DateTime(k.LocalYear, k.LocalMonth, k.LocalDay, k.LocalHour, 0, 0),
|
||||
v =>
|
||||
{
|
||||
double minutesInHour = 60.0;
|
||||
var dbLocalHour = new DateTime(v.LocalYear, v.LocalMonth, v.LocalDay, v.LocalHour, 0, 0);
|
||||
// Строгая проверка: совпадает ли час из базы данных ИМЕННО с текущим часом сегодня?
|
||||
// Если да, то нужно делить не на 60, а на прошедние минуты
|
||||
if (dbLocalHour == currentHourLocal)
|
||||
{
|
||||
// Если идет первая минута часа, делим на 1, чтобы избежать деления на ноль
|
||||
minutesInHour = nowLocal.Minute == 0 ? 1.0 : (double)nowLocal.Minute;
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
//MaxAllowedRobots = t.MaxAllowed,
|
||||
AvgRobots = Math.Round(v.Sum / minutesInHour, 1)
|
||||
};
|
||||
});
|
||||
|
||||
// Заполняем модель
|
||||
var statistics = new List<HourlyRobotTypeAnalyticsDto>();
|
||||
|
||||
#region Алгоритм заполнения пропусков во времени (если сервер был выключен)
|
||||
// Превращаем границы в локальное время для генерации сетки часов
|
||||
var currentHour = queryDto.DateStart.ToOffset(queryDto.Offset).DateTime;
|
||||
var localEnd = queryDto.DateEnd.ToOffset(queryDto.Offset).DateTime;
|
||||
|
||||
// Срезаем минуты/секунды для старта
|
||||
currentHour = new DateTime(currentHour.Year, currentHour.Month, currentHour.Day, currentHour.Hour, 0, 0);
|
||||
|
||||
while (currentHour <= localEnd)
|
||||
{
|
||||
// Ищем значение в словаре. Если нет — ставим 0
|
||||
dbDataDict.TryGetValue(currentHour, out var statInfo);
|
||||
|
||||
// 1. Создаем DateTimeOffset для локального времени с правильным смещением
|
||||
var localDateTimeOffset = new DateTimeOffset(currentHour, queryDto.Offset);
|
||||
|
||||
statistics.Add(new HourlyRobotTypeAnalyticsDto
|
||||
{
|
||||
// Возвращаем DateTimeOffset с нулевым смещением (как в вашем коде) или с queryDto.Offset
|
||||
Hour = localDateTimeOffset.ToUniversalTime(),
|
||||
AvgRobots = statInfo?.AvgRobots ?? 0
|
||||
});
|
||||
|
||||
currentHour = currentHour.AddHours(1);
|
||||
}
|
||||
#endregion
|
||||
|
||||
return statistics;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,24 +342,45 @@ namespace PARR.Core.Services.RobotSnapshotServices
|
||||
Ip = robotSnapshot.Ip
|
||||
};
|
||||
|
||||
var createdResult = await robotSnapshotRepository.CreateAsync(snapshot);
|
||||
var commitResult = await robotSnapshotRepository.CommitAsync();
|
||||
var createdResult = await _robotSnapshotRepository.CreateAsync(snapshot);
|
||||
var commitResult = await _robotSnapshotRepository.CommitAsync();
|
||||
|
||||
if (!createdResult || !commitResult)
|
||||
throw new DbErrorException("Ошибка сохранения в БД");
|
||||
|
||||
var user = await userRepository.Get().AsNoTracking().FirstOrDefaultAsync(t => t.Ip == robotSnapshot.Ip);
|
||||
var user = await _userRepository.Get().AsNoTracking().FirstOrDefaultAsync(t => t.Ip == robotSnapshot.Ip);
|
||||
var userDto = new UserBaseDto { Ip = robotSnapshot.Ip, Description = user?.Description ?? string.Empty, Name = user?.Name ?? string.Empty };
|
||||
|
||||
var result = new RobotSnapshotItemDto
|
||||
{
|
||||
Robot = userDto,
|
||||
Snapshots = mapper.Map<List<RobotSnapshotDto>>(new List<RobotSnapshot> { snapshot })
|
||||
Snapshots = _mapper.Map<List<RobotSnapshotDto>>(new List<RobotSnapshot> { snapshot })
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public Task TakeSnapshotAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// Метод пустой! Нам не нужно собирать данные по таймеру,
|
||||
// так как они и так пишутся сюда через контроллер API.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
public async Task CleanUpOldSnapshotsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var thresholdDate = DateTimeOffset.UtcNow - RetentionPeriod;
|
||||
|
||||
_logger.LogInformation("[{ServiceName}] Запуск очистки старых снапшотов. Удаление данных старше {ThresholdDate}", GetType().Name, thresholdDate);
|
||||
|
||||
// Удаляем старые записи напрямую в PostgreSQL
|
||||
var deletedCount = await _robotSnapshotRepository.Get()
|
||||
.Where(s => s.DateCreated < thresholdDate)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("[{ServiceName}] Очистка завершена. Удалено устаревших строк снапшотов: {Count}", GetType().Name, deletedCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
|
||||
public async Task<RobotTaskTemplate> GetTemplateTaskAsync(TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId)
|
||||
{
|
||||
var templateTask = await GetTaskAsync(RobotsEnum.TemplateOrder, taskStatusCode, acquireTask, robotIp, robotId);
|
||||
var templateTask = await GetTaskAsync(RobotsEnum.TemplateOrder, taskStatusCode, acquireTask, robotIp, robotId, TimeSpan.Zero);
|
||||
|
||||
var task = mapper.Map<RobotTaskTemplate>(templateTask);
|
||||
|
||||
@@ -72,9 +72,9 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
}
|
||||
|
||||
|
||||
public async Task<RobotTaskSchedule> GetScheduleTaskAsync(TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId, IHistoryInitiator historyInitiator)
|
||||
public async Task<RobotTaskSchedule> GetScheduleTaskAsync(TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId, IHistoryInitiator historyInitiator, TimeSpan scheduleCooldownDuration)
|
||||
{
|
||||
var scheduleTask = await GetTaskAsync(RobotsEnum.ScheduleOrder, taskStatusCode, acquireTask, robotIp, robotId);
|
||||
var scheduleTask = await GetTaskAsync(RobotsEnum.ScheduleOrder, taskStatusCode, acquireTask, robotIp, robotId, scheduleCooldownDuration);
|
||||
|
||||
// Проверяем nextRun, lastRun, обновляем их
|
||||
|
||||
@@ -121,14 +121,14 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
/// <param name="robotIp"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotFoundException"></exception>
|
||||
private async Task<RobotConfiguration> GetTaskAsync(RobotsEnum robotCode, TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId)
|
||||
private async Task<RobotConfiguration> GetTaskAsync(RobotsEnum robotCode, TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId, TimeSpan scheduleCooldownDuration)
|
||||
{
|
||||
// 1. Ищем все задания с превышенным кол-вом попыток и просроченным временем, ставим им статус ошибки
|
||||
await robotConfigurationRepository.MarkExpiredTasksAsFailedAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime);
|
||||
|
||||
|
||||
// 2. Ищем доступные задания
|
||||
var availableTasks = await GetAvailableTasksAsync(robotCode, taskStatusCode);
|
||||
var availableTasks = await GetAvailableTasksAsync(robotCode, taskStatusCode, scheduleCooldownDuration);
|
||||
|
||||
if (availableTasks.Count == 0)
|
||||
throw new NotFoundException("Нет доступных заданий для робота");
|
||||
@@ -137,7 +137,7 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
|
||||
if (acquireTask)
|
||||
{
|
||||
// Берем задание в работу, устанавливаем ей статус "В работе"
|
||||
// Берем задание в работу, устанавливаем ему статус "В работе"
|
||||
acquiredTaskId = await AcquireTaskAsync(availableTasks, robotIp, robotId);
|
||||
|
||||
if (acquiredTaskId == null)
|
||||
@@ -165,21 +165,35 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
/// <param name="robotCode"></param>
|
||||
/// <param name="taskStatusCode"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<List<Guid>> GetAvailableTasksAsync(RobotsEnum robotCode, TaskStatusEnum taskStatusCode)
|
||||
private async Task<List<Guid>> GetAvailableTasksAsync(RobotsEnum robotCode, TaskStatusEnum taskStatusCode, TimeSpan scheduleCooldownDuration)
|
||||
{
|
||||
var query = robotConfigurationRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Where(t => t.RobotCode == (int)robotCode && t.TaskStatusCode == (int)taskStatusCode);
|
||||
.Where(t => t.RobotCode == (int)robotCode/* && t.TaskStatusCode == (int)taskStatusCode*/);
|
||||
|
||||
// Если это задание для робота расписаний
|
||||
if (robotCode == RobotsEnum.ScheduleOrder)
|
||||
{
|
||||
// Выбираем только записи с созданными шаблонами (у которых статус 30), а только потом ищем у них расписания
|
||||
var createdTemplates = robotConfigurationRepository.Get()
|
||||
.Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder && t.TaskStatusCode == (int)TaskStatusEnum.Ok)
|
||||
.Select(t => t.TemplateId);
|
||||
#region Старый не оптимизированный запрос
|
||||
//var createdTemplates = robotConfigurationRepository.Get()
|
||||
// .Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder && t.TaskStatusCode == (int)TaskStatusEnum.Ok)
|
||||
// .Select(t => t.TemplateId);
|
||||
//query = query.Where(t => createdTemplates.Contains(t.TemplateId));
|
||||
#endregion
|
||||
query = query.Where(t => t.Template!.RobotConfigurations.Any(x => x.RobotCode == (int)RobotsEnum.TemplateOrder && x.TaskStatusCode == (int)TaskStatusEnum.Ok));
|
||||
|
||||
query = query.Where(t => createdTemplates.Contains(t.TemplateId));
|
||||
|
||||
// Не берем шаблоны, у которых lastRun + 3 часа < сейчас, и у них последний инициатор был или nextRun (10) или esppSchedule (5), это условие применяется только к активированным расписаниям
|
||||
var cooldownThreshold = DateTimeOffset.UtcNow.Add(-scheduleCooldownDuration);
|
||||
query = query.Where(t =>
|
||||
// Условие кулдауна: проверяем, попадает ли шаблон под ЗАПРЕТ
|
||||
!(
|
||||
t.Template!.IsActiveSchedule
|
||||
&& (t.Template.InitiatorParrComponentId == ParrComponentsEnum.EsppScheduleSync || t.Template.InitiatorParrComponentId == ParrComponentsEnum.NextRun)
|
||||
&& t.Template.LastRun >= cooldownThreshold
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Сортируем по nextRun, чтобы те, у кого nextRun ближе к текущей, выполнились скорее
|
||||
@@ -189,7 +203,14 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
var tasks = new List<Guid>();
|
||||
|
||||
// Ещем первые 10 заданий в статусе ОЖИДАНИЕ
|
||||
tasks = await query.Where(t => t.RobotStatusCode == (int)RobotStatusEnum.Wait).Take(TakeTasks).Select(t => t.Id).ToListAsync();
|
||||
tasks = await query
|
||||
.Where(t =>
|
||||
t.RobotStatusCode == (int)RobotStatusEnum.Wait
|
||||
&& t.TaskStatusCode == (int)taskStatusCode
|
||||
).Take(TakeTasks)
|
||||
.Select(t => t.Id)
|
||||
.ToListAsync();
|
||||
|
||||
|
||||
logger.LogDebug("Найдено заданий в статусе 'Ожидание' {Count} шт. Робот '{Robot}'", tasks.Count, robotCode.ToString());
|
||||
|
||||
@@ -203,6 +224,7 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
var endDate = DateTimeOffset.UtcNow.Add(-settingsFromDb.RobotWaitTime);
|
||||
|
||||
tasks = await query.Where(t => t.RobotStatusCode == (int)RobotStatusEnum.InProgress
|
||||
&& t.TaskStatusCode==(int)taskStatusCode
|
||||
&& t.AttemptsNumber < settingsFromDb.RobotAttemptsNumber
|
||||
&& t.LastRobotStatusUpdated < endDate)
|
||||
.Take(TakeTasks)
|
||||
|
||||
@@ -29,7 +29,8 @@ namespace PARR.Core.Services.RobotTask.Interfaces
|
||||
/// <param name="robotIp"></param>
|
||||
/// <param name="robotId"></param>
|
||||
/// <param name="historyInitiator"></param>
|
||||
/// <param name="scheduleCooldownDuration"></param>
|
||||
/// <returns></returns>
|
||||
Task<RobotTaskSchedule> GetScheduleTaskAsync(TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId, IHistoryInitiator historyInitiator);
|
||||
Task<RobotTaskSchedule> GetScheduleTaskAsync(TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId, IHistoryInitiator historyInitiator, TimeSpan scheduleCooldownDuration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ internal class GroupedFieldShortcodeHandler : IShortcodeHandler
|
||||
var unitsList = template.UnitsInTemplate;
|
||||
if (unitsList.Count == 0)
|
||||
{
|
||||
// Данные должны быть загружены оркестратором. Если пусто — значит в БД действительно нет связей.
|
||||
return input.Replace("%ГР_ПОЛЕ-ПН%", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
@@ -67,12 +66,16 @@ internal class GroupedFieldShortcodeHandler : IShortcodeHandler
|
||||
.Where(fv => fieldValueIds.Contains(fv.Id))
|
||||
.ToDictionaryAsync(fv => fv.Id, fv => fv.Value ?? string.Empty, ct);
|
||||
|
||||
var lines = unitsList.Select((uit, i) =>
|
||||
{
|
||||
var uName = unitNames.GetValueOrDefault(uit.UnitId, $"(UnitId={uit.UnitId})");
|
||||
var fVal = fieldValueStrings.GetValueOrDefault(uit.UnitFieldValueId, string.Empty);
|
||||
return $"{i + 1}. {uName} ({fVal})";
|
||||
});
|
||||
// Сортировка по имени юнита для детерминированного результата
|
||||
var lines = unitsList
|
||||
.OrderBy(u => unitNames.GetValueOrDefault(u.UnitId, string.Empty), StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(u => fieldValueStrings.GetValueOrDefault(u.UnitFieldValueId, string.Empty), StringComparer.OrdinalIgnoreCase)
|
||||
.Select((uit, i) =>
|
||||
{
|
||||
var uName = unitNames.GetValueOrDefault(uit.UnitId, $"(UnitId={uit.UnitId})");
|
||||
var fVal = fieldValueStrings.GetValueOrDefault(uit.UnitFieldValueId, string.Empty);
|
||||
return $"{i + 1}. {uName} ({fVal})";
|
||||
});
|
||||
|
||||
return input.Replace("%ГР_ПОЛЕ-ПН%", string.Join("\n", lines), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Common.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
using PARR.Core.Services.Shortcodes.Enums;
|
||||
using PARR.Core.Services.Shortcodes.Handlers;
|
||||
@@ -10,7 +10,7 @@ using PARR.Core.Services.Shortcodes.Models;
|
||||
using PARR.Domain.Cache.Models;
|
||||
using PARR.Domain.DTOs.Shortcode;
|
||||
using PARR.Domain.Entities;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
using PARR.Domain.Enums;
|
||||
using PARR.Domain.Settings;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -79,8 +79,8 @@ internal class ShortcodesService : IShortcodesService
|
||||
Index: template.Index,
|
||||
JobId: template.JobId,
|
||||
UnitId: template.UnitId,
|
||||
UnitName: initialUnitName ?? string.Empty,
|
||||
Job: initialJob,
|
||||
UnitName: initialUnitName ?? string.Empty,
|
||||
Job: initialJob,
|
||||
UnitsInTemplate: initialUnitsInTemplate,
|
||||
UnitTags: new List<string>(),
|
||||
RelatedUnitTags: new Dictionary<Guid, List<string>>()
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.RobotRepositories;
|
||||
using PARR.Core.Services.Snapshots.Interfaces;
|
||||
using PARR.Domain.Entities.RobotEntities;
|
||||
using PARR.Domain.Exceptions;
|
||||
|
||||
namespace PARR.Core.Services.Snapshots.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// Снапшоты для таблицы RobotConfiguration
|
||||
/// </summary>
|
||||
internal class RobotConfigurationSnapshotService : ISnapshotProvider
|
||||
{
|
||||
private readonly IRobotConfigurationRepository _robotConfigurationRepository;
|
||||
private readonly IRobotConfigurationSnapshotRepository _robotConfigurationSnapshotRepository;
|
||||
private readonly ILogger<RobotConfigurationSnapshotService> _logger;
|
||||
private readonly ISnapshotSettings _snapshotSettings;
|
||||
|
||||
public TimeSpan Interval => _snapshotSettings.RobotConfigurationSnapshotInterval;// TimeSpan.FromMinutes(2);
|
||||
|
||||
public TimeSpan RetentionPeriod => _snapshotSettings.RobotConfigurationSnapshotRetentionPeriod;//TimeSpan.FromDays(60);
|
||||
|
||||
public RobotConfigurationSnapshotService(
|
||||
IRobotConfigurationRepository robotConfigurationRepository,
|
||||
IRobotConfigurationSnapshotRepository robotConfigurationSnapshotRepository,
|
||||
ILogger<RobotConfigurationSnapshotService> logger,
|
||||
ISnapshotSettings snapshotSettings
|
||||
)
|
||||
{
|
||||
_robotConfigurationRepository = robotConfigurationRepository;
|
||||
_robotConfigurationSnapshotRepository = robotConfigurationSnapshotRepository;
|
||||
_logger = logger;
|
||||
_snapshotSettings = snapshotSettings;
|
||||
}
|
||||
|
||||
|
||||
public async Task TakeSnapshotAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
_logger.LogDebug("Сбор метрик для снапшота RobotConfigurations...");
|
||||
|
||||
var stats = await _robotConfigurationRepository.Get()
|
||||
.GroupBy(t => new { t.RobotCode, t.RobotStatusCode, t.TaskStatusCode })
|
||||
.Select(g => new
|
||||
{
|
||||
g.Key.RobotCode,
|
||||
g.Key.RobotStatusCode,
|
||||
g.Key.TaskStatusCode,
|
||||
Count = g.Count()
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (!stats.Any())
|
||||
{
|
||||
_logger.LogDebug("Нет активных заданий роботов для создания снапшота.");
|
||||
return;
|
||||
}
|
||||
|
||||
var snapshots = stats.Select(s => new RobotConfigurationSnapshot
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
DateCreated = now,
|
||||
RobotCode = s.RobotCode,
|
||||
RobotStatusCode = s.RobotStatusCode,
|
||||
TaskStatusCode = s.TaskStatusCode,
|
||||
Count = s.Count
|
||||
}).ToList();
|
||||
|
||||
if (!await _robotConfigurationSnapshotRepository.AddRangeAsync(snapshots) || !await _robotConfigurationSnapshotRepository.CommitAsync())
|
||||
throw new DbErrorException("Ошибка при сохранении в БД");
|
||||
|
||||
_logger.LogInformation("Успешно сохранен снапшот RobotConfigurations. Записано строк: {Count}. Периодичность: {Interval}", snapshots.Count, Interval);
|
||||
}
|
||||
|
||||
|
||||
public async Task CleanUpOldSnapshotsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// Вычисляем граничную дату (всё, что было ДО нее — удаляем)
|
||||
var thresholdDate = DateTimeOffset.UtcNow.Subtract(RetentionPeriod);
|
||||
|
||||
_logger.LogInformation("[{ServiceName}] Запуск очистки старых снапшотов. Удаление данных старше {ThresholdDate}", GetType().Name, thresholdDate);
|
||||
|
||||
// Фильтруем старые записи и вызываем ExecuteDeleteAsync для удаления прямо в базе данных
|
||||
var deletedCount = await _robotConfigurationSnapshotRepository.Get()
|
||||
.Where(s => s.DateCreated < thresholdDate)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("[{ServiceName}] Очистка завершена. Удалено устаревших строк снапшотов: {Count}", GetType().Name, deletedCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
PARR.Core/Services/Snapshots/Interfaces/ISnapshotProvider.cs
Normal file
31
PARR.Core/Services/Snapshots/Interfaces/ISnapshotProvider.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace PARR.Core.Services.Snapshots.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Общий снапшот провайдер.
|
||||
/// Используется для реализации паттерна "Стратегия"
|
||||
/// </summary>
|
||||
public interface ISnapshotProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Периодичность создания снапшота (не меньше 1 минуты).
|
||||
/// </summary>
|
||||
TimeSpan Interval { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Срок хранения снапшотов (например, 30 дней). Всё, что старше — удаляется.
|
||||
/// </summary>
|
||||
TimeSpan RetentionPeriod { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Метод сбора метрик и записи их в базу данных.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task TakeSnapshotAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Метод очистки устаревших снапшотов.
|
||||
/// </summary>
|
||||
Task CleanUpOldSnapshotsAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
35
PARR.Core/Services/Snapshots/Interfaces/ISnapshotSettings.cs
Normal file
35
PARR.Core/Services/Snapshots/Interfaces/ISnapshotSettings.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace PARR.Core.Services.Snapshots.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Настройки снапшотов
|
||||
/// </summary>
|
||||
public interface ISnapshotSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Интервал создания снапшотов для таблицы RobotConfiguration
|
||||
/// </summary>
|
||||
TimeSpan RobotConfigurationSnapshotInterval { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Длительность хранения снапшотов для RobotConfiguration в таблице ConfigurationShapshots
|
||||
/// </summary>
|
||||
TimeSpan RobotConfigurationSnapshotRetentionPeriod { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Длительность хранения снапшотов работы роботов в таблице Shapshots
|
||||
/// </summary>
|
||||
TimeSpan RobotSnapshotRetentionPeriod { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Настройки по умолчанию для ISnapshotSettings
|
||||
/// </summary>
|
||||
internal record DefaultSnapshotSettings : ISnapshotSettings
|
||||
{
|
||||
public TimeSpan RobotConfigurationSnapshotInterval => TimeSpan.FromMinutes(2);
|
||||
|
||||
public TimeSpan RobotConfigurationSnapshotRetentionPeriod => TimeSpan.FromDays(60);
|
||||
|
||||
public TimeSpan RobotSnapshotRetentionPeriod => TimeSpan.FromDays(60);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using PARR.Core.Services.UnitFilterService.Models;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
|
||||
namespace PARR.Core.Services.UnitFilterService
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Services.UnitFilterService.Matchers.Interfaces;
|
||||
using PARR.Core.Services.UnitFilterService.Models;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.Core.Services.UnitFilterService.Matchers;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
using PARR.Core.Services.UnitFilterService.Matchers.Interfaces;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
|
||||
namespace PARR.Core.Services.UnitFilterService.Matchers;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
using PARR.Core.Services.UnitFilterService.Matchers.Interfaces;
|
||||
using PARR.Core.Services.UnitFilterService.Models;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
|
||||
namespace PARR.Core.Services.UnitFilterService.Matchers;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using PARR.Core.Services.UnitFilterService.Models;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
|
||||
namespace PARR.Core.Services.UnitFilterService.Matchers.Interfaces
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
|
||||
namespace PARR.Core.Services.UnitFilterService.Matchers.Interfaces
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using PARR.Core.Services.UnitFilterService.Models;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
|
||||
namespace PARR.Core.Services.UnitFilterService.Matchers.Interfaces
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace PARR.Core.Services.UnitFilterService.Models
|
||||
{
|
||||
internal class UnitFilterMatchResult
|
||||
public class UnitFilterMatchResult
|
||||
{
|
||||
public Guid UnitId { get; set; }
|
||||
public HashSet<Guid> ValidParentIds { get; set; } = new();
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Common.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
using PARR.Core.Services.UnitFilterService.Matchers.Interfaces;
|
||||
using PARR.Core.Services.UnitFilterService.Models;
|
||||
using PARR.Core.Services.UnitService.Interfaces;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
using PARR.Domain.Entities.Unit;
|
||||
using System.Diagnostics;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.TaskRepositories;
|
||||
using PARR.Core.Services.NextRunServices;
|
||||
using PARR.Core.Services.TaskServices.Helpers;
|
||||
@@ -10,7 +10,6 @@ using PARR.Core.Services.Workload.Interfaces;
|
||||
using PARR.Core.Services.Workload.Models;
|
||||
using PARR.Domain.Cache.Models;
|
||||
using PARR.Domain.DTOs.Workload;
|
||||
using PARR.Domain.Entities;
|
||||
using PARR.Domain.Enums;
|
||||
using PARR.Domain.Enums.Workload;
|
||||
|
||||
|
||||
@@ -3,15 +3,15 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using PARR.Core.Common.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
using PARR.Core.Services.UnitFilterService;
|
||||
using PARR.Core.Services.UnitFilterService.Matchers.Interfaces;
|
||||
using PARR.Core.Services.UnitFilterService.Models;
|
||||
using PARR.Core.Services.UnitService.Interfaces;
|
||||
using PARR.DAL.Context;
|
||||
using PARR.Domain.Cache.Models;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
using PARR.Domain.Entities.Unit;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ using PARR.Core.Extensions;
|
||||
using PARR.Domain.Common.Roles;
|
||||
using PARR.Domain.Common.Template;
|
||||
using PARR.Domain.Entities;
|
||||
using PARR.Domain.Entities.Job;
|
||||
using PARR.Domain.Entities.JobEntities;
|
||||
using PARR.Domain.Entities.JobGroupEntities;
|
||||
using PARR.Domain.Entities.RobotEntities;
|
||||
using PARR.Domain.Entities.Schedule;
|
||||
using PARR.Domain.Entities.TaskEntities;
|
||||
@@ -106,16 +107,28 @@ namespace PARR.DAL.Context
|
||||
|
||||
public DbSet<JobUnitFilter> JobUnitFilters { get; set; }
|
||||
|
||||
public DbSet<JobGroup> JobGroups { get; set; }
|
||||
|
||||
public DbSet<JobGroupType> JobGroupTypes { get; set; }
|
||||
|
||||
public DbSet<JobRelationshipFilter> JobRelationshipFilters { get; set; }
|
||||
|
||||
public DbSet<JobAutoControl> JobAutoControls { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region JobGroup
|
||||
|
||||
public DbSet<JobGroup> JobGroups { get; set; }
|
||||
|
||||
public DbSet<JobGroupType> JobGroupTypes { get; set; }
|
||||
|
||||
public DbSet<JobGroupDistributionConfig> JobGroupDistributionConfigs { get; set; }
|
||||
|
||||
public DbSet<JobGroupAutoControl> JobGroupAutoControls { get; set; }
|
||||
|
||||
public DbSet<JobGroupUnitFilter> JobGroupUnitFilters { get; set; }
|
||||
|
||||
public DbSet<JobGroupFieldFilter> JobGroupFieldFilters { get; set; }
|
||||
|
||||
public DbSet<JobGroupRelationshipFilter> JobGroupRelationshipFilters { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tasks
|
||||
@@ -130,6 +143,7 @@ namespace PARR.DAL.Context
|
||||
#region Robot
|
||||
|
||||
public DbSet<RobotSnapshot> RobotSnapshots { get; set; }
|
||||
public DbSet<RobotConfigurationSnapshot> RobotConfigurationSnapshots { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -166,9 +180,9 @@ namespace PARR.DAL.Context
|
||||
modelBuilder.Entity<JobGroupType>(f =>
|
||||
{
|
||||
f.HasData(
|
||||
new() { Id = new Guid("4FA62E79-86BB-47C2-BE1A-72A716A170FA"), DateCreated = dateCreated, DateModified = null, Name = JobGroupTypesEnum.Simple.ToString(), Code = JobGroupTypesEnum.Simple, Description = "Обычный" },
|
||||
new() { Id = new Guid("6EC58B1A-5C40-47B9-B036-4FA490E8D503"), DateCreated = dateCreated, DateModified = null, Name = JobGroupTypesEnum.Umbrella.ToString(), Code = JobGroupTypesEnum.Umbrella, Description = "Зонтик" },
|
||||
new() { Id = new Guid("318625E5-F833-436A-99DF-2A382A1E71C7"), DateCreated = dateCreated, DateModified = null, Name = JobGroupTypesEnum.Group.ToString(), Code = JobGroupTypesEnum.Group, Description = "Сгруппированный" }
|
||||
new() { Id = new Guid("4FA62E79-86BB-47C2-BE1A-72A716A170FA"), DateCreated = dateCreated, DateModified = null, Name = JobGroupTypesEnum.Simple.ToString(), Code = JobGroupTypesEnum.Simple, Description = "Обычный", IsAllowJobUnitFilter = true, IsJobGroupAutoControl = false, IsRelationshipsAllowed = false },
|
||||
new() { Id = new Guid("6EC58B1A-5C40-47B9-B036-4FA490E8D503"), DateCreated = dateCreated, DateModified = null, Name = JobGroupTypesEnum.Umbrella.ToString(), Code = JobGroupTypesEnum.Umbrella, Description = "Зонтик", IsAllowJobUnitFilter = false, IsJobGroupAutoControl = true, IsRelationshipsAllowed = true },
|
||||
new() { Id = new Guid("318625E5-F833-436A-99DF-2A382A1E71C7"), DateCreated = dateCreated, DateModified = null, Name = JobGroupTypesEnum.Group.ToString(), Code = JobGroupTypesEnum.Group, Description = "Сгруппированный", IsAllowJobUnitFilter = false, IsJobGroupAutoControl = true, IsRelationshipsAllowed = true }
|
||||
);
|
||||
});
|
||||
#endregion
|
||||
@@ -205,7 +219,7 @@ namespace PARR.DAL.Context
|
||||
{
|
||||
// При добавлении записей, добавлять тоже в PARR.DAL.Contracts.SettingsFromDb
|
||||
f.HasData(
|
||||
new { Name = nameof(SettingsFromDb.Initiator), Description = "Инициатор регламентной работы, указывается при создании шаблона в ЕСПП.", Value = "ОВЧАРЕНКО АЛЕКСЕЙ ВИТАЛЬЕВИЧ (OVCHARENKOAV@GVC.OAO.RZD)" },
|
||||
new { Name = nameof(SettingsFromDb.Initiator), Description = "Инициатор регламентной работы, указывается при создании шаблона в ЕСПП.", Value = "АКСЕНОВ АЛЕКСАНДР ЕВГЕНЬЕВИЧ (AKSENOVAE@GVC.OAO.RZD)" },
|
||||
new { Name = nameof(SettingsFromDb.ClosingCode), Description = "Код закрытия регламентной работы, указывается при создании шаблона в ЕСПП.", Value = "выполнен" },
|
||||
new { Name = nameof(SettingsFromDb.Category), Description = "Категория создаваемого объекта в ЕСПП", Value = "регламентная работа" },
|
||||
new { Name = nameof(SettingsFromDb.TemplatePrefixName), Description = "Префикс имени шаблона в ЕСПП", Value = "%PREFIX%-ЭИТИ-ПТК-ПАРР" },
|
||||
@@ -229,7 +243,14 @@ namespace PARR.DAL.Context
|
||||
},
|
||||
new { Name = nameof(SettingsFromDb.EsppUnitTag), Description = "Префикс тега в поле ЭК \"Дополнительная информация\"", Value = "ПАРР_" },
|
||||
new { Name = nameof(SettingsFromDb.JobIdForUnusedTemplates), Description = "Job в который перемещаем ниспользованные шаблоны", Value = "8f85a91c-a223-4686-bb69-1f0ee73624f2" },
|
||||
new { Name = nameof(SettingsFromDb.DefaultResponseAreaToTimeOffset), Description = "Зона ответственности для получения тайм зоны по умолчанию для расписаний", Value = "17-МСК" }
|
||||
new { Name = nameof(SettingsFromDb.DefaultResponseAreaToTimeOffset), Description = "Зона ответственности для получения тайм зоны по умолчанию для расписаний", Value = "17-МСК" },
|
||||
new
|
||||
{
|
||||
Name = nameof(SettingsFromDb.MinRelationshipsThresholdPct),
|
||||
Description = "Минимально допустимый процент связей для продолжения синхронизации. Защищает от удаления данных, " +
|
||||
"если передающая система АИХ ИТ передала аномально малый объем связей. 0 — Защита ОТКЛЮЧЕНА, 100 — Строгий режим.",
|
||||
Value = "97"
|
||||
}
|
||||
);
|
||||
});
|
||||
#endregion
|
||||
|
||||
@@ -3,7 +3,8 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.JobGroupRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.JobRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.RobotRepositories;
|
||||
using PARR.Core.Repositories.Interfaces.Schedule;
|
||||
using PARR.Core.Repositories.Interfaces.TaskRepositories;
|
||||
@@ -12,6 +13,8 @@ using PARR.DAL.Configurations.DbSettings;
|
||||
using PARR.DAL.Context;
|
||||
using PARR.DAL.Repositories;
|
||||
using PARR.DAL.Repositories.Job;
|
||||
using PARR.DAL.Repositories.JobGroupRepositories;
|
||||
using PARR.DAL.Repositories.JobRepositories;
|
||||
using PARR.DAL.Repositories.RobotRepositories;
|
||||
using PARR.DAL.Repositories.Schedule;
|
||||
using PARR.DAL.Repositories.TaskRepositories;
|
||||
@@ -81,8 +84,12 @@ namespace PARR.DAL
|
||||
services.AddTransient<IParrComponentRepository, ParrComponentRepository>();
|
||||
services.AddTransient<ITemplateStatusTypeRepository, TemplateStatusTypeRepository>();
|
||||
|
||||
services.AddScoped<IRobotSnapshotRepository, RobotSnapshotRepository>();
|
||||
#region Robot
|
||||
|
||||
services.AddScoped<IRobotSnapshotRepository, RobotSnapshotRepository>();
|
||||
services.AddScoped<IRobotConfigurationSnapshotRepository, RobotConfigurationSnapshotRepository>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Schedule
|
||||
|
||||
@@ -115,6 +122,15 @@ namespace PARR.DAL
|
||||
|
||||
#endregion
|
||||
|
||||
#region JobGroup
|
||||
|
||||
services.AddScoped<IJobGroupFieldFilterRepository, JobGroupFieldFilterRepository>();
|
||||
services.AddScoped<IJobGroupRelationshipFilterRepository, JobGroupRelationshipFilterRepository>();
|
||||
services.AddScoped<IJobGroupUnitFilterRepository, JobGroupUnitFilterRepository>();
|
||||
services.AddScoped<IJobGroupAutoControlRepository, JobGroupAutoControlRepository>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Task
|
||||
|
||||
services.AddScoped<ITaskErrorRepository, TaskErrorRepository>();
|
||||
|
||||
4014
PARR.DAL/Migrations/20260615065234_tblsJobGroups.Designer.cs
generated
Normal file
4014
PARR.DAL/Migrations/20260615065234_tblsJobGroups.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
284
PARR.DAL/Migrations/20260615065234_tblsJobGroups.cs
Normal file
284
PARR.DAL/Migrations/20260615065234_tblsJobGroups.cs
Normal file
@@ -0,0 +1,284 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PARR.DAL.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class tblsJobGroups : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: "jobGroup");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "GroupTypes",
|
||||
schema: "job",
|
||||
newName: "GroupTypes",
|
||||
newSchema: "jobGroup");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "Groups",
|
||||
schema: "job",
|
||||
newName: "Groups",
|
||||
newSchema: "jobGroup");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "GroupDistributionConfigs",
|
||||
schema: "job",
|
||||
newName: "GroupDistributionConfigs",
|
||||
newSchema: "jobGroup");
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsAllowJobUnitFilter",
|
||||
schema: "jobGroup",
|
||||
table: "GroupTypes",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false,
|
||||
comment: "Разрешить Job фильтры. Влияет только на интерфейс. Мэтчер собирает все фильтры из группы и работ всегда.");
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsJobGroupAutoControl",
|
||||
schema: "jobGroup",
|
||||
table: "GroupTypes",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false,
|
||||
comment: "Автоконтролем управляет JobGroup? true - да JobGroup, false - Job. Влияет на интерфейс и на логику работы автоконтроля.");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AutoControls",
|
||||
schema: "jobGroup",
|
||||
columns: table => new
|
||||
{
|
||||
JobGroupId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
IsEnable = table.Column<bool>(type: "boolean", nullable: false),
|
||||
InitUsedTemplateState = table.Column<bool>(type: "boolean", nullable: false),
|
||||
InitUsedScheduleState = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AutoControls", x => x.JobGroupId);
|
||||
table.ForeignKey(
|
||||
name: "FK_AutoControls_Groups_JobGroupId",
|
||||
column: x => x.JobGroupId,
|
||||
principalSchema: "jobGroup",
|
||||
principalTable: "Groups",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
},
|
||||
comment: "Таблица управления автоконтролем для группы работ");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UnitFilters",
|
||||
schema: "jobGroup",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
DateCreated = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
UnitFilter = table.Column<string>(type: "text", nullable: false),
|
||||
JobGroupId = table.Column<Guid>(type: "uuid", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UnitFilters", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_UnitFilters_Groups_JobGroupId",
|
||||
column: x => x.JobGroupId,
|
||||
principalSchema: "jobGroup",
|
||||
principalTable: "Groups",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
},
|
||||
comment: "Таблица описания критериев выборки ЭК, описание полей в АСУ ЕСПП");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "FieldFilters",
|
||||
schema: "jobGroup",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
DateCreated = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
UnitFilterId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
FieldId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
ValueMask = table.Column<string>(type: "text", nullable: false),
|
||||
IsInverse = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_FieldFilters", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_FieldFilters_Fields_FieldId",
|
||||
column: x => x.FieldId,
|
||||
principalSchema: "unit",
|
||||
principalTable: "Fields",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_FieldFilters_UnitFilters_UnitFilterId",
|
||||
column: x => x.UnitFilterId,
|
||||
principalSchema: "jobGroup",
|
||||
principalTable: "UnitFilters",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
},
|
||||
comment: "Таблица описания критериев выборки аттрибутов ЭК");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RelationshipFilters",
|
||||
schema: "jobGroup",
|
||||
columns: table => new
|
||||
{
|
||||
UnitFilterId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
FieldId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
IsParent = table.Column<bool>(type: "boolean", nullable: false),
|
||||
ValueMask = table.Column<string>(type: "text", nullable: false),
|
||||
IsFullMatch = table.Column<bool>(type: "boolean", nullable: false),
|
||||
IsInverse = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RelationshipFilters", x => new { x.UnitFilterId, x.FieldId });
|
||||
table.ForeignKey(
|
||||
name: "FK_RelationshipFilters_Fields_FieldId",
|
||||
column: x => x.FieldId,
|
||||
principalSchema: "unit",
|
||||
principalTable: "Fields",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_RelationshipFilters_UnitFilters_UnitFilterId",
|
||||
column: x => x.UnitFilterId,
|
||||
principalSchema: "jobGroup",
|
||||
principalTable: "UnitFilters",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
},
|
||||
comment: "Таблица фильтров связей ЭК");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
schema: "jobGroup",
|
||||
table: "GroupTypes",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("318625e5-f833-436a-99df-2a382a1e71c7"),
|
||||
columns: new[] { "IsAllowJobUnitFilter", "IsJobGroupAutoControl" },
|
||||
values: new object[] { false, true });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
schema: "jobGroup",
|
||||
table: "GroupTypes",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("4fa62e79-86bb-47c2-be1a-72a716a170fa"),
|
||||
columns: new[] { "IsAllowJobUnitFilter", "IsJobGroupAutoControl" },
|
||||
values: new object[] { true, false });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
schema: "jobGroup",
|
||||
table: "GroupTypes",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("6ec58b1a-5c40-47b9-b036-4fa490e8d503"),
|
||||
columns: new[] { "IsAllowJobUnitFilter", "IsJobGroupAutoControl" },
|
||||
values: new object[] { false, true });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Settings",
|
||||
keyColumn: "Name",
|
||||
keyValue: "Initiator",
|
||||
column: "Value",
|
||||
value: "АКСЕНОВ АЛЕКСАНДР ЕВГЕНЬЕВИЧ (AKSENOVAE@GVC.OAO.RZD)");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_FieldFilters_FieldId1",
|
||||
schema: "jobGroup",
|
||||
table: "FieldFilters",
|
||||
column: "FieldId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_FieldFilters_UnitFilterId1",
|
||||
schema: "jobGroup",
|
||||
table: "FieldFilters",
|
||||
column: "UnitFilterId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RelationshipFilters_FieldId1",
|
||||
schema: "jobGroup",
|
||||
table: "RelationshipFilters",
|
||||
column: "FieldId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UnitFilters_JobGroupId",
|
||||
schema: "jobGroup",
|
||||
table: "UnitFilters",
|
||||
column: "JobGroupId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_FieldFilters_Fields_FieldId",
|
||||
schema: "job",
|
||||
table: "FieldFilters");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_RelationshipFilters_Fields_FieldId",
|
||||
schema: "job",
|
||||
table: "RelationshipFilters");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AutoControls",
|
||||
schema: "jobGroup");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "FieldFilters",
|
||||
schema: "jobGroup");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RelationshipFilters",
|
||||
schema: "jobGroup");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UnitFilters",
|
||||
schema: "jobGroup");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsAllowJobUnitFilter",
|
||||
schema: "jobGroup",
|
||||
table: "GroupTypes");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsJobGroupAutoControl",
|
||||
schema: "jobGroup",
|
||||
table: "GroupTypes");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "GroupTypes",
|
||||
schema: "jobGroup",
|
||||
newName: "GroupTypes",
|
||||
newSchema: "job");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "Groups",
|
||||
schema: "jobGroup",
|
||||
newName: "Groups",
|
||||
newSchema: "job");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "GroupDistributionConfigs",
|
||||
schema: "jobGroup",
|
||||
newName: "GroupDistributionConfigs",
|
||||
newSchema: "job");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Settings",
|
||||
keyColumn: "Name",
|
||||
keyValue: "Initiator",
|
||||
column: "Value",
|
||||
value: "ОВЧАРЕНКО АЛЕКСЕЙ ВИТАЛЬЕВИЧ (OVCHARENKOAV@GVC.OAO.RZD)");
|
||||
}
|
||||
}
|
||||
}
|
||||
4021
PARR.DAL/Migrations/20260615235401_tblGroupTypesAddRelationshipsSettings.Designer.cs
generated
Normal file
4021
PARR.DAL/Migrations/20260615235401_tblGroupTypesAddRelationshipsSettings.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PARR.DAL.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class tblGroupTypesAddRelationshipsSettings : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsRelationshipsAllowed",
|
||||
schema: "jobGroup",
|
||||
table: "GroupTypes",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false,
|
||||
comment: "Разрешена настройка кол-ва связей");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
schema: "jobGroup",
|
||||
table: "GroupTypes",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("318625e5-f833-436a-99df-2a382a1e71c7"),
|
||||
column: "IsRelationshipsAllowed",
|
||||
value: true);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
schema: "jobGroup",
|
||||
table: "GroupTypes",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("4fa62e79-86bb-47c2-be1a-72a716a170fa"),
|
||||
column: "IsRelationshipsAllowed",
|
||||
value: false);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
schema: "jobGroup",
|
||||
table: "GroupTypes",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("6ec58b1a-5c40-47b9-b036-4fa490e8d503"),
|
||||
column: "IsRelationshipsAllowed",
|
||||
value: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsRelationshipsAllowed",
|
||||
schema: "jobGroup",
|
||||
table: "GroupTypes");
|
||||
}
|
||||
}
|
||||
}
|
||||
4090
PARR.DAL/Migrations/20260713002251_tblRobotConfigurationSnapshots.Designer.cs
generated
Normal file
4090
PARR.DAL/Migrations/20260713002251_tblRobotConfigurationSnapshots.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PARR.DAL.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class tblRobotConfigurationSnapshots : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ConfigurationSnapshots",
|
||||
schema: "robot",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
DateCreated = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
RobotCode = table.Column<int>(type: "integer", nullable: false),
|
||||
RobotStatusCode = table.Column<int>(type: "integer", nullable: false),
|
||||
TaskStatusCode = table.Column<int>(type: "integer", nullable: false),
|
||||
Count = table.Column<int>(type: "integer", nullable: false, comment: "Количество заданий в этой комбинации статусов")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ConfigurationSnapshots", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ConfigurationSnapshots_RobotStatuses_RobotStatusCode",
|
||||
column: x => x.RobotStatusCode,
|
||||
principalTable: "RobotStatuses",
|
||||
principalColumn: "Code",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ConfigurationSnapshots_Robots_RobotCode",
|
||||
column: x => x.RobotCode,
|
||||
principalTable: "Robots",
|
||||
principalColumn: "Code",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ConfigurationSnapshots_TaskStatuses_TaskStatusCode",
|
||||
column: x => x.TaskStatusCode,
|
||||
principalTable: "TaskStatuses",
|
||||
principalColumn: "Code",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
},
|
||||
comment: "Снимки заданий роботам");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ConfigurationSnapshots_RobotCode_DateCreated",
|
||||
schema: "robot",
|
||||
table: "ConfigurationSnapshots",
|
||||
columns: new[] { "RobotCode", "DateCreated" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ConfigurationSnapshots_RobotStatusCode",
|
||||
schema: "robot",
|
||||
table: "ConfigurationSnapshots",
|
||||
column: "RobotStatusCode");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ConfigurationSnapshots_TaskStatusCode",
|
||||
schema: "robot",
|
||||
table: "ConfigurationSnapshots",
|
||||
column: "TaskStatusCode");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ConfigurationSnapshots",
|
||||
schema: "robot");
|
||||
}
|
||||
}
|
||||
}
|
||||
4096
PARR.DAL/Migrations/20260714002637_tblSettingsFromDbMinRelationshipsThresholdPct.Designer.cs
generated
Normal file
4096
PARR.DAL/Migrations/20260714002637_tblSettingsFromDbMinRelationshipsThresholdPct.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user