feat(campaigns): submit/approve/reject service functions

This commit is contained in:
kitos
2026-07-03 09:14:40 +02:00
parent 79b573fb60
commit 385d402a57
2 changed files with 220 additions and 0 deletions
@@ -306,6 +306,98 @@ def create_campaign(
return serialize_campaign(db, campaign)
def submit_campaign_for_approval(
db: Session,
campaign_id: str,
*,
submitter_id: uuid.UUID,
submitter_role: str,
) -> Campaign:
"""Submit a draft campaign into the manager's approval queue.
Raises EntityNotFoundError, PermissionViolation, BusinessRuleViolation.
Does not commit; caller commits.
"""
campaign = db.query(Campaign).filter(Campaign.id == uuid.UUID(campaign_id)).first()
if not campaign:
raise EntityNotFoundError("Campaign", campaign_id)
if str(campaign.created_by) != str(submitter_id) and submitter_role != "admin":
raise PermissionViolation("Only the creator or admin can submit this campaign for approval")
if campaign.status != "draft":
raise BusinessRuleViolation("Only draft campaigns can be submitted for approval")
test_count = db.query(CampaignTest).filter(CampaignTest.campaign_id == campaign.id).count()
if test_count == 0:
raise BusinessRuleViolation("Campaign must have at least one test to submit for approval")
campaign.status = "pending_approval"
db.flush()
return campaign
def approve_campaign(
db: Session,
campaign_id: str,
*,
approver_id: uuid.UUID,
start_date: str,
) -> Campaign:
"""Approve a pending campaign: fix its start date and activate it.
Raises EntityNotFoundError, BusinessRuleViolation.
Does not commit; caller commits.
"""
campaign = db.query(Campaign).filter(Campaign.id == uuid.UUID(campaign_id)).first()
if not campaign:
raise EntityNotFoundError("Campaign", campaign_id)
if campaign.status != "pending_approval":
raise BusinessRuleViolation("Only campaigns pending approval can be approved")
if not start_date:
raise BusinessRuleViolation("start_date is required to approve a campaign")
campaign.start_date = datetime.fromisoformat(start_date)
campaign.status = "active"
campaign.approved_by = approver_id
campaign.approved_at = datetime.utcnow()
campaign.rejection_reason = None
db.flush()
return campaign
def reject_campaign(
db: Session,
campaign_id: str,
*,
rejecter_id: uuid.UUID,
reason: str,
) -> Campaign:
"""Reject a pending campaign, returning it to draft with a mandatory reason.
Raises EntityNotFoundError, BusinessRuleViolation.
Does not commit; caller commits.
"""
campaign = db.query(Campaign).filter(Campaign.id == uuid.UUID(campaign_id)).first()
if not campaign:
raise EntityNotFoundError("Campaign", campaign_id)
if campaign.status != "pending_approval":
raise BusinessRuleViolation("Only campaigns pending approval can be rejected")
if not reason or not reason.strip():
raise BusinessRuleViolation("A rejection reason is required")
campaign.status = "draft"
campaign.rejection_reason = reason.strip()
campaign.approved_by = None
campaign.approved_at = None
db.flush()
return campaign
# Define function get_campaign_detail
def get_campaign_detail(db: Session, campaign_id: str) -> dict:
"""Get detailed campaign info including tests and progress.