Commit Graph

485 Commits

Author SHA1 Message Date
Fringg 7dd67e36b3 feat: add tariff identification to all notifications for multi-tariff mode
When MULTI_TARIFF_ENABLED=true users can have multiple subscriptions,
so notifications must identify which tariff they relate to.

- Webhook notifications: _notify_user auto-injects tariff_label from
  subscription.tariff.name into all 16 webhook notification strings
- Monitoring service: tariff labels in expired, expiring, trial ending,
  follow-up waves, autopay success/failed notifications
- Daily subscription: tariff in insufficient balance notification
- Recurrent payments: tariff in card autopay success/failed
- Auto-purchase: tariff in all 4 auto-purchase notification paths,
  with pre-captured names to avoid MissingGreenlet after db.commit()
- Channel checker: plural form for multi-subscription deactivation
- Payment providers: tariff in YooKassa and Stars activation messages
- Admin: tariff in bulk expiry reminder
- Localization: {tariff_label} in 20 notification keys across all 5
  locales (ru, en, ua, zh, fa) + _MULTI channel keys
- Fix: selectinload(Subscription.tariff) in trial expiring query
- Fix: capture tariff_name before expire_subscription to prevent
  MissingGreenlet from db.refresh() expiring ORM relationships
2026-03-28 19:12:34 +03:00
c0mrade 565c08366b feat: add Remnawave panel 2.7.0 API support
- Add MONTH_ROLLING traffic reset strategy (enum, UI, mapping)
- Fix squad remove-users HTTP method POST → DELETE
- Add forceRestart param to restart_all_nodes (API, service, routes)
- Replace removed /bandwidth-stats/nodes/realtime with /system/nodes/metrics
- Fix parse_bytes suffix matching bug (B was catching GB/MB/KB/TB)
- Add torrent_blocker.report webhook event with admin notifications
- Add subpage_config_changed → auto-invalidate app config cache
- Pass webhook meta field to handlers (notConnectedAfterHours)
- Improve CRM/login webhook formatting (providerName, loginAttempt)
- Update bandwidth display: remove /s suffix, show inbound traffic totals
2026-03-28 12:34:15 +03:00
c0mrade 95ba739958 fix: admin tariff purchase now creates separate RemnaWave user per tariff
In multi-tariff mode _resolve_admin_subscription was returning any active
subscription regardless of tariff_id, causing new tariff purchases to
overwrite the existing RemnaWave user instead of creating a new one.
2026-03-26 16:34:30 +03:00
Fringg fec374edba chore: ruff format 2026-03-26 10:34:14 +03:00
Fringg a49e52cc92 fix: multi-tariff Stage 4 critical fixes — keyboards, guest purchase, monitoring, tariff deletion
- inline.py: open_subscription_link/subscription_connect callbacks now include
  :{subscription_id} suffix in multi-tariff mode. Main menu uses subscription_connect
  (picker) instead of bare open_subscription_link.
- guest_purchase_service: activate_purchase non-tariff path uses proper ordering
  (non-daily, max days_left) instead of arbitrary _active[0]
- monitoring_service: _send_expired_day1_notification and discount notification
  keyboards use se:{subscription.id} in multi-tariff (2 more hardcoded callbacks fixed)
- admin/tariffs: delete_tariff_confirmed now checks active subscription count
  before deletion (RESTRICT FK). Prompt shows blocking message when active subs exist.
  New CRUD function get_active_subscriptions_count_by_tariff_id.
2026-03-26 08:46:00 +03:00
Fringg 49db5f5eed fix: multi-tariff Stage 3 critical fixes — panel sync UUID, admin grant, wheel
- remnawave_service: sync_users_to_panel uses sub.remnawave_uuid in
  multi-tariff instead of user.remnawave_uuid (was targeting wrong panel user)
- admin/users: admin_buy_subscription_execute saves UUID to
  subscription.remnawave_uuid in multi-tariff (was saving to user)
