fix(schema): синк дрейфа lead-region в schema.sql + guard миграции 05_31 (v8.46)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+14
-14
@@ -24,7 +24,7 @@ return new class extends Migration
|
||||
|
||||
DB::unprepared(<<<'SQL'
|
||||
-- 1. phone_ranges_imports (журнал импортов; на него FK из phone_ranges, создаём первым)
|
||||
CREATE TABLE phone_ranges_imports (
|
||||
CREATE TABLE IF NOT EXISTS phone_ranges_imports (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
imported_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
source_url TEXT NOT NULL,
|
||||
@@ -40,7 +40,7 @@ return new class extends Migration
|
||||
'Журнал импортов реестра Россвязи (idempotency по checksum_sha256, atomic-swap откат).';
|
||||
|
||||
-- 2. phone_ranges (реестр диапазонов Россвязи; SaaS-level, без RLS — публичные данные)
|
||||
CREATE TABLE phone_ranges (
|
||||
CREATE TABLE IF NOT EXISTS phone_ranges (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
def_code SMALLINT NOT NULL,
|
||||
from_num BIGINT NOT NULL,
|
||||
@@ -55,14 +55,14 @@ return new class extends Migration
|
||||
CONSTRAINT chk_phone_ranges_subject_code CHECK (subject_code IS NULL OR subject_code BETWEEN 1 AND 89),
|
||||
CONSTRAINT chk_phone_ranges_range_valid CHECK (from_num <= to_num)
|
||||
);
|
||||
CREATE INDEX idx_phone_ranges_lookup ON phone_ranges (def_code, from_num, to_num);
|
||||
CREATE INDEX IF NOT EXISTS idx_phone_ranges_lookup ON phone_ranges (def_code, from_num, to_num);
|
||||
COMMENT ON TABLE phone_ranges IS
|
||||
'Реестр диапазонов нумерации Россвязи (rossvyaz.gov.ru). Локальный fallback для LeadRegionResolver. Обновляется ежемесячным cron-импортом.';
|
||||
|
||||
GRANT SELECT ON phone_ranges, phone_ranges_imports TO crm_app_user, crm_supplier_worker;
|
||||
|
||||
-- 3. lead_region_resolution_log (SaaS-level, партиционирован по received_at, паттерн activity_log)
|
||||
CREATE TABLE lead_region_resolution_log (
|
||||
CREATE TABLE IF NOT EXISTS lead_region_resolution_log (
|
||||
id BIGSERIAL,
|
||||
supplier_lead_id BIGINT NOT NULL,
|
||||
received_at TIMESTAMPTZ NOT NULL,
|
||||
@@ -89,8 +89,8 @@ return new class extends Migration
|
||||
PRIMARY KEY (id, received_at)
|
||||
) PARTITION BY RANGE (received_at);
|
||||
|
||||
CREATE INDEX idx_lrrl_lead_id ON lead_region_resolution_log (supplier_lead_id);
|
||||
CREATE INDEX idx_lrrl_source ON lead_region_resolution_log (region_source, received_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_lrrl_lead_id ON lead_region_resolution_log (supplier_lead_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_lrrl_source ON lead_region_resolution_log (region_source, received_at);
|
||||
COMMENT ON TABLE lead_region_resolution_log IS
|
||||
'Аудит каждого резолва региона лида (источник, qc, оператор, шаг каскада). Партиции помесячно по received_at (MonthlyPartitionManager).';
|
||||
|
||||
@@ -98,26 +98,26 @@ return new class extends Migration
|
||||
GRANT SELECT ON lead_region_resolution_log TO crm_app_user;
|
||||
|
||||
-- Стартовые партиции (далее их подхватывает partitions:create-months после Task 1.2).
|
||||
CREATE TABLE lead_region_resolution_log_y2026_m05
|
||||
CREATE TABLE IF NOT EXISTS lead_region_resolution_log_y2026_m05
|
||||
PARTITION OF lead_region_resolution_log
|
||||
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
|
||||
CREATE TABLE lead_region_resolution_log_y2026_m06
|
||||
CREATE TABLE IF NOT EXISTS lead_region_resolution_log_y2026_m06
|
||||
PARTITION OF lead_region_resolution_log
|
||||
FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');
|
||||
|
||||
-- 4. supplier_leads: +4 колонки (denormalized display + persistent idempotency для retry).
|
||||
ALTER TABLE supplier_leads
|
||||
ADD COLUMN resolved_subject_code SMALLINT
|
||||
ADD COLUMN IF NOT EXISTS resolved_subject_code SMALLINT
|
||||
CHECK (resolved_subject_code IS NULL OR resolved_subject_code BETWEEN 1 AND 89),
|
||||
ADD COLUMN region_source TEXT
|
||||
ADD COLUMN IF NOT EXISTS region_source TEXT
|
||||
CHECK (region_source IN ('dadata','rossvyaz','tag','unknown')),
|
||||
ADD COLUMN dadata_qc SMALLINT,
|
||||
ADD COLUMN phone_operator TEXT;
|
||||
ADD COLUMN IF NOT EXISTS dadata_qc SMALLINT,
|
||||
ADD COLUMN IF NOT EXISTS phone_operator TEXT;
|
||||
|
||||
-- 5. deals: +2 колонки (UI-карточка + флаг подмены региона).
|
||||
ALTER TABLE deals
|
||||
ADD COLUMN phone_operator TEXT,
|
||||
ADD COLUMN region_substituted BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
ADD COLUMN IF NOT EXISTS phone_operator TEXT,
|
||||
ADD COLUMN IF NOT EXISTS region_substituted BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
SQL);
|
||||
|
||||
// Регистрация retention для lead_region_resolution_log (system_settings, 12 месяцев ≈ 365 дней).
|
||||
|
||||
+29
-1
@@ -2,7 +2,35 @@
|
||||
|
||||
**Назначение:** консолидированный журнал изменений `schema.sql`. Содержит тридцать записей в обратном хронологическом порядке (v8.33 → v8.32 → v8.31 → v8.30 → v8.29 → v8.28 → v8.27 → v8.26 → v8.25 → v8.24 → v8.23 → v8.22 → v8.21 → v8.20 → v8.19 → v8.18 → v8.17 → v8.16 → v8.15 → v8.14 → v8.13 → v8.12 → v8.11 → v8.10 → v8.9 → v8.8 → v8.7 → v8.6 → v8.5 → v8.4 → v8.3 → v8.2), как принято в keep-a-changelog.
|
||||
|
||||
**Файл схемы:** `schema.sql` (текущая версия — v8.41, консолидированная — разворачивает БД с нуля).
|
||||
**Файл схемы:** `schema.sql` (текущая версия — v8.46, консолидированная — разворачивает БД с нуля).
|
||||
|
||||
## v8.46 (2026-06-19) — Sync дрейфа: слой lead-region в schema.sql
|
||||
|
||||
Добавлен слой lead-region, отсутствовавший в `schema.sql` (на проде живёт с 31.05.2026,
|
||||
DDL держался только в дельта-миграции `2026_05_31_100000` — см. v8.40, где это сделано
|
||||
осознанно «как v8.39»). Реконсиляция дрейфа: теперь объекты есть в теле канонической
|
||||
схемы, а миграция guard-нута (`IF NOT EXISTS` / `ADD COLUMN IF NOT EXISTS`) и под
|
||||
`migrate:fresh` (schema.sql уже содержит объекты) становится no-op.
|
||||
|
||||
Источник: миграция `app/database/migrations/2026_05_31_100000_create_phone_ranges_and_resolution_log.php`.
|
||||
|
||||
**Добавлено в schema.sql:**
|
||||
|
||||
- **`phone_ranges_imports`** — журнал импортов реестра Россвязи (regular, без RLS).
|
||||
- **`phone_ranges`** — реестр диапазонов нумерации Россвязи (regular, без RLS) +
|
||||
индекс `idx_phone_ranges_lookup` + `GRANT SELECT` для `crm_app_user`/`crm_supplier_worker`.
|
||||
- **`lead_region_resolution_log`** — партиционированный (`PARTITION BY RANGE (received_at)`,
|
||||
composite PK `(id, received_at)`) аудит резолва региона лида + индексы `idx_lrrl_lead_id`,
|
||||
`idx_lrrl_source` + 2 стартовые партиции `_y2026_m05` / `_y2026_m06` +
|
||||
`GRANT SELECT, INSERT` для `crm_supplier_worker`, `GRANT SELECT` для `crm_app_user`.
|
||||
- **`supplier_leads` +4 колонки:** `resolved_subject_code` (CHECK 1..89), `region_source`
|
||||
(CHECK enum dadata/rossvyaz/tag/unknown), `dadata_qc`, `phone_operator`.
|
||||
- **`deals` +2 колонки:** `phone_operator`, `region_substituted` (NOT NULL DEFAULT FALSE).
|
||||
NB: обе отсутствовали в теле schema.sql (не только `phone_operator`).
|
||||
|
||||
**Счётчики:** таблиц `72 → 75` (regular `64 → 66`, partitioned parents `8 → 9`) /
|
||||
партиций `12 → 14` / индексов `117 → 120`. RLS-политики / функции / триггеры — без
|
||||
изменений (lead-region — SaaS-level, без RLS).
|
||||
|
||||
## v8.45 (2026-06-19) — G3: удалена таблица reminders
|
||||
|
||||
|
||||
+105
-2
@@ -1,6 +1,7 @@
|
||||
-- =============================================================================
|
||||
-- schema.sql — единая схема БД для SaaS-аналога crm.bp-gr.ru («Лидерра»)
|
||||
-- Версия: v8.45 (19.06.2026 — G3: DROP таблицы reminders (фича «Напоминания» снята с продукта). Удалены: таблица reminders, 4 индекса (idx_reminders_due/deal/tenant_user_active/tenant_active), RLS-политика tenant_isolation, COMMENT'ы; ключ "reminder" убран из DEFAULT users.notification_preferences. Счётчики: таблиц 73→72 (regular 65→64) / индексов 121→117 / RLS-политик 40→39. Миграция 2026_06_19_130000_drop_reminders_table)
|
||||
-- Версия: v8.46 (19.06.2026 — Sync дрейфа lead-region: слой миграции 2026_05_31_100000 (live на проде с 31.05) добавлен в тело schema.sql, отсутствовал ранее (v8.40 держал DDL только в дельта-миграции). Добавлены таблицы phone_ranges_imports, phone_ranges (+idx_phone_ranges_lookup, GRANT) — regular; lead_region_resolution_log — partitioned-parent (PARTITION BY received_at, +idx_lrrl_lead_id/idx_lrrl_source, +2 стартовые партиции m05/m06, GRANT). +4 колонки supplier_leads (resolved_subject_code/region_source/dadata_qc/phone_operator), +2 колонки deals (phone_operator/region_substituted). Счётчики: таблиц 72→75 (regular 64→66, partitioned parents 8→9) / партиций 12→14 / индексов 117→120. Миграция 2026_05_31_100000 (теперь guard-нута под migrate:fresh))
|
||||
-- Базовая версия: v8.45 (19.06.2026 — G3: DROP таблицы reminders (фича «Напоминания» снята с продукта). Удалены: таблица reminders, 4 индекса (idx_reminders_due/deal/tenant_user_active/tenant_active), RLS-политика tenant_isolation, COMMENT'ы; ключ "reminder" убран из DEFAULT users.notification_preferences. Счётчики: таблиц 73→72 (regular 65→64) / индексов 121→117 / RLS-политик 40→39. Миграция 2026_06_19_130000_drop_reminders_table)
|
||||
-- Базовая версия: v8.44 (19.06.2026 — G2-B: users.notification_preferences DEFAULT new_lead.email false→true (почтовый дайджест новых сделок по умолчанию; существующие пользователи дотягиваются UPDATE false→true, только живые). Структурно без изменений — правка дефолтного значения; счётчики таблиц/индексов/RLS/функций/триггеров без изменений. Миграция 2026_06_19_120000, спека docs/superpowers/specs/2026-06-19-g2b-new-lead-email-default-on-design.md)
|
||||
-- Базовая версия: v8.43 (18.06.2026 — G1/SP2 реквизиты клиента: tenant_requisites (1:1 tenants), RLS tenant isolation, лёгкие поля (тип лица/контакт/телефон) гейтят первый проект; тяжёлые (ИНН/КПП/ОГРН/юр.адрес/банк) nullable. Миграция 2026_06_18_140000, спека docs/superpowers/specs/2026-06-18-g1-sp2-requisites-gate-spec-v1.md)
|
||||
-- Базовая версия: v8.42 (18.06.2026 — G1/SP1 самозапись клиента: email_verifications +code_hash +failed_attempts (6-значный код подтверждения почты, bcrypt, TTL 15м, 5 попыток) + tenants.status расширен VARCHAR(20)→(30) (латентный дефект: CHECK допускал 21-символьное 'pending_email_confirm', колонка была 20). Счётчики таблиц/индексов/RLS/функций/триггеров без изменений. Миграция 2026_06_18_120000, спека docs/superpowers/specs/2026-06-18-g1-sp1-self-registration-email-spec-v3.md)
|
||||
@@ -17,7 +18,7 @@
|
||||
-- Базовая версия: v8.29 (22.05.2026 — webhook_log: supplier audit columns)
|
||||
-- Базовая версия: v8.28 (22.05.2026 — tenant_operations_log: журнал тенант-уровневых операций вне сделок (проекты, API-ключи, webhook URL), append-only hash-chain, P2 operational journaling closure)
|
||||
-- Базовая версия: v8.27 (21.05.2026 — drop projects.archived_at: feature архива заменена настоящим удалением с защитой по сделкам (ProjectService::delete()))
|
||||
-- Метрики: 72 базовые таблицы (64 regular + 8 partitioned parents: deals + supplier_lead_costs + 6 audit) + 12 партиций / 117 индексов / 39 RLS-политик / 5 функций / 15 триггеров
|
||||
-- Метрики: 75 базовых таблиц (66 regular + 9 partitioned parents: deals + supplier_lead_costs + 6 audit + lead_region_resolution_log) + 14 партиций / 120 индексов / 39 RLS-политик / 5 функций / 15 триггеров
|
||||
-- Базовая версия: v8.25 (19.05.2026 — supplier_manual_sync_queue: SaaS-level Tier 3 очередь резерва канала миграции проектов)
|
||||
-- Базовая версия: v8.24 (18.05.2026 — supplier_leads.vid → nullable для CSV-recovered лидов (Путь 2))
|
||||
-- Базовая версия: v8.20 (11.05.2026 — Plan 5 frontend projects UI: projects.archived_at TIMESTAMPTZ NULL для soft archive flow; tenants.limits JSONB NOT NULL DEFAULT '{}' для per-tenant project/user лимитов)
|
||||
@@ -1711,6 +1712,10 @@ CREATE TABLE deals (
|
||||
-- enrichment-сервиса). Используется для filter в §10.3 + аналитики §12.
|
||||
region_code VARCHAR(8),
|
||||
subject_code SMALLINT, -- v8.26: субъект РФ 1..89 из тега поставщика (raw_payload[tag]); NULL = вся РФ/неизвестно
|
||||
-- v8.40 (lead region resolution): оператор связи (UI-карточка) + флаг подмены
|
||||
-- региона каскадом DaData→Россвязь→tag (миграция 2026_05_31_100000).
|
||||
phone_operator TEXT,
|
||||
region_substituted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
city VARCHAR(100),
|
||||
-- v8.5 (Биз-22): простая lead scoring модель без ML.
|
||||
-- time_in_form_seconds — сколько секунд физлицо заполняло форму
|
||||
@@ -1996,6 +2001,14 @@ CREATE TABLE supplier_leads (
|
||||
recovered_from_csv_at TIMESTAMPTZ,
|
||||
deals_created_count INTEGER,
|
||||
error TEXT,
|
||||
-- v8.40 (lead region resolution): денормализованный display + persistent
|
||||
-- idempotency для retry резолва региона лида (миграция 2026_05_31_100000).
|
||||
resolved_subject_code SMALLINT
|
||||
CHECK (resolved_subject_code IS NULL OR resolved_subject_code BETWEEN 1 AND 89),
|
||||
region_source TEXT
|
||||
CHECK (region_source IN ('dadata','rossvyaz','tag','unknown')),
|
||||
dadata_qc SMALLINT,
|
||||
phone_operator TEXT,
|
||||
|
||||
CONSTRAINT chk_supplier_leads_platform
|
||||
CHECK (platform IN ('B1','B2','B3','DIRECT')),
|
||||
@@ -2018,6 +2031,96 @@ CREATE INDEX supplier_leads_recovered_from_csv_partial
|
||||
--
|
||||
-- REVOKE ALL ON supplier_leads FROM crm_app_user;
|
||||
|
||||
-- =============================================================================
|
||||
-- Lead-region (телефон → регион) — реестр Россвязи + аудит резолва
|
||||
-- v8.40 (31.05.2026), миграция 2026_05_31_100000. SaaS-level (без RLS):
|
||||
-- phone_ranges/phone_ranges_imports — публичные данные; lead_region_resolution_log
|
||||
-- — партиционированный аудит (паттерн activity_log). Порядок: imports → phone_ranges
|
||||
-- (FK на imports) → lead_region_resolution_log.
|
||||
-- =============================================================================
|
||||
|
||||
-- phone_ranges_imports (журнал импортов; на него FK из phone_ranges, создаётся первым)
|
||||
CREATE TABLE phone_ranges_imports (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
imported_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
source_url TEXT NOT NULL,
|
||||
rows_inserted INTEGER NOT NULL DEFAULT 0,
|
||||
rows_updated INTEGER NOT NULL DEFAULT 0,
|
||||
checksum_sha256 TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'in_progress'
|
||||
CHECK (status IN ('in_progress','completed','failed','rolled_back')),
|
||||
error TEXT,
|
||||
completed_at TIMESTAMPTZ
|
||||
);
|
||||
COMMENT ON TABLE phone_ranges_imports IS
|
||||
'Журнал импортов реестра Россвязи (idempotency по checksum_sha256, atomic-swap откат).';
|
||||
|
||||
-- phone_ranges (реестр диапазонов Россвязи; SaaS-level, без RLS — публичные данные)
|
||||
CREATE TABLE phone_ranges (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
def_code SMALLINT NOT NULL,
|
||||
from_num BIGINT NOT NULL,
|
||||
to_num BIGINT NOT NULL,
|
||||
operator TEXT NOT NULL,
|
||||
region TEXT NOT NULL,
|
||||
region_normalized TEXT,
|
||||
subject_code SMALLINT,
|
||||
imported_at TIMESTAMPTZ NOT NULL,
|
||||
import_id BIGINT NOT NULL REFERENCES phone_ranges_imports(id),
|
||||
CONSTRAINT chk_phone_ranges_def_code CHECK (def_code BETWEEN 300 AND 999),
|
||||
CONSTRAINT chk_phone_ranges_subject_code CHECK (subject_code IS NULL OR subject_code BETWEEN 1 AND 89),
|
||||
CONSTRAINT chk_phone_ranges_range_valid CHECK (from_num <= to_num)
|
||||
);
|
||||
CREATE INDEX idx_phone_ranges_lookup ON phone_ranges (def_code, from_num, to_num);
|
||||
COMMENT ON TABLE phone_ranges IS
|
||||
'Реестр диапазонов нумерации Россвязи (rossvyaz.gov.ru). Локальный fallback для LeadRegionResolver. Обновляется ежемесячным cron-импортом.';
|
||||
|
||||
GRANT SELECT ON phone_ranges, phone_ranges_imports TO crm_app_user, crm_supplier_worker;
|
||||
|
||||
-- lead_region_resolution_log (SaaS-level, партиционирован по received_at, паттерн activity_log)
|
||||
CREATE TABLE lead_region_resolution_log (
|
||||
id BIGSERIAL,
|
||||
supplier_lead_id BIGINT NOT NULL,
|
||||
received_at TIMESTAMPTZ NOT NULL,
|
||||
phone_masked TEXT NOT NULL,
|
||||
subject_code_resolved SMALLINT,
|
||||
subject_code_from_tag SMALLINT,
|
||||
region_source TEXT NOT NULL
|
||||
CHECK (region_source IN ('dadata','rossvyaz','tag','unknown')),
|
||||
dadata_qc SMALLINT,
|
||||
dadata_provider TEXT,
|
||||
dadata_type TEXT,
|
||||
dadata_response_masked JSONB,
|
||||
rossvyaz_matched BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
actual_subject_code SMALLINT
|
||||
CHECK (actual_subject_code IS NULL OR actual_subject_code BETWEEN 1 AND 89),
|
||||
substituted_subject_code SMALLINT
|
||||
CHECK (substituted_subject_code IS NULL OR substituted_subject_code BETWEEN 1 AND 89),
|
||||
routing_step SMALLINT
|
||||
CHECK (routing_step IS NULL OR routing_step BETWEEN 1 AND 3),
|
||||
phone_operator TEXT,
|
||||
cache_hit BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
duration_ms INTEGER,
|
||||
resolved_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (id, received_at)
|
||||
) PARTITION BY RANGE (received_at);
|
||||
CREATE INDEX idx_lrrl_lead_id ON lead_region_resolution_log (supplier_lead_id);
|
||||
CREATE INDEX idx_lrrl_source ON lead_region_resolution_log (region_source, received_at);
|
||||
COMMENT ON TABLE lead_region_resolution_log IS
|
||||
'Аудит каждого резолва региона лида (источник, qc, оператор, шаг каскада). Партиции помесячно по received_at (MonthlyPartitionManager).';
|
||||
|
||||
GRANT SELECT, INSERT ON lead_region_resolution_log TO crm_supplier_worker;
|
||||
GRANT SELECT ON lead_region_resolution_log TO crm_app_user;
|
||||
|
||||
-- Стартовые партиции (далее их подхватывает partitions:create-months / MonthlyPartitionManager).
|
||||
CREATE TABLE lead_region_resolution_log_y2026_m05
|
||||
PARTITION OF lead_region_resolution_log
|
||||
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
|
||||
CREATE TABLE lead_region_resolution_log_y2026_m06
|
||||
PARTITION OF lead_region_resolution_log
|
||||
FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- supplier_lead_deliveries — замок «одна поставка одному клиенту = один раз»
|
||||
-- (Billing v2 Spec B). Ключ по поставке (supplier_lead_id), НЕ по телефону —
|
||||
|
||||
Reference in New Issue
Block a user