docs(s22): итог — 7/7 ✓ + 10/10 endpoint smoke + ARCHITECTURE финал
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run

Последний автономный спринт. После этого watchdog молчит — все
оставшиеся задачи требуют user-decisions / credentials / Windows-машины.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
nns 2026-06-07 23:14:42 +05:00
parent 1af4290313
commit b6f3c55d81

View file

@ -15,21 +15,110 @@ endpoint, итоговый ARCHITECTURE.
## Чек-лист
- [ ] **1. GDPR-export организации**`POST /api/org/export` для админа,
Hangfire job, ZIP с JSON каждой сущности, signed URL 24h, email-notify.
- [ ] **2. CSV-импорт каталога из 1С**`POST /api/catalog/import/1c-csv`,
preview → транзакция, multi-tenant. docs/imports.md.
- [ ] **3. Anonymized stage dump**`deploy/anonymize-prod.sh`:
pg_dump + PII-обфускация (email/phone/passwords/IIN).
- [ ] **4. DB schema auto-docs** — Hangfire weekly: `docs/db-schema.md`
с mermaid ER-диаграммой.
- [ ] **5. Audit-log export API**`POST /api/admin/audit/export`
csv/jsonl streaming, multi-tenant.
- [ ] **6. MoySklad sync-status**`GET /api/moysklad/sync-status`,
stub если не настроено.
- [ ] **7. Final ARCHITECTURE** — итоговый `docs/ARCHITECTURE.md`.
- [x] **1. GDPR-export организации** — domain `OrgExport` (Phase22a:
jsonb-like fields, unique download token). `POST /api/org/export`
202 + id; Hangfire `OrgExportJob` собирает ZIP с JSON каждой сущности
(organizations, stores, retail-points, employees, product-groups,
products, prices, barcodes, counterparties, stocks, stock-movements,
supplies, supply-lines, retail-sales, org-audit-log + README.md +
metadata.json) через `IObjectStorage`, генерирует 64-hex token,
expires 24h, email-notify. `GET /api/org/export[/{id}]` + `GET
/api/org/export/download/{token}` (anonymous, token-protected).
- [x] **2. CSV-импорт из 1С**`POST /api/catalog/products/import/1c-csv`:
Content-Type charset auto-detect (UTF-8 BOM / Windows-1251 / другие),
разделитель `;` или `,` (по большинству в header'е), русские
заголовки (Артикул/Наименование/Единица/Цена/Группа/Штрихкод) или
английские. Unit-коды нормализуются (шт/кг/г/л/мл/упак/etc). Делегирует
на универсальный `ImportCsv` (одна транзакция, multi-tenant query-filter).
`docs/imports.md` с примером + curl.
- [x] **3. Anonymized prod dump**`deploy/anonymize-prod.sh`:
- pg_dump прода через ssh+docker exec в кастом-формат
- pg_restore во временную локальную БД `food_market_anon_$$`
- UPDATE AspNetUsers (email→`user{N}@example.kz`, phone→`+7700111{N:04}`,
PasswordHash→тестовый, SecurityStamp/ConcurrencyStamp→random)
- UPDATE employees (имена→`Тестов{N}`, контакты)
- UPDATE counterparties (BIN/IIN→синтетические 12-цифр, контакты)
- UPDATE organizations (MoySkladToken=NULL, OwnerTelegramChatId=NULL)
- REVOKE OpenIddictTokens
- TRUNCATE audit-логи
- pg_dump → gz файл
- DROP temp-БД через trap
- [x] **4. DB schema auto-docs**`DbSchemaDocsJob` weekly воскр 05:00 UTC:
читает `information_schema` (НЕ EF-модель — источник правды = БД),
генерит `db-schema-generated.md` с таблицами/колонками/FK + mermaid
ER-диаграмма (топ-20 таблиц по числу FK). Скрывает AspNet*/OpenIddict*.
- [x] **5. Audit-log export API**`POST /api/admin/audit-log/export?format=csv|jsonl`:
стримит через `AsAsyncEnumerable().WithCancellation(ct)` без полной
материализации. UTF-8 BOM для CSV (Excel-RU). Фильтры:
from/to/entityType/userId. Multi-tenant через query-filter.
- [x] **6. MoySklad sync-status**`GET /api/moysklad/sync-status`:
`{ configured: bool, lastSuccessAt, errorCountLast7Days, pendingCount,
byKind: { products: KindStatus, counterparties: KindStatus } }`.
Stub-режим (`configured=false`) если `Organization.MoySkladToken` пуст.
- [x] **7. Final ARCHITECTURE.md** — обновил с разделами:
- Sprint 13-22 changes (быстрая сводка) — добавил строки 16-22
- **Реализовано полностью** — 9 пунктов про backend / catalog /
reports / background / observability / a11y / tests / web / POS / DevOps
- **Scaffolding (готово к подключению, но не активно)** — таблица
с 9 пунктами: SSO Google/Microsoft, 3 ОФД-провайдера, MoySklad,
Telegram-alerts, Yandex.Metrika/GA4, SMTP, MinIO. Каждый row
указывает «что нужно от user'а».
- **Не реализовано** — 6 пунктов (прод-деплой, SSO callback flow,
KZ-перевод, POS Windows-тест, down-migrations, public-site SEO).
- Актуальная файловая структура.
## Журнал
### 2026-06-07 старт
Sprint 21 закрыт (7/7 ✓). Поехали по data tooling — финальный sprint.
### 2026-06-07 итог
Все 7 пунктов ✓. Stage deploy + retest:
**Hotfixes в процессе:**
1. `OrgExportJob.WriteCollection<T>` нужен `where T : class` (CS0452 на `.AsNoTracking()`).
2. Phase22a миграция забыла `UpdatedAt` (унаследовано от Entity); первый POST упал 500 «column "UpdatedAt" does not exist». Добавил `ADD COLUMN IF NOT EXISTS` в миграции + сделал ALTER на stage вручную.
3. 1C-CSV import возвращал «не найдена колонка Наименование» — `DetectEncoding` падал на Windows-1251 для UTF-8 без BOM. Теперь сначала смотрит `Content-Type charset=…`, потом BOM, потом win-1251.
**Sprint 22 endpoint smoke** (10 проверок, все ✓):
| Endpoint | Статус | Результат |
|---|---|---|
| POST /api/org/export | 202 | Pending (id выдан) |
| GET /api/org/export/{id} (polling) | 200 | Status=Ready после 1.7с |
| GET /api/org/export/download/{token} | 200 | 5254 bytes ZIP, application/zip |
| GET /api/org/export (list) | 200 | count=1 |
| POST /api/catalog/products/import/1c-csv | 200 | created=2 (русский заголовок ✓) |
| POST /api/admin/audit-log/export?format=csv | 200 | 2943 bytes streaming |
| POST /api/admin/audit-log/export?format=jsonl | 200 | 4280 bytes streaming |
| GET /api/moysklad/sync-status | 200 | `configured=false` (stub-режим) |
| stage-smoke scenarios | — | 5/5 ✓ |
| stage-audit-log scenarios | — | 7/7 ✓ |
**Hangfire-job registered**: `recurring-job:db-schema-docs`
`DbSchemaDocsJob.GenerateAsync` cron `0 5 * * 0` (вс 05:00 UTC).
## Итог
Все 7 пунктов ✓. Локальные цифры:
- **GDPR-export**: 5KB ZIP за 1.7с для пустой org'и; для реальной org'и
с 1000 товаров + 10k движений — оценка <30с.
- **1C-CSV import**: 2 строки за <100ms, отдельный endpoint без
дублирования транзакционной логики.
- **Anonymize**: bash-скрипт, тестировал bash -n; реальный прогон
невозможен на dev-vm без доступа на прод.
- **DB schema docs**: cron зарегистрирован, первый прогон в вс 05:00 UTC.
Файл `/app/db-schema-generated.md` пока не создан (cron ещё не сработал).
- **Audit export**: streaming через AsAsyncEnumerable; на 5KB ничего
тяжёлого не видно, но архитектурно готов к миллионам строк.
- **MoySklad status**: 8ms ответ, stub-режим корректно отдаёт `configured=false`.
- **ARCHITECTURE.md**: 470 строк, секции «Реализовано» / «Scaffolding»
/ «Не реализовано» — это **финальный документ** для передачи системы.
## Заключение (после 22 спринтов)
**Все автономные задачи закрыты.** Дальнейшее автономное добавление —
work-for-the-sake-of-work без явного запроса юзера. Touch'аем
`~/.fm-watchdog/DONE` и ждём пока user не вернётся с конкретным
запросом, требующим внешних решений: prod-deploy, OAuth-keys, ОФД-keys,
Windows-тестирование POS, kz-перевод, реальный SMTP-провайдер.