- wheel_service: _process_days_payment and _apply_prize require subscription
  in multi-tariff mode, fallback converts to balance bonus for prizes
2026-03-26 08:26:09 +03:00
Fringg c6bedc6a06 fix: multi-tariff Stage 2 HIGH fixes — 18 issues across 12 files
Bot handlers (H1-H5):
- confirm_extend_subscription: error alert instead of wrong sub fallback
- open_subscription_link/subscription_connect: startswith registration
- handle_subscription_settings: multi-tariff guard
- confirm_reset_traffic: FSM state check in multi-tariff

Services (H6-H12):
- subscription_service: 5 UUID fallback fixes — no user.remnawave_uuid in
  multi-tariff, return None if subscription.remnawave_uuid missing
- auto_purchase: use cart subscription_id for tariff match
- remnawave_service: migrate_squad_users checks subscription.remnawave_uuid
- campaign_service: extend existing sub or create new in multi-tariff
- broadcast_service: check ALL subs for paid-subscription guard
- blocked_users_service: remnawave_uuids list, iterate in cleanup
- user_service: log sub.remnawave_uuid in multi-tariff

Admin (H13-H16):
- grant_trial/paid_subscription: allow in multi-tariff mode
- promo_offers: pick sub with URL, aggregate squads from all subs

CRUD/Frontend (H17-H18):
- get_users_list: .unique() for outerjoin dedup
- refreshTraffic: withSubId in params instead of body
2026-03-26 08:09:07 +03:00
c0mrade 9a27e6db31 fix: admin server/devices/traffic buttons pass subscription_id in multi-tariff
All 4 remaining admin buttons (change server, devices limit, traffic limit,
reset devices) now include _s{subscription_id} suffix in callback_data.
Handlers extract subscription_id and operate on the correct subscription.
2026-03-25 15:11:24 +03:00
c0mrade 147ef6b22b fix: admin handlers use _resolve_admin_subscription + per-subscription UUID
Centralized admin subscription resolution with smart selection (non-daily,
most days left). All 12+ admin operations use the new helper. UUID operations
(disable, enable, reset devices, update) now use subscription.remnawave_uuid
in multi-tariff mode instead of user-level UUID.
2026-03-25 11:46:36 +03:00
c0mrade d071269b8c fix: comprehensive multi-subscription audit fixes across routes, handlers, and services
- Fix UUID resolution in monitoring and webhook services for multi-tariff mode
- Update cabinet routes to properly resolve per-subscription UUIDs
- Fix account merge service for multi-tariff subscription transfers
- Update admin handlers (users, promo_offers, servers) for multi-subscription
- Fix traffic, devices, servers, daily subscription modules
- Update payment handlers (stars, yookassa) and purchase services
- Fix broadcast, promocode, and subscription auto-purchase services
- Add multi-subscription support to keyboards and localization
- Fix CRUD operations for subscription queries
- Resolve code quality issues (ruff linting)
2026-03-23 18:40:23 +03:00
c0mrade 335be66980 feat: multi-subscription support (1 user = N subscriptions)
- Add MULTI_TARIFF_ENABLED feature flag for gradual rollout
- Migration 0041: remove unique constraint on subscriptions.user_id
- Migration 0042: add remnawave_short_id (NOT NULL, UNIQUE) to subscriptions
- Each subscription gets its own Remnawave user (user_{tg_id}_{short_id})
- Add _resolve_subscription() to all 30+ bot handlers for per-subscription routing
- Add my_subscriptions.py with list/detail views and delegation handlers
- Refactor cabinet subscription.py (4687 lines -> 10 focused modules)
- Add subscription_id parameter to all cabinet API endpoints
- Adapt all services: autopay, wheel, contests, campaign, guest purchase,
  monitoring, blocked users, account merge, promo codes, payments
