Admin APIAdmin Dashboard API
PATCH /ai/admin/dashboard/applications/{applicationId}/prices
단건 application 가격 4종(희망가/제안가/기존가/노출가) 부분 수정 + 자동계산 트리거
단건 application 가격 4종 부분 수정
어드민이 단건 CampaignApplication의 4종 가격(희망가/제안가/기존가/노출가)을 한 번의 요청으로 부분 수정합니다.
- 모든 가격 필드는 nullable — 전달된 필드만 변경됩니다.
- 희망가는
CampaignApplication.defaultUnitPrice에 직접 UPDATE. - 제안가는
TB_PRICE_NEGOTIATION에 신규 ACCEPT row INSERT (proposed_by="ADMIN", 기존 이력 보존). - 기존가/노출가는 두 가지 동작:
- 명시 전달 시 → 그 값으로 직접 upsert (MANUAL)
- 미전달 + 제안가 변경 시 → 제안가 기반 자동계산값 적용 (AUTO_CALCULATED)
- 미전달 + 제안가 미변경 시 → 손대지 않음 (UNCHANGED)
- 응답의
updatedFields/quotePriceSource/currentPriceSource로 실제 결과 확인. - 변경 사항은 INFO 로그 (
[AdminPrice] ...) 로 남아 CS 감사 활용 가능.
4종 가격 ↔ 저장 위치
| 화면 용어 | 요청 필드 | 저장 위치 | 처리 방식 |
|---|---|---|---|
| 희망가 | defaultUnitPrice | TB_CAMPAIGN_APPLICATION.default_unit_price | UPDATE |
| 제안가 | proposedPrice | TB_PRICE_NEGOTIATION.proposed_price | 신규 ACCEPT row INSERT |
| 기존가 | quotePrice | TB_APPLICATION_MATCHING.quote_price | upsert (수동/자동) |
| 노출가 (글로우비 특별가) | currentPrice | TB_APPLICATION_MATCHING.current_price | upsert (수동/자동) |
자동계산 공식
Collab.feeType = PERCENT 이고 Collab.feeValue 가 양수일 때만 자동계산 트리거됨. 둘 중 하나라도 만족 못 하면 자동계산 skip (기존가/노출가는 UNCHANGED).
currentPrice = floor10000(proposedPrice × (1 + feeValue / 100))
quotePrice = floor10000(currentPrice × 1.5)만원 단위 버림(floor).
예시 (캠페인 1534, feeValue=15)
proposedPrice 400,000 → currentPrice 460,000 → quotePrice 690,000ACCEPT 차단과 forceOverride
해당 신청에 이미 수락(ACCEPT)된 PriceNegotiation 이 존재할 때 제안가(proposedPrice) 변경은 기본 차단됩니다.
| 조건 | 동작 |
|---|---|
| ACCEPT 협상 없음 | 정상 처리 |
ACCEPT 있음 + forceOverride 미전송 또는 false | 400 차단 ("이미 수락된 협상이 있습니다. 제안가 변경 시 forceOverride=true 가 필요합니다.") |
ACCEPT 있음 + forceOverride: true | 정상 처리. 응답의 overrideApplied = true |
희망가/기존가/노출가만 수정하는 경우엔 차단 체크가 적용되지 않습니다.
HTTP 요청
PATCH /ai/admin/dashboard/applications/{applicationId}/prices
Authorization: Bearer {access_token}
Content-Type: application/jsonPath Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
applicationId | Long | 예 | 신청 ID (TB_CAMPAIGN_APPLICATION.id) |
Request Body
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
defaultUnitPrice | Long | 아니오 | 희망가. 전달 시 직접 UPDATE |
proposedPrice | Long | 아니오 | 제안가. 전달 시 PriceNegotiation 신규 ACCEPT INSERT + (옵션) quote/current 자동계산 |
quotePrice | Long | 아니오 | 기존가. 전달 시 그 값으로 강제 (MANUAL). 미전달 + proposedPrice 변경 시 자동계산 |
currentPrice | Long | 아니오 | 노출가. 전달 시 그 값으로 강제 (MANUAL). 미전달 + proposedPrice 변경 시 자동계산 |
forceOverride | Boolean | 아니오 | 기본 false. true 시 ACCEPT 협상 존재해도 제안가 변경 허용 |
요청 예시 1 — 제안가만 보내고 quote/current 자동계산 (대표 케이스)
{
"defaultUnitPrice": 400000,
"proposedPrice": 400000
}→ 응답: quotePriceSource=AUTO_CALCULATED, currentPriceSource=AUTO_CALCULATED
요청 예시 2 — 희망가만 정정 (다른 가격 손대지 않음)
{
"defaultUnitPrice": 400000
}→ quotePriceSource=UNCHANGED, currentPriceSource=UNCHANGED
요청 예시 3 — 자동공식 무시하고 어드민 수동 가격 강제
{
"proposedPrice": 400000,
"quotePrice": 500000,
"currentPrice": 350000
}→ 명시값 그대로 (MANUAL). 자동계산 결과 무시.
요청 예시 4 — quote/current 만 수동 조정 (제안가 미변경)
{
"quotePrice": 500000,
"currentPrice": 350000
}→ 제안가는 그대로, ApplicationMatching 만 upsert. MANUAL.
요청 예시 5 — ACCEPT 차단 우회
{
"proposedPrice": 350000,
"forceOverride": true
}→ 신규 ACCEPT row INSERT (기존 ACCEPT 보존). overrideApplied=true.
응답
성공 응답 (200 OK) — 예시 1 (자동계산)
{
"status": 200,
"code": null,
"message": "가격 수정 완료",
"data": {
"applicationId": 14853,
"updatedFields": ["defaultUnitPrice", "proposedPrice", "quotePrice", "currentPrice"],
"defaultUnitPrice": 400000,
"quotePrice": 690000,
"currentPrice": 460000,
"newPriceNegotiationId": 7501,
"overrideApplied": false,
"quotePriceSource": "AUTO_CALCULATED",
"currentPriceSource": "AUTO_CALCULATED"
}
}성공 응답 (200 OK) — 예시 2 (희망가만)
{
"status": 200,
"code": null,
"message": "가격 수정 완료",
"data": {
"applicationId": 14853,
"updatedFields": ["defaultUnitPrice"],
"defaultUnitPrice": 400000,
"quotePrice": 690000,
"currentPrice": 460000,
"newPriceNegotiationId": null,
"overrideApplied": false,
"quotePriceSource": "UNCHANGED",
"currentPriceSource": "UNCHANGED"
}
}차단 응답 (400) — ACCEPT 존재 + forceOverride 미전송
{
"status": 400,
"code": "REQ_001",
"message": "이미 수락된 협상이 있습니다. 제안가 변경 시 forceOverride=true 가 필요합니다.",
"data": null
}에러 응답
| 상태 코드 | 코드 | 설명 |
|---|---|---|
400 | REQ_001 | 잘못된 요청 (존재하지 않는 applicationId, 또는 ACCEPT 차단) |
응답 필드
| 필드 | 타입 | 설명 |
|---|---|---|
applicationId | Long | 요청한 신청 ID |
updatedFields | List<String> | 실제로 변경된 필드명 목록 (UNCHANGED 인 quote/current 는 미포함) |
defaultUnitPrice | Long | 변경 후 희망가 |
quotePrice | Long | 변경 후 기존가 (ApplicationMatching 미존재 시 null) |
currentPrice | Long | 변경 후 노출가 |
newPriceNegotiationId | Long | 제안가 변경 시 INSERT 된 신규 PriceNegotiation row id, 그 외 null |
overrideApplied | Boolean | forceOverride=true 로 ACCEPT 차단을 우회했는지 |
quotePriceSource | String | MANUAL / AUTO_CALCULATED / UNCHANGED |
currentPriceSource | String | 동일 |