-- Migration 185: account_quota_overrides — персональные месячные лимиты оценок -- -- WHY: -- Бонусные попытки для платящих пилотов раздавались хаком через SQL-runbook: -- `UPDATE account_estimate_usage SET used = used - N` (negative used). Для user2 -- (Брусника) это дало used=-35 при глобальном limit=15 → GET /quota отдавал -- {limit:15, used:-35, remaining:50} → фронт (EstimateForm.tsx) рендерил абсурд -- «Осталось 50 из 15». Правильное решение — персональный лимит, а не отрицательный -- счётчик использования. -- -- WHAT: -- 1. Таблица account_quota_overrides(username PK, monthly_limit, note, updated_at) — -- per-user override глобального settings.estimate_quota_limit. Читается через -- app.services.account_quota.user_limit(). -- 2. Seed: user2 (Брусника) → monthly_limit=50, пилотный грант 2026-07-13. -- 3. Сброс хака: account_estimate_usage.used < 0 → 0 (для user2, единственного -- затронутого аккаунта). После этой миграции user2: limit=50, used=0, -- remaining=50 — корректно вместо «50 из 15». -- -- IDEMPOTENCY: -- CREATE TABLE IF NOT EXISTS + INSERT ... ON CONFLICT DO UPDATE (upsert, безопасен -- при повторном применении) + UPDATE ... WHERE used < 0 (после первого прогона -- used=0, условие больше не матчит — безопасный no-op при ре-apply). -- -- Dependencies: 076_account_estimate_quota.sql (account_estimate_usage). BEGIN; CREATE TABLE IF NOT EXISTS account_quota_overrides ( username text PRIMARY KEY, monthly_limit integer NOT NULL, note text, updated_at timestamptz NOT NULL DEFAULT now() ); COMMENT ON TABLE account_quota_overrides IS 'Персональный override месячного лимита оценок trade-in (вместо глобального ' 'settings.estimate_quota_limit). Читается app.services.account_quota.user_limit().'; COMMENT ON COLUMN account_quota_overrides.monthly_limit IS 'Персональный лимит успешных оценок в календарный месяц для username.'; COMMENT ON COLUMN account_quota_overrides.note IS 'Причина override (grant / pilot / manual bump) — для аудита ручных изменений.'; INSERT INTO account_quota_overrides (username, monthly_limit, note) VALUES ('user2', 50, 'Брусника — пилот, грант 2026-07-13') ON CONFLICT (username) DO UPDATE SET monthly_limit = EXCLUDED.monthly_limit, note = EXCLUDED.note, updated_at = now(); -- Сбрасываем прежний хак бонусных попыток (negative used), который ломал /quota: -- limit=15, used=-35 → remaining=50 → фронт рендерил «Осталось 50 из 15». UPDATE account_estimate_usage SET used = 0, updated_at = now() WHERE username = 'user2' AND used < 0; COMMIT;