mirror of
https://github.com/rnvm9wjdtj-bot/myaps_api.git
synced 2026-06-02 05:54:40 +00:00
928 lines
43 KiB
Python
928 lines
43 KiB
Python
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
|