feat(cupang): 박스 계산 임시 저장 / 불러오기
계산 중 화면 상태를 서버에 이름 붙여 저장하고 다시 불러온다. 다른 PC 에서도
이어서 작업할 수 있어야 해서 localStorage 대신 DB 에 둔다.
저장 대상은 화면 스냅샷(입력 품목·열어둔 센터·분배 내역·도장 상태)뿐이고,
박스 수는 불러온 뒤 기존 계산 API 로 서버에서 다시 구한다. 저장값을 그대로
믿지 않으므로 입수량 규칙이 바뀌어도 어긋나지 않고, 계산 결과에서 사라진
항목은 제외한 건수를 알려준다. 같은 제목으로 저장하면 덮어써 목록이 무한히
늘어나지 않는다.
- scripts/sql/cupang_db_003_box_calc_drafts.sql (신규 테이블 + 권한, 멱등)
- db.py: list/get/save/delete_box_calc_draft
- router: GET·POST /cupang/api/box-calc/drafts, GET·DELETE /{id}
- box_calc.html: [임시 저장]·[불러오기] 버튼 + 목록 대화상자
This commit is contained in:
@@ -908,6 +908,88 @@ async def product_delete(
|
||||
return RedirectResponse(url="/cupang/products", status_code=303)
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 박스 계산 임시 저장 (화면 상태 스냅샷)
|
||||
# ════════════════════════════════════════════════════════════
|
||||
@router.get("/api/box-calc/drafts")
|
||||
async def box_calc_drafts_list(
|
||||
request: Request,
|
||||
user: dict[str, Any] = Depends(_require_user),
|
||||
) -> JSONResponse:
|
||||
store = _store(request)
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||
return JSONResponse({"drafts": store.list_box_calc_drafts()})
|
||||
|
||||
|
||||
@router.get("/api/box-calc/drafts/{draft_id:int}")
|
||||
async def box_calc_draft_get(
|
||||
request: Request,
|
||||
draft_id: int,
|
||||
user: dict[str, Any] = Depends(_require_user),
|
||||
) -> JSONResponse:
|
||||
store = _store(request)
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||
draft = store.get_box_calc_draft(draft_id=draft_id)
|
||||
if draft is None:
|
||||
raise HTTPException(status_code=404, detail="저장된 내용을 찾을 수 없습니다.")
|
||||
return JSONResponse({"draft": draft})
|
||||
|
||||
|
||||
@router.post("/api/box-calc/drafts")
|
||||
async def box_calc_draft_save(
|
||||
request: Request,
|
||||
payload: dict[str, Any] = Body(...),
|
||||
user: dict[str, Any] = Depends(_require_user),
|
||||
) -> JSONResponse:
|
||||
"""{title, payload, draft_id?} → 저장(같은 제목이면 덮어쓰기)."""
|
||||
store = _store(request)
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||
|
||||
title = str(payload.get("title") or "").strip()
|
||||
if not title:
|
||||
raise HTTPException(status_code=400, detail="제목을 입력하세요.")
|
||||
if len(title) > 100:
|
||||
title = title[:100]
|
||||
snapshot = payload.get("payload")
|
||||
if not isinstance(snapshot, dict):
|
||||
raise HTTPException(status_code=400, detail="payload 는 객체여야 합니다.")
|
||||
|
||||
raw_id = payload.get("draft_id")
|
||||
draft_id = int(raw_id) if isinstance(raw_id, int) or (isinstance(raw_id, str) and raw_id.isdigit()) else None
|
||||
|
||||
try:
|
||||
draft = store.save_box_calc_draft(
|
||||
title=title,
|
||||
payload=snapshot,
|
||||
created_by=str(user.get("name") or user.get("email") or ""),
|
||||
draft_id=draft_id,
|
||||
)
|
||||
except KeyError:
|
||||
raise HTTPException(status_code=404, detail="저장된 내용을 찾을 수 없습니다.")
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
return JSONResponse({"draft": draft})
|
||||
|
||||
|
||||
@router.delete("/api/box-calc/drafts/{draft_id:int}")
|
||||
async def box_calc_draft_delete(
|
||||
request: Request,
|
||||
draft_id: int,
|
||||
user: dict[str, Any] = Depends(_require_user),
|
||||
) -> JSONResponse:
|
||||
store = _store(request)
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||
try:
|
||||
store.delete_box_calc_draft(draft_id=draft_id)
|
||||
except KeyError:
|
||||
raise HTTPException(status_code=404, detail="저장된 내용을 찾을 수 없습니다.")
|
||||
return JSONResponse({"deleted": True})
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
async def health() -> dict[str, str]:
|
||||
return {"status": "ok", "module": "cupang"}
|
||||
|
||||
Reference in New Issue
Block a user