from pydantic import BaseModel, Field, field_validator class CategoryResponse(BaseModel): id: int name: str sort_order: int class AttachmentStatusItem(BaseModel): category_id: int name: str expected_qty: int boxed_qty: int location_code: str | None = None complete: bool class AttachmentStatusResponse(BaseModel): zongpai_no: str determination: str all_complete: bool items: list[AttachmentStatusItem] = Field(default_factory=list) class AttachmentConfigItem(BaseModel): category_id: int expected_qty: int @field_validator("expected_qty") @classmethod def _positive(cls, v: int) -> int: if v <= 0: raise ValueError("INVALID_QUANTITY") return v class AttachmentConfigRequest(BaseModel): zongpai_no: str determination: str items: list[AttachmentConfigItem] = Field(default_factory=list) @field_validator("zongpai_no") @classmethod def _zongpai(cls, v: str) -> str: import re v = v.strip().upper() if not re.match(r"^(\d{2}(B|C|T)\d+|\d{2}(BW|CW)\d{4})$", v): raise ValueError("INVALID_ZONGPAI") return v @field_validator("determination") @classmethod def _det(cls, v: str) -> str: if v not in ("none", "has"): raise ValueError("INVALID_DETERMINATION") return v class AttachmentLocationRequest(BaseModel): zongpai_no: str category_id: int location_code: str @field_validator("zongpai_no") @classmethod def _zongpai(cls, v: str) -> str: import re v = v.strip().upper() if not re.match(r"^(\d{2}(B|C|T)\d+|\d{2}(BW|CW)\d{4})$", v): raise ValueError("INVALID_ZONGPAI") return v @field_validator("location_code") @classmethod def _location(cls, v: str) -> str: import re v = v.strip().upper() normal = re.match(r"^[A-Z]+\d+-\d+-\d+$", v) transit = re.match(r"^TRANS-\d+", v) if not normal and not transit: raise ValueError("INVALID_LOCATION") return v class AttachmentLocationResponse(BaseModel): zongpai_no: str category_id: int location_code: str created_at: str previous_location: str | None = None class AttachmentBoxSaveRequest(BaseModel): zongpai_no: str category_id: int box_no: int quantity: int @field_validator("zongpai_no") @classmethod def _zongpai(cls, v: str) -> str: import re v = v.strip().upper() if not re.match(r"^(\d{2}(B|C|T)\d+|\d{2}(BW|CW)\d{4})$", v): raise ValueError("INVALID_ZONGPAI") return v @field_validator("box_no") @classmethod def _box_no(cls, v: int) -> int: if v <= 0: raise ValueError("INVALID_BOX_NO") return v @field_validator("quantity") @classmethod def _qty(cls, v: int) -> int: if v <= 0: raise ValueError("INVALID_QUANTITY") return v class AttachmentBoxSaveResponse(BaseModel): box_item_id: int paichan_no: str box_no: int zongpai_no: str category_id: int quantity: int created_at: str class AttachmentBoxUpdateRequest(BaseModel): box_no: int quantity: int @field_validator("box_no") @classmethod def _box_no(cls, v: int) -> int: if v <= 0: raise ValueError("INVALID_BOX_NO") return v @field_validator("quantity") @classmethod def _qty(cls, v: int) -> int: if v <= 0: raise ValueError("INVALID_QUANTITY") return v class AttachmentBoxUpdateResponse(BaseModel): box_item_id: int paichan_no: str box_no: int zongpai_no: str category_id: int quantity: int updated_at: str class AttachmentBoxDeleteResponse(BaseModel): box_item_id: int deleted: bool