# Sprint 22 — data tooling: export/import, schema docs, anonymized dump Цель: финальный спринт автономной работы. GDPR-ready export, 1С-import, схема-документация, audit-export, anonymized stage dump, MoySklad status endpoint, итоговый ARCHITECTURE. Старт: 2026-06-07 (после Sprint 21). Исполнитель: Claude Opus 4.7. **Последний автономный спринт — после очередь пустая, watchdog молчит.** ## Принципы - Все export/import — multi-tenant строго (нельзя выгрузить чужое). - Долгие операции — Hangfire job + status polling (не блокировать HTTP). - НЕ трогать: `global.json`, prod admin.food-market.kz, POS WPF. ## Чек-лист - [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` нужен `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-провайдер.