feat: harden campaign delivery and editing
This commit is contained in:
@@ -13,7 +13,11 @@ from govoplan_campaign.backend.campaign.models import CampaignConfig
|
||||
from govoplan_campaign.backend.campaign.mail_profile_boundary import campaign_mail_profile_id
|
||||
from govoplan_campaign.backend.persistence.campaigns import load_version_config
|
||||
from govoplan_campaign.backend.reports.campaigns import CampaignReportError, generate_campaign_report, generate_jobs_csv
|
||||
from govoplan_campaign.backend.integrations import SmtpConfigurationError
|
||||
from govoplan_campaign.backend.integrations import (
|
||||
MailDeliveryCommandError,
|
||||
SmtpConfigurationError,
|
||||
mail_integration,
|
||||
)
|
||||
from govoplan_campaign.backend.sending.execution import ExecutionSnapshotError, ensure_execution_snapshot
|
||||
|
||||
|
||||
@@ -32,6 +36,9 @@ class CampaignReportEmailResult:
|
||||
attached_jobs_csv: bool
|
||||
attached_report_json: bool
|
||||
accepted_count: int | None = None
|
||||
command_id: str | None = None
|
||||
delivery_status: str | None = None
|
||||
duplicate: bool = False
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
@@ -44,6 +51,22 @@ class CampaignReportEmailResult:
|
||||
"attached_jobs_csv": self.attached_jobs_csv,
|
||||
"attached_report_json": self.attached_report_json,
|
||||
"accepted_count": self.accepted_count,
|
||||
"command_id": self.command_id,
|
||||
"delivery_status": self.delivery_status,
|
||||
"duplicate": self.duplicate,
|
||||
}
|
||||
|
||||
def audit_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"campaign_id": self.campaign_id,
|
||||
"version_id": self.version_id,
|
||||
"recipient_count": len(self.to),
|
||||
"dry_run": self.dry_run,
|
||||
"attached_jobs_csv": self.attached_jobs_csv,
|
||||
"attached_report_json": self.attached_report_json,
|
||||
"command_id": self.command_id,
|
||||
"delivery_status": self.delivery_status,
|
||||
"duplicate": self.duplicate,
|
||||
}
|
||||
|
||||
|
||||
@@ -149,6 +172,8 @@ def send_campaign_report_email(
|
||||
attach_jobs_csv: bool = False,
|
||||
attach_report_json: bool = False,
|
||||
dry_run: bool = False,
|
||||
idempotency_key: str | None = None,
|
||||
created_by_user_id: str | None = None,
|
||||
) -> CampaignReportEmailResult:
|
||||
campaign = session.get(Campaign, campaign_id)
|
||||
if not campaign or campaign.tenant_id != tenant_id:
|
||||
@@ -175,11 +200,16 @@ def send_campaign_report_email(
|
||||
) from exc
|
||||
if not snapshot.smtp_transport_revision:
|
||||
raise CampaignReportEmailError("Campaign build evidence has no SMTP transport revision")
|
||||
mail = mail_integration()
|
||||
if not dry_run:
|
||||
raise CampaignReportEmailError(
|
||||
"Report email delivery is disabled until it uses a durable, idempotent Mail-owned outbox with unknown-outcome reconciliation."
|
||||
)
|
||||
|
||||
if not mail.durable_delivery_available:
|
||||
raise CampaignReportEmailError(
|
||||
"Report email delivery requires the durable, idempotent Mail-owned outbox."
|
||||
)
|
||||
if not str(idempotency_key or "").strip():
|
||||
raise CampaignReportEmailError(
|
||||
"Live report email delivery requires an idempotency key"
|
||||
)
|
||||
report = generate_campaign_report(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
@@ -202,6 +232,48 @@ def send_campaign_report_email(
|
||||
jobs_csv=jobs_csv,
|
||||
report_json=report_json,
|
||||
)
|
||||
if not dry_run:
|
||||
clean_idempotency_key = str(idempotency_key or "").strip()
|
||||
from_email, _from_name = _effective_from(config)
|
||||
if not snapshot.mail_profile_id:
|
||||
raise CampaignReportEmailError(
|
||||
"Campaign build evidence has no Mail profile reference"
|
||||
)
|
||||
try:
|
||||
command = mail.submit_delivery_command(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
command_type="campaign_report",
|
||||
source_module="campaigns",
|
||||
source_resource_type="campaign",
|
||||
source_resource_id=campaign.id,
|
||||
source_version_id=version.id,
|
||||
idempotency_key=clean_idempotency_key,
|
||||
profile_id=snapshot.mail_profile_id,
|
||||
message_bytes=message.as_bytes(),
|
||||
envelope_from=from_email,
|
||||
envelope_recipients=to,
|
||||
from_header=str(message["From"]),
|
||||
expected_smtp_transport_revision=snapshot.smtp_transport_revision,
|
||||
smtp_server_id=snapshot.smtp_server_id,
|
||||
smtp_credential_id=snapshot.smtp_credential_id,
|
||||
created_by_user_id=created_by_user_id,
|
||||
)
|
||||
except MailDeliveryCommandError as exc:
|
||||
raise CampaignReportEmailError(str(exc)) from exc
|
||||
return CampaignReportEmailResult(
|
||||
campaign_id=campaign.id,
|
||||
version_id=version.id,
|
||||
to=to,
|
||||
subject=str(message["Subject"]),
|
||||
dry_run=False,
|
||||
sent=False,
|
||||
attached_jobs_csv=jobs_csv is not None,
|
||||
attached_report_json=report_json is not None,
|
||||
command_id=str(command["id"]),
|
||||
delivery_status=str(command["status"]),
|
||||
duplicate=bool(command.get("duplicate")),
|
||||
)
|
||||
return CampaignReportEmailResult(
|
||||
campaign_id=campaign.id,
|
||||
version_id=version.id,
|
||||
|
||||
Reference in New Issue
Block a user