feat(cupang): 달력 상세에서 출고 삭제 (건별 / 날짜 전체)
분배 확정으로 만든 출고를 지우려면 상세 페이지까지 들어가야 했다. 달력에서
날짜를 고른 뒤 오른쪽 카드에서 바로 지울 수 있게 한다. 센터가 여러 건일 때를
위해 목록 헤더에 "이 날짜 전체 삭제" 도 둔다. 둘 다 가운데 확인 팝업을 거친다.
삭제는 기존 정책대로 status='취소' soft delete 이고, 대신 달력·목록에서
취소 건을 걸러내 사용자에게는 삭제로 보이게 했다(기록은 DB 에 남는다).
- router: /{id}/delete 에 next 폼값(내부 경로만 허용) 추가, POST /cupang/day-delete 신설
- index.html: 카드 헤더에 삭제 버튼, 앵커 안에 버튼이 들어가지 않게 구조 정리
This commit is contained in:
@@ -129,7 +129,11 @@ async def index(request: Request) -> HTMLResponse:
|
||||
store, user = guard
|
||||
|
||||
year, month = _ym(request)
|
||||
shipments = store.list_shipments(year=year, month=month)
|
||||
# 취소된 묶음은 달력·목록 어디에도 보이지 않는다(soft delete = 삭제로 취급).
|
||||
shipments = [
|
||||
s for s in store.list_shipments(year=year, month=month)
|
||||
if s.get("status") != "취소"
|
||||
]
|
||||
|
||||
# 달력 칸에는 출고 건수와 센터 수만 보여준다.
|
||||
counts: dict[str, dict[str, Any]] = {}
|
||||
@@ -340,10 +344,19 @@ async def update(
|
||||
return RedirectResponse(url=f"/cupang/{shipment_id}", status_code=303)
|
||||
|
||||
|
||||
def _safe_next(raw: str, fallback: str) -> str:
|
||||
"""열린 리다이렉트 방지 — /cupang/ 안쪽 경로만 허용."""
|
||||
nxt = (raw or "").strip()
|
||||
if nxt.startswith("/cupang/") and "//" not in nxt[1:]:
|
||||
return nxt
|
||||
return fallback
|
||||
|
||||
|
||||
@router.post("/{shipment_id:int}/delete")
|
||||
async def delete(
|
||||
request: Request,
|
||||
shipment_id: int,
|
||||
next: str = Form(""),
|
||||
user: dict[str, Any] = Depends(_require_user),
|
||||
) -> RedirectResponse:
|
||||
"""운영 안전: 기본은 status='취소' soft delete."""
|
||||
@@ -354,7 +367,9 @@ async def delete(
|
||||
store.soft_delete(shipment_id=shipment_id)
|
||||
except KeyError:
|
||||
raise HTTPException(status_code=404, detail="출고 묶음을 찾을 수 없습니다.")
|
||||
return RedirectResponse(url=f"/cupang/{shipment_id}", status_code=303)
|
||||
return RedirectResponse(
|
||||
url=_safe_next(next, f"/cupang/{shipment_id}"), status_code=303
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{shipment_id:int}/hard-delete")
|
||||
@@ -374,6 +389,36 @@ async def hard_delete(
|
||||
return RedirectResponse(url="/cupang/", status_code=303)
|
||||
|
||||
|
||||
@router.post("/day-delete")
|
||||
async def day_delete(
|
||||
request: Request,
|
||||
date: str = Form(...),
|
||||
next: str = Form(""),
|
||||
user: dict[str, Any] = Depends(_require_user),
|
||||
) -> RedirectResponse:
|
||||
"""선택한 출고일의 묶음을 한 번에 취소 처리한다(soft delete)."""
|
||||
store = _store(request)
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||
|
||||
day = (date or "").strip()
|
||||
try:
|
||||
_date.fromisoformat(day)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="날짜 형식이 올바르지 않습니다.")
|
||||
|
||||
for ship in store.list_shipments(date_from=day, date_to=day):
|
||||
if ship.get("status") == "취소":
|
||||
continue
|
||||
try:
|
||||
store.soft_delete(shipment_id=ship["id"])
|
||||
except KeyError:
|
||||
continue
|
||||
return RedirectResponse(
|
||||
url=_safe_next(next, f"/cupang/?date={day}"), status_code=303
|
||||
)
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 입고센터 관리
|
||||
# ════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user