Add bulk calendar invitation delivery
This commit is contained in:
@@ -73,6 +73,7 @@ from govoplan_campaign.backend.integrations import (
|
||||
MailProfileError,
|
||||
SmtpConfigurationError,
|
||||
SmtpSendError,
|
||||
calendar_integration,
|
||||
files_integration,
|
||||
mail_integration,
|
||||
)
|
||||
@@ -86,6 +87,75 @@ class SendJobError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def _mark_accepted_job_artifacts(session: Session, job: CampaignJob) -> None:
|
||||
files_integration().mark_job_attachment_uses_sent(session, job)
|
||||
provenance = (
|
||||
dict(job.delivery_provenance)
|
||||
if isinstance(job.delivery_provenance, dict)
|
||||
else {}
|
||||
)
|
||||
invitation = provenance.get("calendar_invitation")
|
||||
if not isinstance(invitation, dict) or invitation.get("state") == "mirrored":
|
||||
return
|
||||
request_payload = invitation.get("request")
|
||||
if not isinstance(request_payload, dict):
|
||||
return
|
||||
integration = calendar_integration()
|
||||
next_invitation = dict(invitation)
|
||||
if not integration.available:
|
||||
next_invitation.update(
|
||||
{
|
||||
"state": "unavailable",
|
||||
"last_error": (
|
||||
"The Calendar invitation capability is not active; Mail "
|
||||
"delivery was accepted without a Calendar mirror."
|
||||
),
|
||||
}
|
||||
)
|
||||
else:
|
||||
try:
|
||||
request = integration.request_from_payload(request_payload)
|
||||
ref = integration.upsert_invitation(
|
||||
session,
|
||||
tenant_id=job.tenant_id,
|
||||
user_id=(
|
||||
str(request.metadata.get("prepared_by_user_id"))
|
||||
if request.metadata.get("prepared_by_user_id")
|
||||
else None
|
||||
),
|
||||
request=request,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001 - delivery must remain accepted.
|
||||
next_invitation.update(
|
||||
{
|
||||
"state": "mirror_failed",
|
||||
"last_error": (
|
||||
"Calendar mirror failed after delivery acceptance "
|
||||
f"({type(exc).__name__})."
|
||||
),
|
||||
}
|
||||
)
|
||||
else:
|
||||
next_invitation.update(
|
||||
{
|
||||
"state": "mirrored",
|
||||
"event_id": ref.event_id,
|
||||
"calendar_id": ref.calendar_id,
|
||||
"uid": ref.uid,
|
||||
"external_state": ref.external_state,
|
||||
"outbox_operation_id": ref.outbox_operation_id,
|
||||
"reply_ingress": ref.reply_ingress,
|
||||
"recurrence_supported": ref.recurrence_supported,
|
||||
"degraded_reasons": list(ref.degraded_reasons),
|
||||
"mirrored_at": datetime.now(timezone.utc).isoformat(),
|
||||
"last_error": None,
|
||||
}
|
||||
)
|
||||
provenance["calendar_invitation"] = next_invitation
|
||||
job.delivery_provenance = provenance
|
||||
session.add(job)
|
||||
|
||||
|
||||
class SynchronousSendRejected(QueueingError):
|
||||
def __init__(
|
||||
self,
|
||||
@@ -2148,7 +2218,7 @@ def _send_single_message_direct(
|
||||
if delivery_context.snapshot.delivery.imap_append_sent.enabled
|
||||
else JobImapStatus.NOT_REQUESTED.value
|
||||
)
|
||||
files_integration().mark_job_attachment_uses_sent(session, job)
|
||||
_mark_accepted_job_artifacts(session, job)
|
||||
session.add(job)
|
||||
if campaign.current_version_id == version.id:
|
||||
_update_campaign_after_job(
|
||||
@@ -2342,7 +2412,7 @@ def reconcile_job_outcome(
|
||||
if snapshot.delivery.imap_append_sent.enabled
|
||||
else JobImapStatus.NOT_REQUESTED.value
|
||||
)
|
||||
files_integration().mark_job_attachment_uses_sent(session, job)
|
||||
_mark_accepted_job_artifacts(session, job)
|
||||
attempt_status = "reconciled_smtp_accepted"
|
||||
elif decision == "not_sent":
|
||||
job.send_status = JobSendStatus.FAILED_TEMPORARY.value
|
||||
@@ -2535,7 +2605,7 @@ def _reconcile_imap_append_outcome(
|
||||
if decision == "imap_appended":
|
||||
job.imap_status = JobImapStatus.APPENDED.value
|
||||
attempt_status = "reconciled_imap_appended"
|
||||
files_integration().mark_job_attachment_uses_sent(session, job)
|
||||
_mark_accepted_job_artifacts(session, job)
|
||||
elif decision == "imap_not_appended":
|
||||
# FAILED is deliberately retryable only after this explicit,
|
||||
# evidence-backed operator decision.
|
||||
@@ -3532,7 +3602,7 @@ def _finalize_multichannel_job(
|
||||
)
|
||||
):
|
||||
job.sent_at = job.sent_at or _utcnow()
|
||||
files_integration().mark_job_attachment_uses_sent(session, job)
|
||||
_mark_accepted_job_artifacts(session, job)
|
||||
if not (mail and mail.accepted):
|
||||
job.imap_status = JobImapStatus.NOT_REQUESTED.value
|
||||
session.add(job)
|
||||
@@ -3719,7 +3789,7 @@ def _record_smtp_send_success(
|
||||
else JobImapStatus.NOT_REQUESTED.value
|
||||
)
|
||||
job.last_error = refused_warning
|
||||
files_integration().mark_job_attachment_uses_sent(session, job)
|
||||
_mark_accepted_job_artifacts(session, job)
|
||||
session.add(attempt)
|
||||
session.add(job)
|
||||
_update_campaign_after_job(session, job.campaign_id, job.campaign_version_id)
|
||||
@@ -4061,7 +4131,7 @@ def _record_imap_append_success(
|
||||
attempt.folder = folder
|
||||
attempt.error_message = None
|
||||
session.add(attempt)
|
||||
files_integration().mark_job_attachment_uses_sent(session, job)
|
||||
_mark_accepted_job_artifacts(session, job)
|
||||
session.commit()
|
||||
session.expire_all()
|
||||
return AppendSentResult(
|
||||
|
||||
Reference in New Issue
Block a user