feat(dispatch): 날짜별 사방넷 재고 엑셀 다운로드 + 달력 UI

- 배치 목록을 좌(리스트)/우(달력) 2단 배치
- 달력에서 배치 있는 날짜 클릭 → 그날 전 배치 출고를 낱개로 분해해 다운로드
  · _N 배수 × 주문수량 × 콤보 BOM 구성수량, MD-(뚜껑) 제외
  · 사방넷 4열 템플릿(A 상품코드[필수]=사방넷코드 / B 가용수량=수량 /
    C 불용수량=0 / D 바코드=아이템코드), 주황 헤더, xlsx
- db.stock_aggregate_for_date, export.explode_to_singles/build_stock_xlsx
- 라우터 GET /dispatch/stock-export?date=YYYY-MM-DD

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 17:25:05 +09:00
parent 2c7f98ef30
commit 9221efe925
4 changed files with 274 additions and 2 deletions
+23
View File
@@ -286,6 +286,29 @@ class DispatchStore:
)
return len(ids)
def stock_aggregate_for_date(self, *, dispatch_date: str) -> list[dict[str, Any]]:
"""해당 날짜 모든 배치의 SKU별 합산 수량.
반환: [{"seller_sku", "qty"}]. _N 배수/콤보 분해는 호출부(export)에서 적용.
"""
with self._pool.connection() as conn:
rows = conn.execute(
"""
SELECT i.seller_sku, SUM(i.quantity)::int AS qty
FROM dispatch_items i
JOIN dispatch_parcels p ON p.id = i.parcel_id
JOIN dispatch_batches b ON b.id = p.batch_id
WHERE b.dispatch_date = %s
GROUP BY i.seller_sku
ORDER BY i.seller_sku ASC
""",
(dispatch_date,),
).fetchall()
return [
{"seller_sku": r["seller_sku"] or "", "qty": int(r["qty"] or 0)}
for r in rows
]
def parcel_batch_id(self, *, parcel_id: int) -> int | None:
with self._pool.connection() as conn:
row = conn.execute(