Sla #11
@@ -25,7 +25,7 @@ jobs:
|
||||
run: dotnet restore backend/Tnsu.ApprovalRoute.sln
|
||||
|
||||
- name: Publish (local)
|
||||
run: dotnet publish backend/src/Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj -c Release -o "$LOCAL_PUBLISH"
|
||||
run: dotnet publish backend/Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj -c Release -o "$LOCAL_PUBLISH"
|
||||
|
||||
- name: Install cifs-utils
|
||||
run: |
|
||||
|
||||
@@ -22,7 +22,6 @@ backend/
|
||||
Directory.Build.props # общие настройки таргета (net10.0, nullable, etc.)
|
||||
Directory.Packages.props # централизованные версии пакетов
|
||||
Tnsu.ApprovalRoute.sln
|
||||
src/
|
||||
Tnsu.ApprovalRoute.Domain/ # сущности и enum'ы домена
|
||||
Tnsu.ApprovalRoute.Application/ # сервисы, DTO, валидаторы
|
||||
Tnsu.ApprovalRoute.Infrastructure/ # EF Core DbContext, миграции, клиент tnsu-portal
|
||||
@@ -61,7 +60,7 @@ npm run build # heft test + package-solution --production
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
dotnet run --project src/Tnsu.ApprovalRoute.Api
|
||||
dotnet run --project Tnsu.ApprovalRoute.Api
|
||||
```
|
||||
|
||||
Требуется PostgreSQL (строка подключения — `ConnectionStrings:Postgres` в `appsettings.json`).
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /src
|
||||
COPY Directory.Build.props Directory.Packages.props ./
|
||||
COPY Tnsu.ApprovalRoute.Domain/Tnsu.ApprovalRoute.Domain.csproj Tnsu.ApprovalRoute.Domain/
|
||||
COPY Tnsu.ApprovalRoute.Application/Tnsu.ApprovalRoute.Application.csproj Tnsu.ApprovalRoute.Application/
|
||||
COPY Tnsu.ApprovalRoute.Infrastructure/Tnsu.ApprovalRoute.Infrastructure.csproj Tnsu.ApprovalRoute.Infrastructure/
|
||||
COPY Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj Tnsu.ApprovalRoute.Api/
|
||||
RUN dotnet restore Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj
|
||||
COPY Tnsu.ApprovalRoute.Domain/ Tnsu.ApprovalRoute.Domain/
|
||||
COPY Tnsu.ApprovalRoute.Application/ Tnsu.ApprovalRoute.Application/
|
||||
COPY Tnsu.ApprovalRoute.Infrastructure/ Tnsu.ApprovalRoute.Infrastructure/
|
||||
COPY Tnsu.ApprovalRoute.Api/ Tnsu.ApprovalRoute.Api/
|
||||
RUN dotnet publish Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj -c Release -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
|
||||
WORKDIR /app
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=build /app/publish .
|
||||
ENV ASPNETCORE_URLS=http://+:8080
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["dotnet", "Tnsu.ApprovalRoute.Api.dll"]
|
||||
@@ -3,15 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Domain", "Tnsu.ApprovalRoute.Domain\Tnsu.ApprovalRoute.Domain.csproj", "{67C6FECB-26BF-4A7E-AC40-574F4D88EEAC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Domain", "src\Tnsu.ApprovalRoute.Domain\Tnsu.ApprovalRoute.Domain.csproj", "{67C6FECB-26BF-4A7E-AC40-574F4D88EEAC}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Application", "Tnsu.ApprovalRoute.Application\Tnsu.ApprovalRoute.Application.csproj", "{2EEA333B-16C7-460E-BD07-AB1F32D6A9A9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Application", "src\Tnsu.ApprovalRoute.Application\Tnsu.ApprovalRoute.Application.csproj", "{2EEA333B-16C7-460E-BD07-AB1F32D6A9A9}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Infrastructure", "Tnsu.ApprovalRoute.Infrastructure\Tnsu.ApprovalRoute.Infrastructure.csproj", "{46EF4247-8B98-4CC9-A981-186C8D24163C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Infrastructure", "src\Tnsu.ApprovalRoute.Infrastructure\Tnsu.ApprovalRoute.Infrastructure.csproj", "{46EF4247-8B98-4CC9-A981-186C8D24163C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Api", "src\Tnsu.ApprovalRoute.Api\Tnsu.ApprovalRoute.Api.csproj", "{4697B342-BE47-435B-B854-75F9277AD769}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnsu.ApprovalRoute.Api", "Tnsu.ApprovalRoute.Api\Tnsu.ApprovalRoute.Api.csproj", "{4697B342-BE47-435B-B854-75F9277AD769}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
|
||||
EndProject
|
||||
@@ -92,10 +90,6 @@ Global
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{67C6FECB-26BF-4A7E-AC40-574F4D88EEAC} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||
{2EEA333B-16C7-460E-BD07-AB1F32D6A9A9} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||
{46EF4247-8B98-4CC9-A981-186C8D24163C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||
{4697B342-BE47-435B-B854-75F9277AD769} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||
{F5E2BA0E-7978-414C-98D4-D3C91C162FDE} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"ExpandedNodes": [
|
||||
"",
|
||||
"\\Tnsu.ApprovalRoute.Api",
|
||||
"\\Tnsu.ApprovalRoute.Application"
|
||||
],
|
||||
"SelectedNode": "\\Tnsu.ApprovalRoute.Application",
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\Tnsu.ApprovalRoute.Api\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Tnsu.ApprovalRoute.Api\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "appsettings.json",
|
||||
"DocumentMoniker": "C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\Tnsu.ApprovalRoute.Api\\appsettings.json",
|
||||
"RelativeDocumentMoniker": "Tnsu.ApprovalRoute.Api\\appsettings.json",
|
||||
"ToolTip": "C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\Tnsu.ApprovalRoute.Api\\appsettings.json",
|
||||
"RelativeToolTip": "Tnsu.ApprovalRoute.Api\\appsettings.json",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAABUAAAAXAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
|
||||
"WhenOpened": "2026-07-28T05:52:49.686Z",
|
||||
"EditorCaption": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\Tnsu.ApprovalRoute.Api\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Tnsu.ApprovalRoute.Api\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "appsettings.json",
|
||||
"DocumentMoniker": "C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\Tnsu.ApprovalRoute.Api\\appsettings.json",
|
||||
"RelativeDocumentMoniker": "Tnsu.ApprovalRoute.Api\\appsettings.json",
|
||||
"ToolTip": "C:\\Users\\trishina.mariya\\Documents\\Projects\\tnsu-approval-route\\backend\\src\\Tnsu.ApprovalRoute.Api\\appsettings.json",
|
||||
"RelativeToolTip": "Tnsu.ApprovalRoute.Api\\appsettings.json",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAABUAAAAXAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
|
||||
"WhenOpened": "2026-07-28T05:52:49.686Z",
|
||||
"EditorCaption": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /src
|
||||
COPY Directory.Build.props Directory.Packages.props ./
|
||||
COPY src/Tnsu.ApprovalRoute.Domain/Tnsu.ApprovalRoute.Domain.csproj src/Tnsu.ApprovalRoute.Domain/
|
||||
COPY src/Tnsu.ApprovalRoute.Application/Tnsu.ApprovalRoute.Application.csproj src/Tnsu.ApprovalRoute.Application/
|
||||
COPY src/Tnsu.ApprovalRoute.Infrastructure/Tnsu.ApprovalRoute.Infrastructure.csproj src/Tnsu.ApprovalRoute.Infrastructure/
|
||||
COPY src/Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj src/Tnsu.ApprovalRoute.Api/
|
||||
RUN dotnet restore src/Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj
|
||||
COPY src/ src/
|
||||
RUN dotnet publish src/Tnsu.ApprovalRoute.Api/Tnsu.ApprovalRoute.Api.csproj -c Release -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
|
||||
WORKDIR /app
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=build /app/publish .
|
||||
ENV ASPNETCORE_URLS=http://+:8080
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["dotnet", "Tnsu.ApprovalRoute.Api.dll"]
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Tnsu.ApprovalRoute.Infrastructure\Tnsu.ApprovalRoute.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\..\src\Tnsu.ApprovalRoute.Application\Tnsu.ApprovalRoute.Application.csproj" />
|
||||
<ProjectReference Include="..\..\Tnsu.ApprovalRoute.Infrastructure\Tnsu.ApprovalRoute.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\..\Tnsu.ApprovalRoute.Application\Tnsu.ApprovalRoute.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ services:
|
||||
api:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: src/Tnsu.ApprovalRoute.Api/Dockerfile
|
||||
dockerfile: Tnsu.ApprovalRoute.Api/Dockerfile
|
||||
container_name: tnsu-approval-route-api
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
|
||||
@@ -62,6 +62,12 @@ export function AsyncSearchableSelect<T>({
|
||||
// Растущий счётчик запросов: если ответ пришёл не от последнего запроса
|
||||
// (например, пользователь допечатал ещё символ) — просто игнорируем его.
|
||||
const requestIdRef = React.useRef(0);
|
||||
// getValue нужен внутри runSearch (дедуп при доклейке страницы), но в deps его
|
||||
// класть нельзя: пропс приходит инлайновой стрелкой и меняет ссылку на каждом
|
||||
// рендере — runSearch пересоздавался бы постоянно и перезапускал эффект
|
||||
// бесконечной прокрутки. Держим последнюю версию в ref.
|
||||
const getValueRef = React.useRef(getValue);
|
||||
getValueRef.current = getValue;
|
||||
|
||||
const runSearch = React.useCallback((q: string, targetPage: number, append: boolean) => {
|
||||
const requestId = ++requestIdRef.current;
|
||||
@@ -71,7 +77,16 @@ export function AsyncSearchableSelect<T>({
|
||||
search(q, targetPage)
|
||||
.then(result => {
|
||||
if (requestIdRef.current !== requestId) return; // устарел
|
||||
setItems(prev => (append ? [...prev, ...result.items] : result.items));
|
||||
// Дедуп при доклейке: соседние страницы могут пересекаться, если индекс
|
||||
// переупорядочился между запросами (StartRow смещает окно по РАНГУ, а не
|
||||
// по устойчивому курсору). Дубликат здесь = два пункта с одинаковым
|
||||
// React-ключом, то есть заявка в списке дважды. Внутри одной страницы
|
||||
// дубли отсекает уже источник (см. searchRequests в requestManageService).
|
||||
setItems(prev => {
|
||||
if (!append) return result.items;
|
||||
const shown = new Set(prev.map(i => getValueRef.current(i)));
|
||||
return [...prev, ...result.items.filter(i => !shown.has(getValueRef.current(i)))];
|
||||
});
|
||||
setHasMore(result.hasMore);
|
||||
setPage(targetPage);
|
||||
})
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from '../models';
|
||||
|
||||
// Тонкий 1:1 фасад над контроллерами бэкенда:
|
||||
// backend/src/Tnsu.ApprovalRoute.Api/Controllers/{RequestTypes,TemplateRoutes,TemplateRouteSteps}Controller.cs
|
||||
// backend/Tnsu.ApprovalRoute.Api/Controllers/{RequestTypes,TemplateRoutes,TemplateRouteSteps}Controller.cs
|
||||
|
||||
export class ApprovalRouteApi {
|
||||
constructor(private readonly http: IHttpClient) {}
|
||||
|
||||
@@ -152,7 +152,22 @@ export class RequestManageService {
|
||||
});
|
||||
|
||||
const rawRows = (result.PrimarySearchResults ?? []) as Array<{ ListItemID?: string }>;
|
||||
const ids = rawRows.map(r => Number(r.ListItemID)).filter(id => !isNaN(id) && id > 0);
|
||||
// Дедуп по Id обязателен: TrimDuplicates: false выше выключает штатную
|
||||
// дедупликацию Search (она иногда схлопывает нужные нам заявки), и тогда
|
||||
// индекс может отдать один и тот же элемент несколькими хитами
|
||||
// (переиндексация, несколько «версий» в crawl). Без этого фильтра хиты
|
||||
// превращались в пункты комбобокса один-в-один, и заявка показывалась в
|
||||
// списке дважды — с одинаковым React-ключом. Та же причина и то же лечение,
|
||||
// что в request-portal (features/myRequests/myRequests.service.ts/hitsToEntries).
|
||||
const seen = new Set<number>();
|
||||
const ids = rawRows
|
||||
.map(r => Number(r.ListItemID))
|
||||
.filter(id => !isNaN(id) && id > 0)
|
||||
.filter(id => {
|
||||
if (seen.has(id)) return false;
|
||||
seen.add(id);
|
||||
return true;
|
||||
});
|
||||
if (ids.length === 0) return { items: [], hasMore: false };
|
||||
|
||||
const list = sp.web.getList(requestType.listId);
|
||||
|
||||
Reference in New Issue
Block a user