Corp/docs/completed/CALENDAR_COMPANY_VISIBILITY_FIX.md
Игорь Надеин 28e2f1615f feat: реализован drag & drop для перемещения событий между календарями
- Создан модуль calendarEventDragDrop.js с классами DragDropState и CalendarUtils
- Реализовано перетаскивание событий между календарями через модальное окно
- Добавлена кнопка 'Переместить все' для массового перемещения
- Модальное окно отображает источник и цель перемещения
- Поддержка legacy-календарей (company/department/personal)
- Добавлены стили для подсветки drop-target (папки и календари)
- Backend: исправлена обработка null значений в serializer
- Backend: добавлен endpoint list_raw для получения немодерируемых событий
- Использованы стандартные Bootstrap стили для модалов
- Исправлено всплытие событий dragover/drop (stopPropagation)
- Эмодзи иконки для типов календарей (����)
- Toast уведомления об успехе/ошибках перемещения
2026-02-12 15:59:53 +03:00

7.8 KiB
Raw Blame History

Исправление снятия выбора календарей компании

Дата: 12 февраля 2026 г. Тип: Исправление бага Компонент: Менеджер календарей (calendarManager.js)

Проблема

При попытке снять выбор (скрыть) календарь компании возникала ошибка 403 Forbidden:

PATCH http://localhost:9000/api/v1/calendar/subscriptions/8/ 403 (Forbidden)
Error: Только владелец календаря может изменять права подписки.

Причина

Система пыталась обновить подписку (subscription) через API для всех новых календарей, включая календари компании, к которым пользователь не имеет прав редактирования.

Логика была:

  • Legacy календари → localStorage ✅
  • Новые календари → API (updateSubscription) ❌

Проблема: Календари компании (scope: 'company') имеют подписки, но пользователь не владелец и не может изменять их через API.

Решение

Изменена логика определения способа сохранения видимости календаря. Теперь проверяется не только is_legacy, но и права редактирования (user_can_edit):

До исправления:

if (calendar?.is_legacy) {
  // localStorage
} else {
  // API (updateSubscription) - ОШИБКА для календарей компании!
}

После исправления:

const canUseAPI = calendar && !calendar.is_legacy && calendar.user_can_edit;

if (!canUseAPI) {
  // localStorage - для legacy ИЛИ без прав редактирования
  localStorage.setItem(`calendar_visible_${calendarId}`, String(isVisible));
} else {
  // API - только для календарей с правами редактирования
  await updateSubscription(subscription.id, { is_visible: isVisible });
}

Изменения в коде

1. Функция toggleCalendarVisibility()

Файл: backend/static/js/components/calendarManager.js

Было:

if (calendar?.is_legacy) {
  // localStorage
} else {
  // API
}

Стало:

const canUseAPI = calendar && !calendar.is_legacy && calendar.user_can_edit;

if (!canUseAPI) {
  // localStorage для legacy ИЛИ календарей без прав редактирования
  localStorage.setItem(`calendar_visible_${calendarId}`, String(isVisible));
} else {
  // API только для календарей с правами редактирования
  await updateSubscription(subscription.id, { is_visible: isVisible });
}

2. Функция loadCalendars() - загрузка видимости

Было:

if (cal.is_legacy) {
  // Читать из localStorage
} else {
  // Читать из подписок
}

Стало:

if (cal.is_legacy || !cal.user_can_edit) {
  // Читать из localStorage для legacy ИЛИ без прав редактирования
  const stored = localStorage.getItem(`calendar_visible_${cal.id}`);
  isVisible = stored === null ? true : stored === "true";
} else {
  // Читать из подписок для календарей с правами редактирования
  const subscription = subscriptions.find((s) => s.calendar === cal.id);
  isVisible = subscription ? subscription.is_visible : true;
}

Логика работы

Типы календарей и методы сохранения видимости:

Тип календаря is_legacy user_can_edit Метод сохранения
Legacy компания ✅ true ❌ false localStorage
Legacy личный ✅ true ✅ true localStorage
Legacy отдел ✅ true ❌ false localStorage
Новый компании (не владелец) ❌ false ❌ false localStorage ✅
Новый компании (владелец) ❌ false ✅ true API
Новый личный (владелец) ❌ false ✅ true API
Новый отдела (не владелец) ❌ false ❌ false localStorage ✅
Новый отдела (владелец/админ) ❌ false ✅ true API

Ключевое изменение:

Раньше: Все новые календари пытались использовать API Теперь: Только календари с user_can_edit: true используют API

Результат

✅ Календари компании можно скрывать/показывать без ошибок ✅ Видимость сохраняется в localStorage для календарей без прав редактирования ✅ API используется только для календарей, где пользователь имеет права ✅ Fallback на localStorage работает при ошибках API

Технические детали

Почему нельзя изменять подписки через API?

Backend API /api/v1/calendar/subscriptions/{id}/ имеет ограничение:

# Только владелец календаря может изменять права подписки
if subscription.calendar.owner_user_id != request.user.id:
    return Response(
        {"detail": "Только владелец календаря может изменять права подписки."},
        status=status.HTTP_403_FORBIDDEN
    )

Это правильная логика безопасности - обычные пользователи не должны изменять подписки других пользователей.

Почему localStorage - правильное решение?

Видимость календаря (is_visible) - это локальная настройка UI конкретного пользователя:

  • Не влияет на других пользователей
  • Не требует синхронизации между устройствами
  • Не нуждается в серверном хранении для календарей без прав редактирования

Для календарей, где пользователь владелец, имеет смысл использовать API, чтобы:

  • Синхронизировать настройки между устройствами
  • Сохранять как часть профиля пользователя

Тестирование

После исправления проверьте:

  1. ✅ Создайте календарь компании (как админ)
  2. ✅ Зайдите как обычный пользователь
  3. ✅ Попробуйте скрыть календарь компании
  4. ✅ Убедитесь, что нет ошибки 403
  5. ✅ Обновите страницу - видимость должна сохраниться
  6. ✅ Проверьте, что ваш личный календарь по-прежнему использует API

Связанные файлы

  • backend/static/js/components/calendarManager.js - основная логика
  • backend/static/js/api/calendarsApi.js - API функции
  • Backend: backend/calendar_app/views.py - API endpoint с проверкой прав