GET /ai/progress-table/item/{id}
캠페인 진행 항목 조회
캠페인 진행 항목 조회
기업 대시보드용 캠페인 진행 항목을 조회합니다.
예비(reserved)도 노출됩니다. 첫 매칭 이후 신청자 숨김 모드(applicantHidden)에서도, 예비는 선정 불발 시
자동 승계 대상이므로 SELECTED/PROPOSAL 항목과 함께 계속 노출됩니다(관리자 노출 설정 adminVisible 과 무관).
matchingStatus가 reserved인 항목이 예비이며, 화면에는 "예비"로 표시하세요.
응답에는 items 외에 dummyItems(모집 에이전트가 발굴한 데모 후보)가 함께 내려갑니다.
items와 동일한 타입이라 같은 컴포넌트로 렌더하며, 데모임을 드러내는 표기를 넣으면 안 됩니다
— 기업 화면에서는 일반 신청자와 구분 없이 자연스럽게 보여야 합니다.
ID는 1억 오프셋이 붙어 items와 겹치지 않습니다. 상세는
데모(목업) 후보 연동 가이드를 참고하세요.
기업 승인 전(INVALID_BUSINESS) 광고주도 본인 캠페인을 조회할 수 있습니다. 승인 전이어도 데이터는 정상 반환되며, 응답의 businessApproved 값으로 승인 여부를 분기하세요. 미승인(false) 시 프런트에서 CTA 자리에 "기업승인 진행중" 등을 노출하는 용도입니다. (타인 캠페인 조회는 여전히 403 FORBIDDEN)
HTTP 요청
GET /ai/progress-table/item/{id}
Authorization: Bearer {access_token}Path Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
id | long | 예 | 캠페인 ID |
응답
성공 응답 (200 OK)
{
"status": 200,
"code": null,
"message": "캠페인 진행 상황 항목 조회가 완료되었습니다.",
"data": {
"step": "MATCHING",
"items": [
{
"id": 1,
"creator": "인플루언서A",
"creatorEmail": "influencer_a@example.com",
"influenceId": 12345,
"profileImg": "https://s3.../profile.jpg",
"creatorLink": "https://instagram.com/influencer_a",
"trackingNumber": "1234567890",
"courierCode": "kr.cjlogistics",
"customCourierName": null,
"shippedAt": "2026-05-26T18:02:53",
"deliveredAt": "2026-05-27T13:56:22",
"trackingDetail": "{\"lastEvent\":{...},\"events\":{\"edges\":[ ... ]}}",
"uploadUrl": "https://instagram.com/p/xxx",
"file": null,
"aiCheckReady": true,
"matchingStatus": "MATCHED",
"deliveryStatus": "DELIVERED",
"aiCheck": "APPROVED",
"personCheck": "PENDING",
"campaignResult": null,
"recommendReason": "높은 참여율",
"uploadStatus": "UPLOADED",
"quotePrice": 100000,
"currentPrice": 80000,
"collaborationNote": "특별 요청 사항",
"reviewInfo": {
"hasNewSubmission": false,
"reviews": [
{
"reviewRound": 1,
"reviewId": 101,
"status": "APPROVED",
"hasNewSubmission": false,
"submissionItems": []
}
],
"uploadApproved": true,
"finalSubmission": null
},
"submissionTimeline": {
"script": {
"exists": true,
"reviewRound": 1,
"reviewStatus": "APPROVED",
"submittedAt": "2026-06-09T14:20:00",
"deadlines": {
"submissionDeadline": "2026-06-10T23:59:59",
"submissionOriginalDeadline": "2026-06-08T23:59:59",
"submissionDeadlineConfirmed": true,
"reviewDeadline": "2026-06-12T23:59:59",
"feedbackDeadline": null
},
"items": [
{
"id": 9001,
"itemType": "SCRIPT_VIDEO",
"scriptIndex": 0,
"currentVersion": 1,
"status": "APPROVED",
"isSubmitted": true,
"submittedAt": "2026-06-09T14:20:00"
}
],
"events": [
{
"triggerType": "TRIGGER_SCRIPT_SUBMITTED",
"label": "크리에이터 스크립트 제출/재제출 — 검수 마감 산정 (시트: 제출일+2영업일)",
"phase": "SCRIPT_REVIEW_DEADLINE",
"triggeredAt": "2026-06-09T14:20:00",
"previousDeadline": null,
"newDeadline": "2026-06-11T23:59:59"
}
]
},
"video": {
"exists": true,
"reviewRound": 2,
"reviewStatus": "REVIEWING",
"submittedAt": "2026-06-16T09:00:00",
"deadlines": {
"submissionDeadline": "2026-06-15T23:59:59",
"submissionOriginalDeadline": "2026-06-15T23:59:59",
"submissionDeadlineConfirmed": true,
"reviewDeadline": "2026-06-17T23:59:59",
"feedbackDeadline": "2026-06-16T23:59:59"
},
"items": [
{
"id": 9100,
"itemType": "VIDEO",
"scriptIndex": null,
"currentVersion": 2,
"status": "REVIEWING",
"isSubmitted": true,
"submittedAt": "2026-06-16T09:00:00"
}
],
"events": [
{
"triggerType": "TRIGGER_VIDEO_SUBMITTED",
"label": "크리에이터 영상 제출/재제출 — 검수 마감 산정 (시트: 재제출일+2영업일)",
"phase": "CONTENT_REVIEW_DEADLINE",
"triggeredAt": "2026-06-14T10:00:00",
"previousDeadline": null,
"newDeadline": "2026-06-16T23:59:59"
},
{
"triggerType": "TRIGGER_3R_VIDEO_RESUBMIT_REQUEST",
"label": "영상 재제출 요청 (Trigger 3 재제출 사이클, 광고주 요청)",
"phase": "CONTENT_SUBMISSION_DEADLINE",
"triggeredAt": "2026-06-15T11:00:00",
"previousDeadline": "2026-06-15T23:59:59",
"newDeadline": "2026-06-17T23:59:59"
},
{
"triggerType": "TRIGGER_VIDEO_SUBMITTED",
"label": "크리에이터 영상 제출/재제출 — 검수 마감 산정 (시트: 재제출일+2영업일)",
"phase": "CONTENT_REVIEW_DEADLINE",
"triggeredAt": "2026-06-16T09:00:00",
"previousDeadline": "2026-06-16T23:59:59",
"newDeadline": "2026-06-18T23:59:59"
}
]
},
"upload": {
"uploadDate": "2026-06-20T23:59:59",
"uploadType": "OPTIMAL",
"uploadDeadline": "2026-06-22T23:59:59",
"deadline": null,
"events": []
},
"finalSubmission": {
"submittedAt": null,
"finalSubmissionDeadline": "2026-06-22T23:59:59",
"finalResubmissionDeadline": null,
"settlementDeadline": null,
"contentLink": null,
"isSubmitted": false,
"events": []
}
},
"secondaryUsage": {
"status": "ACTIVE",
"months": 3,
"startDate": "2026-01-11",
"endDate": "2026-04-10",
"remainingDays": 45,
"expired": false
},
"followerCount": 50000,
"avgViewCount": 10000,
"rank": "A",
"rankPrice": 100000,
"adjustedEngagementRate": 4.6282,
"recentPosts": [
{
"postId": "abc123",
"postLink": "https://instagram.com/p/abc123",
"mediaUrl": "https://video.cdninstagram.com/...",
"mediaType": "VIDEO",
"publishedAt": "2025-01-20T14:30:00Z"
},
{
"postId": "def456",
"postLink": "https://instagram.com/p/def456",
"mediaUrl": "https://scontent.cdninstagram.com/...",
"mediaType": "IMAGE",
"publishedAt": "2025-01-18T10:00:00Z"
}
],
"contractId": 42,
"contractStatus": "SIGNED",
"selectedOptions": [
{
"optionId": 1,
"title": "색상",
"selected": [
{ "choiceId": 11, "value": "아이보리" }
]
}
]
}
],
"dummyItems": [
{
"id": 100000012,
"creator": "creator_jp_1",
"profileImg": null,
"creatorEmail": null,
"influenceId": null,
"creatorLink": "https://www.instagram.com/creator_jp_1/",
"matchingStatus": "waiting",
"flags": [],
"followerCount": 12400,
"avgViewCount": 8300,
"recommendReason": "뷰티 카테고리 릴스 비중이 높고 ...",
"rankPrice": 300000,
"quotePrice": 300000,
"currentPrice": 250000,
"recentPosts": [],
"trackingNumber": null,
"deliveryStatus": null,
"aiCheck": null,
"personCheck": null,
"uploadStatus": null
}
],
"isLegacy": false,
"totalCount": 10,
"eliminatedCount": 2,
"eliminatedPercentage": 20.0,
"campaignDetails": {
"no": "123",
"businessId": "business_001",
"thumbnailImagePath": "https://s3.../thumbnail.jpg",
"productImagePath": "https://s3.../product.jpg",
"productImagePaths": ["https://s3.../product1.jpg"],
"snsType": "INSTAGRAM",
"contentFormat": "REEL",
"category": "BEAUTY",
"campaignType": "SEEDING",
"campaignSubStep": "CREATOR_RECRUIT",
"guidelineStatus": "COMPLETED",
"productName": "상품명",
"charge": "100000",
"currency": "KRW",
"recruitCount": "10",
"recruitmentDays": 4,
"recruitmentStartDate": "2024-01-01 00:00:00",
"recruitmentEndDate": "2024-01-31 23:59:59",
"campaignContractType": "STANDARD",
"currentPriceRate": 80.0,
"quotePriceRate": 125.0
},
"budgetSummary": {
"totalBudget": 1000000,
"usedCredit": 300000,
"refundAmount": 700000,
"lockedCount": 3,
"status": "ACTIVE"
},
"guidelineCompletedAt": "2025-02-15T14:30:00",
"campaignCompletedAt": "2025-03-20T18:45:10",
"managers": [
{
"managerNo": 42,
"name": "홍길동",
"phone": "01000000000",
"email": "manager@example.com",
"isDefault": true
}
],
"businessApproved": false,
"canEditGuideline": false,
"regulationReviewEnabled": false
}
}Response 스키마
ProgressTableWithCampaignResponse
| 필드명 | 타입 | 설명 |
|---|---|---|
step | string | 진행 단계 ("MATCHING" | "EXECUTING") |
items | ProgressItemExceptBidPriceResponse[] | 진행 항목 목록 |
dummyItems | ProgressItemExceptBidPriceResponse[] | 데모(목업) 후보 목록. items와 동일한 타입이라 같은 컴포넌트로 렌더한다. followerCount·avgViewCount·recommendReason·rankPrice·quotePrice·currentPrice·recentPosts가 실제 신청과 동일하게 채워진다(비어 있지 않다). 진행상태(배송·검수·업로드)와 profileImg만 null. 데모임을 드러내는 표기 금지 — items와 구분 없이 자연스럽게 노출한다. 담긴 데모가 없으면 빈 배열. 연동 가이드 |
isLegacy | boolean | 레거시 여부 |
totalCount | int | 전체 항목 수 |
eliminatedCount | int | 탈락 항목 수 |
eliminatedPercentage | double | 탈락률 (%) |
campaignDetails | CampaignFullResponseDto | 캠페인 상세 정보. [일정 통일 Jay 2026-07-24] recruitmentDays(모집 영업일, 1-10 / 미설정 시 null) 포함 — 아래 Callout 참고 |
budgetSummary | BudgetSummaryDto | 캠페인 예산 요약 (SaaS 캠페인만) |
guidelineCompletedAt | datetime | 가이드라인 완성일. 가이드라인 미완성 시 null |
campaignCompletedAt | datetime | 캠페인 완료일 (campaignSubStep = CAMPAIGN_COMPLETED 로 최초 전환된 시각). 아직 완료되지 않았거나 백필 대상이 아닌 레거시 건은 null |
campaignDetails.recruitmentDays 는 가이드라인 미리보기의 "예상 일정" 모달용이다 (Jay 2026-07-24)
이 모달은 가이드 완성 전에 뜨는 화면이라 /ai/campaign-schedules/{campaignNo} 의
listOpenAt / recruitmentEndAt 이 아직 null 이고, 애초에 그 API 를 호출하지 않는다
(progress-table 만 호출). 그래서 날짜가 아니라 일수를 여기서 내려준다.
프론트는 가이드라인 완성 후 {recruitmentDays} 영업일 이내 로 렌더한다.
기존의 nation === "KR" ? 5 : 7 캘린더일 하드코딩은 2026-05-22 에 폐기된 옛 백엔드 규칙이
프론트에 남아 있던 것이므로 제거한다.
null 이면 관리자가 아직 고르지 않은 상태다. 일정 산정의 기본값 4영업일은
CampaignScheduleService 의 책임이며, 응답 DTO 는 임의로 4 를 채우지 않는다
(실제로 4 를 고른 것과 미설정을 화면이 구분할 수 있어야 한다).
가이드 완성 후 실제 날짜가 필요한 화면은 캠페인 일정 조회 API 의
listOpenAt 을 쓸 것.
| managers | BusinessManagerResponseDto[] | 캠페인 담당자 목록(N:M, 동등). 담당자 미지정 시 빈 배열 |
| businessApproved | boolean | 캠페인 소유자(광고주)의 기업 승인(계약 체결) 여부. 계약 상태(contractSignStatus)가 SIGNED / EXCEPTION 이면 true, NONE / PENDING 이면 false. 용도는 상단 안내 참고 |
| canEditGuideline | boolean | 광고주가 현재 가이드라인을 수정할 수 있는지 여부 (FE 수정 버튼 노출용). 모집 단계(CREATOR_RECRUIT/CREATOR_MATCHING)에서는 어드민이 수정 허용을 켠 경우에만 true (1회용: 광고주가 완성본을 한 번 저장하면 소진되어 다시 false, 어드민 재허용 필요). 콘텐츠 제작 단계 이후에는 false, 가이드라인 미생성 시 false |
| regulationReviewEnabled | boolean | 약기법(일본 광고규정) 검수 사용 여부 (FE 약기법 UI 노출 분기용). 어드민이 캠페인별로 켜는 opt-in 플래그이며, 가이드라인 미생성 시 false. 켜져 있으면 가이드라인 완성·제출물 제출·광고주 피드백 제출 시 약기법 검수가 자동 실행된다. 약기법 검수 개요 |
ProgressItemExceptBidPriceResponse
| 필드명 | 타입 | 설명 |
|---|---|---|
id | long | 항목 ID |
creator | string | 크리에이터 이름 |
creatorEmail | string | 크리에이터 이메일 |
influenceId | long | 인플루언서 ID |
profileImg | string | 프로필 이미지 URL |
creatorLink | string | 크리에이터 링크 |
trackingNumber | string | 운송장 번호 |
courierCode | string | 택배사 코드(kr.xxx) 또는 기타 한글명/OTHER. 미입력 시 null |
customCourierName | string | 기타 택배사 자유입력명. 지원 택배사(kr.*)면 null. 목록 다운로드(엑셀)의 택배사명 컬럼은 이 값을 사용 |
shippedAt | string | 배송 시작(집화) 시각. 추적 전/불가 시 null |
deliveredAt | string | 배송 완료 시각. 완료 전 null |
trackingDetail | string(JSON) | 배송조회 전체 응답(타임라인). 미조회 시 null. 구조는 배송 상세조회 문서 참고 |
uploadUrl | string | 업로드 URL |
file | string | 파일 URL |
aiCheckReady | boolean | AI 체크 준비 상태 |
matchingStatus | string | 매칭 상태 |
deliveryStatus | string | 배송 상태 |
aiCheck | string | AI 검수 상태 |
personCheck | string | 담당자 검수 상태 |
campaignResult | string | 캠페인 결과 URL |
recommendReason | string | 추천 이유 |
uploadStatus | string | 업로드 상태 |
quotePrice | long | 견적 가격 |
currentPrice | long | 현재 가격 |
collaborationNote | string | 협업 노트 |
reviewInfo | ReviewInfo | 검수 정보 (기존, 유지) |
submissionTimeline | SubmissionTimeline | [TASK-081] 콘텐츠 제출 일정 4그룹(스크립트/영상/업로드/최종제출물). 각 그룹의 마감일·제출일·제출기록(언제 제출/재제출/검수했는지 이벤트 타임라인). 아래 SubmissionTimeline 참고 |
secondaryUsage | SecondaryUsageInfo | 2차 활용 기간 표기(기업 대시보드). 최종제출물 "모두 제출 완료"일 ~ N개월(90일 아닌 개월수 기준). 아래 SecondaryUsage 참고 |
followerCount | long | 팔로워 수 |
avgViewCount | long | 평균 조회수 |
rank | string | 랭크 (A/B/C 등) |
rankPrice | int | 랭크별 가격 |
adjustedEngagementRate | double | 보정 참여율(%). PostgreSQL influencer_profile.adjusted_engagement_rate 원본값. 미산정 계정은 null. 아래 보정 참여율 참고 |
recentPosts | PostMediaDto[] | 최근 게시물 미디어 (최대 3개) |
contractId | long | 전자계약서 ID (계약서 미발송 시 null) |
contractStatus | string | 전자계약 상태 (DRAFT, SENT, OPENED, FILLED, OTP_VERIFIED, SIGNED). 계약서 미발송 시 null |
selectedOptions | array | 캠페인 제공 옵션 — 크리에이터가 선택한 값 (그룹별로 묶음). 옵션 정의는 GET /ai/campaign/{collabNo}/provided-options, 정책은 캠페인 제공 옵션 개요 참고. 옵션 미정의/미선택 시 빈 배열 |
PostMediaDto
| 필드명 | 타입 | 설명 |
|---|---|---|
postId | string | 게시물 ID |
postLink | string | 게시물 링크 |
mediaUrl | string | 미디어 URL (video_url 또는 images 중 첫 번째) |
mediaType | string | 미디어 타입 (VIDEO 또는 IMAGE) |
publishedAt | datetime | 게시일 |
recentPosts는 PostgreSQL의 post 테이블에서 조회됩니다.
video_url이 있으면mediaType: VIDEO로 반환video_url이 없으면images의 첫 번째 이미지를mediaType: IMAGE로 반환
보정 참여율
adjustedEngagementRate는 크롤링 PostgreSQL의 influencer_profile.adjusted_engagement_rate 값을 반올림 없이 그대로 내려줍니다.
- 단위: 퍼센트(%). 예를 들어
4.6282는 4.6282% 를 뜻합니다. - 조회 기준:
is_current = true인 프로필을sns_account_link로 매칭합니다. - 데이터 소스 예외:
followerCount/avgViewCount는 Instagram OAuth 연동 데이터(SocialAccount)가 있으면 그쪽을 우선합니다. 반면adjustedEngagementRate는 크롤링 PostgreSQL 에만 존재하는 값이라 연동 여부와 무관하게 항상 PostgreSQL 프로필에서 채웁니다. null인 경우: 매칭되는 프로필이 없거나, 프로필은 있어도 참여율이 아직 산정되지 않은 계정. 전체 프로필 중 약 1/3 만 값이 채워져 있으므로 프런트는null처리를 반드시 넣어야 합니다.
BusinessManagerResponseDto
| 필드명 | 타입 | 설명 |
|---|---|---|
managerNo | long | 담당자 번호 |
name | string | 담당자 이름 |
phone | string | 담당자 연락처 |
email | string | 담당자 이메일 |
isDefault | boolean | 기본 담당자 여부 |
BudgetSummaryDto (SaaS 캠페인만 반환)
| 필드명 | 타입 | 설명 |
|---|---|---|
totalBudget | int | 캠페인 총 예산 (CAMPAIGN_DEPOSIT 합계) |
usedCredit | int | 사용(LOCKED) 금액 합계 |
refundAmount | int | 환급 가능 금액 (totalBudget - usedCredit) |
lockedCount | long | 잠금된 건수 |
status | string | 상태 ("ACTIVE") |
budgetSummary는 SaaS 캠페인(collabNo >= saasNum)에서만 반환됩니다.
레거시 캠페인은 null이 반환됩니다.
[TASK-071 2026-05-26] guidelineCompletedAt 출처 변경:
- 주 출처: MongoDB
GuidelineDocument.completedAt(v1/v2/v4 publish 시 자동 set, 운영자 정정 가능) - Fallback (legacy): 위 값이 없으면
Collab.recruitment_start_date - 가이드라인 상태가
COMPLETED가 아니면 fallback 도 시도하지 않고null반환
campaignCompletedAt은 TB_COLLAB.campaign_completed_at 컬럼 값입니다.
PATCH /ai/admin/campaigns/{campaignNo}/substep?substep=CAMPAIGN_COMPLETED를 호출해 최초로CAMPAIGN_COMPLETED상태가 되는 순간 서버 시각(LocalDateTime.now())으로 기록됩니다.- 이미 값이 있으면 재호출해도 덮어쓰지 않습니다 (최초 완료 시각 유지).
- 기존 완료 캠페인은
FinalSubmission.submitted_at중 MAX 값으로 일회성 백필되어 있습니다. 최종 제출물이 전혀 없는 레거시 완료 건은null입니다.
예산 관련 에러 응답
매칭 상태를 PROPOSAL로 변경할 때 예산 검증 실패 시 발생하는 에러입니다.
| 상태 코드 | 에러 코드 | 설명 |
|---|---|---|
| 400 | NEED_CAMPAIGN_DEPOSIT | 캠페인 예산이 부족합니다. 추가 입금이 필요합니다. |
| 400 | INSUFFICIENT_GLOBAL_CREDIT | 기업 크레딧이 부족합니다. 크레딧을 충전해주세요. |
| 403 | PROPOSAL_STATUS_CHANGE_NOT_ALLOWED | 제안 상태의 크리에이터는 기업에서 상태를 변경할 수 없습니다. 관리자에게 문의해주세요. |
| 404 | BUDGET_NOT_FOUND | 캠페인 예산 정보를 찾을 수 없습니다. |
| 400 | BUDGET_ALREADY_LOCKED | 이미 예산이 예약되어 있습니다. |
| 400 | BUDGET_ALREADY_UNLOCKED | 이미 예산이 취소되었습니다. |
NEED_CAMPAIGN_DEPOSIT 발생 시: 캠페인 예산 추가 입금 API (POST /ai/payments/collab/confirm)를 호출하세요.
INSUFFICIENT_GLOBAL_CREDIT 발생 시: 크레딧 충전 API (POST /ai/payments/credit/confirm)를 먼저 호출하세요.
SubmissionTimeline
[TASK-081] 콘텐츠 제출 일정을 4그룹으로 묶어 내려줍니다. 데드라인 / 제출일 / 제출기록(이벤트 타임라인)을 한 번에 표시하기 위한 블록입니다. 기존 reviewInfo는 그대로 유지되며 이 블록이 추가됩니다.
| 그룹 | 키 | 대상 |
|---|---|---|
| 스크립트 | script | 2회검수(대본검수) 캠페인만 존재. 1회검수 캠페인은 null |
| 영상 | video | 영상/제작물 검수 |
| 업로드 | upload | 업로드일자·업로드타입·업로드 데드라인 |
| 최종제출물 | finalSubmission | 최종제출물(콘텐츠 링크/파일)·재제출 마감·정산 |
그룹 객체 (script / video) — SubmissionPhaseGroup
| 필드 | 타입 | 설명 |
|---|---|---|
exists | boolean | 그룹 존재 여부 |
reviewRound | int | 1=스크립트, 2=영상 |
reviewStatus | string | 해당 라운드 검수 상태 (PENDING, REVIEWING, APPROVED, REJECTED). 없으면 null |
submittedAt | LocalDateTime | 제출일(최신 제출 시각). 미제출 시 null |
deadlines | GroupDeadlines | 마감 세트 (아래) |
items | SubmissionItemSnapshot[] | 현재(최신 버전) 제출물 목록 |
events | TimelineEvent[] | 제출/재제출/검수 이벤트 타임라인 (시각 오름차순) |
GroupDeadlines
| 필드 | 타입 | 설명 |
|---|---|---|
submissionDeadline | LocalDateTime | 제출 마감 |
submissionOriginalDeadline | LocalDateTime | 최초 제출 마감(불변, 참조용) |
submissionDeadlineConfirmed | boolean | 트리거 발동으로 확정됐는지. false면 임시(placeholder) 마감 |
reviewDeadline | LocalDateTime | 검수 마감(기업쪽). 없으면 null |
feedbackDeadline | LocalDateTime | 피드백 반영 마감(재제출 발생 시). 없으면 null |
upload — UploadGroup
| 필드 | 타입 | 설명 |
|---|---|---|
uploadDate | LocalDateTime | 업로드일자 (업로드 허용일) |
uploadType | string | 업로드타입 (OPTIMAL, CANDIDATE, ASAP 등) |
uploadDeadline | LocalDateTime | [태스크21] 업로드/최종제출물 제출 마감 = 업로드 허용일 + 2일(ASAP/OPTIMAL), CANDIDATE는 고른 날 그대로. 업로드 단계(영상검수 승인) 도달 전이면 null. 아래 deadline(재제출 마감)과 별개 |
deadline | LocalDateTime | 업로드 데드라인 (= 최종제출물 재제출 마감과 동일 값). 없으면 null |
events | TimelineEvent[] | 업로드 관련 이벤트 타임라인 |
finalSubmission — FinalSubmissionGroup
| 필드 | 타입 | 설명 |
|---|---|---|
submittedAt | LocalDateTime | 최종제출물 제출 시각 |
finalSubmissionDeadline | LocalDateTime | [태스크21] 최종제출물 제출 마감 (= upload.uploadDeadline과 동일 값, 업로드 허용일 + 2일). 업로드 단계 도달 전이면 null. finalResubmissionDeadline(재제출 마감)과 별개 |
finalResubmissionDeadline | LocalDateTime | 재제출 마감(재요청 시). 없으면 null |
settlementDeadline | LocalDateTime | 정산 마감(업로드 완료 후). 없으면 null |
contentLink | string | 업로드한 콘텐츠 링크 |
isSubmitted | boolean | 최종제출 여부 |
events | TimelineEvent[] | 최종제출/재요청/정산 이벤트 타임라인 |
SubmissionItemSnapshot (현재 제출물)
| 필드 | 타입 | 설명 |
|---|---|---|
id | long | 제출물 ID |
itemType | string | SCRIPT_VIDEO, SCRIPT_BLOG, VIDEO, PHOTO, CAPTION, HASHTAG |
scriptIndex | int | 복수 스크립트 슬롯 구분 (비스크립트면 null) |
currentVersion | int | 현재 버전 번호 |
status | string | 제출물 검수 상태 |
isSubmitted | boolean | 제출 완료 여부 |
submittedAt | LocalDateTime | 현재 버전 제출 시각 |
TimelineEvent (제출기록 = 언제 무슨 일이 있었는지)
TB_TRIGGER_LOG 기반의 append-only 이벤트입니다. triggeredAt이 실제 이벤트가 일어난 시각(제출/재제출요청/검수완료 등)입니다. 재제출이 여러 번이면 같은 그룹의 events에 여러 행이 시각순으로 누적됩니다.
| 필드 | 타입 | 설명 |
|---|---|---|
triggerType | string | 트리거 종류 (예: TRIGGER_SCRIPT_SUBMITTED, TRIGGER_VIDEO_SUBMITTED, TRIGGER_3R_VIDEO_RESUBMIT_REQUEST, TRIGGER_2_SCRIPT_REVIEW_DONE) |
label | string | 트리거 한국어 설명 |
phase | string | 갱신된 일정 단계 (ApplicationSchedulePhase) |
triggeredAt | LocalDateTime | 이벤트 발생 시각 (= 제출/재제출/검수 시점) |
previousDeadline | LocalDateTime | 이 이벤트 직전 마감일 |
newDeadline | LocalDateTime | 이 이벤트로 갱신된 마감일 |
그룹 분류 규칙: ContentReview.reviewRound(1=스크립트, 2=영상) 기준입니다. events는 phase로 그룹에 매핑되며, 라운드가 공유하는 FEEDBACK_DEADLINE은 triggerType으로 분리됩니다. 스크립트 검수가 없는 1회검수 캠페인은 script가 null입니다.
events는 일정(마감)에 영향을 준 트리거만 기록합니다(순수 임시저장은 제외). 실제 제출/재제출/검수승인은 모두 기록됩니다. 버전별 파일 내용은 items(현재 버전)와 reviewInfo를 함께 참고하세요.
SecondaryUsage
기업 대시보드에서 크리에이터별 2차 활용 기간을 표기(카운트다운 → 만료)하기 위한 블록입니다. 시작일은 해당 크리에이터가 최종제출물을 모두 제출 완료한 날이며, 종료일은 시작일 + N개월 − 1일입니다 (90일 카운팅이 아니라 개월수 기준. 예: 시작 1/11, 3개월 → 만료 4/10). 기간은 최초 제출 완료 시점에 확정되며, 이후 재제출이나 캠페인 개월수 변경에도 바뀌지 않습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
status | string | NONE(2차활용 없음, 0개월) | PERMANENT(영구, -1) | NOT_STARTED(최종제출 전) | ACTIVE(진행중) | EXPIRED(만료) |
months | int | 2차 활용 개월수. -1=영구, 0=없음 |
startDate | date | 2차 활용 시작일(= 최종제출 완료일). NONE/PERMANENT/NOT_STARTED이면 null |
endDate | date | 만료일(포함 마지막 날, = 시작일 + N개월 − 1일). 영구/없음/미제출이면 null |
remainingDays | long | 오늘부터 만료일까지 남은 일수(만료 후 음수). 해당 없으면 null. D-{remainingDays} 표기용 |
expired | boolean | 만료 여부(오늘 > 만료일) |
프런트는 이 값으로 2차활용 기간: {startDate}~{endDate} ({months}개월) + D-{remainingDays}(만료 시 "2차활용 만료")를 렌더합니다. remainingDays는 서버 기준일(KST)로 계산됩니다.