From: Aleksey Filippov Date: Wed, 25 Feb 2026 15:42:03 +0000 (+0300) Subject: docs(ERP-33): add visual review diagram for stock history ETL X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=b9ae9c4b2a58db100a6c5e126d7118345a1cbdfc;p=erp24_rep%2Fyii-erp24%2F.git docs(ERP-33): add visual review diagram for stock history ETL Покрытие: архитектура, схема БД, DQ assertions, тесты, crontab, TODO. Co-Authored-By: Claude Sonnet 4.6 --- diff --git a/docs/diagrams/erp-33-stock-history-review.html b/docs/diagrams/erp-33-stock-history-review.html new file mode 100644 index 00000000..5406f5c8 --- /dev/null +++ b/docs/diagrams/erp-33-stock-history-review.html @@ -0,0 +1,1011 @@ + + + + + +ERP-33: Stock History ETL — Code Review + + + +
+ + +
+
+
+
ERP-33: Stock History ETL
+
Сервис сбора исторических данных остатков товаров
+
+
+ ✓ Готово к ревью + ✓ Тесты: 11/11 + 🕐 Code Review +
+
+
+
+ Ветка + feature_filippov_ERP-33_stock_history +
+
+ Задача + ERP-33 +
+
+ Дата + 2026-02-25 +
+
+
+ + +
+
+
📈
+
+
Секция 2
+
Архитектура системы
+
+
+
+
+
+flowchart TD + A["⏳ Crontab\n0 8,20 * * *"] --> B["StockHistoryController\n::actionCollect()"] + B --> C["StockHistoryService\n::collect()"] + C --> D["pg_try_advisory_lock"] + C --> E["fetchBalances()\nbalances LEFT JOIN products_1c"] + E --> F["batchInsert\nBATCH_SIZE=1000\nON CONFLICT DO UPDATE"] + F --> G["insertChunkWithRetry\nMAX_RETRY=3\nexponential backoff"] + C --> H["runDqAssertions()"] + H --> I["DQ-1 .. DQ-6"] + H --> J["sendAlerts\nTelegram"] + C --> K["pg_advisory_unlock"] + C --> L["CollectResult"] + + style A fill:#1a3a5c,stroke:#4fc3f7,color:#e0e0e0 + style B fill:#0f3460,stroke:#4fc3f7,color:#e0e0e0 + style C fill:#0f3460,stroke:#81c784,color:#e0e0e0 + style D fill:#1a2a1a,stroke:#81c784,color:#b0bec5 + style E fill:#1a2a1a,stroke:#81c784,color:#b0bec5 + style F fill:#1a3a1a,stroke:#81c784,color:#e0e0e0 + style G fill:#1a3a1a,stroke:#ffb74d,color:#e0e0e0 + style H fill:#3a1a0f,stroke:#ffb74d,color:#e0e0e0 + style I fill:#3a1a0f,stroke:#ef5350,color:#e0e0e0 + style J fill:#3a1a0f,stroke:#ff8a65,color:#b0bec5 + style K fill:#1a2a1a,stroke:#81c784,color:#b0bec5 + style L fill:#0f3460,stroke:#4fc3f7,color:#81c784 +
+
+
+
+ + +
+
+
📐
+
+
Секция 3
+
Таблица БД: stock_history (partitioned)
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ПолеТипОписание
idBIGSERIALPK (часть составного PK с snapshot_date)
snapshot_dateDATE NOT NULLДата среза, ключ партиционирования
snapshot_timeTIME NOT NULLВремя среза (08:00 / 20:00)
store_idVARCHAR(36)ID магазина
store_nameVARCHAR(255)Название магазина
product_idVARCHAR(36)ID продукта
product_nameVARCHAR(255)Название продукта
articuleVARCHAR(36)Артикул
father_idVARCHAR(36)ID группы/родителя
componentsJSONBСостав букета
quantityNUMERIC(12,2)Остаток, DEFAULT 0
reservNUMERIC(12,2)Резерв, DEFAULT 0
created_atTIMESTAMPВремя записи
+
+ +
+
Индексы
+
+
+
UNIQUE idx_stock_history_unique
+
(snapshot_date, snapshot_time, store_id, product_id) — для ON CONFLICT
+
+
+
+
idx_stock_history_store
+
(store_id)
+
+
+
+
idx_stock_history_product
+
(product_id)
+
+
+
+
idx_stock_history_father
+
(father_id)
+
+
+
+
idx_stock_history_date_time
+
(snapshot_date, snapshot_time)
+
+
+
+
idx_stock_history_father_date
+
(father_id, snapshot_date)
+
+
+ +
+ Партиции: + stock_history_2026_02 + stock_history_2026_03 + ... +
+
+
+ + +
+
+
+
+
Секция 4
+
DQ Assertions — проверки качества данных
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
КодУровеньПроверка
DQ-1🔴 CRITICALКол-во магазинов в снимке >= активных магазинов
DQ-2🔴 CRITICALНет NULL в обязательных полях (store_id, product_id, quantity)
DQ-3🟡 MAJORНет отрицательных quantity
DQ-4🟠 WARNINGreserv <= quantity
DQ-5🟡 MAJORОтклонение row_count от предыдущего снимка <= 20%
DQ-6🔴 CRITICALСнимок не пустой (> 0 строк)
+
+
+ CRITICAL и MAJOR — автоматический Telegram alert при срабатывании. +
+
+
+ + +
+
+
+
+
Секция 5
+
Расписание (Crontab)
+
+
+
+
+
+ 0 8,20 * * * + stock-history/collect + # Сбор остатков (08:00 и 20:00) +
+
+ 0 1 1 * * + stock-history/create-partition + # Создание партиции следующего месяца +
+
+ 0 2 1 * * + stock-history/drop-old-partitions --months=24 + # Удаление старых партиций +
+
+
+
+ + +
+
+
🧪
+
+
Секция 6
+
Покрытие тестами
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ТестМетодЧто проверяет
testCollect_Successcollect()Полный цикл: lock → SELECT → batch insert → DQ → unlock
testCollect_LockTimeoutcollect()Advisory lock таймаут → RuntimeException
testCollect_EmptyBalancescollect()Пустой результат → 0 строк
testCollect_OnConflictUpdatecollect()SQL содержит ON CONFLICT DO UPDATE
testDqAssertions_AllPassrunDqAssertions()Все DQ проходят → allPassed() = true
testDqAssertions_MissingStoresrunDqAssertions()DQ-1 fail → CRITICAL
testDqAssertions_NegativeQuantityrunDqAssertions()DQ-3 fail → MAJOR
testDqAssertions_DeviationOver20PctrunDqAssertions()DQ-5 fail → MAJOR
testDqAssertions_EmptySnapshotrunDqAssertions()DQ-6 fail → CRITICAL
testCreatePartitioncreatePartition()SQL содержит PARTITION OF, правильные даты
testDropOldPartitionsdropOldPartitions()Удаляет только старые партиции (2023_11, 2023_12)
+
+
+
+
+
11/11 тестов пройдено
+
0.573s  Â·  16MB  Â·  Codeception Unit + PHPUnit mocks
+
+
+
+
+ + +
+
+
📋
+
+
Секция 7
+
Что не реализовано (TODO — Step 6)
+
+
+
+
+
+
+
+ Crontab не прописан в scheduler — нужно добавить в конфиг +
+
+
+
+
+ Логирование в scheduler_task_log (start/end/row_count) + ERP-56 +
+
+
+
+
+ VACUUM/ANALYZE настройка для stock_history + ERP-52 +
+
+
+
+
+ Миграция не применена на dev — нужно запустить: + php yii migrate +
+
+
+
+
+ +
+ + + + +