- Replace all user.subscription (singular) with user.subscriptions iteration
- IDOR protection via get_subscription_by_id_for_user on all endpoints
- Full backward compatibility: MULTI_TARIFF_ENABLED=False = legacy behavior
2026-03-23 18:37:17 +03:00
Fringg 8175bc8bfe fix: comprehensive security hardening across payment and API layers
- CloudPayments: require webhook signature when secret configured (all 4 handlers)
- Platega: timing-safe HMAC comparison via hmac.compare_digest
- CryptoBot/Heleket: return False when API token unconfigured
- Tribute: return 503 when API key not configured
- Freekassa: use request.client.host instead of X-Forwarded-For
- Pal24: verify webhook amount matches stored payment amount
- YooKassa: reject test-mode payments in production; add YOOKASSA_TEST_MODE config
- CloudPayments: reject test-mode payments in production
- WebAPI: add upper bounds to duration_days, traffic_limit_gb, device_limit schemas
- WebAPI: bound balance update amount to ±100M kopeks
- WebAPI: sign-dispatch for balance updates (negative → subtract_user_balance)
- WebAPI miniapp: add blocked/deleted user checks, restriction_topup/subscription guards
- Admin handlers: add @admin_required and @error_handler to moderator panel
- add_user_balance: guard against negative amounts (use subtract_user_balance instead)
2026-03-23 16:44:38 +03:00
Fringg 2f19c76357 fix: add user ID to payment descriptions for all providers and fix tuple bug
- Add telegram_user_id and user_db_id to get_balance_payment_description() across
  8 cabinet balance providers (heleket, mulenpay, pal24, wata, cloudpayments,
  freekassa, kassa_ai, riopay), all miniapp endpoints, and recurrent payments
- For email/OAuth users without telegram_id, fallback to DB ID with (U{id}) format
- Fix pre-existing tuple bug in bot_configuration.py (trailing comma created tuple)
- Fix typo in nalogo_queue_service.py log message ("Чек уже попыток")
2026-03-23 14:48:12 +03:00
Fringg 9de34900a2 fix: comprehensive html.escape() for all user/admin data in Telegram HTML messages
Bot uses default HTML parse mode — all messages are HTML-parsed by Telegram.
Added html.escape() to all user-controlled and admin-controlled strings
before interpolation into HTML messages to prevent injection and parse errors.

49 files, ~250+ injection points fixed:
- user.full_name, first_name across all handlers and services
- tariff.name/description in purchase flow, admin panel, auto-purchase service
- campaign.name, start_parameter in admin and user-facing handlers
- group.name, promo_group.name across promo management
- contest.title, prize_text, leaderboard names (including public channels)
- transaction.description (contains raw user.full_name from referral service)
- restriction_reason across all balance and subscription handlers
- ticket.title, message_text, poll.title, poll.description
- welcome text template placeholders (first_name, username)
- maintenance reason, admin_name, selected_prize.display_name

New helpers in app/utils/formatting.py:
- safe_html_name() for escaping display names
- user_html_link() replacing 15+ duplicated inline link patterns
2026-03-23 08:06:15 +03:00
Fringg c307278231 fix: prevent MESSAGE_TOO_LONG in promo groups list
With 20+ promo groups, the full details per group exceeded Telegram's
4096 char limit. Simplified list to one compact line per group with
name and member count. Full details remain in the group detail view.
2026-03-22 04:17:32 +03:00
Fringg 9eab802000 fix: handle spurious user.deleted webhooks — preserve active subscriptions and prevent orphaned panel users
- Smart end_date check: subscriptions with future end_date are preserved (not expired) and panel user is re-created automatically
- Recreation loop guard: in-memory 120s cooldown prevents unbounded recreate→delete→recreate cycles with stale entry eviction
- Race condition protection: guard timestamp stamped before any await point so concurrent coroutines are serialized
- Admin deletion: force_panel_delete=True ensures panel user is always removed, preventing orphaned subscriptions
2026-03-22 03:20:50 +03:00
Fringg 13ea3768b5 feat: custom broadcast buttons and fix home button to use bot menu
- Add custom buttons support: admins can add up to 10 custom buttons
  with callback_data or URL action types to broadcast messages
