3be444b05e
Серверная часть интеграции по эталону 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>
65 lines
3.4 KiB
Python
65 lines
3.4 KiB
Python
"""Сигналы синхронизации почтовых ящиков аптек с Carbonio CE.
|
|
|
|
Драйвер — модель Pharmacy (в отличие от AD/Mattermost, где драйвер Employee):
|
|
почта заводится на аптеку, а не на сотрудника. По умолчанию
|
|
Pharmacy.carbonio_sync_enabled=False, поэтому обычные сохранения аптек
|
|
сигналы не трогают — прод дёргается только для явно включённых аптек.
|
|
"""
|
|
import logging
|
|
|
|
from django.db.models.signals import post_save, pre_save
|
|
from django.dispatch import receiver
|
|
|
|
from apps.locations.models import Pharmacy
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _queue(task, pharmacy, action: str) -> None:
|
|
"""Ставим Celery-задачу; недоступный брокер не должен ронять сохранение."""
|
|
try:
|
|
task.delay(pharmacy.pk)
|
|
logger.info(f'Carbonio: поставлена задача {action} для аптеки {pharmacy.pk}')
|
|
except Exception as e:
|
|
logger.error(f'Carbonio: не удалось поставить задачу {action} для аптеки {pharmacy.pk}: {e}')
|
|
|
|
|
|
@receiver(pre_save, sender=Pharmacy)
|
|
def cache_pharmacy_carbonio_state(sender, instance, **kwargs):
|
|
"""Запоминаем прежний статус, чтобы в post_save увидеть закрытие/открытие."""
|
|
if not instance.pk:
|
|
instance._carbonio_pre_save_status = None
|
|
return
|
|
try:
|
|
old = Pharmacy.objects.values('status').get(pk=instance.pk)
|
|
instance._carbonio_pre_save_status = old['status']
|
|
except Pharmacy.DoesNotExist:
|
|
instance._carbonio_pre_save_status = None
|
|
|
|
|
|
@receiver(post_save, sender=Pharmacy)
|
|
def create_pharmacy_carbonio(sender, instance, created, **kwargs):
|
|
"""Заводим ящик при новой аптеке с включённой синхронизацией или когда
|
|
синхронизацию включили, а аккаунта ещё нет. Наличие account_id гасит
|
|
повторный вызов (после save() в sync_service) — рекурсии нет."""
|
|
if not instance.carbonio_sync_enabled or instance.carbonio_account_id:
|
|
return
|
|
from apps.mail_connector.tasks import sync_pharmacy_to_carbonio_task
|
|
_queue(sync_pharmacy_to_carbonio_task, instance, 'создания ящика')
|
|
|
|
|
|
@receiver(post_save, sender=Pharmacy)
|
|
def toggle_pharmacy_carbonio_on_status(sender, instance, created, **kwargs):
|
|
"""Закрываем ящик при закрытии аптеки, снова открываем — при возврате в open."""
|
|
if created or not instance.carbonio_account_id:
|
|
return
|
|
old_status = getattr(instance, '_carbonio_pre_save_status', None)
|
|
if old_status is None:
|
|
return
|
|
if old_status == 'open' and instance.status != 'open':
|
|
from apps.mail_connector.tasks import deactivate_pharmacy_carbonio_task
|
|
_queue(deactivate_pharmacy_carbonio_task, instance, 'закрытия ящика')
|
|
elif old_status != 'open' and instance.status == 'open' and instance.carbonio_sync_enabled:
|
|
from apps.mail_connector.tasks import sync_pharmacy_to_carbonio_task
|
|
_queue(sync_pharmacy_to_carbonio_task, instance, 'открытия ящика')
|