Делает HEAD целостным: коммит 5366799 уже импортировал эти файлы, но они
не были добавлены в git (свежий checkout падал с ImportError, а migrate не
создавал таблицу GroupLocationAccess).
- core/location_access.py: область данных (accessible_pharmacies/locations,
scoped_queryset/employees, has_all_locations) — fail-closed
- apps/locations/models/access.py + миграция 0014: GroupLocationAccess,
право locations.view_all_locations
- core/tests_location_access.py: 15 сценариев scoping
- apps/reports/*: снятие лишнего гейта has_all_locations с IT-отчёта отдела
(руководитель не обязан иметь доступ ко всей сети) + тест
- apps/rro/tests: чужая аптека теперь 404 (единая область доступа), а не
«200 с урезанными панелями» — под новую модель
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ранее разделы с perm=None (Новости, Задачи, Дислокация, Документы, Чат,
Календарь, HR-портал, Справка) были доступны любому вошедшему — сотрудник
без назначенной группы видел почти весь портал. Теперь такие разделы
требуют хотя бы одной группы; без групп доступны только базовые:
Главная, Профиль, Поддержка IT.
- user_can_access: BASELINE_OPEN_KEYS + gate «есть ли группа» для perm=None
- PortalSectionAccessMiddleware: барьер по префиксу URL (в т.ч. вложенные
страницы вроде /tasks/5/), /settings/ не гейтим (редирект на свой профиль)
- index.html: пункты меню под can_section (сайдбар, дропдаун, bottom-nav),
секция «Управление» скрывается целиком, если пусто
- тесты: политика без-группы + прямой заход по URL; тест-фабрики выдают
группу (реальный сотрудник её имеет)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Отдельное право на редактирование карточки аптеки, формы, пресеты колонок
дислокации и правки видимости карточки.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Let admins set per-section access levels so operational data on pharmacy detail is no longer all-or-nothing for every logged-in user.
Co-authored-by: Cursor <cursoragent@cursor.com>
Вариант А — пароль ящика хранится обратимо-зашифрованным, чтобы показать
заведующему для входа:
- Pharmacy.carbonio_password (EncryptedCharField) — шифруется в БД
- sync_service при создании ящика генерирует пароль, отдаёт его в Carbonio
и сохраняет свою зашифрованную копию; на update-пути пароль не трогается
- Пароль виден в админке аптеки (readonly, расшифрованный) — вкладка Carbonio
- 2 теста (мок CarbonioService): пароль сохранён, совпадает с отданным в
Carbonio, в БД лежит шифртекстом; update не перетирает пароль
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Серверная часть интеграции по эталону AD-коннектора (драйвер — Pharmacy):
- carbonio_service.py: клиент admin API Carbonio/Zimbra (JSON-SOAP на 6071):
auth + get/create/modify(status)/delete account, ping для read-only проверки
- services/sync_service.py: sync_pharmacy_to_carbonio (идемпотентно),
deactivate_pharmacy_carbonio; логирование в CarbonioSyncLog
- signals.py: post_save/pre_save на Pharmacy — создание ящика при включённой
синхронизации, закрытие/открытие при смене статуса аптеки; защита от рекурсии
(account_id гасит повторный вызов), sync_enabled=False по умолчанию не трогает прод
- tasks.py: Celery-обёртки; CarbonioSyncLog + read-only админка
- команда sync_pharmacies_to_carbonio (dry-run по умолчанию, --apply)
- Carbonio-поля в админке Pharmacy; убран конфликт models.py vs models/
Без живого сервера деградирует мягко: CarbonioConfigError не роняет сохранение.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Фундамент интеграции Carbonio на уровне модели Pharmacy (без обращений к серверу):
- Поля Pharmacy: carbonio_email, carbonio_account_id, carbonio_sync_enabled
(default False), carbonio_sync_status, carbonio_last_sync, carbonio_sync_error
- Генерация адреса <mail_prefix организации><номер>@домен (a1@, t1@, p1@...);
префикс из Directory.extra_data['mail_prefix'], домен из ConnectorConfig(carbonio)
с дефолтом aks-apteka.com.ua
- Ручной override адреса не перетирается автогенерацией
- Уникальность carbonio_email: clean() + частичный unique-констрейнт в БД
(пустые не конфликтуют); автоген при коллизии оставляет поле пустым
- 10 тестов (apps.locations.tests) — у приложения было 0
API-слой (клиент/sync/сигналы/задачи) — следующим шагом.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>