- CustomBroadcastButton Pydantic model with validation:
  callback_data checked in UTF-8 bytes (Telegram 64-byte limit),
  URLs restricted to https:// and tg:// schemes only
- Fix home button: removed from CABINET_MINIAPP_BUTTON_KEYS so it
  uses back_to_menu callback instead of opening cabinet WebApp
- Both legacy and combined broadcast endpoints pass custom_buttons
2026-03-22 01:54:05 +03:00
Fringg 48265f1cd4 chore: remove redundant comments from DISABLED status fix 2026-03-21 09:07:43 +03:00
Fringg 79cfcbcece fix: send DISABLED instead of EXPIRED status to RemnaWave API
RemnaWave API only accepts ACTIVE/DISABLED for user status updates —
EXPIRED and LIMITED are managed internally. The bot was sending EXPIRED
status and past expireAt dates, causing 400 validation errors.

- Change UserStatus.EXPIRED → UserStatus.DISABLED in all 5 call sites
- Add 1-minute buffer to expire_at for inactive subscriptions to avoid
  "expiration date in the past" rejections (matches _safe_expire_at_for_panel)
- Include TRIAL status in is_actually_active checks (consistent with
  remnawave_service.py sync_users_to_panel)
2026-03-21 09:00:59 +03:00
Fringg 06a00e367c feat: добавлен SeverPay в админ-панель и настройки кабинета
- Категория SEVERPAY в настройках бота
- Кнопка тестового платежа
- Конфигурация метода в кабинете
- DEFAULT_METHOD_ORDER обновлён
2026-03-18 03:55:24 +03:00
Fringg 3f0b24c1ec fix: add sync_squads=True to admin tariff change handler
Missed in the previous fix — admin tariff change at
handlers/admin/users.py sets connected_squads from tariff but
did not pass sync_squads=True to update_remnawave_user.
2026-03-17 22:25:01 +03:00
Fringg e453521098 fix: устранена отправка externalSquadUuid=null в RemnaWave API (A039) и исправлен reduce_devices
- reduce_devices: убрано молчаливое проглатывание ошибки RemnaWave, теперь при неудаче делается rollback и возвращается HTTP 502
- Убрана отправка external_squad_uuid=None в 8 местах: subscription_service, monitoring_service, remnawave_service, admin/users, cabinet/admin_users
2026-03-16 03:25:24 +03:00
Fringg 8d3cd50098 refactor: централизация всех расчётов цен в PricingEngine
- Мигрирован confirm_purchase() на calculate_classic_new_subscription_price()
- Мигрирован compute_simple_subscription_price на делегацию в PricingEngine
- Мигрирован handle_custom_confirm на calculate_tariff_purchase_price()
- Мигрированы daily confirm handlers (confirm_daily_tariff_purchase,
  confirm_daily_tariff_switch, confirm_instant_switch daily path)
- Мигрирован gift.py на calculate_tariff_purchase_price()
- Мигрированы FSM cache prices (select_period, select_devices, toggle_country)
- Добавлен lock_user_for_pricing в admin_buy_tariff_execute (TOCTOU fix)
- Добавлен lock + recompute в _auto_add_devices и _auto_add_traffic
- Исправлено двойное применение promo-offer в simple_subscription (критический баг)
- Унифицирован daily price display (group+offer) на всех 6 поверхностях
- PricingEngine.get_addon_discount_percent: добавлен promo_group= kwarg
- PricingEngine._calculate_switch_to/from_daily: добавлен promo-offer discount
- Удалён мёртвый код из common.py (_get_addon_discount_percent_for_user)
- Miniapp period_discounts: исправлен доступ через get_discount_percent()
2026-03-16 03:10:22 +03:00
Fringg a5fbd7400f fix: user deletion FK error + connected_squads None TypeError
Bug 1: DELETE /cabinet/admin/users/{id}/full failed with
"saved_payment_methods_user_id_fkey" FK violation.
Root cause: delete_user_account() didn't clean up saved_payment_methods
and riopay_payments before deleting the user row.
Fix: add DELETE for both tables before final user deletion.

