POST /ai/admin/finance/budget/{budgetId}/unlock
캠페인 LOCK 예산 해제 (가용 예산으로 복귀, audit row 없음)
캠페인 LOCK 예산 해제
크리에이터 제안 시 LOCK 된 CampaignBudget 한 행을 LOCKED → UNLOCKED 로 바꿉니다.
해제 금액은 그 캠페인의 가용 예산으로 즉시 복귀합니다.
글로벌 잔액(Business.remainCredit) 은 변동 없고, CreditTransaction audit row 도
만들지 않습니다 (캠페인 예산 내부 상태 변경). 해제 이력은 예산 행 상태에서 파생되어
캠페인 상세 타임라인의 BUDGET_UNLOCK 항목으로 노출됩니다.
신청건의 선정 상태(selectionStatus) 는 바뀌지 않습니다. 예산만 풀리므로 "제안 중인데 예산은 안 잡힌" 상태가 됩니다.
해당 신청건을 다시 PROPOSAL 로 전환하면 이 UNLOCKED 행이 재사용(CAMPAIGN_LOCK_REUSE)되어 그 시점 금액으로 다시 LOCK 됩니다.
제안 자체를 취소하려면 진행 테이블에서 상태를 변경하세요 (상태 변경 시 UNLOCK 이 자동으로 함께 일어납니다).
HTTP 요청
POST /ai/admin/finance/budget/{budgetId}/unlock?adminId={adminId}
Authorization: Bearer {access_token}Request body 없음.
Path / Query Parameters
| 파라미터 | 위치 | 타입 | 필수 | 설명 |
|---|---|---|---|---|
budgetId | path | Long | 예 | CampaignBudget.id. 캠페인 상세 타임라인의 budgetLock:{id} 에서 얻습니다 |
adminId | query | String | 아니오 | 처리자 ID. 미지정 시 admin. 서버 로그에만 남습니다 |
동작
budget = CampaignBudget(id = budgetId) // 없으면 404
reject if budget.type == DEFICIT // 부족분 전용 API 사용
reject if budget.application == null
reject if budget.status == UNLOCKED // 이미 해제됨
budget.status = UNLOCKED // updatedAt 갱신
save budget
recalcDeficitIfEngaged(budget.collabNo) // 부족분 lock 걸린 캠페인이면 금액 재계산
// Business.remainCredit 변동 없음
// CreditTransaction 생성 없음
// selectionStatus 변동 없음실제 해제는 CampaignBudgetService#unlockBudget 단일 경로를 재사용합니다
(행 잠금 조회 + [CAMPAIGN_UNLOCK] 로그 + 부족분 재계산 포함).
응답 (200 OK)
{
"status": 200,
"code": null,
"message": "예산 LOCK 해제 완료",
"data": {
"budgetId": 88,
"collabNo": 482,
"applicationId": 9876,
"influenceName": "홍길동",
"amount": 250000,
"status": "UNLOCKED",
"type": "NORMAL",
"unlockedAt": "2026-07-30T14:22:11",
"unlockedBy": "admin01"
}
}| 필드 | 타입 | 설명 |
|---|---|---|
budgetId | Long | 해제한 예산 행 id |
collabNo | Integer | 캠페인 번호 |
applicationId | Long | 연결된 신청건 id |
influenceName | String | 크리에이터 이름 |
amount | Integer | 가용 예산으로 복귀한 금액 |
status | String | 해제 후 상태 — 항상 UNLOCKED |
type | String | 항상 NORMAL (DEFICIT 은 대상 아님) |
unlockedAt | String | 해제 시각 (CampaignBudget.updatedAt) |
unlockedBy | String | 요청 adminId 반사값 (DB 미저장) |
에러
| 상태 | code | 조건 |
|---|---|---|
409 | FINANCE_002 | 이미 해제된 예산 |
409 | FINANCE_002 | 부족분(DEFICIT) 행 — 전용 API 사용 |
409 | FINANCE_002 | 신청건이 연결되지 않은 예산 행 |
404 | INVALID_DATA | 해당 budgetId 예산 없음 |
{
"status": 409,
"code": "FINANCE_002",
"message": "이미 예산이 취소되었습니다. budgetId=88",
"data": null
}에러도 HTTP 200 으로 내려가고 실제 코드는 body 의 status 필드에 담깁니다. HTTP 상태만 보고 성공 판정하면 안 됩니다.
부족분(DEFICIT) lock 은 별도 API
type = DEFICIT 행(최초 입금액 80% 기준 부족분 lock)은 재계산 로직과 충돌하므로 이 API 로 해제할 수 없습니다.
DELETE /ai/campaigns/{collabNo}/budget/deficit-lock관련 API
| 목적 | 엔드포인트 |
|---|---|
| LOCK 금액만 수정 (상태 유지) | PATCH /ai/admin/finance/budget/{budgetId}/amount |
| budgetId 조회 | GET /ai/admin/finance/collab/{collabNo} |
| 신청 id 기준 해제 (확장 프로그램) | POST /extension/campaign-budgets/{applicationId}/unlock |
| 캠페인 전체 해제 + 잔액 환급 | POST /ai/admin/campaigns/{campaignNo}/terminate |
예산을 LOCK 하는 전용 API 는 없습니다. 진행 테이블에서 PROPOSAL 로 전환하거나
POST /ai/progress-table/items/bulk/proposal-with-charge 를 호출할 때 자동으로 LOCK 됩니다.
결과 확인 (DB)
SELECT id, collab_no, application_id, amount, status, type, updated_at
FROM TB_CAMPAIGN_BUDGET
WHERE id = 88;
-- 캠페인 LOCK 합계 (가용 예산 확인)
SELECT status, type, SUM(amount) AS total, COUNT(*) AS cnt
FROM TB_CAMPAIGN_BUDGET
WHERE collab_no = 482
GROUP BY status, type;