# app/schemas/accessory.py import re from pydantic import BaseModel, Field, field_validator PAICHAN_PATTERN = re.compile(r"^[A-Z]{1,2}\d{5}(-J)?$") LOCATION_NORMAL_PATTERN = re.compile(r"^[A-Z]+\d+-\d+-\d+$") LOCATION_TRANS_PATTERN = re.compile(r"^TRANS-\d+") # ── Work order query ── class WorkOrderGroup(BaseModel): work_order_no: str zongpai_nos: list[str] class WorkOrderQueryResponse(BaseModel): paichan_no: str work_orders: list[WorkOrderGroup] # ── Accessory CRUD ── class AccessoryCreateRequest(BaseModel): paichan_no: str zongpai_no: str accessory_type: str quantity: int location_code: str | None = None @field_validator("paichan_no") @classmethod def validate_paichan_no(cls, v: str) -> str: v = v.strip().upper() if not PAICHAN_PATTERN.match(v): raise ValueError("INVALID_PAICHAN") return v @field_validator("zongpai_no") @classmethod def validate_zongpai_no(cls, v: str) -> str: 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("quantity") @classmethod def validate_quantity(cls, v: int) -> int: if v <= 0: raise ValueError("INVALID_QUANTITY") return v @field_validator("location_code") @classmethod def validate_location_code(cls, v: str | None) -> str | None: if v is None: return v v = v.strip().upper() if not LOCATION_NORMAL_PATTERN.match(v) and not LOCATION_TRANS_PATTERN.match(v): raise ValueError("INVALID_LOCATION") return v class AccessoryResponse(BaseModel): id: int paichan_no: str zongpai_no: str accessory_type: str quantity: int location_code: str | None = None is_boxed: bool = False created_at: str class AccessoryListResponse(BaseModel): paichan_no: str items: list[AccessoryResponse] class AccessoryUpdateRequest(BaseModel): accessory_type: str | None = None quantity: int | None = None location_code: str | None = None @field_validator("quantity") @classmethod def validate_quantity(cls, v: int | None) -> int | None: if v is not None and v <= 0: raise ValueError("INVALID_QUANTITY") return v @field_validator("location_code") @classmethod def validate_location_code(cls, v: str | None) -> str | None: if v is None: return v v = v.strip().upper() if not LOCATION_NORMAL_PATTERN.match(v) and not LOCATION_TRANS_PATTERN.match(v): raise ValueError("INVALID_LOCATION") return v # ── Accessory type management ── class AccessoryTypeItem(BaseModel): id: int name: str sort_order: int class AccessoryTypeListResponse(BaseModel): types: list[AccessoryTypeItem] class AccessoryTypeCreateRequest(BaseModel): name: str sort_order: int = 0 @field_validator("name") @classmethod def validate_name(cls, v: str) -> str: v = v.strip() if not v: raise ValueError("INVALID_TYPE_NAME") return v class AccessoryTypeUpdateRequest(BaseModel): name: str | None = None sort_order: int | None = None