Files
dbx-main/app/modules/cupang/store.py
T
king 9e707be5ac feat(cupang): 쿠팡 밀크런 모듈 추가 (출고 달력/박스계산/입고센터)
- app/modules/cupang: router/db/store/itemcode + 템플릿 5종
- cupang_db 전용(JSON 폴백 없음). CUPANG_DB_URL 미설정 시 안내 페이지
- 월간 달력 + 출고 묶음(헤더+라인), 박스 입수량 자동계산(서버 재계산)
- 입고센터 관리(사용중 soft delete), 박스규칙 upsert, 엑셀 내보내기
- 상품 검색은 itemcode_db 읽기 전용(미설정 시 수동 입력)
- MODULE_KEYS/메뉴/아이콘/라벨 동기화, scripts/sql/cupang_db_init.sql(멱등)
- app/data/ gitignore 추가(PII)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 20:47:32 +09:00

112 lines
3.0 KiB
Python

"""쿠팡 밀크런 모듈 상수 및 공용 헬퍼.
- 데이터 저장은 cupang_db(PostgreSQL) 전용이다(`db.py`).
운영 데이터가 JSON 과 DB 로 갈라지는 것을 막기 위해 JSON 폴백을 두지 않는다.
CUPANG_DB_URL 미설정 시 라우터가 "설정 필요" 안내 페이지를 보여준다.
- 이 모듈에는 DB/JSON 양쪽이 공유하는 상수와 순수 계산 헬퍼만 둔다.
"""
from __future__ import annotations
import math
from typing import Any
# 출고 묶음 상태 (expense 의 STATUSES 패턴과 동일하게 한글 라벨 그대로 저장)
STATUSES: tuple[str, ...] = (
"작성중",
"출고준비",
"출고완료",
"센터입고완료",
"취소",
)
# 출고방식 기본 후보 (자유 입력 허용, 아래는 select 기본값)
SHIP_METHODS: tuple[str, ...] = ("택배", "직접배송", "화물", "기타")
# 엑셀/시트 컬럼 순서 — 기존 구글시트와 동일하게 유지
SHEET_COLUMNS: tuple[str, ...] = (
"구분",
"작성일",
"출고일",
"센터입고일",
"입고센터",
"출고방식",
"제품코드",
"제품명",
"수량",
"출고",
"작업자",
)
# 초기 입고센터 seed — cupang_db_init.sql 에도 동일 목록을 INSERT 한다.
# 화면에서 추가/수정/비활성화 가능. 사용 중인 센터는 hard delete 하지 않는다.
DEFAULT_CENTERS: tuple[str, ...] = (
"대구3",
"인천32",
"이천1",
"인천42",
"인천26",
"인천16",
"인천28",
"안성8",
"천안8(RC)",
"시흥2",
"인천36",
"MGMH5",
"XRC10(RC)",
"인천14",
"경기광주5",
"경기광주3",
"XRC06(RC)",
"용인1",
"인천30",
"마장1",
"안성4",
"대구6",
"전라광주2",
"창원1",
"고양1",
"동탄1",
"이천4",
"XRC09(RC)",
)
def compute_boxes(quantity: int, units_per_box: int | None) -> dict[str, Any]:
"""수량 + 박스당 입수량으로 필요한 박스 수를 계산한다.
클라이언트 계산을 신뢰하지 않고 서버에서 이 함수로 재계산한다.
- units_per_box 가 없거나 0 이하면 "미설정" — 자동 계산하지 않는다.
- full_boxes = quantity // units_per_box
- remainder_units = quantity % units_per_box
- required_boxes = ceil(quantity / units_per_box)
"""
try:
qty = int(quantity)
except (TypeError, ValueError):
qty = 0
upb: int | None
try:
upb = int(units_per_box) if units_per_box is not None else None
except (TypeError, ValueError):
upb = None
if not upb or upb <= 0 or qty <= 0:
return {
"configured": False,
"units_per_box": upb if (upb and upb > 0) else None,
"full_boxes": None,
"remainder_units": None,
"required_boxes": None,
}
return {
"configured": True,
"units_per_box": upb,
"full_boxes": qty // upb,
"remainder_units": qty % upb,
"required_boxes": math.ceil(qty / upb),
}