Bug 2: show_user_management crashed with TypeError on
len(subscription.connected_squads) when connected_squads was None.
Root cause: remnawave_webhook_service explicitly set connected_squads=None
when clearing subscription data, but 5 call sites assumed it was always a list.
Fix: change None assignment to [] + add "or []" guards at all 5 call sites.
2026-03-13 06:08:54 +03:00
Fringg 04697fd4cb style: ruff format 8 files 2026-03-13 05:53:02 +03:00
Fringg e24b911283 refactor: migrate all callers to pricing_engine singleton + fix miniapp discount
- 13 PricingEngine() instantiation sites → import pricing_engine singleton
- miniapp _apply_promo_discount now delegates to PricingEngine.apply_discount
  (fixes float division vs floor division inconsistency)
2026-03-13 05:22:18 +03:00
Fringg 5e9a462261 refactor: extract shared formatting helpers into app/utils/formatting.py
Consolidate duplicated _format_traffic, _format_price_kopeks, _format_period
from tariff_purchase.py and admin/tariffs.py into a shared module.
2026-03-13 05:22:04 +03:00
Fringg 75dbd2b4fc refactor: migrate remaining callers to PricingEngine + cleanup dead CRUD
- Migrate bot purchase handlers, menu, admin users to PricingEngine
- SubscriptionRenewalService.finalize() accepts both old and new pricing types
- Remove dead subscription CRUD pricing functions (get_subscription_renewal_cost etc.)
- Remove dead pricing_utils functions
2026-03-13 05:12:32 +03:00
Fringg ae99358ae9 fix: pricing audit — display/charge parity, race conditions, balance locks
M-2: tariff_purchase.py — _apply_promo_discount delegates to PricingEngine,
     _get_user_period_discount returns (group_pct, offer_pct, combined),
     all ~15 call sites updated for display/charge price parity

M-4: miniapp switch_tariff — add FOR UPDATE lock on subscription,
     commit=False for atomic balance+transaction, emit_transaction_side_effects

M-6: CryptoBot — defer status commit (commit=False) so webhook retry works
     if fulfillment fails

WARNING: add lock_user_for_update before balance_kopeks mutations in
     contest_attempt_service, wheel_service, admin/referrals,
     account_merge_service, cabinet/routes/contests
2026-03-13 05:11:59 +03:00
Fringg 49c0f3fc10 refactor: migrate admin user price calculation to PricingEngine
Replace SubscriptionService.calculate_renewal_price() with PricingEngine
in _calculate_subscription_period_price for admin panel.
2026-03-12 23:04:29 +03:00
Fringg 8f434525eb feat: add LIMITED subscription status and preserve extra devices on tariff switch
- Add SubscriptionStatus.LIMITED for traffic-exhausted subscriptions
- Webhook user.limited now sets LIMITED directly instead of DISABLED
- Add LIMITED to reactivation, extend, resume, auto-purchase, contest eligibility
- Add traffic_exhausted error response in miniapp API
- Fix device_limit being overwritten on tariff switch in all code paths:
  admin change_tariff, user switch-tariff, miniapp, bot tariff_purchase,
  auto_purchase_service — now preserves extra purchased devices via
  calc_device_limit_on_tariff_switch() helper
