From 939d44063e80a1127c22146681097f8a0cc598e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=BA=D0=BE=D0=B2=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D1=85=20=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9?= Date: Tue, 8 Sep 2026 21:38:56 +0500 Subject: [PATCH] =?UTF-8?q?DAL-=D1=81=D0=BB=D0=BE=D0=B9=20chk:=20=D1=84?= =?UTF-8?q?=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20=D0=91=D0=94,=20=D1=81?= =?UTF-8?q?=D0=BF=D0=B5=D1=86=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8,=20psql-=D1=82=D0=B5=D1=81=D1=82=D1=8B,=20=D1=80=D0=B5?= =?UTF-8?q?=D0=BF=D0=BE=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D0=B8=20(MT.Ch?= =?UTF-8?q?ecks)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DB/chk: 7 хранимых функций (p_*), func_specs, psql-тесты, статусы DEV/CODE - MT.Checks: домен (ChkCheck, ChkType, ChkStatus), репозитории ChkCheck/ChkType/ChkStatus (Kit.Helpers, inline-конвертеры) - 1-docs/2-core/chk/repository_tests: спецификации тестов репозиториев - db/manifest.json, отчёт Plan/dal_report.md --- .gitignore | 7 + .../repository_tests/ChkCheckRepository.md | 87 +++++++++ .../repository_tests/ChkStatusRepository.md | 33 ++++ .../chk/repository_tests/ChkTypeRepository.md | 33 ++++ 1-docs/Замечания.txt | 3 + DB/chk/func_specs/chk_check_delete.md | 21 +++ DB/chk/func_specs/chk_check_get.md | 21 +++ DB/chk/func_specs/chk_check_insert.md | 33 ++++ DB/chk/func_specs/chk_check_select.md | 28 +++ DB/chk/func_specs/chk_check_update.md | 29 +++ DB/chk/func_specs/chk_status_select.md | 20 +++ DB/chk/func_specs/chk_type_select.md | 20 +++ DB/chk/functions.md | 37 ++-- DB/chk/functions/chk_check_delete.sql | 22 +++ DB/chk/functions/chk_check_get.sql | 35 ++++ DB/chk/functions/chk_check_insert.sql | 53 ++++++ DB/chk/functions/chk_check_select.sql | 41 +++++ DB/chk/functions/chk_check_update.sql | 40 +++++ DB/chk/functions/chk_status_select.sql | 17 ++ DB/chk/functions/chk_type_select.sql | 17 ++ DB/chk/tables.md | 6 +- DB/chk/tests/test_chk_check_delete.psql | 40 +++++ DB/chk/tests/test_chk_check_get.psql | 60 +++++++ DB/chk/tests/test_chk_check_insert.psql | 115 ++++++++++++ DB/chk/tests/test_chk_check_select.psql | 106 +++++++++++ DB/chk/tests/test_chk_check_update.psql | 72 ++++++++ DB/chk/tests/test_chk_status_select.psql | 33 ++++ DB/chk/tests/test_chk_type_select.psql | 33 ++++ DB/manifest.json | 152 ++++++++++++++++ DB/readme.md | 3 +- MT.Checks.slnx | 3 + MT.Checks/Domain/ChkCheck.cs | 38 ++++ MT.Checks/Domain/ChkStatus.cs | 31 ++++ MT.Checks/Domain/ChkType.cs | 37 ++++ MT.Checks/MT.Checks.csproj | 14 ++ MT.Checks/Repository/ChkCheckRepository.cs | 166 ++++++++++++++++++ MT.Checks/Repository/ChkStatusRepository.cs | 43 +++++ MT.Checks/Repository/ChkTypeRepository.cs | 43 +++++ MT.Checks/Repository/Filter/ChkCheckFilter.cs | 19 ++ Plan/backup/2026-09-08/dal_report.md | 90 ++++++++++ Plan/dal_report.md | 99 +++++++++++ 41 files changed, 1784 insertions(+), 16 deletions(-) create mode 100644 .gitignore create mode 100644 1-docs/2-core/chk/repository_tests/ChkCheckRepository.md create mode 100644 1-docs/2-core/chk/repository_tests/ChkStatusRepository.md create mode 100644 1-docs/2-core/chk/repository_tests/ChkTypeRepository.md create mode 100644 1-docs/Замечания.txt create mode 100644 DB/chk/func_specs/chk_check_delete.md create mode 100644 DB/chk/func_specs/chk_check_get.md create mode 100644 DB/chk/func_specs/chk_check_insert.md create mode 100644 DB/chk/func_specs/chk_check_select.md create mode 100644 DB/chk/func_specs/chk_check_update.md create mode 100644 DB/chk/func_specs/chk_status_select.md create mode 100644 DB/chk/func_specs/chk_type_select.md create mode 100644 DB/chk/functions/chk_check_delete.sql create mode 100644 DB/chk/functions/chk_check_get.sql create mode 100644 DB/chk/functions/chk_check_insert.sql create mode 100644 DB/chk/functions/chk_check_select.sql create mode 100644 DB/chk/functions/chk_check_update.sql create mode 100644 DB/chk/functions/chk_status_select.sql create mode 100644 DB/chk/functions/chk_type_select.sql create mode 100644 DB/chk/tests/test_chk_check_delete.psql create mode 100644 DB/chk/tests/test_chk_check_get.psql create mode 100644 DB/chk/tests/test_chk_check_insert.psql create mode 100644 DB/chk/tests/test_chk_check_select.psql create mode 100644 DB/chk/tests/test_chk_check_update.psql create mode 100644 DB/chk/tests/test_chk_status_select.psql create mode 100644 DB/chk/tests/test_chk_type_select.psql create mode 100644 DB/manifest.json create mode 100644 MT.Checks.slnx create mode 100644 MT.Checks/Domain/ChkCheck.cs create mode 100644 MT.Checks/Domain/ChkStatus.cs create mode 100644 MT.Checks/Domain/ChkType.cs create mode 100644 MT.Checks/MT.Checks.csproj create mode 100644 MT.Checks/Repository/ChkCheckRepository.cs create mode 100644 MT.Checks/Repository/ChkStatusRepository.cs create mode 100644 MT.Checks/Repository/ChkTypeRepository.cs create mode 100644 MT.Checks/Repository/Filter/ChkCheckFilter.cs create mode 100644 Plan/backup/2026-09-08/dal_report.md create mode 100644 Plan/dal_report.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d045ef --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Одноразовые артефакты прогона project_dal (c-skl-3) — в git не попадают +Plan/dal_state.json +Plan/env_passport.json +check_report.json + +bin/ +obj/ diff --git a/1-docs/2-core/chk/repository_tests/ChkCheckRepository.md b/1-docs/2-core/chk/repository_tests/ChkCheckRepository.md new file mode 100644 index 0000000..a6ebcfb --- /dev/null +++ b/1-docs/2-core/chk/repository_tests/ChkCheckRepository.md @@ -0,0 +1,87 @@ +# Тесты `ChkCheckRepository` (слой Chk) + +> Репозиторий `MT.Check.Core.Dal.Chk.Repository`: IChkCheckRepository / ChkCheckRepository +> (правила — c-dal; концепция unit-тестов без БД — c-tst; генерация — d-tst). +> Домен: `MT.Check.Core.Dal.Chk.Domain.ChkCheck` (Id, CheckDate, TypeId, Type, +> StatusId, Status, SmevMessageId, RrOrganizationId, Params, ParentCheckId). + +## Методы → функции БД + +| Метод | Транспорт | Функция БД | +|---|---|---| +| `Insert(ChkCheck): int` | `IConnectionString.PrepareExecute().AsStoredProcedure().AddParameter*` + `ExecuteScalar()` | `chk.chk_check_insert` | +| `Update(ChkCheck): int` | то же + `ExecuteScalar()` | `chk.chk_check_update` | +| `Delete(int id): int` | то же + `ExecuteScalar()` | `chk.chk_check_delete` | +| `Select(ChkCheckFilter): IList` | `PrepareExecute().AddConverter(...)` + `ExecuteSelectMany()` | `chk.chk_check_select` | +| `Get(int id): ChkCheck?` | `PrepareExecute().AddConverter(...)` + `ExecuteSelectMany()` | `chk.chk_check_get` | + +## Механика теста (общая) + +- Транспорт подменяется фейком `ConnectionStringTest : IConnectionString` (d-tst-11): + конструктор принимает строгий набор ожидаемых параметров (`Dictionary`) + и таблицу фикстуры; `VerifyAll` — в конце каждого теста (d-tst-12). +- Одна таблица ответа = один конвертер (d-tst-11); слово «курсор» не используется (d-tst-22). +- Строгий набор параметров: имена `p_check_date`, `p_chk_type_id`, `p_chk_status_id`, + `p_smev_message_id`, `p_rr_organization_id`, `p_params`, `p_parent_check_id`, `p_id` + (c-dal-5, d-tst-13). Строки домена без NULL (c-dm-10): пустое значение → `""`. +- Update — полная запись объекта: сценарии «null = не менять» не проектируются (db-m-8, c-dal-17). +- Тестируем репозиторий, а не БД: SQL-семантика фильтров не воспроизводится (d-tst-5, d-tst-10). +- Счётчики — из фикстуры, без литералов (d-tst-23); тело теста по этапам + `init test env by fixtures` → `call repository method` → `check result` (c-tst-3). + +## Заготовки (DataTable) + +Таблица ответа `chk.chk_check_select` / `chk.chk_check_get` (одна на оба метода): +`id:int, check_date:DateTime, chk_type_id:int, chk_status_id:int, +smev_message_id:string, rr_organization_id:string, params:string, parent_check_id:int?`. + +Строки-фикстуры: + +- `c1`: id=101, check_date=2026-05-01, chk_type_id=1, chk_status_id=2, + smev_message_id='smev-101', rr_organization_id='rr-101', + params='{"org_count":5}', parent_check_id=NULL; +- `c2`: id=102, check_date=2026-05-01, chk_type_id=1, chk_status_id=3, + smev_message_id='', rr_organization_id='rr-102', params='', parent_check_id=101; +- `c3`: id=103, check_date=2026-05-02, chk_type_id=5, chk_status_id=4, + smev_message_id='smev-103', rr_organization_id='', params='', parent_check_id=NULL. + +Скалярные заготовки: `chk.chk_check_insert` → 201 (id новой записи); +`chk.chk_check_update` → 1; `chk.chk_check_delete` → 1. + +## Тесты: `Insert` + +| № | Тип | Условие | Ожидаемый результат | +|---|---|---|---| +| 1 | основной | все поля объекта заполнены (строка c1 без Id) | вызов `chk.chk_check_insert`; строгий набор 7 параметров (`p_parent_check_id` = значение); возврат 201 из scalar-заготовки | +| 2 | граничный | необязательные поля пустые: строки `""`, `ParentCheckId = null` | строгий набор: `p_smev_message_id` = `""`, `p_params` = `""`, `p_parent_check_id` = NULL (`DBNull`) | + +## Тесты: `Update` + +| № | Тип | Условие | Ожидаемый результат | +|---|---|---|---| +| 1 | основной | полный объект (строка c2): Id=102 + все поля | вызов `chk.chk_check_update`; строгий набор 8 параметров (p_id + все поля); возврат 1 | +| 2 | исключение | объект с `Id = 0` | ArgumentException до транспорта; транспорт не вызывался | + +## Тесты: `Delete` + +| № | Тип | Условие | Ожидаемый результат | +|---|---|---|---| +| 1 | основной | Id = 103 | вызов `chk.chk_check_delete`; параметр `p_id` = 103; возврат 1 | +| 2 | исключение | Id = 0 | ArgumentException до транспорта; транспорт не вызывался | + +## Тесты: `Select` + +| № | Тип | Условие | Ожидаемый результат | +|---|---|---|---| +| 1 | основной | фильтр: CheckDate=2026-05-01, TypeId=1; таблица c1+c2+c3 | 2 сущности (по строкам c1, c2); строгий набор: `p_check_date`, `p_chk_type_id` заданы, `p_chk_status_id`/`p_rr_organization_id` = NULL | +| 2 | граничный | фильтр пустой (все поля null, c-dal-12: filter ??= new) | 3 сущности; все 4 параметра = NULL | +| 3 | основной | таблица c2+c3; пустой фильтр | поля сущностей сверены со строками (AssertEquals: id, check_date, chk_type_id, chk_status_id, smev_message_id, rr_organization_id, params, parent_check_id) | +| 4 | граничный | пустая таблица | пустая коллекция (не null) | + +## Тесты: `Get` + +| № | Тип | Условие | Ожидаемый результат | +|---|---|---|---| +| 1 | основной | Id = 101; таблица c1 | 1 сущность; строгий набор: `p_id` = 101; поля сверены со строкой c1; Type/Status-объекты не заполняются транспортом (null) | +| 2 | граничный | пустая таблица (id не найден) | null | +| 3 | исключение | Id = 0 | ArgumentException до транспорта; транспорт не вызывался | diff --git a/1-docs/2-core/chk/repository_tests/ChkStatusRepository.md b/1-docs/2-core/chk/repository_tests/ChkStatusRepository.md new file mode 100644 index 0000000..d8389dd --- /dev/null +++ b/1-docs/2-core/chk/repository_tests/ChkStatusRepository.md @@ -0,0 +1,33 @@ +# Тесты `ChkStatusRepository` (слой Chk) + +> Репозиторий `MT.Check.Core.Dal.Chk.Repository`: IChkStatusRepository / ChkStatusRepository +> (правила — c-dal; концепция unit-тестов без БД — c-tst; генерация — d-tst). +> Системный справочник (фиксированные id) — только методы чтения (c-dal-2). +> Домен: `MT.Check.Core.Dal.Chk.Domain.ChkStatus` (Id, Name) + константы `ChkStatuses`. + +## Методы → функции БД + +| Метод | Транспорт | Функция БД | +|---|---|---| +| `Select(): IList` | `IConnectionString.PrepareExecute().AddConverter(...)` + `ExecuteSelectMany()` | `chk.chk_status_select` | + +## Механика теста (общая) + +- Транспорт — фейк `ConnectionStringTest : IConnectionString` (d-tst-11); `VerifyAll` в конце (d-tst-12). +- Одна таблица ответа = один конвертер (d-tst-11); «таблица», не «курсор» (d-tst-22). +- Функция без параметров: строгий набор — пустой словарь (d-tst-13). +- Счётчики — из фикстуры (d-tst-23); этапы теста — c-tst-3. + +## Заготовки (DataTable) + +Таблица ответа `chk.chk_status_select`: `id:int, name:string`. + +Строки-фикстуры: `s1` (id=1, name='Запрос отправлен'), +`s4` (id=4, name='Ошибка при обработке'). + +## Тесты: `Select` + +| № | Тип | Условие | Ожидаемый результат | +|---|---|---|---| +| 1 | основной | таблица s1+s4 | 2 сущности; строгий набор: параметров нет; поля сверены со строками (id, name) | +| 2 | граничный | пустая таблица | пустая коллекция (не null) | diff --git a/1-docs/2-core/chk/repository_tests/ChkTypeRepository.md b/1-docs/2-core/chk/repository_tests/ChkTypeRepository.md new file mode 100644 index 0000000..997abe3 --- /dev/null +++ b/1-docs/2-core/chk/repository_tests/ChkTypeRepository.md @@ -0,0 +1,33 @@ +# Тесты `ChkTypeRepository` (слой Chk) + +> Репозиторий `MT.Check.Core.Dal.Chk.Repository`: IChkTypeRepository / ChkTypeRepository +> (правила — c-dal; концепция unit-тестов без БД — c-tst; генерация — d-tst). +> Системный справочник (фиксированные id) — только методы чтения (c-dal-2). +> Домен: `MT.Check.Core.Dal.Chk.Domain.ChkType` (Id, Name) + константы `ChkTypes`. + +## Методы → функции БД + +| Метод | Транспорт | Функция БД | +|---|---|---| +| `Select(): IList` | `IConnectionString.PrepareExecute().AddConverter(...)` + `ExecuteSelectMany()` | `chk.chk_type_select` | + +## Механика теста (общая) + +- Транспорт — фейк `ConnectionStringTest : IConnectionString` (d-tst-11); `VerifyAll` в конце (d-tst-12). +- Одна таблица ответа = один конвертер (d-tst-11); «таблица», не «курсор» (d-tst-22). +- Функция без параметров: строгий набор — пустой словарь (d-tst-13). +- Счётчики — из фикстуры (d-tst-23); этапы теста — c-tst-3. + +## Заготовки (DataTable) + +Таблица ответа `chk.chk_type_select`: `id:int, name:string`. + +Строки-фикстуры: `t1` (id=1, name='Запрос изменений (дельты) ЕГРЮЛ'), +`t6` (id=6, name='Актуализация организации СОУТ'). + +## Тесты: `Select` + +| № | Тип | Условие | Ожидаемый результат | +|---|---|---|---| +| 1 | основной | таблица t1+t6 | 2 сущности; строгий набор: параметров нет; поля сверены со строками (id, name) | +| 2 | граничный | пустая таблица | пустая коллекция (не null) | diff --git a/1-docs/Замечания.txt b/1-docs/Замечания.txt new file mode 100644 index 0000000..db1cb5d --- /dev/null +++ b/1-docs/Замечания.txt @@ -0,0 +1,3 @@ +1. имя проектов и namespaces +2. Коментарии несущие не нужную информацибю с одной стороно сдругой не дающие никаой инофрмации для дальнейшей поддрежки кода. +3. Не понятный репозитории RepositoryTransport.cs diff --git a/DB/chk/func_specs/chk_check_delete.md b/DB/chk/func_specs/chk_check_delete.md new file mode 100644 index 0000000..48b4359 --- /dev/null +++ b/DB/chk/func_specs/chk_check_delete.md @@ -0,0 +1,21 @@ +# `chk.chk_check_delete` + +Удаление проверки по `id`. + +| Параметр | Тип | Обяз. | Описание | +|---|---|---|---| +| `p_id` | int | да | идентификатор проверки | + +**Возвращает** `int` — число удалённых строк (db-m-3). + +## Логика/особенности + +- NULL `p_id` → `RAISE EXCEPTION` (db-m-4). Несуществующий `id` — не ошибка: возврат 0. +- Потомков у сущности нет; удаление родительской проверки, на которую ссылаются дочерние, даст исключение FK. + +## Тесты + +| № | Тип | Условие | Ожидаемый результат | +|---|---|---|---| +| 1 | основной сценарий | существующая проверка (фикстура `TEST-`) | возврат 1; записи в таблице нет | +| 2 | граничный случай | несуществующий `p_id` | возврат 0 | diff --git a/DB/chk/func_specs/chk_check_get.md b/DB/chk/func_specs/chk_check_get.md new file mode 100644 index 0000000..d0924ff --- /dev/null +++ b/DB/chk/func_specs/chk_check_get.md @@ -0,0 +1,21 @@ +# `chk.chk_check_get` + +Выборка одной проверки по идентификатору. + +| Параметр | Тип | Обяз. | Описание | +|---|---|---|---| +| `p_id` | int | да | идентификатор проверки | + +**Возвращает** `TABLE` — колонки `chk_check`: `id int`, `check_date date`, `chk_type_id int`, `chk_status_id int`, `smev_message_id text`, `rr_organization_id text`, `params jsonb`, `parent_check_id int`; не более одной строки. + +## Логика/особенности + +- NULL `p_id` → `RAISE EXCEPTION` (db-m-4). Несуществующий `id` — не ошибка: пустая таблица. + +## Тесты + +| № | Тип | Условие | Ожидаемый результат | +|---|---|---|---| +| 1 | основной сценарий | существующая проверка (фикстура `TEST-`) | 1 строка; все колонки = значениям записи | +| 2 | граничный случай | несуществующий `p_id` | пустая таблица | +| 3 | исключение | NULL `p_id` | исключение «не задан p_id» | diff --git a/DB/chk/func_specs/chk_check_insert.md b/DB/chk/func_specs/chk_check_insert.md new file mode 100644 index 0000000..ca0e01f --- /dev/null +++ b/DB/chk/func_specs/chk_check_insert.md @@ -0,0 +1,33 @@ +# `chk.chk_check_insert` + +Создание новой проверки организаций. + +| Параметр | Тип | Обяз. | Описание | +|---|---|---|---| +| `p_check_date` | date | да | день (дата) проверки | +| `p_chk_type_id` | int | да | тип проверки (→ `chk.chk_type.id`) | +| `p_chk_status_id` | int | да | статус проверки (→ `chk.chk_status.id`) | +| `p_smev_message_id` | text | нет | идентификатор сообщения СМЭВ | +| `p_rr_organization_id` | text | нет | идентификатор организации РР | +| `p_params` | text | нет | параметры проверки (JSON); передаётся как `text`, каст в `jsonb` внутри (db-m-5) | +| `p_parent_check_id` | int | нет | родительская проверка (→ `chk.chk_check.id`) | + +**Возвращает** `int` — id созданной проверки. + +## Логика/особенности + +- Обязательные параметры проверяются в начале функции: NULL → `RAISE EXCEPTION` (db-m-4). +- `p_params` — некорректный JSON → исключение приведения типа. + +## Тесты + +| № | Тип | Условие | Ожидаемый результат | +|---|---|---|---| +| 1 | основной сценарий | все поля заполнены (фикстура `TEST-`) | id > 0; поля записи совпадают с параметрами | +| 2 | граничный случай | только обязательные параметры | запись создана; необязательные поля NULL | +| 3 | граничный случай | `p_params` — валидный JSON | в записи `params` = переданный JSON | +| 4 | исключение | NULL `p_check_date` | исключение «не задан p_check_date» | +| 5 | исключение | NULL `p_chk_type_id` | исключение «не задан p_chk_type_id» | +| 6 | исключение | NULL `p_chk_status_id` | исключение «не задан p_chk_status_id» | +| 7 | исключение | несуществующий `p_chk_type_id` | исключение FOREIGN KEY | +| 8 | исключение | `p_params` — не JSON (`'не-json'`) | исключение приведения jsonb | diff --git a/DB/chk/func_specs/chk_check_select.md b/DB/chk/func_specs/chk_check_select.md new file mode 100644 index 0000000..d117bc6 --- /dev/null +++ b/DB/chk/func_specs/chk_check_select.md @@ -0,0 +1,28 @@ +# `chk.chk_check_select` + +Выборка проверок с необязательными фильтрами (NULL параметра = фильтр не применяется). + +| Параметр | Тип | Обяз. | Описание | +|---|---|---|---| +| `p_check_date` | date | нет | фильтр по дню проверки; NULL = без фильтра | +| `p_chk_type_id` | int | нет | фильтр по типу проверки; NULL = без фильтра | +| `p_chk_status_id` | int | нет | фильтр по статусу проверки; NULL = без фильтра | +| `p_rr_organization_id` | text | нет | фильтр по организации РР; NULL = без фильтра | + +**Возвращает** `TABLE` — колонки `chk_check`: `id int`, `check_date date`, `chk_type_id int`, `chk_status_id int`, `smev_message_id text`, `rr_organization_id text`, `params jsonb`, `parent_check_id int`; сортировка по `id`. + +## Логика/особенности + +- Фильтры реализованы шаблоном `(p_x IS NULL OR t.x = p_x)` (db-func-7); фильтры — на корневом объекте (db-s-8). + +## Тесты + +| № | Тип | Условие | Ожидаемый результат | +|---|---|---|---| +| 1 | основной сценарий | фикстура `TEST-` (3 проверки: разные дата/тип/статус/организация); все фильтры NULL | 3 строки фикстуры + существующие; все колонки заполнены | +| 2 | основной сценарий | фильтр по `p_check_date` (дата двух фикстур) | только проверки с этой датой | +| 3 | основной сценарий | фильтр по `p_chk_type_id` | только проверки этого типа | +| 4 | основной сценарий | фильтр по `p_chk_status_id` | только проверки этого статуса | +| 5 | основной сценарий | фильтр по `p_rr_organization_id` | только проверки этой организации | +| 6 | основной сценарий | комбинированный фильтр (дата + статус) | только строки, удовлетворяющие обоим условиям | +| 7 | граничный случай | фильтр без совпадений | пустая таблица | diff --git a/DB/chk/func_specs/chk_check_update.md b/DB/chk/func_specs/chk_check_update.md new file mode 100644 index 0000000..fdeff49 --- /dev/null +++ b/DB/chk/func_specs/chk_check_update.md @@ -0,0 +1,29 @@ +# `chk.chk_check_update` + +Полное обновление проверки по `id` (read-modify-write, db-m-8). + +| Параметр | Тип | Обяз. | Описание | +|---|---|---|---| +| `p_id` | int | да | идентификатор проверки | +| `p_check_date` | date | да | день (дата) проверки | +| `p_chk_type_id` | int | да | тип проверки (→ `chk.chk_type.id`) | +| `p_chk_status_id` | int | да | статус проверки (→ `chk.chk_status.id`) | +| `p_smev_message_id` | text | да | идентификатор сообщения СМЭВ (может быть NULL-значением) | +| `p_rr_organization_id` | text | да | идентификатор организации РР (может быть NULL-значением) | +| `p_params` | text | да | параметры проверки (JSON, `text` → `jsonb` внутри, db-m-5) | +| `p_parent_check_id` | int | да | родительская проверка (может быть NULL-значением) | + +**Возвращает** `int` — число строк, найденных по `id` и записанных (db-m-3). + +## Логика/особенности + +- Все поля пишутся безусловно: без `COALESCE` и `IS DISTINCT FROM` (db-m-8); NULL-семантики «не менять» нет. +- NULL `p_id` → `RAISE EXCEPTION` (db-m-4). Несуществующий `id` — не ошибка: возврат 0. + +## Тесты + +| № | Тип | Условие | Ожидаемый результат | +|---|---|---|---| +| 1 | основной сценарий | существующая проверка (фикстура `TEST-`); все поля новые | возврат 1; все поля записи = новым значениям | +| 2 | граничный случай | несуществующий `p_id` | возврат 0; записи не изменились | +| 3 | исключение | NULL `p_id` | исключение «не задан p_id» | diff --git a/DB/chk/func_specs/chk_status_select.md b/DB/chk/func_specs/chk_status_select.md new file mode 100644 index 0000000..47642b1 --- /dev/null +++ b/DB/chk/func_specs/chk_status_select.md @@ -0,0 +1,20 @@ +# `chk.chk_status_select` + +Выборка всех строк справочника статусов проверок. + +| Параметр | Тип | Обяз. | Описание | +|---|---|---|---| +| — | — | — | параметров нет | + +**Возвращает** `TABLE` — строки справочника: `id int`, `name text`, по возрастанию `id`. + +## Логика/особенности + +- Системный справочник (фиксированные id) — только чтение; функции редактирования не предусмотрены (c-dal-2). + +## Тесты + +| № | Тип | Условие | Ожидаемый результат | +|---|---|---|---| +| 1 | основной сценарий | справочник сидирован (4 статуса, init.sql) | 4 строки; колонки id/name; сортировка по id | +| 2 | граничный случай | строка, добавленная фикстурой `TEST-` | строка присутствует в результате | diff --git a/DB/chk/func_specs/chk_type_select.md b/DB/chk/func_specs/chk_type_select.md new file mode 100644 index 0000000..26ac35a --- /dev/null +++ b/DB/chk/func_specs/chk_type_select.md @@ -0,0 +1,20 @@ +# `chk.chk_type_select` + +Выборка всех строк справочника типов проверок. + +| Параметр | Тип | Обяз. | Описание | +|---|---|---|---| +| — | — | — | параметров нет | + +**Возвращает** `TABLE` — строки справочника: `id int`, `name text`, по возрастанию `id`. + +## Логика/особенности + +- Системный справочник (фиксированные id) — только чтение; функции редактирования не предусмотрены (c-dal-2). + +## Тесты + +| № | Тип | Условие | Ожидаемый результат | +|---|---|---|---| +| 1 | основной сценарий | справочник сидирован (6 типов, init.sql) | 6 строк; колонки id/name; сортировка по id | +| 2 | граничный случай | строка, добавленная фикстурой `TEST-` | строка присутствует в результате | diff --git a/DB/chk/functions.md b/DB/chk/functions.md index 2fd6f2d..e15c9a0 100644 --- a/DB/chk/functions.md +++ b/DB/chk/functions.md @@ -1,16 +1,29 @@ -# Слой `chk` — функции +# Слой `chk` — функции -## Список функций +## Список функций -| Сущность | Функция | Статус | Параметры | Возвращает | Функция | Репозиторий | Комментарий | +| Сущность | Функция | Статус | Параметры | Возвращает | Функция | Репозиторий | Комментарий | |---|---|---|---|---|---|---|---| -| chk_type | chk_type_select | PLAN | — | TABLE — строки справочника | - | - | — | -| chk_status | chk_status_select | PLAN | — | TABLE — строки справочника | - | - | — | -| chk_check | chk_check_insert | PLAN | p_check_date, p_chk_type_id, p_chk_status_id, p_smev_message_id, p_rr_organization_id, p_params (JSON), p_parent_check_id | int — id проверки | - | - | — | -| chk_check | chk_check_update | PLAN | p_id + все поля записи | int — число строк (db-m-3) | - | - | полная запись объекта (db-m-8) | -| chk_check | chk_check_delete | PLAN | p_id | int — число строк | - | - | — | -| chk_check | chk_check_select | PLAN | p_check_date, p_chk_type_id, p_chk_status_id, p_rr_organization_id | TABLE — строки chk_check | - | - | NULL параметра = фильтр не применяется | -| chk_check | chk_check_get | PLAN | p_id | TABLE — строка chk_check | - | - | — | +| chk_type | chk_type_select | DEV | — | TABLE — строки справочника | CODE | REQ | код: `functions/chk_type_select.sql`; тесты: `tests/test_chk_type_select.psql`; репозиторий: `MT.Checks/Repository/ChkTypeRepository.cs` | +| chk_status | chk_status_select | DEV | — | TABLE — строки справочника | CODE | REQ | код: `functions/chk_status_select.sql`; тесты: `tests/test_chk_status_select.psql`; репозиторий: `MT.Checks/Repository/ChkStatusRepository.cs` | +| chk_check | chk_check_insert | DEV | p_check_date, p_chk_type_id, p_chk_status_id, p_smev_message_id, p_rr_organization_id, p_params (JSON), p_parent_check_id | int — id проверки | CODE | REQ | код: `functions/chk_check_insert.sql`; тесты: `tests/test_chk_check_insert.psql`; репозиторий: `MT.Checks/Repository/ChkCheckRepository.cs` | +| chk_check | chk_check_update | DEV | p_id + все поля записи | int — число строк (db-m-3) | CODE | REQ | полная запись объекта (db-m-8); код: `functions/chk_check_update.sql`; тесты: `tests/test_chk_check_update.psql` | +| chk_check | chk_check_delete | DEV | p_id | int — число строк | CODE | REQ | код: `functions/chk_check_delete.sql`; тесты: `tests/test_chk_check_delete.psql` | +| chk_check | chk_check_select | DEV | p_check_date, p_chk_type_id, p_chk_status_id, p_rr_organization_id | TABLE — строки chk_check | CODE | REQ | NULL параметра = фильтр не применяется; код: `functions/chk_check_select.sql`; тесты: `tests/test_chk_check_select.psql` | +| chk_check | chk_check_get | DEV | p_id | TABLE — строка chk_check | CODE | REQ | код: `functions/chk_check_get.sql`; тесты: `tests/test_chk_check_get.psql` | -Функции в статусе `PLAN`: спецификации и тест-планы отсутствуют, файлы -`func_specs/{функция}.md` создаются при переводе в `DEV` (d-db-9, d-dev-1). +Спецификации функций и тест-планы: `func_specs/{функция}.md` (d-db-9). Код функций: +`functions/{сущность}_{действие}.sql` (db-func-4, расширение `.sql` — соглашение +репозитория). psql-тесты: `tests/test_{сущность}_{действие}.psql` (db-tst), +нумерация проверок = строкам таблиц «Тесты» спецификаций. Выполнение тестов — +на тестовой базе (db-tst-4): `psql -d <база> -f tests/test_*.psql`. + +Статусы (d-dev-1, d-dev-2, d-dev-5): `Статус` = `DEV` (реализация начата); +колонка «Функция» = `CODE` (код и тесты написаны; `DONE` — после утверждения +пользователем); «Репозиторий» = `REQ`. + +Примечание к колонке «Репозиторий» (2026-09-08, решение пользователя): +unit-тесты репозиториев не создаются вовсе; спецификации тестов сохранены как +документация ожидаемого поведения — `1-docs/2-core/chk/repository_tests/`. +Код репозиториев написан (проект `MT.Checks`); статус `CODE` по d-dev-3 +неприменим, Ñ‚.к. требует наличия тестов. diff --git a/DB/chk/functions/chk_check_delete.sql b/DB/chk/functions/chk_check_delete.sql new file mode 100644 index 0000000..30f4d13 --- /dev/null +++ b/DB/chk/functions/chk_check_delete.sql @@ -0,0 +1,22 @@ +-- chk.chk_check_delete: удаление проверки по id. +-- На основе: новая. +-- Параметры: p_id — идентификатор проверки. +-- Возвращает: int — число удалённых строк (db-m-3). +CREATE OR REPLACE FUNCTION chk.chk_check_delete( + p_id int +) +RETURNS int LANGUAGE plpgsql AS $$ +DECLARE + v_count int; +BEGIN + IF p_id IS NULL THEN + RAISE EXCEPTION 'chk.chk_check_delete: не задан p_id'; + END IF; + + DELETE FROM chk.chk_check t + WHERE t.id = p_id; + + GET DIAGNOSTICS v_count = ROW_COUNT; + RETURN v_count; +END; +$$; diff --git a/DB/chk/functions/chk_check_get.sql b/DB/chk/functions/chk_check_get.sql new file mode 100644 index 0000000..8681b6a --- /dev/null +++ b/DB/chk/functions/chk_check_get.sql @@ -0,0 +1,35 @@ +-- chk.chk_check_get: выборка одной проверки по идентификатору. +-- На основе: новая. +-- Параметры: p_id — идентификатор проверки. +-- Возвращает: TABLE — строка chk_check; несуществующий id — пустая таблица. +CREATE OR REPLACE FUNCTION chk.chk_check_get( + p_id int +) +RETURNS TABLE ( + id int, + check_date date, + chk_type_id int, + chk_status_id int, + smev_message_id text, + rr_organization_id text, + params jsonb, + parent_check_id int +) LANGUAGE plpgsql AS $$ +BEGIN + IF p_id IS NULL THEN + RAISE EXCEPTION 'chk.chk_check_get: не задан p_id'; + END IF; + + RETURN QUERY + SELECT t.id, + t.check_date, + t.chk_type_id, + t.chk_status_id, + t.smev_message_id, + t.rr_organization_id, + t.params, + t.parent_check_id + FROM chk.chk_check t + WHERE t.id = p_id; +END; +$$; diff --git a/DB/chk/functions/chk_check_insert.sql b/DB/chk/functions/chk_check_insert.sql new file mode 100644 index 0000000..7eee64c --- /dev/null +++ b/DB/chk/functions/chk_check_insert.sql @@ -0,0 +1,53 @@ +-- chk.chk_check_insert: создание новой проверки организаций. +-- На основе: новая. +-- Параметры: p_check_date — день (дата) проверки; p_chk_type_id — тип проверки +-- (chk.chk_type.id); p_chk_status_id — статус проверки (chk.chk_status.id); +-- p_smev_message_id — идентификатор сообщения СМЭВ; p_rr_organization_id — +-- идентификатор организации РР; p_params — параметры проверки (JSON, text); +-- p_parent_check_id — родительская проверка (chk.chk_check.id). +-- Возвращает: int — id созданной проверки. +CREATE OR REPLACE FUNCTION chk.chk_check_insert( + p_check_date date, + p_chk_type_id int, + p_chk_status_id int, + p_smev_message_id text DEFAULT NULL, + p_rr_organization_id text DEFAULT NULL, + p_params text DEFAULT NULL, + p_parent_check_id int DEFAULT NULL +) +RETURNS int LANGUAGE plpgsql AS $$ +DECLARE + v_id int; +BEGIN + -- обязательные параметры проверяются в начале функции (db-m-4) + IF p_check_date IS NULL THEN + RAISE EXCEPTION 'chk.chk_check_insert: не задан p_check_date'; + END IF; + IF p_chk_type_id IS NULL THEN + RAISE EXCEPTION 'chk.chk_check_insert: не задан p_chk_type_id'; + END IF; + IF p_chk_status_id IS NULL THEN + RAISE EXCEPTION 'chk.chk_check_insert: не задан p_chk_status_id'; + END IF; + + INSERT INTO chk.chk_check ( + check_date, + chk_type_id, + chk_status_id, + smev_message_id, + rr_organization_id, + params, + parent_check_id + ) VALUES ( + p_check_date, + p_chk_type_id, + p_chk_status_id, + p_smev_message_id, + p_rr_organization_id, + p_params::jsonb, -- JSON-параметр передаётся как text, каст внутри (db-m-5) + p_parent_check_id + ) + RETURNING id INTO v_id; + RETURN v_id; +END; +$$; diff --git a/DB/chk/functions/chk_check_select.sql b/DB/chk/functions/chk_check_select.sql new file mode 100644 index 0000000..0bbfe44 --- /dev/null +++ b/DB/chk/functions/chk_check_select.sql @@ -0,0 +1,41 @@ +-- chk.chk_check_select: выборка проверок с необязательными фильтрами. +-- На основе: новая. +-- Параметры: p_check_date — день проверки; p_chk_type_id — тип проверки; +-- p_chk_status_id — статус проверки; p_rr_organization_id — организация РР. +-- NULL параметра = фильтр не применяется. +-- Возвращает: TABLE — строки chk_check. +CREATE OR REPLACE FUNCTION chk.chk_check_select( + p_check_date date DEFAULT NULL, + p_chk_type_id int DEFAULT NULL, + p_chk_status_id int DEFAULT NULL, + p_rr_organization_id text DEFAULT NULL +) +RETURNS TABLE ( + id int, + check_date date, + chk_type_id int, + chk_status_id int, + smev_message_id text, + rr_organization_id text, + params jsonb, + parent_check_id int +) LANGUAGE plpgsql AS $$ +BEGIN + RETURN QUERY + SELECT t.id, + t.check_date, + t.chk_type_id, + t.chk_status_id, + t.smev_message_id, + t.rr_organization_id, + t.params, + t.parent_check_id + FROM chk.chk_check t + WHERE (p_check_date IS NULL OR t.check_date = p_check_date) -- NULL = фильтр не применяется + AND (p_chk_type_id IS NULL OR t.chk_type_id = p_chk_type_id) + AND (p_chk_status_id IS NULL OR t.chk_status_id = p_chk_status_id) + AND (p_rr_organization_id IS NULL + OR t.rr_organization_id = p_rr_organization_id) + ORDER BY t.id; +END; +$$; diff --git a/DB/chk/functions/chk_check_update.sql b/DB/chk/functions/chk_check_update.sql new file mode 100644 index 0000000..fe238de --- /dev/null +++ b/DB/chk/functions/chk_check_update.sql @@ -0,0 +1,40 @@ +-- chk.chk_check_update: полное обновление проверки по id (read-modify-write, db-m-8). +-- На основе: новая. +-- Параметры: p_id — идентификатор проверки; далее все поля объекта без +-- NULL-семантики «не менять»: p_check_date, p_chk_type_id, p_chk_status_id, +-- p_smev_message_id, p_rr_organization_id, p_params (JSON, text), +-- p_parent_check_id. +-- Возвращает: int — число строк, найденных по id и записанных (db-m-3). +CREATE OR REPLACE FUNCTION chk.chk_check_update( + p_id int, + p_check_date date, + p_chk_type_id int, + p_chk_status_id int, + p_smev_message_id text, + p_rr_organization_id text, + p_params text, + p_parent_check_id int +) +RETURNS int LANGUAGE plpgsql AS $$ +DECLARE + v_count int; +BEGIN + IF p_id IS NULL THEN + RAISE EXCEPTION 'chk.chk_check_update: не задан p_id'; + END IF; + + -- полная запись объекта: без COALESCE и IS DISTINCT FROM (db-m-8) + UPDATE chk.chk_check t + SET check_date = p_check_date, + chk_type_id = p_chk_type_id, + chk_status_id = p_chk_status_id, + smev_message_id = p_smev_message_id, + rr_organization_id = p_rr_organization_id, + params = p_params::jsonb, -- каст JSON из text внутри (db-m-5) + parent_check_id = p_parent_check_id + WHERE t.id = p_id; + + GET DIAGNOSTICS v_count = ROW_COUNT; + RETURN v_count; +END; +$$; diff --git a/DB/chk/functions/chk_status_select.sql b/DB/chk/functions/chk_status_select.sql new file mode 100644 index 0000000..a798b06 --- /dev/null +++ b/DB/chk/functions/chk_status_select.sql @@ -0,0 +1,17 @@ +-- chk.chk_status_select: выборка всех строк справочника статусов проверок. +-- На основе: новая. +-- Параметры: нет. +-- Возвращает: TABLE — строки справочника chk_status (id, name), по возрастанию id. +CREATE OR REPLACE FUNCTION chk.chk_status_select() +RETURNS TABLE ( + id int, + name text +) LANGUAGE plpgsql AS $$ +BEGIN + RETURN QUERY + SELECT t.id, + t.name + FROM chk.chk_status t + ORDER BY t.id; +END; +$$; diff --git a/DB/chk/functions/chk_type_select.sql b/DB/chk/functions/chk_type_select.sql new file mode 100644 index 0000000..3ce1a60 --- /dev/null +++ b/DB/chk/functions/chk_type_select.sql @@ -0,0 +1,17 @@ +-- chk.chk_type_select: выборка всех строк справочника типов проверок. +-- На основе: новая. +-- Параметры: нет. +-- Возвращает: TABLE — строки справочника chk_type (id, name), по возрастанию id. +CREATE OR REPLACE FUNCTION chk.chk_type_select() +RETURNS TABLE ( + id int, + name text +) LANGUAGE plpgsql AS $$ +BEGIN + RETURN QUERY + SELECT t.id, + t.name + FROM chk.chk_type t + ORDER BY t.id; +END; +$$; diff --git a/DB/chk/tables.md b/DB/chk/tables.md index 133b056..417e784 100644 --- a/DB/chk/tables.md +++ b/DB/chk/tables.md @@ -6,9 +6,9 @@ | Таблица | Тип | Набор функций | Функции | Комментарий | |---|---|---|---|---| -| `chk_type` | системный справочник | chk_type_select | SELECT | функции запланированы, статус PLAN (functions.md) | -| `chk_status` | системный справочник | chk_status_select | SELECT | функции запланированы, статус PLAN (functions.md) | -| `chk_check` | сущность | chk_check_insert, chk_check_update, chk_check_delete, chk_check_select, chk_check_get | CRUD | функции запланированы, статус PLAN (functions.md) | +| `chk_type` | системный справочник | chk_type_select | SELECT | функции в `functions/`, статус DEV/CODE (functions.md) | +| `chk_status` | системный справочник | chk_status_select | SELECT | функции в `functions/`, статус DEV/CODE (functions.md) | +| `chk_check` | сущность | chk_check_insert, chk_check_update, chk_check_delete, chk_check_select, chk_check_get | CRUD | функции в `functions/`, статус DEV/CODE (functions.md) | ## chk_type — типы проверок diff --git a/DB/chk/tests/test_chk_check_delete.psql b/DB/chk/tests/test_chk_check_delete.psql new file mode 100644 index 0000000..d02afca --- /dev/null +++ b/DB/chk/tests/test_chk_check_delete.psql @@ -0,0 +1,40 @@ +-- Тесты chk.chk_check_delete — по func_specs/chk_check_delete.md (db-tst-6: № = строке таблицы). +-- Запуск: psql -d <тестовая база> -f test_chk_check_delete.psql (одна транзакция, откат в конце). +BEGIN; +DO $$ +DECLARE + v_id int; + v_count int; +BEGIN + -- фикстуры TEST-: справочники вне диапазона сидированных значений + INSERT INTO chk.chk_type (id, name) VALUES (990001, 'TEST-тип') + ON CONFLICT (id) DO NOTHING; + INSERT INTO chk.chk_status (id, name) VALUES (990001, 'TEST-статус') + ON CONFLICT (id) DO NOTHING; + v_id := chk.chk_check_insert(date '2026-04-01', 990001, 990001, + 'TEST-smev', 'TEST-org', NULL, NULL); + + -- №1: существующая проверка — возврат 1, записи в таблице нет + v_count := chk.chk_check_delete(v_id); + SELECT count(*) INTO v_count FROM chk.chk_check WHERE id = v_id; + IF v_count = 0 THEN + RAISE NOTICE 'PASS chk.chk_check_delete #1'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_delete #1: запись не удалена'; + END IF; + + -- №2: несуществующий id — возврат 0 + v_count := chk.chk_check_delete(999999999); + IF v_count = 0 THEN + RAISE NOTICE 'PASS chk.chk_check_delete #2'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_delete #2: ожидался 0, получено %', v_count; + END IF; + + -- удаление фикстур + DELETE FROM chk.chk_type WHERE id = 990001; + DELETE FROM chk.chk_status WHERE id = 990001; + RAISE NOTICE 'ИТОГ chk.chk_check_delete: все проверки выполнены'; +END; +$$; +ROLLBACK; diff --git a/DB/chk/tests/test_chk_check_get.psql b/DB/chk/tests/test_chk_check_get.psql new file mode 100644 index 0000000..6ea9560 --- /dev/null +++ b/DB/chk/tests/test_chk_check_get.psql @@ -0,0 +1,60 @@ +-- Тесты chk.chk_check_get — по func_specs/chk_check_get.md (db-tst-6: № = строке таблицы). +-- Запуск: psql -d <тестовая база> -f test_chk_check_get.psql (одна транзакция, откат в конце). +BEGIN; +DO $$ +DECLARE + v_id int; + v_count int; + v_date date; + v_smev text; + v_org text; + v_json jsonb; +BEGIN + -- фикстуры TEST-: справочники вне диапазона сидированных значений + INSERT INTO chk.chk_type (id, name) VALUES (990001, 'TEST-тип') + ON CONFLICT (id) DO NOTHING; + INSERT INTO chk.chk_status (id, name) VALUES (990001, 'TEST-статус') + ON CONFLICT (id) DO NOTHING; + v_id := chk.chk_check_insert(date '2026-06-01', 990001, 990001, + 'TEST-smev', 'TEST-org', '{"a": 1}', NULL); + + -- №1: существующий id — 1 строка, все колонки = значениям записи + SELECT count(*), max(check_date), max(smev_message_id), + max(rr_organization_id), max(params) + INTO v_count, v_date, v_smev, v_org, v_json + FROM chk.chk_check_get(v_id); + IF v_count = 1 + AND v_date = date '2026-06-01' + AND v_smev = 'TEST-smev' + AND v_org = 'TEST-org' + AND v_json = '{"a": 1}'::jsonb THEN + RAISE NOTICE 'PASS chk.chk_check_get #1'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_get #1: поля не совпали'; + END IF; + + -- №2: несуществующий id — пустая таблица + SELECT count(*) INTO v_id FROM chk.chk_check_get(999999999); + IF v_id = 0 THEN + RAISE NOTICE 'PASS chk.chk_check_get #2'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_get #2: ожидалась пустая таблица'; + END IF; + + -- №3: NULL p_id — исключение + BEGIN + PERFORM chk.chk_check_get(NULL); + RAISE EXCEPTION 'FAIL chk.chk_check_get #3: исключения не было'; + EXCEPTION + WHEN raise_exception THEN + RAISE NOTICE 'PASS chk.chk_check_get #3: %', SQLERRM; + END; + + -- удаление фикстур + PERFORM chk.chk_check_delete(v_id); + DELETE FROM chk.chk_type WHERE id = 990001; + DELETE FROM chk.chk_status WHERE id = 990001; + RAISE NOTICE 'ИТОГ chk.chk_check_get: все проверки выполнены'; +END; +$$; +ROLLBACK; diff --git a/DB/chk/tests/test_chk_check_insert.psql b/DB/chk/tests/test_chk_check_insert.psql new file mode 100644 index 0000000..1fe7404 --- /dev/null +++ b/DB/chk/tests/test_chk_check_insert.psql @@ -0,0 +1,115 @@ +-- Тесты chk.chk_check_insert — по func_specs/chk_check_insert.md (db-tst-6: № = строке таблицы). +-- Запуск: psql -d <тестовая база> -f test_chk_check_insert.psql (одна транзакция, откат в конце). +BEGIN; +DO $$ +DECLARE + v_id int; + v_count int; + v_date date; + v_json jsonb; + v_smev text; + v_org text; + v_parent int; +BEGIN + -- фикстуры TEST-: справочники вне диапазона сидированных значений + INSERT INTO chk.chk_type (id, name) VALUES (990001, 'TEST-тип') + ON CONFLICT (id) DO NOTHING; + INSERT INTO chk.chk_status (id, name) VALUES (990001, 'TEST-статус') + ON CONFLICT (id) DO NOTHING; + -- родительская проверка фикстуры + v_parent := chk.chk_check_insert(date '2026-01-01', 990001, 990001, + 'TEST-parent-smev', 'TEST-parent-org', NULL, NULL); + + -- №1: все поля заполнены — id > 0, поля записи совпадают с параметрами + v_id := chk.chk_check_insert(date '2026-02-01', 990001, 990001, + 'TEST-smev-1', 'TEST-org-1', '{"a": 1}', v_parent); + SELECT check_date, smev_message_id, rr_organization_id, params, parent_check_id + INTO v_date, v_smev, v_org, v_json, v_parent + FROM chk.chk_check_get(v_id); + IF v_id > 0 + AND v_date = date '2026-02-01' + AND v_smev = 'TEST-smev-1' + AND v_org = 'TEST-org-1' + AND v_json = '{"a": 1}'::jsonb + AND v_parent IS NOT NULL THEN + RAISE NOTICE 'PASS chk.chk_check_insert #1'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_insert #1: поля записи не совпали'; + END IF; + + -- №2: только обязательные параметры — необязательные поля NULL + v_id := chk.chk_check_insert(date '2026-02-02', 990001, 990001); + SELECT smev_message_id, rr_organization_id, params, parent_check_id + INTO v_smev, v_org, v_json, v_parent + FROM chk.chk_check_get(v_id); + IF v_id > 0 AND v_smev IS NULL AND v_org IS NULL AND v_json IS NULL AND v_parent IS NULL THEN + RAISE NOTICE 'PASS chk.chk_check_insert #2'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_insert #2: необязательные поля не NULL'; + END IF; + + -- №3: валидный JSON в p_params — сохранён как есть + v_id := chk.chk_check_insert(date '2026-02-03', 990001, 990001, + NULL, NULL, '{"org_count": 5, "req_count": 3}', NULL); + SELECT params INTO v_json FROM chk.chk_check_get(v_id); + IF v_json = '{"org_count": 5, "req_count": 3}'::jsonb THEN + RAISE NOTICE 'PASS chk.chk_check_insert #3'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_insert #3: JSON не совпал'; + END IF; + + -- №4: NULL p_check_date — исключение + BEGIN + PERFORM chk.chk_check_insert(NULL, 990001, 990001); + RAISE EXCEPTION 'FAIL chk.chk_check_insert #4: исключения не было'; + EXCEPTION + WHEN raise_exception THEN + RAISE NOTICE 'PASS chk.chk_check_insert #4: %', SQLERRM; + END; + + -- №5: NULL p_chk_type_id — исключение + BEGIN + PERFORM chk.chk_check_insert(date '2026-02-04', NULL, 990001); + RAISE EXCEPTION 'FAIL chk.chk_check_insert #5: исключения не было'; + EXCEPTION + WHEN raise_exception THEN + RAISE NOTICE 'PASS chk.chk_check_insert #5: %', SQLERRM; + END; + + -- №6: NULL p_chk_status_id — исключение + BEGIN + PERFORM chk.chk_check_insert(date '2026-02-04', 990001, NULL); + RAISE EXCEPTION 'FAIL chk.chk_check_insert #6: исключения не было'; + EXCEPTION + WHEN raise_exception THEN + RAISE NOTICE 'PASS chk.chk_check_insert #6: %', SQLERRM; + END; + + -- №7: несуществующий тип — исключение FOREIGN KEY + BEGIN + PERFORM chk.chk_check_insert(date '2026-02-04', 999999, 990001); + RAISE EXCEPTION 'FAIL chk.chk_check_insert #7: исключения не было'; + EXCEPTION + WHEN foreign_key_violation THEN + RAISE NOTICE 'PASS chk.chk_check_insert #7'; + END; + + -- №8: некорректный JSON — исключение приведения + BEGIN + PERFORM chk.chk_check_insert(date '2026-02-04', 990001, 990001, + NULL, NULL, 'не-json', NULL); + RAISE EXCEPTION 'FAIL chk.chk_check_insert #8: исключения не было'; + EXCEPTION + WHEN OTHERS THEN + RAISE NOTICE 'PASS chk.chk_check_insert #8: %', SQLERRM; + END; + + -- удаление фикстур (родитель каскадом не удаляет — дочерние удаляем явно) + PERFORM chk.chk_check_delete(v_parent); + DELETE FROM chk.chk_check WHERE rr_organization_id LIKE 'TEST-%'; + DELETE FROM chk.chk_type WHERE id = 990001; + DELETE FROM chk.chk_status WHERE id = 990001; + RAISE NOTICE 'ИТОГ chk.chk_check_insert: все проверки выполнены'; +END; +$$; +ROLLBACK; diff --git a/DB/chk/tests/test_chk_check_select.psql b/DB/chk/tests/test_chk_check_select.psql new file mode 100644 index 0000000..99f3194 --- /dev/null +++ b/DB/chk/tests/test_chk_check_select.psql @@ -0,0 +1,106 @@ +-- Тесты chk.chk_check_select — по func_specs/chk_check_select.md (db-tst-6: № = строке таблицы). +-- Запуск: psql -d <тестовая база> -f test_chk_check_select.psql (одна транзакция, откат в конце). +BEGIN; +DO $$ +DECLARE + v_id1 int; + v_id2 int; + v_id3 int; + v_count int; +BEGIN + -- фикстуры TEST-: справочники вне диапазона сидированных значений + INSERT INTO chk.chk_type (id, name) VALUES (990001, 'TEST-тип 1') + ON CONFLICT (id) DO NOTHING; + INSERT INTO chk.chk_type (id, name) VALUES (990002, 'TEST-тип 2') + ON CONFLICT (id) DO NOTHING; + INSERT INTO chk.chk_status (id, name) VALUES (990001, 'TEST-статус 1') + ON CONFLICT (id) DO NOTHING; + INSERT INTO chk.chk_status (id, name) VALUES (990002, 'TEST-статус 2') + ON CONFLICT (id) DO NOTHING; + -- три проверки: 1 и 2 — одна дата/тип/статус, 3 — все значения другие + v_id1 := chk.chk_check_insert(date '2026-05-01', 990001, 990001, + 'TEST-smev-1', 'TEST-org-1', NULL, NULL); + v_id2 := chk.chk_check_insert(date '2026-05-01', 990001, 990001, + 'TEST-smev-2', 'TEST-org-2', NULL, NULL); + v_id3 := chk.chk_check_insert(date '2026-05-02', 990002, 990002, + 'TEST-smev-3', 'TEST-org-3', NULL, NULL); + + -- №1: все фильтры NULL — все 3 строки фикстуры, все колонки + SELECT count(*) INTO v_count + FROM chk.chk_check_select(NULL, NULL, NULL, NULL) + WHERE id IN (v_id1, v_id2, v_id3); + IF v_count = 3 THEN + RAISE NOTICE 'PASS chk.chk_check_select #1'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_select #1: найдено % из 3', v_count; + END IF; + + -- №2: фильтр по дате — только строки с 2026-05-01 + SELECT count(*) INTO v_count + FROM chk.chk_check_select(date '2026-05-01', NULL, NULL, NULL) + WHERE id IN (v_id1, v_id2, v_id3); + IF v_count = 2 THEN + RAISE NOTICE 'PASS chk.chk_check_select #2'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_select #2: найдено %, ожидалось 2', v_count; + END IF; + + -- №3: фильтр по типу + SELECT count(*) INTO v_count + FROM chk.chk_check_select(NULL, 990001, NULL, NULL) + WHERE id IN (v_id1, v_id2, v_id3); + IF v_count = 2 THEN + RAISE NOTICE 'PASS chk.chk_check_select #3'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_select #3: найдено %, ожидалось 2', v_count; + END IF; + + -- №4: фильтр по статусу + SELECT count(*) INTO v_count + FROM chk.chk_check_select(NULL, NULL, 990002, NULL) + WHERE id IN (v_id1, v_id2, v_id3); + IF v_count = 1 THEN + RAISE NOTICE 'PASS chk.chk_check_select #4'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_select #4: найдено %, ожидалось 1', v_count; + END IF; + + -- №5: фильтр по организации РР + SELECT count(*) INTO v_count + FROM chk.chk_check_select(NULL, NULL, NULL, 'TEST-org-2') + WHERE id IN (v_id1, v_id2, v_id3); + IF v_count = 1 THEN + RAISE NOTICE 'PASS chk.chk_check_select #5'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_select #5: найдено %, ожидалось 1', v_count; + END IF; + + -- №6: комбинированный фильтр (дата + статус) — только строка 3 + SELECT count(*) INTO v_count + FROM chk.chk_check_select(date '2026-05-02', NULL, 990002, NULL) + WHERE id IN (v_id1, v_id2, v_id3); + IF v_count = 1 THEN + RAISE NOTICE 'PASS chk.chk_check_select #6'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_select #6: найдено %, ожидалось 1', v_count; + END IF; + + -- №7: фильтр без совпадений — пустая таблица + SELECT count(*) INTO v_count + FROM chk.chk_check_select(date '2026-05-02', NULL, 990001, NULL); + IF v_count = 0 THEN + RAISE NOTICE 'PASS chk.chk_check_select #7'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_select #7: найдено %, ожидалось 0', v_count; + END IF; + + -- удаление фикстур + PERFORM chk.chk_check_delete(v_id1); + PERFORM chk.chk_check_delete(v_id2); + PERFORM chk.chk_check_delete(v_id3); + DELETE FROM chk.chk_type WHERE id IN (990001, 990002); + DELETE FROM chk.chk_status WHERE id IN (990001, 990002); + RAISE NOTICE 'ИТОГ chk.chk_check_select: все проверки выполнены'; +END; +$$; +ROLLBACK; diff --git a/DB/chk/tests/test_chk_check_update.psql b/DB/chk/tests/test_chk_check_update.psql new file mode 100644 index 0000000..5e65c42 --- /dev/null +++ b/DB/chk/tests/test_chk_check_update.psql @@ -0,0 +1,72 @@ +-- Тесты chk.chk_check_update — по func_specs/chk_check_update.md (db-tst-6: № = строке таблицы). +-- Запуск: psql -d <тестовая база> -f test_chk_check_update.psql (одна транзакция, откат в конце). +BEGIN; +DO $$ +DECLARE + v_id int; + v_count int; + v_date date; + v_json jsonb; + v_smev text; + v_org text; + v_type int; + v_status int; +BEGIN + -- фикстуры TEST-: справочники вне диапазона сидированных значений + INSERT INTO chk.chk_type (id, name) VALUES (990001, 'TEST-тип до') + ON CONFLICT (id) DO NOTHING; + INSERT INTO chk.chk_type (id, name) VALUES (990002, 'TEST-тип после') + ON CONFLICT (id) DO NOTHING; + INSERT INTO chk.chk_status (id, name) VALUES (990001, 'TEST-статус до') + ON CONFLICT (id) DO NOTHING; + INSERT INTO chk.chk_status (id, name) VALUES (990002, 'TEST-статус после') + ON CONFLICT (id) DO NOTHING; + v_id := chk.chk_check_insert(date '2026-03-01', 990001, 990001, + 'TEST-smev-до', 'TEST-org-до', '{"a": 1}', NULL); + + -- №1: полное обновление всех полей — возврат 1, все поля = новым значениям + v_count := chk.chk_check_update(v_id, date '2026-03-02', 990002, 990002, + 'TEST-smev-после', 'TEST-org-после', '{"b": 2}', NULL); + SELECT check_date, chk_type_id, chk_status_id, smev_message_id, + rr_organization_id, params + INTO v_date, v_type, v_status, v_smev, v_org, v_json + FROM chk.chk_check_get(v_id); + IF v_count = 1 + AND v_date = date '2026-03-02' + AND v_type = 990002 + AND v_status = 990002 + AND v_smev = 'TEST-smev-после' + AND v_org = 'TEST-org-после' + AND v_json = '{"b": 2}'::jsonb THEN + RAISE NOTICE 'PASS chk.chk_check_update #1'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_update #1: поля не совпали'; + END IF; + + -- №2: несуществующий id — возврат 0 + v_count := chk.chk_check_update(999999999, date '2026-03-02', 990002, 990002, + NULL, NULL, NULL, NULL); + IF v_count = 0 THEN + RAISE NOTICE 'PASS chk.chk_check_update #2'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_check_update #2: ожидался 0, получено %', v_count; + END IF; + + -- №3: NULL p_id — исключение + BEGIN + PERFORM chk.chk_check_update(NULL, date '2026-03-02', 990002, 990002, + NULL, NULL, NULL, NULL); + RAISE EXCEPTION 'FAIL chk.chk_check_update #3: исключения не было'; + EXCEPTION + WHEN raise_exception THEN + RAISE NOTICE 'PASS chk.chk_check_update #3: %', SQLERRM; + END; + + -- удаление фикстур + PERFORM chk.chk_check_delete(v_id); + DELETE FROM chk.chk_type WHERE id IN (990001, 990002); + DELETE FROM chk.chk_status WHERE id IN (990001, 990002); + RAISE NOTICE 'ИТОГ chk.chk_check_update: все проверки выполнены'; +END; +$$; +ROLLBACK; diff --git a/DB/chk/tests/test_chk_status_select.psql b/DB/chk/tests/test_chk_status_select.psql new file mode 100644 index 0000000..4084d4a --- /dev/null +++ b/DB/chk/tests/test_chk_status_select.psql @@ -0,0 +1,33 @@ +-- Тесты chk.chk_status_select — по func_specs/chk_status_select.md (db-tst-6: № = строке таблицы). +-- Запуск: psql -d <тестовая база> -f test_chk_status_select.psql (одна транзакция, откат в конце). +BEGIN; +DO $$ +DECLARE + v_count int; +BEGIN + -- фикстура TEST- (id 990001 — вне диапазона сидированных значений) + INSERT INTO chk.chk_status (id, name) VALUES (990001, 'TEST-статус проверки') + ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name; + + -- №1: сидированный справочник — не меньше 4 строк, сортировка по id + SELECT count(*) INTO v_count FROM chk.chk_status_select(); + IF v_count >= 4 THEN + RAISE NOTICE 'PASS chk.chk_status_select #1'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_status_select #1: строк %, ожидалось >= 4', v_count; + END IF; + + -- №2: фикстура TEST- присутствует в результате + SELECT count(*) INTO v_count FROM chk.chk_status_select() WHERE id = 990001; + IF v_count = 1 THEN + RAISE NOTICE 'PASS chk.chk_status_select #2'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_status_select #2: фикстура не найдена'; + END IF; + + -- удаление фикстур + DELETE FROM chk.chk_status WHERE id = 990001; + RAISE NOTICE 'ИТОГ chk.chk_status_select: все проверки выполнены'; +END; +$$; +ROLLBACK; diff --git a/DB/chk/tests/test_chk_type_select.psql b/DB/chk/tests/test_chk_type_select.psql new file mode 100644 index 0000000..9644d17 --- /dev/null +++ b/DB/chk/tests/test_chk_type_select.psql @@ -0,0 +1,33 @@ +-- Тесты chk.chk_type_select — по func_specs/chk_type_select.md (db-tst-6: № = строке таблицы). +-- Запуск: psql -d <тестовая база> -f test_chk_type_select.psql (одна транзакция, откат в конце). +BEGIN; +DO $$ +DECLARE + v_count int; +BEGIN + -- фикстура TEST- (id 990001 — вне диапазона сидированных значений) + INSERT INTO chk.chk_type (id, name) VALUES (990001, 'TEST-тип проверки') + ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name; + + -- №1: сидированный справочник — не меньше 6 строк, сортировка по id + SELECT count(*) INTO v_count FROM chk.chk_type_select(); + IF v_count >= 6 THEN + RAISE NOTICE 'PASS chk.chk_type_select #1'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_type_select #1: строк %, ожидалось >= 6', v_count; + END IF; + + -- №2: фикстура TEST- присутствует в результате + SELECT count(*) INTO v_count FROM chk.chk_type_select() WHERE id = 990001; + IF v_count = 1 THEN + RAISE NOTICE 'PASS chk.chk_type_select #2'; + ELSE + RAISE EXCEPTION 'FAIL chk.chk_type_select #2: фикстура не найдена'; + END IF; + + -- удаление фикстур + DELETE FROM chk.chk_type WHERE id = 990001; + RAISE NOTICE 'ИТОГ chk.chk_type_select: все проверки выполнены'; +END; +$$; +ROLLBACK; diff --git a/DB/manifest.json b/DB/manifest.json new file mode 100644 index 0000000..6a49fb3 --- /dev/null +++ b/DB/manifest.json @@ -0,0 +1,152 @@ +{ + "Layer": "chk", + "Schemas": [ + "chk" + ], + "BuiltAt": "2026-09-08 18:36", + "SourceHashes": { + "DB\\chk\\functions\\chk_check_delete.sql": "ee9f93c78bf21ec9d3a7e46d097a6384e5f2bac9b7329dcb2b9e414356102744", + "DB\\chk\\functions\\chk_check_get.sql": "586b744830852e2791b976370841098f8b144882e6fe0d64c25aea2cbce6d539", + "DB\\chk\\functions\\chk_check_insert.sql": "f5aaa65daf3ae7b10485100398da3633c7646b025460130dba7500fe94b27624", + "DB\\chk\\functions\\chk_check_select.sql": "466e9aecb0aa1c2f92448c0f41a608a1212700d474f25d9b35f31c0fbf9ac795", + "DB\\chk\\functions\\chk_check_update.sql": "7c9b84c57c6333edc861f369fbf94cc3d6261dce31c6e86d573bc99687ea6a50", + "DB\\chk\\functions\\chk_status_select.sql": "bf12313d3be965031abb3946b4ea3a9f3ec1ff0c5ada1272704ea5e0c1ef4b0c", + "DB\\chk\\functions\\chk_type_select.sql": "90367b389c919971123236a51d6dd560219cfce08553633c29c7c27d9adb7677" + }, + "Entities": { + "ChkCheck": { + "Table": "chk.chk_check", + "Kind": "plain", + "Children": [], + "Functions": { + "delete": { + "Name": "chk.chk_check_delete", + "SourceFile": "DB\\chk\\functions\\chk_check_delete.sql", + "Params": { + "p_id": "int" + }, + "Returns": { + "form": "scalar", + "type": "int" + } + }, + "get": { + "Name": "chk.chk_check_get", + "SourceFile": "DB\\chk\\functions\\chk_check_get.sql", + "Params": { + "p_id": "int" + }, + "Returns": { + "form": "table", + "columns": { + "id": "int", + "check_date": "date", + "chk_type_id": "int", + "chk_status_id": "int", + "smev_message_id": "text", + "rr_organization_id": "text", + "params": "jsonb", + "parent_check_id": "int" + } + } + }, + "insert": { + "Name": "chk.chk_check_insert", + "SourceFile": "DB\\chk\\functions\\chk_check_insert.sql", + "Params": { + "p_check_date": "date", + "p_chk_type_id": "int", + "p_chk_status_id": "int", + "p_smev_message_id": "text", + "p_rr_organization_id": "text", + "p_params": "text", + "p_parent_check_id": "int" + }, + "Returns": { + "form": "scalar", + "type": "int" + } + }, + "select": { + "Name": "chk.chk_check_select", + "SourceFile": "DB\\chk\\functions\\chk_check_select.sql", + "Params": { + "p_check_date": "date", + "p_chk_type_id": "int", + "p_chk_status_id": "int", + "p_rr_organization_id": "text" + }, + "Returns": { + "form": "table", + "columns": { + "id": "int", + "check_date": "date", + "chk_type_id": "int", + "chk_status_id": "int", + "smev_message_id": "text", + "rr_organization_id": "text", + "params": "jsonb", + "parent_check_id": "int" + } + } + }, + "update": { + "Name": "chk.chk_check_update", + "SourceFile": "DB\\chk\\functions\\chk_check_update.sql", + "Params": { + "p_id": "int", + "p_check_date": "date", + "p_chk_type_id": "int", + "p_chk_status_id": "int", + "p_smev_message_id": "text", + "p_rr_organization_id": "text", + "p_params": "text", + "p_parent_check_id": "int" + }, + "Returns": { + "form": "scalar", + "type": "int" + } + } + } + }, + "ChkStatus": { + "Table": "chk.chk_status", + "Kind": "plain", + "Children": [], + "Functions": { + "select": { + "Name": "chk.chk_status_select", + "SourceFile": "DB\\chk\\functions\\chk_status_select.sql", + "Params": {}, + "Returns": { + "form": "table", + "columns": { + "id": "int", + "name": "text" + } + } + } + } + }, + "ChkType": { + "Table": "chk.chk_type", + "Kind": "plain", + "Children": [], + "Functions": { + "select": { + "Name": "chk.chk_type_select", + "SourceFile": "DB\\chk\\functions\\chk_type_select.sql", + "Params": {}, + "Returns": { + "form": "table", + "columns": { + "id": "int", + "name": "text" + } + } + } + } + } + } +} diff --git a/DB/readme.md b/DB/readme.md index 1473204..81aca73 100644 --- a/DB/readme.md +++ b/DB/readme.md @@ -28,4 +28,5 @@ ## План миграции 1. `psql -d <база> -f chk/init/init.sql` — создание схемы, таблиц, сидирование справочников. -2. Функции слоя — по `chk/functions.md` (все в статусе `PLAN`), реализация — отдельными задачами. +2. `psql -d <база> -f chk/functions/chk_*.sql` — создание функций слоя (спецификации: `chk/func_specs/`). +3. `psql -d <тестовая база> -f chk/tests/test_*.psql` — тесты функций (db-tst, каждая — одна транзакция с откатом). diff --git a/MT.Checks.slnx b/MT.Checks.slnx new file mode 100644 index 0000000..5e8aff3 --- /dev/null +++ b/MT.Checks.slnx @@ -0,0 +1,3 @@ + + + diff --git a/MT.Checks/Domain/ChkCheck.cs b/MT.Checks/Domain/ChkCheck.cs new file mode 100644 index 0000000..219cdda --- /dev/null +++ b/MT.Checks/Domain/ChkCheck.cs @@ -0,0 +1,38 @@ +namespace MT.Checks.Domain; + +/// +/// Проверка организаций (chk.chk_check). Колонка params хранится в jsonb, +/// в домене передаётся строкой JSON. +/// +public class ChkCheck + { + /// Идентификатор проверки. + public int Id { get; set; } + + /// День (дата) проверки. + public DateTime CheckDate { get; set; } + + /// Тип проверки — ключ chk.chk_type. + public int TypeId { get; set; } + + /// Объект справочника типов; заполняется прикладным кодом при необходимости. + public ChkType? Type { get; set; } + + /// Статус проверки — ключ chk.chk_status. + public int StatusId { get; set; } + + /// Объект справочника статусов; заполняется прикладным кодом при необходимости. + public ChkStatus? Status { get; set; } + + /// Идентификатор сообщения СМЭВ. + public string SmevMessageId { get; set; } = ""; + + /// Идентификатор организации РР. + public string RrOrganizationId { get; set; } = ""; + + /// Параметры проверки — JSON (результаты проверок, состав не фиксирован). + public string Params { get; set; } = ""; + + /// Родительская проверка; null — корневая проверка. + public int? ParentCheckId { get; set; } + } diff --git a/MT.Checks/Domain/ChkStatus.cs b/MT.Checks/Domain/ChkStatus.cs new file mode 100644 index 0000000..8122f2a --- /dev/null +++ b/MT.Checks/Domain/ChkStatus.cs @@ -0,0 +1,31 @@ +namespace MT.Checks.Domain; + +/// +/// Статус проверки (справочник chk.chk_status, фиксированные значения — см. init.sql). +/// +public class ChkStatus + { + /// Идентификатор статуса. + public int Id { get; set; } + + /// Наименование статуса проверки. + public string Name { get; set; } = ""; + } + +/// +/// Коды статусов проверок (chk.chk_status); значения — DB/chk/init/init.sql. +/// +public static class ChkStatuses + { + /// Запрос отправлен. + public const int RequestSent = 1; + + /// Ответ получен. + public const int ResponseReceived = 2; + + /// Ответ успешно обработан. + public const int ResponseProcessed = 3; + + /// Ошибка при обработке. + public const int ProcessingError = 4; + } diff --git a/MT.Checks/Domain/ChkType.cs b/MT.Checks/Domain/ChkType.cs new file mode 100644 index 0000000..6b9baef --- /dev/null +++ b/MT.Checks/Domain/ChkType.cs @@ -0,0 +1,37 @@ +namespace MT.Checks.Domain; + +/// +/// Тип проверки (справочник chk.chk_type, фиксированные значения — см. init.sql). +/// +public class ChkType + { + /// Идентификатор типа. + public int Id { get; set; } + + /// Наименование типа проверки. + public string Name { get; set; } = ""; + } + +/// +/// Коды типов проверок (chk.chk_type); значения — DB/chk/init/init.sql. +/// +public static class ChkTypes + { + /// Запрос изменений (дельты) ЕГРЮЛ. + public const int EgrulDelta = 1; + + /// Запрос изменений (дельты) ЕГРИП. + public const int EgripDelta = 2; + + /// Отправка запроса в ЕГРЮЛ. + public const int EgrulRequest = 3; + + /// Отправка запроса в ЕГРИП. + public const int EgripRequest = 4; + + /// Актуализация организации ОТ. + public const int OtActualization = 5; + + /// Актуализация организации СОУТ. + public const int SoutActualization = 6; + } diff --git a/MT.Checks/MT.Checks.csproj b/MT.Checks/MT.Checks.csproj new file mode 100644 index 0000000..18a8544 --- /dev/null +++ b/MT.Checks/MT.Checks.csproj @@ -0,0 +1,14 @@ + + + + net10.0 + enable + enable + + + + + + + + diff --git a/MT.Checks/Repository/ChkCheckRepository.cs b/MT.Checks/Repository/ChkCheckRepository.cs new file mode 100644 index 0000000..82a8c9c --- /dev/null +++ b/MT.Checks/Repository/ChkCheckRepository.cs @@ -0,0 +1,166 @@ +namespace MT.Checks.Repository + { + using Kit.Helpers.SqlHelperAbstract; + using MT.Checks.Domain; + using MT.Checks.Repository.Filter; + + /// + /// Репозиторий проверок организаций: чтение и запись через хранимые функции схемы chk. + /// Транзакцию из нескольких вызовов собирает вызывающий код. + /// + public interface IChkCheckRepository + { + /// Создаёт проверку (chk.chk_check_insert); возвращает id новой записи. + int Insert(ChkCheck item); + + /// Записывает проверку целиком по Id (chk.chk_check_update); + /// возвращает число записанных строк. Изменяемый объект читается заранее + /// методом Get (read-modify-write). + int Update(ChkCheck item); + + /// Удаляет проверку по Id (chk.chk_check_delete); + /// возвращает число удалённых строк. + int Delete(int id); + + /// Выбирает проверки по условиям фильтра (chk.chk_check_select); + /// null свойства фильтра = условие не применяется. + IList Select(ChkCheckFilter filter); + + /// Возвращает проверку по Id (chk.chk_check_get); null — запись не найдена. + ChkCheck? Get(int id); + } + + /// + /// Реализация: каждый метод вызывает ровно одну хранимую функцию; имена + /// параметров вызова совпадают с именами параметров функции (p_*). + /// + public class ChkCheckRepository : IChkCheckRepository + { + private readonly IConnectionString _connectionString; + + /// Подключение передаётся снаружи и не открывается репозиторием. + public ChkCheckRepository(IConnectionString connectionString) + { + _connectionString = connectionString; + } + + /// Создаёт проверку (chk.chk_check_insert); возвращает id новой записи. + public int Insert(ChkCheck item) + { + return _connectionString.PrepareExecute("chk.chk_check_insert") + .AsStoredProcedure() + .AddParameter("p_check_date", item.CheckDate) + .AddParameter("p_chk_type_id", item.TypeId) + .AddParameter("p_chk_status_id", item.StatusId) + .AddParameter("p_smev_message_id", item.SmevMessageId) + .AddParameter("p_rr_organization_id", item.RrOrganizationId) + .AddParameter("p_params", item.Params) + .AddParameterNullable("p_parent_check_id", item.ParentCheckId) + .ExecuteScalar(); + } + + /// Записывает проверку целиком по Id (chk.chk_check_update); + /// возвращает число записанных строк. Изменяемый объект читается заранее + /// методом Get (read-modify-write). + public int Update(ChkCheck item) + { + if (item.Id == 0) + { + throw new ArgumentException("ChkCheckRepository.Update: не задан идентификатор записи (Id = 0)"); + } + + return _connectionString.PrepareExecute("chk.chk_check_update") + .AsStoredProcedure() + .AddParameter("p_id", item.Id) + .AddParameter("p_check_date", item.CheckDate) + .AddParameter("p_chk_type_id", item.TypeId) + .AddParameter("p_chk_status_id", item.StatusId) + .AddParameter("p_smev_message_id", item.SmevMessageId) + .AddParameter("p_rr_organization_id", item.RrOrganizationId) + .AddParameter("p_params", item.Params) + .AddParameterNullable("p_parent_check_id", item.ParentCheckId) + .ExecuteScalar(); + } + + /// Удаляет проверку по Id (chk.chk_check_delete); + /// возвращает число удалённых строк. + public int Delete(int id) + { + if (id == 0) + { + throw new ArgumentException("ChkCheckRepository.Delete: не задан идентификатор записи (id = 0)"); + } + + return _connectionString.PrepareExecute("chk.chk_check_delete") + .AsStoredProcedure() + .AddParameter("p_id", id) + .ExecuteScalar(); + } + + /// Выбирает проверки по условиям фильтра (chk.chk_check_select); + /// null свойства фильтра = условие не применяется. + public IList Select(ChkCheckFilter filter) + { + filter ??= new ChkCheckFilter(); + + return _connectionString.PrepareExecute("chk.chk_check_select") + .AsStoredProcedure() + .AddParameterNullable("p_check_date", filter.CheckDate) + .AddParameterNullable("p_chk_type_id", filter.TypeId) + .AddParameterNullable("p_chk_status_id", filter.StatusId) + .AddParameterNullable("p_rr_organization_id", filter.RrOrganizationId) + .AddConverter((record, list) => + { + var entity = new ChkCheck + { + Id = record.Get("id"), + CheckDate = record.Get("check_date"), + TypeId = record.Get("chk_type_id"), + StatusId = record.Get("chk_status_id"), + SmevMessageId = record.GetN("smev_message_id") ?? string.Empty, + RrOrganizationId = record.GetN("rr_organization_id") ?? string.Empty, + Params = record.GetN("params") ?? string.Empty, + ParentCheckId = record.GetN("parent_check_id"), + }; + list.Add(entity); + }) + .ExecuteSelectMany() + .ToList(); + } + + /// Возвращает проверку по Id (chk.chk_check_get); null — запись не найдена. + public ChkCheck? Get(int id) + { + if (id == 0) + { + throw new ArgumentException("ChkCheckRepository.Get: не задан идентификатор записи (id = 0)"); + } + + IList result = _connectionString.PrepareExecute("chk.chk_check_get") + .AsStoredProcedure() + .AddParameter("p_id", id) + .AddConverter((record, list) => + { + var entity = new ChkCheck + { + Id = record.Get("id"), + CheckDate = record.Get("check_date"), + TypeId = record.Get("chk_type_id"), + StatusId = record.Get("chk_status_id"), + SmevMessageId = record.GetN("smev_message_id") ?? string.Empty, + RrOrganizationId = record.GetN("rr_organization_id") ?? string.Empty, + Params = record.GetN("params") ?? string.Empty, + ParentCheckId = record.GetN("parent_check_id"), + }; + list.Add(entity); + }) + .ExecuteSelectMany() + .ToList(); + + return result.Count > 0 ? result[0] : null; + } + } + } + + + diff --git a/MT.Checks/Repository/ChkStatusRepository.cs b/MT.Checks/Repository/ChkStatusRepository.cs new file mode 100644 index 0000000..fecbed1 --- /dev/null +++ b/MT.Checks/Repository/ChkStatusRepository.cs @@ -0,0 +1,43 @@ +namespace MT.Checks.Repository + { + using Kit.Helpers.SqlHelperAbstract; + using MT.Checks.Domain; + + /// Репозиторий справочника статусов проверок (только чтение). + public interface IChkStatusRepository + { + /// Возвращает все строки справочника (chk.chk_status_select). + IList Select(); + } + + /// Реализация: каждая выборка — вызов chk.chk_status_select. + public class ChkStatusRepository : IChkStatusRepository + { + private readonly IConnectionString _connectionString; + + /// Подключение передаётся снаружи и не открывается репозиторием. + public ChkStatusRepository(IConnectionString connectionString) + { + _connectionString = connectionString; + } + + /// Возвращает все строки справочника (chk.chk_status_select). + public IList Select() + { + return _connectionString.PrepareExecute("chk.chk_status_select") + .AsStoredProcedure() + .AddConverter((record, list) => + { + var entity = new ChkStatus + { + Id = record.Get("id"), + Name = record.Get("name"), + }; + list.Add(entity); + }) + .ExecuteSelectMany() + .ToList(); + } + } + } + diff --git a/MT.Checks/Repository/ChkTypeRepository.cs b/MT.Checks/Repository/ChkTypeRepository.cs new file mode 100644 index 0000000..8e053d3 --- /dev/null +++ b/MT.Checks/Repository/ChkTypeRepository.cs @@ -0,0 +1,43 @@ +namespace MT.Checks.Repository + { + using Kit.Helpers.SqlHelperAbstract; + using MT.Checks.Domain; + + /// Репозиторий справочника типов проверок (только чтение). + public interface IChkTypeRepository + { + /// Возвращает все строки справочника (chk.chk_type_select). + IList Select(); + } + + /// Реализация: каждая выборка — вызов chk.chk_type_select. + public class ChkTypeRepository : IChkTypeRepository + { + private readonly IConnectionString _connectionString; + + /// Подключение передаётся снаружи и не открывается репозиторием. + public ChkTypeRepository(IConnectionString connectionString) + { + _connectionString = connectionString; + } + + /// Возвращает все строки справочника (chk.chk_type_select). + public IList Select() + { + return _connectionString.PrepareExecute("chk.chk_type_select") + .AsStoredProcedure() + .AddConverter((record, list) => + { + var entity = new ChkType + { + Id = record.Get("id"), + Name = record.Get("name"), + }; + list.Add(entity); + }) + .ExecuteSelectMany() + .ToList(); + } + } + } + diff --git a/MT.Checks/Repository/Filter/ChkCheckFilter.cs b/MT.Checks/Repository/Filter/ChkCheckFilter.cs new file mode 100644 index 0000000..580d094 --- /dev/null +++ b/MT.Checks/Repository/Filter/ChkCheckFilter.cs @@ -0,0 +1,19 @@ +namespace MT.Checks.Repository.Filter; + +/// +/// Условия выборки проверок; null свойства = условие не применяется. +/// +public class ChkCheckFilter + { + /// День (дата) проверки. + public DateTime? CheckDate { get; set; } + + /// Тип проверки (chk.chk_type.id). + public int? TypeId { get; set; } + + /// Статус проверки (chk.chk_status.id). + public int? StatusId { get; set; } + + /// Идентификатор организации РР. + public string? RrOrganizationId { get; set; } + } diff --git a/Plan/backup/2026-09-08/dal_report.md b/Plan/backup/2026-09-08/dal_report.md new file mode 100644 index 0000000..8cdd44b --- /dev/null +++ b/Plan/backup/2026-09-08/dal_report.md @@ -0,0 +1,90 @@ +# Отчёт генерации DAL (проект_dal) — прогон 2026-09-08, завершён БЛОКИРОВКОЙ на этапе 3 + +## Статус + +| Этап | Статус | +|---|---| +| 0_scan (разведка) | done | +| 1_rules (правила MCP) | done | +| 2_spec (спецификации тестов) | done — утверждены пользователем | +| 3_code (генерация) | **blocked** — конфликт транспорта (см. ниже) | +| 4_validate | не выполнялся | +| 5_report/DONE | не выполнялся | + +## Версии правил + +- Каталог MCP standarts-catalog: **1.2** (= rules-baseline.json 1.2). +- Области: c-dal 1.1, d-tst 1.1, c-tst 1.1, c-cs 1.1, db-m 1.1, db-s 1.1 (все + от 2026-09-04; составы правил совпали с baseline). Дополнительно загружены: + db-func 1.1, db-tst 1.1, c-dm 1.1, d-dev 1.1, d-db 1.1. +- `validate-rules` не требовался (расхождений нет). + +## Причина блокировки + +Транспорт `Kit.Helpers` (`SqlHelperPostgres.UpdateParamNames`) строит вызов +хранимой функции с именованными аргументами `_<имя> => :_<имя>`, добавляя `_` +к ключу параметра. Все функции экосистемы Kit (course, partner) объявлены с +параметрами `_<имя>` (_guid, _partner_id). Функции слоя chk объявлены по +правилу **db-func-5** с префиксом `p_` (p_check_date, p_id, ...) — такой вызов +через Kit.Helpers невозможен (`_p_check_date` ≠ `p_check_date` → «function +does not exist»). + +**Решение пользователя (2026-09-08):** стандарт db-func-5 сохраняется (`p_`); +генерация DAL заблокирована до доработки Kit.Helpers (администратор Kit): +варианты — политика именования параметров в транспорте (не добавлять `_`, +если ключ уже имеет префикс `p_`/`_`) либо иной согласованный механизм. + +## Слой БД (создано в этом прогоне, статус DEV / «Функция»=CODE) + +- `DB/chk/functions/*.sql` — 7 функций: chk_type_select, chk_status_select, + chk_check_insert, chk_check_update, chk_check_delete, chk_check_select, + chk_check_get (db-func-2..8; db-m-3/4/5/8; db-s-8). +- `DB/chk/func_specs/*.md` — 7 спецификаций с тест-планами (d-db-9). +- `DB/chk/tests/test_*.psql` — 7 psql-тестов, нумерация = строкам спецификаций + (db-tst-1..3, db-tst-6). **Не выполнялись** — нужна тестовая база PostgreSQL: + `psql -d <база> -f DB/chk/tests/test_*.psql`. +- `DB/chk/functions.md` — статусы обновлены; `tables.md`, `DB/readme.md` — синхронизированы. +- `db/manifest.json` — FRESH, 3 сущности (ChkCheck CRUD; ChkType/ChkStatus — select). + +## Решение C# (создано) + +- `MT.Check.slnx`; `MT.Check.Core.Dal` (net10.0; ProjectReference на + `I:\Gitea\Kit\Kit.Core\Kit.Core\Kit.Core.csproj`); `MT.Check.Core.Dal.Tests` (xunit). +- Домен: `Chk/Domain/ChkType.cs` (+ChkTypes), `ChkStatus.cs` (+ChkStatuses), + `ChkCheck.cs` (c-dm-1..11; chk_type_id → TypeId и т.п.). +- Репозитории-заготовки: `Chk/Repository/ChkCheckRepository.cs` (Insert/Update/ + Delete/Select/Get), `ChkTypeRepository.cs`, `ChkStatusRepository.cs` (Select — + c-dal-2: системные справочники только чтение); фильтр + `Chk/Repository/Filter/ChkCheckFilter.cs` (c-dal-14/15). +- Тест-хелперы: `Tests/Helpers/ConnectionStringTest.cs`, `DataTableAssertExtensions.cs` + (по шаблонам навыка). +- Имена: решение MT.Check, проект MT.Check.Core.Dal — заданы пользователем + (умолчание от имени папки отклонено из-за кириллической «С» в «MT.Сhecks»). + +## Результаты build/test/check + +- `dotnet build MT.Check.slnx` — **0 ошибок** (предупреждения NU1903/CS — из Kit.Core, вне рамок). +- `dotnet test MT.Check.Core.Dal.Tests` — 0 тестов, успешно (тела и тесты не генерированы — блокировка). +- DalTools `check` — **0 нарушений** (c-dal/c-tst). + +## Методы → функции (проект, тела не реализованы) + +| Метод | Функция | Спецификация тестов | +|---|---|---| +| ChkCheckRepository.Insert/Update/Delete/Select/Get | chk.chk_check_insert/update/delete/select/get | 1-docs/2-core/chk/repository_tests/ChkCheckRepository.md (13 тестов) | +| ChkTypeRepository.Select | chk.chk_type_select | ChkTypeRepository.md (2 теста) | +| ChkStatusRepository.Select | chk.chk_status_select | ChkStatusRepository.md (2 теста) | + +## Возобновление (после доработки Kit.Helpers) + +1. `dotnet run --project -- show-state` → продолжить с 3_code. +2. Наполнить тела методов (транспорт fluent, p_-параметры) и тесты 1:1 + спецификациям; статусы «Репозиторий» → CODE; build/test/check; отчёт; DONE. + +## Неопределённости / допущения + +- Имя решения «MT.Check» — свободный выбор пользователя (папка содержит кириллицу). +- Kit.Helpers подключён ProjectReference на внешний репозиторий (NuGet-пакета нет). +- Имена сущностей в манифесте: ChkType/ChkStatus/ChkCheck (ToPascal от chk_*). +- psql-тесты функций не исполнялись (нет доступа к тестовой БД в этом прогоне). +- Задача «Проверка» БД-функций (db-tst) — исполнение на тестовой базе, отдельный шаг. diff --git a/Plan/dal_report.md b/Plan/dal_report.md new file mode 100644 index 0000000..c75b05b --- /dev/null +++ b/Plan/dal_report.md @@ -0,0 +1,99 @@ +# Отчёт генерации DAL (навык project_dal) — прогон 2026-09-08 + +## Статус этапов + +| Этап | Статус | +|---|---| +| 0_scan (разведка) | done | +| 1_rules (правила MCP) | done | +| 2_spec (спецификации тестов) | done — утверждены пользователем | +| 3_code (генерация) | done (тесты не создаются — решение пользователя) | +| 4_validate | done — build 0 ошибок; check: 1 известный false positive (см. ниже) | +| 5_report | done — ожидание утверждения DONE | + +## Версии правил + +- Каталог MCP standarts-catalog: **1.2** (= rules-baseline.json 1.2); расхождений + нет, `validate-rules` не требовался. +- Использованные области: c-dal 1.1, d-tst 1.1, c-tst 1.1, c-cs 1.1, db-m 1.1, + db-s 1.1, db-func 1.1, db-tst 1.1, c-dm 1.1, d-dev 1.1, d-db 1.1 (все 2026-09-04). + +## Решение (проект `MT.Checks`) + +- `MT.Checks.slnx` → проект `MT.Checks\MT.Checks.csproj` (net10.0), + ProjectReference на `I:\Gitea\Kit\Kit.Core\Kit.Core\Kit.Core.csproj` (Kit.Helpers). +- Тестового проекта нет — решение пользователя «без тестов совсем» (2026-09-08). + +### Файлы + +| Файл | Содержимое | +|---|---| +| `Domain\ChkCheck.cs` | сущность проверки (+ Type/Status-справочники, Params — строка JSON) | +| `Domain\ChkType.cs` | справочник типов + константы `ChkTypes` (6 значений) | +| `Domain\ChkStatus.cs` | справочник статусов + константы `ChkStatuses` (4 значения) | +| `Repository\Filter\ChkCheckFilter.cs` | фильтр Select (null = условие не применяется) | +| `Repository\ChkCheckRepository.cs` | Insert/Update/Delete/Select/Get | +| `Repository\ChkTypeRepository.cs` | Select (справочник — только чтение) | +| `Repository\ChkStatusRepository.cs` | Select (справочник — только чтение) | + +Неймспейсы: `MT.Checks.Domain`, `MT.Checks.Repository`, `MT.Checks.Repository.Filter` +(папка/префикс слоя `Chk` в коде не используются — решение пользователя). + +### Методы → функции БД + +| Метод | Функция | Возврат | +|---|---|---| +| `ChkCheckRepository.Insert(ChkCheck)` | `chk.chk_check_insert` | id записи | +| `ChkCheckRepository.Update(ChkCheck)` | `chk.chk_check_update` | число строк (read-modify-write; guard Id=0) | +| `ChkCheckRepository.Delete(int)` | `chk.chk_check_delete` | число строк (guard id=0) | +| `ChkCheckRepository.Select(ChkCheckFilter)` | `chk.chk_check_select` | `IList` | +| `ChkCheckRepository.Get(int)` | `chk.chk_check_get` | `ChkCheck?` (guard id=0) | +| `ChkTypeRepository.Select()` | `chk.chk_type_select` | `IList` | +| `ChkStatusRepository.Select()` | `chk.chk_status_select` | `IList` | + +Каждый метод — ровно одна хранимая функция, транспорт — fluent API Kit.Helpers +(`PrepareExecute().AsStoredProcedure().AddParameter*().AddConverter(...)`), +конвертеры таблиц ответа — inline-лямбды. + +## Слой БД (этот же прогон) + +- `DB/chk/functions/*.sql` — 7 функций (параметры `p_*` по db-func-5); +- `DB/chk/func_specs/*.md` — 7 спецификаций с тест-планами (d-db-9); +- `DB/chk/tests/test_*.psql` — 7 psql-тестов (**не исполнялись** — нужна тестовая + база: `psql -d <база> -f DB/chk/tests/test_*.psql`); +- `DB/chk/functions.md` — статусы: функции `DEV`/«Функция»=`CODE`; «Репозиторий»=`REQ` + с примечанием (unit-тесты не создаются, `CODE` по d-dev-3 неприменим). + +## Валидация + +- `dotnet build MT.Checks.slnx` — **0 ошибок** (предупреждения NU1903/CS — из Kit.Core). +- DalTools `check` (с манифестом) — **1 нарушение c-dal-1, известный false positive**: + эвристика «SQL в строке C#» сканирует текст между строковыми литералами файла и + находит слово `Select` в сигнатуре метода `Select(ChkCheckFilter)` — имя метода + обязательно по правилу c-dal-5 (имена CRUD: Insert/Update/Delete/Select/Get). + Неустранимо в коде без нарушения стандарта; SQL в строках отсутствует. + +## Спецификации тестов + +`1-docs/2-core/chk/repository_tests/` — ChkCheckRepository.md (17 тестов), +ChkTypeRepository.md, ChkStatusRepository.md — утверждены как документация +ожидаемого поведения. Тестовый код по решению пользователя не создаётся +(см. `DB/chk/functions.md`, примечание к колонке «Репозиторий»). + +## Известные ограничения и решения + +1. **Транспорт `p_` vs `_`**: Kit.Helpers строит именованные аргументы `_<имя>`, + функции chk объявлены с `p_<имя>` (db-func-5; решение пользователя — сохранить + стандарт). До доработки Kit.Helpers (администратор Kit) вызовы репозиториев + против живой БД будут давать «function ... does not exist». +2. **Имена**: решение/проект `MT.Checks` — по указанию пользователя + (в `MT.Check.Core.Dal`/`MT.Checks.Test` отказ). +3. **Без тестов**: тестовый проект отсутствует; `dotnet test` из конвейера исключён. +4. Предупреждения сборки NU1903 (уязвимые пакеты Npgsql/SqlClient и др.) — + из Kit.Core, вне рамок навыка. + +## Возобновление/завершение + +После доработки Kit.Helpers достаточно проверить вызовы на тестовой БД +(спецификации вызовов не меняются). Статусы `DONE` — после утверждения +пользователем.