Вариант А — пароль ящика хранится обратимо-зашифрованным, чтобы показать
заведующему для входа:
- 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>