가이드라인 V4 저장 (상태 전이 + COMPLETED 부가작업)
가이드라인 V4 세션의 상태를 전이시키고 COMPLETED 시 모집 단계 자동화를 수행합니다.
가이드라인 V4 저장
요청 본문으로 전달된 가이드라인 편집본을 세션 문서(data)에 머지 저장하고, 상태를 DRAFT 또는 COMPLETED 로 전이시킵니다. isModify=true 인 경우 편집본 머지는 수행하되 상태/단계는 바꾸지 않고 updatedAt만 갱신.
COMPLETED 저장 시 V2 와 동일한 부가작업(모집 마감일 자동 설정, 캠페인 벡터화, 해시태그 크롤 이벤트, 모집 콘텐츠 생성, 칸반 완료, 추가금 sync)을 함께 수행합니다.
HTTP 요청
PUT /ai/guideline/v4/{collabNo}?isFirst=true&isModify=false
Authorization: Bearer {access_token}
Content-Type: application/jsonPath Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
collabNo | Integer | 예 | 캠페인 번호 |
Query Parameters
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
isFirst | Boolean | true | true: 초안(DRAFT), false: 완성본(COMPLETED) |
isModify | Boolean | false | true: 임시저장 (상태/단계 변경 없음. updatedAt만 갱신) |
Request Body (optional)
본문은 가이드라인 데이터 맵 그 자체입니다(별도 래퍼 없음). 본문에 포함된 키만 세션 문서의 data 에 얕게 머지됩니다.
{
"concept_one_liner": "뷰티 크리에이터를 위한 스페인 10일 올인원 여행 패키지 모집 캠페인",
"required_appeal_points": ["..."],
"optional_appeal_points": ["..."],
"hashtags": ["qabrand", "스페인여행", "인플루언서캠페인"],
"shots": {
"beginning": [],
"middle": [],
"ending": []
},
"upload_requirements": {
"account_name": "brand_official",
"narration_required": true,
"brand_tag": { "enabled": true, "tag_methods": ["PERSON_TAG", "CAPTION_TAG"] },
"sponsor_label": { "enabled": true, "replace_ad_tag": true },
"collaborator": { "enabled": true },
"product_link": { "enabled": true, "channel": "DM", "link": "https://..." },
"is_ai_suggested": false
},
"providedOptions": [
{
"id": null,
"title": "색상",
"selectionType": "SINGLE",
"sortOrder": 0,
"choices": [
{ "id": null, "value": "아이보리", "sortOrder": 0 },
{ "id": null, "value": "그레이", "sortOrder": 1 }
]
}
]
}머지 규칙
| 케이스 | 동작 |
|---|---|
| 본문에 있는 키 (값 not-null) | 해당 키를 data 에 통째 교체 (예: hashtags 배열 전체 대체) |
| 본문에 없는 키 | 기존 data 값 유지 |
본문에 있으나 값이 null | 스킵 (기존 유지) |
providedOptions 키 | data 에 넣지 않고 별도 RDB sync 로만 처리 |
프론트는 편집한 각 필드를 완전한 상태로 보내야 합니다(얕은 머지). 예를 들어
shots를 보낼 땐beginning/middle/ending전체를 포함해야 누락이 없습니다.
providedOptions (별도 RDB sync)
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
providedOptions | array | 아니오 | 캠페인 제공 옵션. 임시저장/DRAFT/COMPLETED 모든 경로에서 RDB 와 diff sync. 키가 없으면(null) sync 스킵 |
- 각 옵션/choice 에
id가 있으면 기존 row 갱신 시도, 없으면 신규 insert. - 입력에서 빠진 기존 row 는 soft delete (
deleted_at셋팅). - 캠페인에
PROPOSAL신청자가 1건이라도 있으면 전체 거부 (PROVIDED_OPTION_LOCKED). - 자세한 정책은 캠페인 제공 옵션 개요 참고.
upload_requirements (타입드 — 추가금 자동 sync 대상)
크리에이터 업로드 항목. 단가 상승이 붙는 항목만 추가금으로 잡혀 TB_CAMPAIGN_ADDON_FEE 에 sync 된다.
| 필드 | 타입 | 추가금 | 설명 |
|---|---|---|---|
account_name | string | null | ❌ | 브랜드 태그/협찬 레이블/공동 작업자에서 공통으로 쓰는 계정명 |
narration_required | boolean | ✅ NARRATION | 나레이션 삽입 |
brand_tag | { enabled, tag_methods } | ❌ | 브랜드 계정 태그. tag_methods: PERSON_TAG, CAPTION_TAG |
sponsor_label | { enabled, replace_ad_tag } | ❌ | 협찬 레이블. 계정명은 account_name 사용 |
collaborator | { enabled } | ✅ COLLABORATOR | enabled=true 면 추가금. 계정명은 account_name 사용 |
product_link | { enabled, channel, link } | ✅ PRODUCT_LINK_* | enabled=true 이고 channel 이 있을 때만 매핑 (아래) |
is_ai_suggested | boolean | ❌ | AI 추출/제안 여부 |
product_link.channel ↔ 추가금 매핑:
| channel | AddonFeeType | 평균 단가 |
|---|---|---|
DM | PRODUCT_LINK_DM | +50% |
PROFILE | PRODUCT_LINK_PROFILE | +30% |
CAPTION | PRODUCT_LINK_CAPTION | +20% |
COMMENT | PRODUCT_LINK_COMMENT | +20% |
product_link.enabled=true 이지만 channel=null 이면 제품 링크 공유 필요 여부만 제안된 상태입니다. 제품 링크 추가금은 channel 이 DM/PROFILE/CAPTION/COMMENT 로 확정될 때만 매핑됩니다. 구버전 product_link_required: true(boolean) 형태는 채널 정보가 없어 추가금으로 매핑되지 않으며, upload_requirements 의 모르는 키는 안전하게 무시됩니다.
권한 체크
userDetails.username이collab.id와 다르고ROLE_ADMIN도 아니면UNAUTHORIZED_ACCESS(403)- 기존 가이드라인이 있고 관리자가 아닌데
campaignSubStep이 콘텐츠 제작 이후 단계면GUIDELINE_MODIFICATION_NOT_ALLOWED
상태 전이
isModify | isFirst | guidelineStatus | campaignSubStep | 부가작업 |
|---|---|---|---|---|
true | (무시) | 변경 없음 | 변경 없음 | updatedAt만 갱신 |
false | true | DRAFT | CAMPAIGN_GUIDELINE | 없음 |
false | false | COMPLETED | CREATOR_RECRUIT | 아래 부가작업 실행 |
COMPLETED 부가작업
isFirst=false, isModify=false 로 호출되면 다음을 순차/비동기 혼합으로 실행합니다:
- 모집 마감일 자동 설정 —
CampaignScheduleService.updateRecruitmentEndDate(collabNo) - 캠페인 벡터화 (비동기) —
PythonApiService.vectorizeCampaignAsync(collabNo). 인플루언서 매칭용 벡터 인덱스 갱신. - 해시태그 자동 크롤 이벤트 —
doc.data.hashtags가 있으면HashtagCrawlRequestedEvent발행.Collab.keywords에도 콤마 join 저장. - 모집 콘텐츠 비동기 생성 —
CollabPythonReportService.forwardToPythonAsync(collabNo, userDetails). 알리미/DM/한줄어필/스토리이미지 생성 → Slack 전송. - 칸반 태스크 완료 —
TB_KANBAN_TASK에taskCode=GUIDELINE_FINAL이 있으면 Node API (/kanban-task/{id}/complete) 호출. - 추가금 sync —
GuidelineV4AddonFeeSyncService.syncFromGuideline(collabNo, doc).doc.data에서 추가금 항목을 파싱해TB_CAMPAIGN_ADDON_FEE와 diff sync.
추가금 sync 규칙
파싱 출처 (doc.data):
shots[*]— shot 의code가AddonFeeType이면 추가금 (예:OUTDOOR). value =additional_price(없거나 0이면 enum 기본값). scene 샷(HOOK/CTA등)은 추가금 항목으로는 무시되지만 장면 수 카운트에 포함.upload_requirements—narration_required→NARRATION,collaborator.enabled→COLLABORATOR,product_link.enabled=true+product_link.channel→PRODUCT_LINK_*. (account_name/brand_tag/sponsor_label제외)shots의 scene 총합 > 7 —EXTRA_SCENE추가금 자동 추가,value = 20 × (총장면수 − 7). 기본 장면 수 한도는 7개(초반 2 + 중반 3 + 후반 2). 장면 수가 7 이하로 떨어지면 row 자동 삭제.
diff 동작:
| 케이스 | 동작 |
|---|---|
| 신규 항목 | is_included=true 로 insert |
| 기존 항목 | value/value_type 갱신 (is_included 는 운영자 설정 보존) |
| 빠진 항목 | hard delete |
SCRIPT_REVIEW / PRODUCT_RETURN | 삭제 제외 (캠페인 생성 시 draft 가 만드는 AMOUNT — 가이드라인 sync 가 건드리지 않음) |
doc.data 에 shots·upload_requirements 키가 모두 없으면 구조 미인식으로 sync 스킵. 둘 중 하나라도 있으면(선택 추가금이 없어도) 가이드라인 관리 타입을 diff 적용.
응답
임시저장 (isModify=true) 응답
{
"status": 200,
"code": null,
"message": "가이드라인 저장 완료",
"data": {
"collabNo": 123,
"isModify": true,
"guidelineStatus": "DRAFT",
"message": "가이드라인이 임시저장되었습니다."
}
}DRAFT / COMPLETED 저장 응답
{
"status": 200,
"code": null,
"message": "가이드라인 저장 완료",
"data": {
"collabNo": 123,
"isFirst": false,
"guidelineStatus": "COMPLETED",
"message": "가이드라인이 저장되었습니다."
}
}에러 응답
| 상태 코드 | 코드 | 설명 |
|---|---|---|
400 | CAMPAIGN_NOT_FOUND | 캠페인을 찾을 수 없음 |
400 | GUIDELINE_MODIFICATION_NOT_ALLOWED | 기존 가이드라인 존재 + 관리자 아님 + 콘텐츠 제작 단계 진입 |
400 | PROVIDED_OPTION_LOCKED | PROPOSAL 신청자 존재 + body 에 providedOptions 동봉 |
400 | (없음) | "V4 세션이 없습니다. 컨셉 생성부터 시작하세요." (getSession 실패) |
401 | - | 인증 실패 |
403 | UNAUTHORIZED_ACCESS | 해당 캠페인 소유자 아니고 관리자도 아님 |