- Fix truthiness checks on device_limit (0 is valid, use `is not None`)
2026-03-12 18:35:59 +03:00
Fringg c8162505ed chore: apply ruff formatting to 4 files 2026-03-12 08:24:47 +03:00
Fringg bf72f241d8 fix: preserve purchased devices when admin changes user tariff
Previously subscription.device_limit was blindly overwritten with the new
tariff's base limit, losing any extra devices the user had purchased.
Now extra devices are calculated from the old tariff base and carried over,
capped at tariff.max_device_limit or global MAX_DEVICES_LIMIT.
2026-03-12 06:55:56 +03:00
Fringg a798f1143e refactor: remove estimated price from balance, simplify server sync, fix HTML injection
- Remove estimated renewal price display from balance top-up screen
- Remove country name generation during server/squad sync, use original RemnaWave name as display_name
- Add html.escape() for all display_name/country name values rendered in HTML-parsed Telegram messages
2026-03-12 04:58:49 +03:00
Fringg 8b35428055 fix: reactivate subscription after traffic top-up when status is EXPIRED
When traffic is exhausted, RemnaWave may send user.expired webhook setting
local status to EXPIRED (not just DISABLED). reactivate_subscription() only
handled DISABLED→ACTIVE, silently ignoring EXPIRED subscriptions. After
purchasing additional GB, the subscription stayed expired and VPN remained
blocked despite payment.

Changes:
- reactivate_subscription() now handles both DISABLED and EXPIRED→ACTIVE
  when end_date is still in the future
- Inverted null end_date guard to block reactivation (defense-in-depth)
- Added enable_remnawave_user() call after update in all traffic/device
  top-up paths to ensure panel exits LIMITED state
- Gated enable call on subscription.status == 'active' to prevent
  enabling when reactivation was a no-op
- Fixed all 12 call sites across bot handlers, cabinet routes,
  miniapp, webapi, and auto-purchase service
2026-03-12 03:57:35 +03:00
Fringg 864a4ed700 fix: record transactions for free tariff switches and admin tariff changes
- Add transaction records for free tariff switches (downgrade, upgrade_cost=0) in miniapp and cabinet
- Add atomic transaction records for admin tariff changes in bot handler and cabinet API
- Use commit=False for admin flows to ensure subscription change and transaction are committed together
2026-03-11 02:17:35 +03:00
firewookie fd3466b75c fix PR problems 2026-03-10 14:29:29 +05:00
FireWookie 5c2e5dfaab Merge branch 'BEDOLAGA-DEV:main' into feature/riopay 2026-03-10 14:14:23 +05:00
Fringg 4a5cacda38 fix: resolve concurrent AsyncSession bug and sanitize error responses
- Fix critical concurrency issue in propagate_tariff_squads: preload
  users/tariffs before asyncio.gather, use single API client, no DB
  operations inside gather, single commit after all API calls
- Replace all str(e) leaks in admin_users.py with sanitized messages
- Fix double callback.answer by using callback.message.answer for
  failure alerts
- Move PropagateSquadsResult to module level, use field(default_factory)
- Compute traffic_strategy once before gather instead of N times
- Add warning logging on tariff refresh failures
- Reset synced counters on commit failure for accurate reporting
2026-03-09 02:26:38 +03:00
Fringg 79161eaae4 refactor: move squad propagation to service layer with parallel Remnawave sync
- Move _propagate_squads_to_subscriptions from handler to
  SubscriptionService.propagate_tariff_squads()
- Use asyncio.gather with semaphore (concurrency=5) for parallel
  Remnawave API calls instead of sequential O(N)
- Track failed subscription IDs for better observability
- Fix get_all_server_squads limit=50 default in admin handlers
  (now limit=10000 to prevent silent truncation)
- Add docstring to force_panel_delete parameter
- Return PropagateSquadsResult dataclass with total/synced/failed_ids
2026-03-09 01:58:23 +03:00
Fringg 7ccfb66690 fix: propagate tariff squad changes to existing subscriptions and fix user deletion from Remnawave
Squad toggle: when admin changes servers for a tariff, the changes now
propagate to all active/trial subscriptions and sync to Remnawave panel.
Previously only took effect on new purchases.

