Files
myaps_api/apps/io_api/schemas.py
T
2026-05-20 22:26:08 +08:00

928 lines
43 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from datetime import datetime, timezone
# import enum
from typing import Literal, Dict, Optional, Any#, List
from decimal import Decimal
from pydantic import BaseModel, Field, model_validator, PrivateAttr#, ValidationError, field_validator
from core.settings import MYAPS_VERSION
from globalobjects import globalconst as gc, ProjectDefaultValues as pdv
def _cache_raw_input_data(cls, values: Dict[str, Any]) -> Dict[str, Any]:
"""
在模型验证之前捕获原始输入数据,并过滤掉不在Pydantic模型中的字段。
'values' 参数就是传入的原始值。
"""
if isinstance(values, dict):
# 获取模型中定义的所有字段名
model_field_names = set(cls.model_fields.keys())
# 过滤掉不在模型字段中的键
filtered_values = {
key: value for key, value in values.items()
if key in model_field_names
}
cls._cached_raw_input_data = filtered_values
return values
def _set_raw_input_data(self):
if not hasattr(self, "_cached_raw_input_data"):
return
raw_input_data = {}
valid_values = dict(self) #self.__dict__
for key, raw_value in self._cached_raw_input_data.items():
valid_value = valid_values.get(key)
value_type = type(valid_value)
if not value_type in (int, float, Decimal):
raw_input_data[key] = raw_value
continue
try:
raw_input_data[key] = value_type(raw_value)
except:
raw_input_data[key] = valid_value
self._raw_input_data = raw_input_data
return
class AcceptMaterial(BaseModel):
materialno: str = Field(..., description="料号", example="M001")
description: str = Field(..., description="物料名称", example="测试物料A")
size: Optional[str] = Field(None, description="规格", example="100x100mm")
plant: str = Field(pdv.MAT_PLANT, example=pdv.MAT_PLANT, description='工厂')
planner: str = Field(pdv.MAT_PLANNER, description="计划员", example="张三")
fifo: int = Field(pdv.MAT_FIFO, ge=0, le=1, description='1-FIFO 0-最近原则')
leadday: int = Field(..., ge=0, description="交期(天)", example=10)
expday: int = Field(pdv.MAT_EXPDAY, ge=0, description="保质期(天)", example=365)
grday: int = Field(..., ge=0, description="收货质检(天)", example=1)
abc: gc.AbcEnum = Field(..., example="A", description="ABC分类")
unit: str = Field(..., description='单位', example="PCS")
price: Decimal = Field(0, description="价格", ge=0, example=100.50)
groupno: str = Field("", description="型号", example="G001")
type: gc.EfEnum = Field(... if MYAPS_VERSION == 'P' else None, example="E", description="物料类型")
phantom: gc.YesNoEnum = Field(pdv.MAT_PHANTOM, example="N", description='虚拟件')
phantommin: int = Field(pdv.MAT_PHANTOMMIN, ge=0, description='虚拟时间(分)', example=0)
firmday: int = Field(pdv.MAT_FIRMDAY, ge=0, description="固定天数", example=0)
daygap: int = Field(pdv.MAT_DAYGAP, ge=0, description='MTO拆分天数', example=1)
candelay: gc.YesNoEnum = Field(pdv.MAT_CANDELAY, example="N", description='可否延迟')
lotsize: gc.LotSizeEnum = Field(pdv.MAT_LOTSIZE, example="EX", description='批量')
lotfix: float = Field(pdv.MAT_LOTFIX, ge=0, description='固定批', example=0.0)
lotmin: float | None = Field(None, ge=0, description='最小批', example=0.0)
lotmax: float | None = Field(None, ge=0, description='最大批', example=0.0)
lotround: float = Field(pdv.MAT_LOTROUND, ge=0, description='取整', example=0.0)
lotss: float = Field(pdv.MAT_LOTSS, ge=0, description='安全库存', example=0.0)
lotpoint: float = Field(pdv.MAT_LOTPOINT, ge=0, description='重订货点', example=0.0)
lottop: float = Field(pdv.MAT_LOTTOP, ge=0, description='最大库存点', example=0.0)
planitem: Optional[str] = Field(None, description='产品组', example="PI001")
preday: int = Field(pdv.MAT_PREDAY, ge=0, description='向前冲销(天)', example=999)
subday: int = Field(pdv.MAT_SUBDAY, ge=0, description='向后冲销(天)', example=999)
free1: Optional[str] = Field(None, max_length=255, description='自定义1', example="自定义内容。。。")
free2: Optional[str] = Field(None, max_length=255, description='自定义2', example="自定义内容。。。")
free3: Optional[str] = Field(None, max_length=255, description='自定义3', example="自定义内容。。。")
memo: Optional[str] = Field(None, description='备注', example="无特殊要求")
_raw_input_data: Dict[str, Any] = PrivateAttr(default=None)
class Config:
title = "验证规则 - 物料"
extra = "ignore"
json_schema_extra = {
"example": {
"materialno": "M001",
"description": "测试物料A",
"size": "100x100mm",
"plant": pdv.MAT_PLANT,
"planner": pdv.MAT_PLANNER,
"fifo": pdv.MAT_FIFO,
"leadday": 7,
"expday": pdv.MAT_EXPDAY,
"grday": 1,
"abc": "A",
"unit": "PCS",
"price": 100.50,
"groupno": "G001",
"type": "E",
"phantom": "N",
"memo": "标准物料"
}
}
@model_validator(mode="before")
@classmethod
def model_valid(cls, values: Dict[str, Any]):
if values.get("price") in gc.NONE_AND_EMPTY:
values["price"] = 0.00
if values.get("phantommin", "") == "":
values["phantommin"] = 0
_cache_raw_input_data(cls, values)
# 转换整数字段 - 支持字符串形式的浮点数(如 "30.00000000000000"
int_fields = ["fifo", "leadday", "expday", "grday", "phantommin", "firmday", "daygap", "preday", "subday"]
for field in int_fields:
if field in values:
try:
# 先转换为 float 再转换为 int,支持字符串形式的浮点数
values[field] = int(float(values[field]))
except:
if values.get(field) in gc.NONE_AND_EMPTY:
pass # 保持空值,后续会填充默认值
else:
values[field] = None
# 转换浮点数字段
float_fields = ["lotfix", "lotmin", "lotmax", "lotround", "lotss", "lotpoint", "lottop"]
for field in float_fields:
if field in values:
try:
values[field] = float(values[field])
except:
if values.get(field) in gc.NONE_AND_EMPTY:
pass # 保持空值,后续会填充默认值
else:
values[field] = None
if "fifo" not in pdv.no_fill_defaults and values.get("fifo", "") == "":
values["fifo"] = pdv.MAT_FIFO
if "expday" not in pdv.no_fill_defaults and values.get("expday", "") == "":
values["expday"] = pdv.MAT_EXPDAY
if "firmday" not in pdv.no_fill_defaults and values.get("firmday", "") == "":
values["firmday"] = pdv.MAT_FIRMDAY
if "lotfix" not in pdv.no_fill_defaults and values.get("lotfix", "") == "":
values["lotfix"] = pdv.MAT_LOTFIX
if "lotmin" not in pdv.no_fill_defaults and values.get("lotmin", "") == "":
values["lotmin"] = pdv.MAT_LOTMIN
if "lotmax" not in pdv.no_fill_defaults and values.get("lotmax", "") == "":
values["lotmax"] = pdv.MAT_LOTMAX
if "lotround" not in pdv.no_fill_defaults and values.get("lotround", "") == "":
values["lotround"] = pdv.MAT_LOTROUND
if "lotss" not in pdv.no_fill_defaults and values.get("lotss", "") == "":
values["lotss"] = pdv.MAT_LOTSS
if "lotpoint" not in pdv.no_fill_defaults and values.get("lotpoint", "") == "":
values["lotpoint"] = pdv.MAT_LOTPOINT
if "lottop" not in pdv.no_fill_defaults and values.get("lottop", "") == "":
values["lottop"] = pdv.MAT_LOTTOP
if "preday" not in pdv.no_fill_defaults and values.get("preday", "") == "":
values["preday"] = pdv.MAT_PREDAY
if "subday" not in pdv.no_fill_defaults and values.get("subday", "") == "":
values["subday"] = pdv.MAT_SUBDAY
if "leadday" not in pdv.no_fill_defaults and values.get("leadday", "") == "":
values["leadday"] = pdv.MAT_LEADDAY_E if values.get("type") == "E" else pdv.MAT_LEADDAY_F
if "grday" not in pdv.no_fill_defaults and values.get("grday", "") == "":
values["grday"] = pdv.MAT_GRDAY_E if values.get("type") == "E" else pdv.MAT_GRDAY_F
if "abc" not in pdv.no_fill_defaults and values.get("abc", "") == "":
values["abc"] = "A" if values.get("type") == "E" else "B"
if "plant" not in pdv.no_fill_defaults and values.get("plant", "") == "":
values["plant"] = pdv.MAT_PLANT
if "planner" not in pdv.no_fill_defaults and values.get("planner", "") == "":
values["planner"] = pdv.MAT_PLANNER
return values
@model_validator(mode="after")
def model_valid_after(self):
_set_raw_input_data(self)
return self
class AcceptWorkcenter(BaseModel):
workcenter: str = Field(..., max_length=32, description="工作中心代码", example="WC001")
workcentername: str = Field(..., max_length=255, description="工作中心名称", example="装配车间")
pri_wc: int = Field(pdv.WC_PRIORITY, description='优先级', example=1)
bottleneck: gc.YesNoEnum = Field(None, example="N", description='瓶颈')
sortno: Optional[str] = Field(None, max_length=4, description="序号", example="0001")
plant: str = Field(pdv.MAT_PLANT, max_length=32, description="工厂", example="1600")
location: Optional[str] = Field(None, max_length=32, description="车间", example="A区")
finite: gc.YesNoEnum = Field(gc.YesNoEnum.YES, example="N", description='有限')
type: gc.YesNoEnum = Field(gc.YesNoEnum.YES, example="N", description="首页显示")
capnum: int | None = Field(pdv.WC_CAPNUM, gt=0, description="默认机台数", example=6)
capmax: int | None = Field(pdv.WC_CAPMAX, gt=0, description="最大机台数", example=10)
worker: float = Field(pdv.WC_WORKER, ge=0, description='工时', example=8.0)
setupno: str | None = Field(None, max_length=6, description='切换组别', example="S001")
grpno: str | None = Field(None, max_length=6, description='同组号', example="G001")
memo: Optional[str] = Field(None, max_length=255, description="备注", example="标准工作中心")
_raw_input_data: Dict[str, Any] = PrivateAttr(default=None)
class Config:
title = "验证规则 - 工作中心"
extra = "ignore"
json_schema_extra = {
"example": {
"workcenter": "WC001",
"workcentername": "装配线",
"pri_wc": 1,
"bottleneck": "N",
"sortno": "0001",
"plant": pdv.MAT_PLANT,
"capnum": 6,
"capmax": 10,
"worker": 8.0,
"memo": "标准工作中心"
}
}
@model_validator(mode="before")
@classmethod
def model_valid(cls, values: Dict[str, Any]):
_cache_raw_input_data(cls, values)
# 转换整数字段 - 支持字符串形式的浮点数(如 "30.00000000000000"
int_fields = ["pri_wc", "capnum", "capmax"]
for field in int_fields:
if field in values:
try:
# 先转换为 float 再转换为 int,支持字符串形式的浮点数
values[field] = int(float(values[field]))
except:
if values.get(field) in gc.NONE_AND_EMPTY:
pass # 保持空值,后续会填充默认值
else:
values[field] = None
# 转换浮点数字段
if "worker" in values:
try:
values["worker"] = float(values["worker"])
except:
if values.get("worker") in gc.NONE_AND_EMPTY:
pass # 保持空值,后续会填充默认值
else:
values["worker"] = None
if values.get("sortno") is None:
values["sortno"] = ""
if values.get("bottleneck") in gc.NONE_AND_EMPTY:
values["bottleneck"] = "N"
if values.get("location") in gc.NONE_AND_EMPTY:
values["location"] = pdv.MAT_LOCATION
if values.get("worker") in gc.NONE_AND_EMPTY:
values["worker"] = pdv.WC_WORKER
if values.get("pri_wc") in gc.NONE_AND_EMPTY:
values["pri_wc"] = pdv.WC_PRIORITY
if values.get("capnum") in gc.NONE_AND_EMPTY:
values["capnum"] = pdv.WC_CAPNUM
if values.get("capmax") in gc.NONE_AND_EMPTY:
values["capmax"] = pdv.WC_CAPMAX
if values.get("setupno") is None:
values["setupno"] = ""
if values.get("grpno") is None:
values["grpno"] = ""
capmax = values.get("capmax") or 0
capnum = values.get("capnum") or 0
if capmax < capnum:
values["capmax"] = capnum
return values
@model_validator(mode="after")
def model_valid_after(self):
_set_raw_input_data(self)
return self
class AcceptMatWc(BaseModel):
materialno: str = Field(..., max_length=64, description='料号', example="M001")
matver: str = Field(..., max_length=4, example=pdv.MATVER, description='产线版本')
itemno: Optional[str] = Field(None, max_length=6, description='工序项目', example=pdv.ITEMNO)
workcenter: str = Field(..., max_length=32, description='工作中心', example="WC001")
sortno: int = Field(..., ge=0, le=999, description='序号', example=1)
basesec: float = Field(..., ge=0, description='节拍T/T(秒/100)', example=600)
fixqty: int = Field(0, ge=0, description='额定量', example=100)
fixsec: int = Field(0, ge=0, description='额定时间(秒)', example=300)
sf: gc.SfEnum = Field(gc.SfEnum.F, example="F", description='并行S/串行F')
offsetsec: int = Field(0, description='偏置+/-(秒)', example=0)
rate: float = Field(pdv.MATWC_RATE, ge=0, description='配比', example=pdv.MATWC_RATE)
memo: Optional[str] = Field(None, max_length=255, description='备注', example="标准工序")
_raw_input_data: Dict[str, Any] = PrivateAttr(default=None)
class Config:
title = "验证规则 - 工序"
extra = "ignore"
json_schema_extra = {
"example": {
"materialno": "M001",
"matver": pdv.MATVER,
"itemno": pdv.ITEMNO,
"workcenter": "WC001",
"sortno": 1,
"basesec": 600,
"fixqty": 100,
"fixsec": 300,
"sf": "F",
"offsetsec": 0,
"rate": pdv.MATWC_RATE,
"memo": "标准工序"
}
}
@model_validator(mode="before")
@classmethod
def model_valid(cls, values):
_cache_raw_input_data(cls, values)
try:
# 先转换为 float 再转换为 int,支持字符串形式的浮点数(如 "30.00000000000000"
values["sortno"] = int(float(values["sortno"]))
except:
values["sortno"] = None
if values.get("itemno") in gc.NONE_AND_EMPTY and "sortno" in values:
values["itemno"] = f"{pdv.itemno_prefix}{values['sortno']:0{pdv.itemno_width}d}"
try:
values["basesec"] = float(values["basesec"])
except:
values["basesec"] = None
try:
# 先转换为 float 再转换为 int,支持字符串形式的浮点数(如 "30.00000000000000"
values["offsetsec"] = int(float(values["offsetsec"]))
except:
if values.get("offsetsec") in gc.NONE_AND_EMPTY:
values["offsetsec"] = 0
else:
values["offsetsec"] = None
try:
# 先转换为 float 再转换为 int,支持字符串形式的浮点数(如 "30.00000000000000"
values["fixqty"] = int(float(values["fixqty"]))
except:
if values.get("fixqty") in gc.NONE_AND_EMPTY:
values["fixqty"] = 0
else:
values["fixqty"] = None
try:
# 先转换为 float 再转换为 int,支持字符串形式的浮点数(如 "30.00000000000000"
values["fixsec"] = int(float(values["fixsec"]))
except:
if values.get("fixsec") in gc.NONE_AND_EMPTY:
values["fixsec"] = 0
else:
values["fixsec"] = None
if values.get("sf") in gc.NONE_AND_EMPTY:
values["sf"] = "F"
return values
@model_validator(mode="after")
def model_valid_after(self):
_set_raw_input_data(self)
return self
class AcceptMatVer(BaseModel):
materialno: str = Field(..., max_length=64, description='料号', example="M001")
matver: str = Field(..., example=pdv.MATVER, max_length=4, description='产线版本号')
lotfrom: int = Field(pdv.MATVER_LOTFROM, description='批量起点', example=1)
lotto: int = Field(pdv.MATVER_LOTTO, description='批量终点', example=9999999)
priority: int = Field(pdv.MATVER_PRIORITY, description='优先级', example=1)
refno: Optional[str] = Field(None, max_length=64, description='MTO订单号/认证线', example="SO123456")
active: gc.YesNoEnum = Field(gc.YesNoEnum.YES, example="Y", description='生效')
memo: Optional[str] = Field(None, max_length=255, description='备注', example="标准版本")
_raw_input_data: Dict[str, Any] = PrivateAttr(default=None)
class Config:
title = "验证规则 - 产线版本"
extra = "ignore"
json_schema_extra = {
"example": {
"materialno": "M001",
"matver": pdv.MATVER,
"lotfrom": 1,
"lotto": 9999999,
"priority": 1,
"active": "Y",
"memo": "标准版本"
}
}
@model_validator(mode="before")
@classmethod
def model_valid(cls, values):
_cache_raw_input_data(cls, values)
# 转换整数字段 - 支持字符串形式的浮点数(如 "30.00000000000000"
int_fields = ["lotfrom", "lotto", "priority"]
for field in int_fields:
if field in values:
try:
# 先转换为 float 再转换为 int,支持字符串形式的浮点数
values[field] = int(float(values[field]))
except:
if values.get(field) in gc.NONE_AND_EMPTY:
pass # 保持空值,后续会填充默认值
else:
values[field] = None
if values.get("lotfrom") in gc.NONE_AND_EMPTY:
values["lotfrom"] = pdv.MATVER_LOTFROM
if values.get("lotto") in gc.NONE_AND_EMPTY:
values["lotto"] = pdv.MATVER_LOTTO
if values.get("priority") in gc.NONE_AND_EMPTY:
values["priority"] = pdv.MATVER_PRIORITY
if values.get("active") in gc.NONE_AND_EMPTY:
values["active"] = "Y"
return values
@model_validator(mode="after")
def model_valid_after(self):
_set_raw_input_data(self)
return self
class AcceptMatWcBom(BaseModel):
productno: str = Field(..., max_length=64, description='产品料号', example="P001")
matver: str = Field(..., example=pdv.MATVER, max_length=4, description='产线版本')
itemno: str = Field(..., max_length=6, description='工序项目', example=pdv.ITEMNO)
materialno: str = Field(..., max_length=64, description='子件料号', example="M001")
qty: float = Field(..., ge=0, description='数量', example=2.0)
offsethour: int = Field(0, description='偏置+/-(小时)', example=0)
# treeno: Optional[int] = Field(None, description='层级', example=1)
mto: gc.YesNoEnum = Field(gc.YesNoEnum.NO, example="N", description='MTO')
scrap: float = Field(0, ge=0, description='报废率%', example=0.0)
alt: gc.YesNoEnum = Field(gc.YesNoEnum.NO, example="N", description='是否是替代')
memo: Optional[str] = Field(None, max_length=255, description='备注', example="标准BOM组件")
denominator: Optional[float | str] = Field(None, description='用量分母', example=1)
_raw_input_data: Dict[str, Any] = PrivateAttr(default=None)
class Config:
title = "验证规则 - BOM"
extra = "ignore"
json_schema_extra = {
"example": {
"productno": "P001",
"matver": pdv.MATVER,
"itemno": pdv.ITEMNO,
"materialno": "M001",
"qty": 2.0,
"offsethour": 0,
# "treeno": 1,
"mto": "N",
"scrap": 0.0,
"alt": "N",
"memo": "标准BOM组件"
}
}
@model_validator(mode="before")
@classmethod
def model_valid(cls, values):
_cache_raw_input_data(cls, values)
# 转换整数字段 - 支持字符串形式的浮点数(如 "30.00000000000000"
# int_fields = ["offsethour", "treeno"]
int_fields = ["offsethour",]
for field in int_fields:
if field in values:
try:
# 先转换为 float 再转换为 int,支持字符串形式的浮点数
values[field] = int(float(values[field]))
except:
if values.get(field) in gc.NONE_AND_EMPTY:
pass # 保持空值,后续会填充默认值
else:
values[field] = None
if values.get("itemno", "") == "":
values["itemno"] = pdv.ITEMNO
denominator = values.get("denominator")
if denominator:
try:
values["denominator"] = float(denominator) or 1
except:
values["denominator"] = 1
values["qty"] /= values["denominator"]
values.pop("denominator") # 删除 denominator ,避免在数据库中存储
return values
@model_validator(mode="after")
def model_valid_after(self):
_set_raw_input_data(self)
return self
class AcceptMold(BaseModel):
moldno: str = Field(..., max_length=64, description='模具编号', example="MOLD001")
moldname: str = Field(..., max_length=64, description='模具名称', example="测试模具A")
type: str = Field(..., example="T1", max_length=4, description='类型')
status: str = Field(..., max_length=6, description='状态', example="AVL")
moldnum: int = Field(..., ge=1, description='模具穴数', example=4)
qty: int = Field(..., gt=1, description='模具台数', example=2)
memo: Optional[str] = Field(None, max_length=255, description="备注", example="标准模具")
_raw_input_data: Dict[str, Any] = PrivateAttr(default=None)
class Config:
title = "验证规则 - 模具"
extra = "ignore"
json_schema_extra = {
"example": {
"moldno": "MOLD001",
"moldname": "测试模具A",
"type": "T1",
"status": "AVL",
"moldnum": 4,
"qty": 2,
"memo": "标准模具"
}
}
@model_validator(mode="before")
@classmethod
def model_valid(cls, values):
_cache_raw_input_data(cls, values)
# 转换整数字段 - 支持字符串形式的浮点数(如 "30.00000000000000"
int_fields = ["moldnum", "qty"]
for field in int_fields:
if field in values:
try:
# 先转换为 float 再转换为 int,支持字符串形式的浮点数
values[field] = int(float(values[field]))
except:
values[field] = None
return values
@model_validator(mode="after")
def model_valid_after(self):
_set_raw_input_data(self)
return self
class AcceptMatWcMold(BaseModel):
materialno: str = Field(..., max_length=64, description='料号', example="M001")
workcenter: str = Field(..., max_length=64, description='工作中心', example="WC001")
itemno: str = Field(..., max_length=6, description='工序项目', example=pdv.ITEMNO)
moldno: str = Field('', max_length=64, description='模具编号', example="MOLD001")
basesec: float = Field(..., ge=0, description='节拍T/T(秒/100)', example=600)
fixsec: int = Field(0, ge=0, description='额定时间(秒)', example=300)
priority: int = Field(..., description='优先级', example=1)
memo: Optional[str] = Field(None, max_length=255, description='备注', example="标准机台模具配置")
_raw_input_data: Dict[str, Any] = PrivateAttr(default=None)
class Config:
title = "验证规则 - 机台模具"
extra = "ignore"
json_schema_extra = {
"example": {
"materialno": "M001",
"workcenter": "WC001",
"itemno": pdv.ITEMNO,
"moldno": "MOLD001",
"basesec": 600,
"fixsec": 300,
"priority": 1,
"memo": "标准机台模具配置"
}
}
@model_validator(mode="before")
@classmethod
def model_valid(cls, values):
_cache_raw_input_data(cls, values)
try:
# 先转换为 float 再转换为 int,支持字符串形式的浮点数(如 "30.00000000000000"
values["fixsec"] = int(float(values["fixsec"])) # 数据库该字段为整形
except:
values["fixsec"] = 0
try:
# 先转换为 float 再转换为 int,支持字符串形式的浮点数(如 "30.00000000000000"
values["basesec"] = int(float(values["basesec"])) # 数据库该字段为整形
except:
values["basesec"] = None
try:
# 先转换为 float 再转换为 int,支持字符串形式的浮点数(如 "30.00000000000000"
values["priority"] = int(float(values["priority"])) # 数据库该字段为整形
except:
values["priority"] = None
if values.get("moldno") is None:
values["moldno"] = ''
return values
@model_validator(mode="after")
def model_valid_after(self):
_set_raw_input_data(self)
return self
class AcceptSupply(BaseModel):
materialno: str = Field(..., max_length=64, description='料号', example="M001")
supplyno: str = Field(..., max_length=64, description='供应单号', example="MO123456")
matver: Optional[str] = Field(None, max_length=32, example=pdv.MATVER, description='产线版本')
itemno: Optional[str] = Field(None, max_length=6, description='项目号', example=pdv.ITEMNO)
type: gc.SupplyTypeEnum = Field(..., example="MO", description='类型 PL-生产计划 MO-生产工单 ST-库存 PO-采购订单')
category: gc.ProductCategoryEnum = Field(gc.ProductCategoryEnum.MTS, example="MTS", description='分类(MTO/MTS)')
priority: int = Field(0, description='优先级', example=0)
status: gc.OrderStatusEnum = Field("CRE", example="CRE", description='状态 NEW-新增 CRE-已创建 SCH-计划 REL-已发布 PNF-已报工, CMP-已完成')
avail_qty: float = Field(..., ge=0, description='可用数量', example=100.0)
create_date: Optional[str] = Field(None, description='创建日期', example="2023-01-01")
avail_date: str = Field(..., description='可用日期 / 开工日期', example="2023-01-01")
dt_req: str = Field(..., description='需求日期 / 完工日期', example="2023-01-07")
avail_end_date: Optional[str] = Field(None, description='可用结束日期', example="2023-01-07")
batchno: Optional[str] = Field(None, max_length=64, description='批次号', example="BATCH001")
vendorno: Optional[str] = Field(None, max_length=64, description='源销售订单号', example="SO001")
partnerno: Optional[str] = Field(None, max_length=64, description='合作商编号', example="P001")
partnername: Optional[str] = Field(None, max_length=255, description='合作商名称', example="合作伙伴A")
free1: Optional[str] = Field(None, max_length=255, description='自定义1', example="自定义内容。。。")
free2: Optional[str] = Field(None, max_length=255, description='自定义2', example="自定义内容。。。")
free3: Optional[str] = Field(None, max_length=255, description='自定义3', example="自定义内容。。。")
apiex_sn: Optional[str] = Field(None, max_length=32, description='API扩展字段 APS原生序列号', example="MO123456")
apiex_id: Optional[str] = Field(None, max_length=32, description='API扩展字段外部系统单据ID', example="1")
apiex_entryid: Optional[str] = Field(None, max_length=32, description='API扩展字段外部系统单据条目ID', example="1")
memo: Optional[str] = Field(None, max_length=255, description='备注', example="标准供应单")
# plno: Optional[str] = Field(None, max_length=64, description='原PL(若传入此值则将对应的PL号改写成MO号,若索引不到原PL则新增MO', example="PL123456")
_raw_input_data: Dict[str, Any] = PrivateAttr(default=None) # 使用PrivateAttr定义一个不参与序列化和验证的私有属性来保存原始值
class Config:
title = "验证规则 - 供应"
extra = "ignore"
json_schema_extra = {
"example": {
"materialno": "M001",
"supplyno": "MO123456",
"matver": pdv.MATVER,
"itemno": pdv.ITEMNO,
"type": "MO",
"category": "MTO",
"priority": 1,
"status": "NEW",
"avail_qty": 100.0,
"avail_date": "2025-01-01",
"dt_req": "2025-01-07",
"memo": "标准生产工单"
}
}
@model_validator(mode="before")
@classmethod
def model_valid(cls, values):
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if values.get("apiex_id") not in gc.NONE_AND_EMPTY and values.get("apiex_entryid") in gc.NONE_AND_EMPTY:
values["apiex_entryid"] = values["apiex_id"]
_cache_raw_input_data(cls, values)
# 转换整数字段 - 支持字符串形式的浮点数(如 "30.00000000000000"
if "priority" in values:
try:
# 先转换为 float 再转换为 int,支持字符串形式的浮点数
values["priority"] = int(float(values["priority"]))
except:
if values.get("priority") in gc.NONE_AND_EMPTY:
values["priority"] = 0
else:
values["priority"] = None
# 转换浮点数字段
if "avail_qty" in values:
try:
values["avail_qty"] = float(values["avail_qty"])
except:
values["avail_qty"] = None
if values.get('itemno') in gc.NONE_AND_EMPTY:
values['itemno'] = pdv.ITEMNO
if values.get("status") not in gc.OrderStatusEnum.__members__:
values["status"] = gc.OrderStatusEnum.CRE
if values.get("create_date") in gc.NONE_AND_EMPTY:
values["create_date"] = now
if values.get("avail_end_date") in gc.NONE_AND_EMPTY:
values["avail_end_date"] = "9999-12-31 00:00:00"
if values.get("avail_date") in gc.NONE_AND_EMPTY:
values["avail_date"] = now
if values.get("dt_req") in gc.NONE_AND_EMPTY:
values["dt_req"] = now
# if values.get("category") == gc.ProductCategoryEnum.MTO and values.get("vendorno") in gc.NONE_AND_EMPTY:
# raise ValueError("MTO订单号vendorno不能为空")
if values.get("vendorno"):
values["category"] = gc.ProductCategoryEnum.MTO
else:
values["category"] = gc.ProductCategoryEnum.MTS
return values
@model_validator(mode='after')
def model_valid_after(self):
_set_raw_input_data(self)
return self
class ModifySupply(BaseModel):
supplyno: Optional[str] = Field(None, max_length=64, description='供应号', example="MO123456")
status: gc.OrderStatusEnum = Field(None,
example="CRE", description=f'状态 {list(gc.OrderStatusEnum.__members__.keys())}')
avail_qty: Optional[float] = Field(None, ge=0, description='可用数量', example=100.0)
apiex_sn: Optional[str | int] = Field(None, description='API扩展字段 APS原生序列号', example="MO123456")
apiex_id: Optional[str | int] = Field(None, description='API扩展字段外部系统单据ID', example="1")
apiex_entryid: Optional[str | int] = Field(None, description='API扩展字段外部系统单据条目ID', example="1")
memo: Optional[str] = Field(None, max_length=255, description='备注', example="标准生产工单")
_raw_input_data: Dict[str, Any] = PrivateAttr(default=None)
class Config:
title = "验证规则 - 生产计划"
extra = "ignore"
json_schema_extra = {
"example": {
"supplyno": "MO123456",
"status": "CRE"
}
}
@model_validator(mode="before")
@classmethod
def model_valid(cls, values):
_cache_raw_input_data(cls, values)
if values.get("avail_qty") is not None:
try:
values["avail_qty"] = float(values["avail_qty"])
except ValueError:
values["avail_qty"] = None
status = values.get("status")
if status and status not in gc.OrderStatusEnum.__members__:
values["status"] = gc.OrderStatusEnum.CRE
memo = values.get("memo")
if memo:
try:
values["memo"] = memo[:255]
except Exception as e:
pass
return values
@model_validator(mode="after")
def model_valid_after(self):
_set_raw_input_data(self)
return self
class AcceptDemand(BaseModel):
materialno: str = Field(..., max_length=64, description='料号', example="M001")
demandno: str = Field(..., max_length=64, description='需求单号', example="SO123456")
itemno: str = Field(..., max_length=6, description='项目号(若类型为SO则可传入订单号或其他标识符,不超过6位)', example=pdv.ITEMNO)
type: gc.DemandTypeEnum = Field(..., example="SO", description='类型 SO-销售订单 DM-计划需求 RS-工单预留 FC-预测 SS-安全库存')
category: gc.ProductCategoryEnum = Field(gc.ProductCategoryEnum.MTS, example="MTS", description='分类(MTO/MTS)')
priority: int = Field(..., description='优先级', example=1)
workcenter: Optional[str] = Field(None, max_length=32, description='工作中心', example="WC001")
status: gc.OrderStatusEnum = Field("CRE", example="CRE", description=f'状态 {list(gc.OrderStatusEnum.__members__.keys())}')
req_qty: float = Field(..., description='需求数量(须为负数,若输入正数则自动转为负数)', example=-100.0)
req_date: datetime = Field(..., description='需求日期', example="2023-01-07T10:00:00")
refno: Optional[str] = Field(None, max_length=64, description='MTO订单号', example="MTO123456")
partnerno: Optional[str] = Field(None, max_length=64, description='合作商编号', example="P001")
partnername: Optional[str] = Field(None, max_length=255, description='合作商名称', example="客户A")
ori_qty: Optional[float] = Field(None, ge=0, description='原始需求数量', example=100.0)
apiex_sn: Optional[str] = Field(None, max_length=32, description='API扩展字段 APS原生序列号', example="SO123456")
apiex_id: Optional[str] = Field(None, max_length=32, description='API扩展字段外部系统单据ID', example="1")
apiex_entryid: Optional[str] = Field(None, max_length=32, description='API扩展字段外部系统单据条目ID', example="1")
memo: Optional[str] = Field(None, max_length=255, description='备注', example="标准销售订单")
free1: Optional[str] = Field(None, max_length=255, description='自定义字段1', example="自定义字段1")
free2: Optional[str] = Field(None, max_length=255, description='自定义字段2', example="自定义字段2")
free3: Optional[str] = Field(None, max_length=255, description='自定义字段3', example="自定义字段3")
_raw_input_data: Dict[str, Any] = PrivateAttr(default=None)
class Config:
title = "验证规则 - 需求"
extra = "ignore"
json_schema_extra = {
"example": {
"materialno": "M001",
"demandno": "SO123456",
"itemno": f"{pdv.itemno_prefix}{1:0{pdv.itemno_width}d}",
"type": "SO",
"category": "MTO",
"priority": 1,
"workcenter": "WC001",
"status": "NEW",
"req_qty": -100.0,
"req_date": "2025-01-07 10:00:00",
"refno": "MTO123456",
"ori_qty": 100.0,
"memo": "标准销售订单"
}
}
@model_validator(mode="before")
@classmethod
def model_valid(cls, values):
if values.get("apiex_id") not in gc.NONE_AND_EMPTY and values.get("apiex_entryid") in gc.NONE_AND_EMPTY:
values["apiex_entryid"] = values["apiex_id"]
_cache_raw_input_data(cls, values)
# 转换整数字段 - 支持字符串形式的浮点数(如 "30.00000000000000"
if "priority" in values:
try:
# 先转换为 float 再转换为 int,支持字符串形式的浮点数
values["priority"] = int(float(values["priority"]))
except:
values["priority"] = None
# 转换浮点数字段
try:
req_qty = float(values.get("req_qty"))
if req_qty > 0:
values["req_qty"] = -1 * req_qty
except ValueError:
req_qty = None
if "ori_qty" in values:
try:
values["ori_qty"] = float(values["ori_qty"])
except:
values["ori_qty"] = None
if values.get("category") == gc.ProductCategoryEnum.MTO and values.get("refno") in gc.NONE_AND_EMPTY:
raise ValueError("MTO订单号refno不能为空")
# if values.get("status") in gc.NONE_AND_EMPTY:
# values["status"] = "NEW"
# if values.get("category") in gc.NONE_AND_EMPTY:
# values["category"] = "MTO"
# if values.get("priority") in gc.NONE_AND_EMPTY:
# values["priority"] = 0
return values
@model_validator(mode='after')
def model_valid_after(self):
_set_raw_input_data(self)
return self
class ModifyDemand(BaseModel):
status: Optional[gc.OrderStatusEnum] = Field(None, example='CRE', description=f'状态 {list(gc.OrderStatusEnum.__members__.keys())}')
req_qty: Optional[float] = Field(None, description='需求数量(须为负数,若输入正数则自动转为负数)', example=-100.0)
req_date: Optional[datetime | str] = Field(None, description='需求日期', example="2023-01-07T10:00:00")
memo: Optional[str] = Field(None, max_length=255, description='备注', example="标准销售订单")
_raw_input_data: Dict[str, Any] = PrivateAttr(default=None)
@model_validator(mode="before")
@classmethod
def model_valid(cls, values):
_cache_raw_input_data(cls, values)
req_qty = values.get("req_qty")
if req_qty is not None:
try:
req_qty = float(req_qty)
if req_qty > 0:
values["req_qty"] = -1 * req_qty
except ValueError:
values["req_qty"] = None
memo = values.get("memo")
if memo:
try:
values["memo"] = memo[:255]
except Exception as e:
pass
return values
@model_validator(mode="after")
def model_valid_after(self):
_set_raw_input_data(self)
return self
class AcceptConfirm(BaseModel):
supplyno: str = Field(..., max_length=64, description='供应单号', example="MO123456")
itemno: str = Field(..., max_length=6, description='工序项目', example=pdv.ITEMNO)
recordqty: float = Field(..., description='报工数量', gt=0, example=100)
recorddt: datetime = Field(..., description='报工日期', example="2025-01-07 10:00:00")
status: gc.YesNoEnum = Field(..., description='状态')
sysuser: Optional[str] = Field(None, max_length=32, description='系统用户', example="张三")
_raw_input_data: Dict[str, Any] = PrivateAttr(default=None)
class Config:
title = "验证规则 - 报工"
extra = "ignore"
json_schema_extra = {
"example": {
"supplyno": "MO123456",
"itemno": pdv.ITEMNO,
"workcenter": "WC001",
"recordqty": 100.0,
"recorddt": "2025-01-07 10:00:00",
"status": "Y",
"sysuser": "张三"
}
}
@model_validator(mode="before")
@classmethod
def model_valid(cls, values):
_cache_raw_input_data(cls, values)
# 基本验证和默认值设置
values["status"] = pdv.WORKREPORT_STATUS
# 处理 recorddt 字段,支持时间戳转换
recorddt = values.get("recorddt")
if recorddt in gc.NONE_AND_EMPTY:
values["recorddt"] = datetime.now(timezone.utc)
else:
try:
# 尝试将时间戳转换为 datetime
if isinstance(recorddt, (int, float, str)):
timestamp = float(recorddt)
# 判断是毫秒还是秒
if timestamp > 10000000000: # 毫秒时间戳
values["recorddt"] = datetime.fromtimestamp(timestamp / 1000, tz=timezone.utc)
else: # 秒时间戳
values["recorddt"] = datetime.fromtimestamp(timestamp, tz=timezone.utc)
except (ValueError, TypeError):
# 如果转换失败,保持原样让 Pydantic 处理
pass
return values
@model_validator(mode='after')
def model_valid_after(self):
_set_raw_input_data(self)
return self