merge: подтянул main после выката починок модерации Яндекса
Мой коммит уехал в main отдельно — пересадкой на актуальную вершину и оттуда на боевой. Возвращаю main к себе, чтобы ветка не разъезжалась. Пришло 9 коммитов, из них главное — работа соседней сессии по воронке продаж, уже влитая в main и запушенная. Конфликта два, оба в общих машинных файлах: - cspell-words.txt — сторону не выбирал, объединил. Все слова обеих сторон на месте; убрана ровно одна строка — мой же дубль «админский», который прошлый коммит добавил дважды. Проверено сравнением с версией до слияния: другого отличия нет; - docs/observer/STATUS.md — машинный файл наблюдателя со столбиком часов процессов, взята своя версия, его всё равно переписывает хук. Замечание на будущее: патч «минимум площадки считается по длине периода» живёт в двух коммитах —948807507у меня и в ветке робота,9df7846fdв main. В main уехала только вторая копия, main чист. Когда моя ветка и ветка робота пойдут в main, git встретит этот патч второй раз. Статанализ остановил слияние, и это была НЕ ложная тревога: файл-эталон phpstan-baseline.neon склеился из двух версий и перестал соответствовать коду. Все 16 ошибок — в чужих файлах воронки продаж, пришедших из main байт в байт, известного ложного класса про Pest. Эталон из main один не подошёл: всплыли записи, нужные этой ветке. Пересобрал по проектной процедуре в два шага, phpstan.neon вернул на место. Итог 0 ошибок, правка эталона мелкая: +43 -31. Моих файлов среди добавленных записей нет ни одного. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Воронка «Потенциальные клиенты»: корзина и журнал движений.
|
||||
*
|
||||
* 1) stage += 'trash' («Корзина» — карточка, с которой больше не работаем).
|
||||
* 2) prev_stage — откуда карточка приехала на текущую стадию. Кормит счётчик
|
||||
* «69/1» в шапке «Отказа»: сколько отказов пришло из ручного тестирования.
|
||||
* 3) sales_prospect_moves — журнал ВСЕХ движений карточки по стадиям, включая
|
||||
* автоматические (деньги → testing/topped_up/user). Раньше автодвижения не
|
||||
* оставляли следа вообще, и фильтр «что менялось за день» было не из чего
|
||||
* построить. Пишется событием модели SalesProspect — один шов на всех.
|
||||
*
|
||||
* Схема sales_* живёт только в миграциях (в db/schema.sql её нет, см. CHANGELOG v8.72).
|
||||
* Спека: docs/superpowers/specs/2026-08-01-korzina-filtry-schetchik-design.md §4.
|
||||
* План: docs/superpowers/plans/2026-08-01-korzina-filtry-schetchik.md Task 1.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$db = DB::connection('pgsql_supplier');
|
||||
|
||||
// Порядок значений в CHECK = порядок колонок канбана (для читаемости; БД порядок не хранит).
|
||||
$db->statement('ALTER TABLE sales_prospects DROP CONSTRAINT IF EXISTS sales_prospects_stage_check');
|
||||
$db->statement(<<<'SQL'
|
||||
ALTER TABLE sales_prospects ADD CONSTRAINT sales_prospects_stage_check
|
||||
CHECK (stage IN ('new','in_work','negotiation','manual_testing','registered',
|
||||
'testing','topped_up','user','kp_sent','rejected','no_answer','trash'))
|
||||
SQL);
|
||||
|
||||
$db->statement('ALTER TABLE sales_prospects ADD COLUMN IF NOT EXISTS prev_stage VARCHAR(16) NULL');
|
||||
|
||||
$db->statement(<<<'SQL'
|
||||
CREATE TABLE IF NOT EXISTS sales_prospect_moves (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
prospect_id BIGINT NOT NULL REFERENCES sales_prospects(id) ON DELETE CASCADE,
|
||||
sales_user_id BIGINT REFERENCES sales_users(id),
|
||||
from_stage VARCHAR(16),
|
||||
to_stage VARCHAR(16) NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
SQL);
|
||||
|
||||
// История одной карточки — свежие сверху.
|
||||
$db->statement(<<<'SQL'
|
||||
CREATE INDEX IF NOT EXISTS idx_prospect_move_card
|
||||
ON sales_prospect_moves (prospect_id, created_at DESC)
|
||||
SQL);
|
||||
|
||||
// Фильтр «что менялось за период» — выборка по всему отделу за диапазон дат.
|
||||
$db->statement(<<<'SQL'
|
||||
CREATE INDEX IF NOT EXISTS idx_prospect_move_when
|
||||
ON sales_prospect_moves (created_at)
|
||||
SQL);
|
||||
|
||||
// 🔴 GRANTs обеим ролям admin-db (как в create_sales_prospect_notes_table).
|
||||
// Без них на бою будет отказ прав, а на dev под superuser это невидимо.
|
||||
$db->statement(<<<'SQL'
|
||||
DO $$
|
||||
DECLARE target TEXT;
|
||||
BEGIN
|
||||
FOREACH target IN ARRAY ARRAY['crm_admin_user','crm_supplier_worker'] LOOP
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = target) THEN
|
||||
EXECUTE format('GRANT SELECT, INSERT ON sales_prospect_moves TO %I', target);
|
||||
EXECUTE format('GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO %I', target);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END $$
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$db = DB::connection('pgsql_supplier');
|
||||
|
||||
$db->statement('DROP TABLE IF EXISTS sales_prospect_moves CASCADE');
|
||||
$db->statement('ALTER TABLE sales_prospects DROP COLUMN IF EXISTS prev_stage');
|
||||
|
||||
// Карточки из корзины — в «Отказ», иначе прежний CHECK не встанет.
|
||||
$db->statement("UPDATE sales_prospects SET stage = 'rejected' WHERE stage = 'trash'");
|
||||
$db->statement('ALTER TABLE sales_prospects DROP CONSTRAINT IF EXISTS sales_prospects_stage_check');
|
||||
$db->statement(<<<'SQL'
|
||||
ALTER TABLE sales_prospects ADD CONSTRAINT sales_prospects_stage_check
|
||||
CHECK (stage IN ('new','in_work','negotiation','manual_testing','registered',
|
||||
'testing','topped_up','user','kp_sent','rejected','no_answer'))
|
||||
SQL);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user