User deletion: full delete from Cabinet now actually deletes from Remnawave
panel. Previously lied about panel deletion status and skipped deletion
for users with active subscriptions.
2026-03-09 01:46:45 +03:00
Fringg 536525c9c0 fix: admin tariff server selection - 64-byte overflow and callback routing conflicts
1. Shortened squad toggle callback_data from admin_tariff_toggle_squad
   to trf_sq to stay within Telegram's 64-byte callback_data limit
   (was overflowing at tariff_id >= 10)

2. Fixed toggle_tariff handler capturing squad/promo/daily/traffic_topup
   toggle callbacks by adding exclusion filters

3. Fixed admin_tariff_edit_traffic capturing admin_tariff_edit_traffic_topup
   by registering traffic_topup handler before traffic handler

4. Fixed admin_tariff_delete capturing admin_tariff_delete_confirm
   by registering delete_confirm handler before delete handler
2026-03-09 01:23:03 +03:00
Fringg 6349b2f442 fix: align tariff pricing with calculate_renewal_price reference
- balance/main.py: single period discount on combined total (base + devices),
  add promo-offer discount, fix device_limit fallback to tariff_device_limit
- pricing.py: same combined discount + promo-offer, proper device_limit
  fallback matching reference (is not None check)
- admin/users.py: delegate to calculate_renewal_price() which handles both
  tariff and classic modes correctly, removing classic-only calculate_subscription_price
- menu.py: use renewal_service.calculate_pricing() for both price check and
  charge to ensure consistency, add try/except with user-facing error,
  show actual charged amount in success message
2026-03-09 00:39:16 +03:00
Fringg 1f664a9083 fix: remove is_active_paid_subscription guard from admin deactivation
The guard silently blocked admins from deactivating active paid
subscriptions, returning a generic error with no explanation.
Admin deactivation is intentional (with confirmation step) and
should not be prevented. The guard remains in automated processes
(monitoring, broadcast, user_service) where it makes sense.
2026-03-08 13:05:52 +03:00
firewookie 555b887952 Merge remote-tracking branch 'origin/dev' into dev 2026-03-08 09:23:08 +05:00
Fringg c10d6780ba feat: add external squad support for tariffs
- Add external_squad_uuid column to Tariff model with Alembic migration
- Add external_squad_uuid parameter to RemnaWave API create_user/update_user
- Pass external squad from tariff to RemnaWave on subscription creation/update
- Sync external squad in monitoring service, sync service, admin user management
- Clear external squad when tariff has none (consistent across all call sites)
- Add GET /available-external-squads endpoint with UUID validation and response model
- Update tariff schemas with UUID pattern validation
- Fix db.refresh to include tariff relationship for async safety
2026-03-07 05:44:19 +03:00
firewookie 7ed91b13eb - RioPay payment system integration 2026-03-06 11:44:22 +05:00
Fringg 04562fd7e7 fix: кнопка «Назад» в тарифах ведёт в админ панель, а не в настройки
Тарифы доступны напрямую из главного меню админки, но кнопка назад
вела в подменю настроек. Исправлено во всех 4 местах.
2026-03-06 04:23:05 +03:00
Fringg 6713b34978 fix: исправления системы реферальных конкурсов
- float precision: int(round(amount * 100)) вместо int(amount * 100) для рублей→копейки
- порядок регистрации callback-хендлеров (специфичные startswith первыми)
- FSM state filter на callback хендлере для предотвращения случайных срабатываний
- upsert паттерн в add_contest_event вместо дубликатов
- расширенный SQL фильтр в get_contests_for_events (все активные конкурсы)
- нормализация end-of-day (23:59:59.999999) для границ конкурсных периодов
- guard is_completed в create_transaction
2026-03-06 01:33:06 +03:00
Fringg 900be65617 fix: добавить пробелы в формат тарифов (1000 ГБ / 2 📱) 2026-03-05 11:16:53 +03:00