Only one writeable campaign version at a time

This commit is contained in:
2026-06-13 22:08:07 +02:00
parent ffbddfc773
commit 0bb72541af
6 changed files with 177 additions and 8 deletions

View File

@@ -135,6 +135,12 @@ def create_campaign_version_from_json(
session.add(campaign)
session.flush()
else:
current = session.get(CampaignVersion, campaign.current_version_id) if campaign.current_version_id else None
if current and not _version_is_audit_safe_snapshot(current):
raise CampaignPersistenceError(
f"Campaign already has active working version #{current.version_number}. "
"Continue editing or unlock that version instead of importing a parallel draft."
)
campaign.name = config.campaign.name
campaign.description = config.campaign.description
@@ -168,6 +174,24 @@ def _version_is_user_locked(version: CampaignVersion) -> bool:
return _version_user_lock_state(version) is not None
def _version_is_audit_safe_snapshot(version: CampaignVersion) -> bool:
return _version_user_lock_state(version) == "permanent" or version.workflow_state in {
CampaignVersionWorkflowState.QUEUED.value,
CampaignVersionWorkflowState.SENDING.value,
CampaignVersionWorkflowState.COMPLETED.value,
CampaignVersionWorkflowState.CANCELLED.value,
CampaignVersionWorkflowState.ARCHIVED.value,
}
def _ensure_current_campaign_version(campaign: Campaign, version: CampaignVersion, *, action: str) -> None:
if campaign.current_version_id != version.id:
raise CampaignPersistenceError(
f"Historical campaign versions are read-only and cannot be used to {action}. "
"Open the current working version instead."
)
def _version_is_validated_and_locked(version: CampaignVersion) -> bool:
validation_summary = version.validation_summary if isinstance(version.validation_summary, dict) else {}
return bool(version.locked_at and validation_summary.get("ok") is True and not _version_is_user_locked(version))
@@ -203,6 +227,7 @@ def validate_campaign_version(
campaign = session.get(Campaign, version.campaign_id)
if not campaign or campaign.tenant_id != tenant_id:
raise CampaignPersistenceError("Campaign version is not accessible for this tenant")
_ensure_current_campaign_version(campaign, version, action="validate")
if _version_is_user_locked(version) or version.workflow_state in {
CampaignVersionWorkflowState.QUEUED.value,
CampaignVersionWorkflowState.SENDING.value,
@@ -320,6 +345,7 @@ def build_campaign_version(
campaign = session.get(Campaign, version.campaign_id)
if not campaign or campaign.tenant_id != tenant_id:
raise CampaignPersistenceError("Campaign version is not accessible for this tenant")
_ensure_current_campaign_version(campaign, version, action="build")
if version.workflow_state == CampaignVersionWorkflowState.COMPLETED.value:
raise CampaignPersistenceError("Sent campaign versions cannot be rebuilt")
validation_summary = version.validation_summary if isinstance(version.validation_summary, dict) else {}