From 8038e4014e2e782c2e36bc1df01cae030368e417 Mon Sep 17 00:00:00 2001 From: michael Date: Thu, 8 Jan 2026 09:49:23 -0600 Subject: [PATCH] Make Workfront Campaign ID optional and propagate to proofs The Workfront Campaign ID field is now optional when creating campaigns. When provided, it is used as the base for proof workfront IDs instead of generating random ones. Co-Authored-By: Claude Opus 4.5 --- backend/app/repositories/proof_repository.py | 14 ++++++++++--- frontend/components/CreateCampaignModal.tsx | 22 ++++++++++++-------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/backend/app/repositories/proof_repository.py b/backend/app/repositories/proof_repository.py index be121bb..9ac0f83 100755 --- a/backend/app/repositories/proof_repository.py +++ b/backend/app/repositories/proof_repository.py @@ -5,7 +5,7 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload -from app.models.models import Proof, ProofVersion +from app.models.models import Campaign, Proof, ProofVersion class ProofRepository: @@ -198,9 +198,17 @@ class ProofRepository: latest_version = await self.get_latest_version_number(proof.id) new_version_number = latest_version + 1 - # Generate workfront ID + # Get campaign's workfront_id to use as base for proof workfront IDs + campaign_result = await self.session.execute( + select(Campaign).where(Campaign.id == campaign_id) + ) + campaign = campaign_result.scalar_one_or_none() + campaign_workfront_id = campaign.workfront_id if campaign else None + + # Use campaign's workfront_id as base, or proof's existing workfront_id, + # or generate random one as fallback import random - base_id = proof.workfront_id or f"#WF_{random.randint(10000, 99999)}" + base_id = proof.workfront_id or campaign_workfront_id or f"#WF_{random.randint(10000, 99999)}" version_workfront_id = f"{base_id.split('-V')[0]}-V{new_version_number}" if not proof.workfront_id: diff --git a/frontend/components/CreateCampaignModal.tsx b/frontend/components/CreateCampaignModal.tsx index bcfb24b..0346fd3 100755 --- a/frontend/components/CreateCampaignModal.tsx +++ b/frontend/components/CreateCampaignModal.tsx @@ -37,13 +37,18 @@ export const CreateCampaignModal: React.FC = ({ isOpen }, [isOpen]); const validateWorkfrontId = (id: string): boolean => { + // Empty is valid (field is optional) + if (id.length === 0) { + setError(null); + return true; + } const isValid = /^#WF_\d+$/.test(id); - if (!isValid && id.length > 0) { + if (!isValid) { setError("Workfront Campaign ID must be in the format '#WF_12345'"); } else { setError(null); } - return isValid || id.length === 0; + return isValid; }; const handleWorkfrontIdChange = (e: React.ChangeEvent) => { @@ -54,25 +59,25 @@ export const CreateCampaignModal: React.FC = ({ isOpen const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - const isIdValidOnSubmit = /^#WF_\d+$/.test(workfrontId); - if (!name.trim() || !clientLead.trim() || !agencyLead.trim() || !workfrontId.trim() || !selectedBrandGuideline.trim()) { + if (!name.trim() || !clientLead.trim() || !agencyLead.trim() || !selectedBrandGuideline.trim()) { return; } - if (!isIdValidOnSubmit) { + // Validate workfrontId format only if provided + if (workfrontId.trim() && !/^#WF_\d+$/.test(workfrontId)) { setError("Workfront Campaign ID must be in the format '#WF_12345'"); return; } setError(null); - onAddCampaign({ name, workfrontId, clientLead, agencyLead, brandGuidelines: selectedBrandGuideline }); + onAddCampaign({ name, workfrontId: workfrontId.trim(), clientLead, agencyLead, brandGuidelines: selectedBrandGuideline }); onClose(); }; if (!isOpen) return null; - const isFormInvalid = !name.trim() || !workfrontId.trim() || !clientLead.trim() || !agencyLead.trim() || !selectedBrandGuideline.trim(); + const isFormInvalid = !name.trim() || !clientLead.trim() || !agencyLead.trim() || !selectedBrandGuideline.trim(); return (
= ({ isOpen
- + = ({ isOpen onChange={handleWorkfrontIdChange} className={`mt-1 block w-full p-2 border rounded-md shadow-sm focus:ring-brand-accent focus:border-brand-accent transition bg-white text-gray-900 placeholder:text-gray-400 ${error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : 'border-gray-300'}`} placeholder="#WF_12345" - required aria-invalid={!!error} aria-describedby="workfront-id-error" />