forked from erp-dev/erp
feat: refactor flower.utils to sub package, added some sync api in package, added plate_order sync result model but relation of plate_order was not query (do it next step)
This commit is contained in:
96
flower/utils/mingdaoyun/__init__.py
Normal file
96
flower/utils/mingdaoyun/__init__.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from .client import (
|
||||
HTTPMethod,
|
||||
MDY_APP_KEY,
|
||||
MDY_BASE_URL,
|
||||
MDY_ENDPOINT_GET_FILTER_ROWS,
|
||||
MDY_SIGN,
|
||||
MingDaoYunClient,
|
||||
get_default_mingdaoyun_client,
|
||||
)
|
||||
from .fetch import (
|
||||
fetch_customers_from_mingdaoyun,
|
||||
fetch_plate_orders_from_mingdaoyun,
|
||||
fetch_products_from_mingdaoyun,
|
||||
fetch_row_by_rowid_from_mingdaoyun,
|
||||
sync_fabric_from_mingdaoyun,
|
||||
)
|
||||
from .mappings import (
|
||||
MDY_WORKSHEET_ID_CUSTOMER,
|
||||
MDY_WORKSHEET_ID_FABRIC,
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER,
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER_COLORING,
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER_COLOR_SCHEME,
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER_DRAWING,
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER_IMAGE_DEVELOPMENT,
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER_MODIFY_DRAWING,
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER_PATTERN_SET,
|
||||
MDY_WORKSHEET_ID_PRODUCT,
|
||||
customer_type_map,
|
||||
fabric_type_map,
|
||||
mdy_table_map,
|
||||
plate_order_field_definitions,
|
||||
plate_order_related_worksheet_map,
|
||||
plate_order_related_worksheet_map_cn,
|
||||
plate_order_type_map,
|
||||
product_type_map,
|
||||
)
|
||||
from .models import (
|
||||
Customer,
|
||||
Fabric,
|
||||
MDYAttachmentItem,
|
||||
MDYCollaboratorItem,
|
||||
MDYPlateOrder,
|
||||
MDYRelationItem,
|
||||
Product,
|
||||
ProductListResponse,
|
||||
)
|
||||
from .parsers import pick_customer, pick_fabric, pick_product
|
||||
|
||||
__all__ = [
|
||||
# client
|
||||
"HTTPMethod",
|
||||
"MingDaoYunClient",
|
||||
"get_default_mingdaoyun_client",
|
||||
"MDY_BASE_URL",
|
||||
"MDY_ENDPOINT_GET_FILTER_ROWS",
|
||||
"MDY_APP_KEY",
|
||||
"MDY_SIGN",
|
||||
# worksheet ids / maps
|
||||
"mdy_table_map",
|
||||
"MDY_WORKSHEET_ID_PRODUCT",
|
||||
"MDY_WORKSHEET_ID_CUSTOMER",
|
||||
"MDY_WORKSHEET_ID_FABRIC",
|
||||
"MDY_WORKSHEET_ID_PLATE_ORDER",
|
||||
"MDY_WORKSHEET_ID_PLATE_ORDER_DRAWING",
|
||||
"MDY_WORKSHEET_ID_PLATE_ORDER_COLORING",
|
||||
"MDY_WORKSHEET_ID_PLATE_ORDER_PATTERN_SET",
|
||||
"MDY_WORKSHEET_ID_PLATE_ORDER_MODIFY_DRAWING",
|
||||
"MDY_WORKSHEET_ID_PLATE_ORDER_COLOR_SCHEME",
|
||||
"MDY_WORKSHEET_ID_PLATE_ORDER_IMAGE_DEVELOPMENT",
|
||||
"product_type_map",
|
||||
"customer_type_map",
|
||||
"fabric_type_map",
|
||||
"plate_order_field_definitions",
|
||||
"plate_order_related_worksheet_map",
|
||||
"plate_order_related_worksheet_map_cn",
|
||||
"plate_order_type_map",
|
||||
# models
|
||||
"Product",
|
||||
"ProductListResponse",
|
||||
"Customer",
|
||||
"Fabric",
|
||||
"MDYRelationItem",
|
||||
"MDYAttachmentItem",
|
||||
"MDYCollaboratorItem",
|
||||
"MDYPlateOrder",
|
||||
# parsers
|
||||
"pick_product",
|
||||
"pick_customer",
|
||||
"pick_fabric",
|
||||
# fetch
|
||||
"fetch_products_from_mingdaoyun",
|
||||
"fetch_customers_from_mingdaoyun",
|
||||
"fetch_row_by_rowid_from_mingdaoyun",
|
||||
"fetch_plate_orders_from_mingdaoyun",
|
||||
"sync_fabric_from_mingdaoyun",
|
||||
]
|
||||
109
flower/utils/mingdaoyun/client.py
Normal file
109
flower/utils/mingdaoyun/client.py
Normal file
@@ -0,0 +1,109 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import aiohttp
|
||||
|
||||
# 明道云开放接口:基础配置
|
||||
MDY_BASE_URL = "https://api.mingdao.com"
|
||||
MDY_ENDPOINT_GET_FILTER_ROWS = "/v2/open/worksheet/getFilterRows"
|
||||
|
||||
# NOTE:
|
||||
# - 目前项目里 appKey/sign 仍是硬编码(与现有同步代码保持一致)
|
||||
# - 后续如果要做多环境/更安全配置,建议迁移到环境变量或 Django settings
|
||||
MDY_APP_KEY = "208e55fea5cea59f"
|
||||
MDY_SIGN = "MWU0YmViYjkwZmM1ZDIzYzRiN2U3ZGQ4MmE4ZGNkMjc0MWM1ZmQ2ZjkwMjljODE4YmNkZTBhMzA0OTU2YzE2NA=="
|
||||
|
||||
|
||||
class HTTPMethod(Enum):
|
||||
"""HTTP 请求方法枚举"""
|
||||
|
||||
GET = "GET"
|
||||
POST = "POST"
|
||||
|
||||
|
||||
class MingDaoYunClient:
|
||||
"""明道云 API 客户端(异步)"""
|
||||
|
||||
def __init__(self, app_key: str, sign: str, base_url: str = MDY_BASE_URL):
|
||||
self.app_key = app_key
|
||||
self.sign = sign
|
||||
self.base_url = base_url
|
||||
|
||||
async def request(
|
||||
self,
|
||||
method: HTTPMethod,
|
||||
endpoint: str,
|
||||
data: Optional[Dict[str, Any]] = None,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
) -> Any:
|
||||
"""通用异步请求函数"""
|
||||
|
||||
url = f"{self.base_url}{endpoint}"
|
||||
|
||||
default_headers: Dict[str, str] = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
if headers:
|
||||
default_headers.update(headers)
|
||||
|
||||
# 添加认证参数
|
||||
auth_params = {
|
||||
"appKey": self.app_key,
|
||||
"sign": self.sign,
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession(headers=default_headers) as session:
|
||||
if method == HTTPMethod.GET:
|
||||
async with session.get(url, params=params, json=auth_params) as response:
|
||||
response.raise_for_status()
|
||||
return await response.json()
|
||||
|
||||
if method == HTTPMethod.POST:
|
||||
payload: Dict[str, Any] = dict(data or {})
|
||||
payload.update(auth_params)
|
||||
async with session.post(url, json=payload) as response:
|
||||
response.raise_for_status()
|
||||
return await response.json()
|
||||
|
||||
raise ValueError(f"Unsupported HTTP method: {method}")
|
||||
|
||||
async def get(
|
||||
self,
|
||||
endpoint: str,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
) -> Any:
|
||||
"""发送 GET 请求"""
|
||||
|
||||
return await self.request(
|
||||
method=HTTPMethod.GET,
|
||||
endpoint=endpoint,
|
||||
params=params,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
async def post(
|
||||
self,
|
||||
endpoint: str,
|
||||
data: Optional[Dict[str, Any]] = None,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
) -> Any:
|
||||
"""发送 POST 请求"""
|
||||
|
||||
return await self.request(
|
||||
method=HTTPMethod.POST,
|
||||
endpoint=endpoint,
|
||||
data=data,
|
||||
params=params,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
|
||||
def get_default_mingdaoyun_client(base_url: str = MDY_BASE_URL) -> MingDaoYunClient:
|
||||
"""获取默认配置的明道云客户端(复用项目现有 appKey/sign)"""
|
||||
|
||||
return MingDaoYunClient(app_key=MDY_APP_KEY, sign=MDY_SIGN, base_url=base_url)
|
||||
225
flower/utils/mingdaoyun/fetch.py
Normal file
225
flower/utils/mingdaoyun/fetch.py
Normal file
@@ -0,0 +1,225 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
from .client import MDY_ENDPOINT_GET_FILTER_ROWS, get_default_mingdaoyun_client
|
||||
from .mappings import (
|
||||
MDY_WORKSHEET_ID_CUSTOMER,
|
||||
MDY_WORKSHEET_ID_FABRIC,
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER,
|
||||
MDY_WORKSHEET_ID_PRODUCT,
|
||||
)
|
||||
from .models import Customer, Fabric, Product
|
||||
from .parsers import pick_customer, pick_fabric, pick_product
|
||||
|
||||
|
||||
async def fetch_products_from_mingdaoyun(page: int = 1, page_size: int = 100) -> tuple[list[Product], int]:
|
||||
"""从明道云获取产品列表"""
|
||||
|
||||
client = get_default_mingdaoyun_client()
|
||||
response = await client.post(
|
||||
endpoint=MDY_ENDPOINT_GET_FILTER_ROWS,
|
||||
data={
|
||||
"worksheetId": MDY_WORKSHEET_ID_PRODUCT,
|
||||
"pageIndex": page,
|
||||
"pageSize": page_size,
|
||||
"sortId": "ctime",
|
||||
"isAsc": True,
|
||||
},
|
||||
)
|
||||
|
||||
data = response.get("data") if isinstance(response, dict) else None
|
||||
if data:
|
||||
print(
|
||||
f'[product-sync] page={page} size={page_size} rows={len(data.get("rows", []))} total={data.get("total")}'
|
||||
)
|
||||
else:
|
||||
print(f"[product-sync] page={page} size={page_size} received empty data")
|
||||
if not data:
|
||||
return [], 0
|
||||
|
||||
products = [pick_product(item) for item in data.get("rows", [])]
|
||||
total_count = data.get("total", 0)
|
||||
return products, total_count
|
||||
|
||||
|
||||
async def fetch_customers_from_mingdaoyun(page: int = 1, page_size: int = 100) -> tuple[list[Customer], int]:
|
||||
"""从明道云获取客户列表"""
|
||||
|
||||
client = get_default_mingdaoyun_client()
|
||||
response = await client.post(
|
||||
endpoint=MDY_ENDPOINT_GET_FILTER_ROWS,
|
||||
data={
|
||||
"worksheetId": MDY_WORKSHEET_ID_CUSTOMER,
|
||||
"pageIndex": page,
|
||||
"pageSize": page_size,
|
||||
"sortId": "ctime",
|
||||
"isAsc": True,
|
||||
},
|
||||
)
|
||||
|
||||
data = response.get("data") if isinstance(response, dict) else None
|
||||
if data:
|
||||
print(
|
||||
f'[customer-sync] page={page} size={page_size} rows={len(data.get("rows", []))} total={data.get("total")}'
|
||||
)
|
||||
else:
|
||||
print(f"[customer-sync] page={page} size={page_size} received empty data")
|
||||
if not data:
|
||||
return [], 0
|
||||
|
||||
customers = [pick_customer(item) for item in data.get("rows", [])]
|
||||
total_count = data.get("total", 0)
|
||||
return customers, total_count
|
||||
|
||||
|
||||
async def fetch_row_by_rowid_from_mingdaoyun(
|
||||
worksheet_id: str,
|
||||
rowid: str,
|
||||
*,
|
||||
sort_id: str = "ctime",
|
||||
is_asc: bool = False,
|
||||
list_type: int = 1,
|
||||
not_get_total: bool = True,
|
||||
data_type: int = 2,
|
||||
filter_type: int = 3,
|
||||
control_id: str = "rowId",
|
||||
) -> dict[str, Any] | None:
|
||||
"""按 rowId 查询工作表的单条记录(仅封装 rowId 等值查询)。
|
||||
|
||||
按照明道云 getFilterRows 的 filters 结构构造请求:
|
||||
- dataType=2 表示字符串
|
||||
- filterType=3 表示“相等”
|
||||
|
||||
返回:
|
||||
- 匹配到记录:返回第一条 row dict
|
||||
- 未匹配或响应无 data:返回 None
|
||||
"""
|
||||
|
||||
client = get_default_mingdaoyun_client()
|
||||
response = await client.post(
|
||||
endpoint=MDY_ENDPOINT_GET_FILTER_ROWS,
|
||||
data={
|
||||
"worksheetId": worksheet_id,
|
||||
"listType": list_type,
|
||||
"sortId": sort_id,
|
||||
"isAsc": is_asc,
|
||||
"notGetTotal": not_get_total,
|
||||
"filters": [
|
||||
{
|
||||
"controlId": control_id,
|
||||
"dataType": data_type,
|
||||
"filterType": filter_type,
|
||||
"value": rowid,
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
data = response.get("data") if isinstance(response, dict) else None
|
||||
if not data:
|
||||
return None
|
||||
|
||||
rows = data.get("rows") or []
|
||||
if not rows:
|
||||
return None
|
||||
if isinstance(rows[0], dict):
|
||||
return rows[0]
|
||||
return None
|
||||
|
||||
|
||||
async def fetch_plate_orders_from_mingdaoyun(
|
||||
page: int = 1,
|
||||
page_size: int = 100,
|
||||
*,
|
||||
sort_id: str = "ctime",
|
||||
is_asc: bool = True,
|
||||
) -> tuple[list[dict[str, Any]], int]:
|
||||
"""从明道云获取“开版数据表”记录列表(原始行字典,不做字段映射解析)"""
|
||||
|
||||
client = get_default_mingdaoyun_client()
|
||||
response = await client.post(
|
||||
endpoint=MDY_ENDPOINT_GET_FILTER_ROWS,
|
||||
data={
|
||||
"worksheetId": MDY_WORKSHEET_ID_PLATE_ORDER,
|
||||
"pageIndex": page,
|
||||
"pageSize": page_size,
|
||||
"sortId": sort_id,
|
||||
"isAsc": is_asc,
|
||||
},
|
||||
)
|
||||
|
||||
data = response.get("data") if isinstance(response, dict) else None
|
||||
if data:
|
||||
print(
|
||||
f'[plate-order-fetch] page={page} size={page_size} rows={len(data.get("rows", []))} total={data.get("total")}'
|
||||
)
|
||||
else:
|
||||
print(f"[plate-order-fetch] page={page} size={page_size} received empty data")
|
||||
if not data:
|
||||
return [], 0
|
||||
|
||||
rows = data.get("rows", [])
|
||||
total_count = data.get("total", 0)
|
||||
return rows, total_count
|
||||
|
||||
|
||||
async def sync_fabric_from_mingdaoyun(page: int = 1, page_size: int = 100) -> int:
|
||||
"""从明道云同步面料数据(用于创建/更新 QuickInput: 布料名)"""
|
||||
|
||||
client = get_default_mingdaoyun_client()
|
||||
response = await client.post(
|
||||
endpoint=MDY_ENDPOINT_GET_FILTER_ROWS,
|
||||
data={
|
||||
"worksheetId": MDY_WORKSHEET_ID_FABRIC,
|
||||
"pageIndex": page,
|
||||
"pageSize": page_size,
|
||||
"sortId": "ctime",
|
||||
"isAsc": False,
|
||||
},
|
||||
)
|
||||
|
||||
data = response.get("data") if isinstance(response, dict) else None
|
||||
if data:
|
||||
rows = data.get("rows", [])
|
||||
print(f"[fabric-sync] fetch rows={len(rows)} total={data.get('total')}")
|
||||
if rows:
|
||||
print(f"[fabric-sync] first row keys: {list(rows[0].keys())[:10]}")
|
||||
print(f"[fabric-sync] first row sample: {rows[0]}")
|
||||
else:
|
||||
print(f"[fabric-sync] fetch empty data (page={page}, size={page_size})")
|
||||
if not data:
|
||||
return 0
|
||||
|
||||
fabrics = [pick_fabric(item) for item in data.get("rows", [])]
|
||||
total_count = data.get("total", 0)
|
||||
|
||||
await sync_to_async(_create_fabric_quick_inputs)(
|
||||
page,
|
||||
page_size,
|
||||
fabrics,
|
||||
)
|
||||
return total_count
|
||||
|
||||
|
||||
def _create_fabric_quick_inputs(page: int, page_size: int, fabrics: list[Fabric]) -> None:
|
||||
from basic_info import models as basic_models
|
||||
|
||||
total = len(fabrics)
|
||||
created_count = 0
|
||||
sample_names = [fabric.name for fabric in fabrics[:5]]
|
||||
print(f"[fabric-sync] page={page} size={page_size} fetched={total}")
|
||||
print(f"[fabric-sync] sample names: {sample_names}")
|
||||
for fabric in fabrics:
|
||||
if not fabric.name:
|
||||
continue
|
||||
obj, created = basic_models.QuickInput.objects.update_or_create(
|
||||
name=fabric.name,
|
||||
group="布料名",
|
||||
defaults={"value": fabric.name},
|
||||
)
|
||||
if created:
|
||||
created_count += 1
|
||||
print(f"[fabric-sync] page={page} new_records={created_count} updated_or_existing={total - created_count}")
|
||||
167
flower/utils/mingdaoyun/mappings.py
Normal file
167
flower/utils/mingdaoyun/mappings.py
Normal file
@@ -0,0 +1,167 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 明道云:worksheetId 常量
|
||||
# ------------------------------------------------------------------------------
|
||||
MDY_WORKSHEET_ID_PRODUCT = "spmx"
|
||||
MDY_WORKSHEET_ID_CUSTOMER = "quanbu"
|
||||
MDY_WORKSHEET_ID_FABRIC = "668ba100fb551c850214067d"
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER = "668ba100fb551c8502140660" # 开版数据表
|
||||
|
||||
# 开版表关联数据 worksheetId(用于 Relation 关联查询)
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER_DRAWING = "668ba100fb551c850214066b" # 画图
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER_COLORING = "668ba100fb551c850214066c" # 调色
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER_PATTERN_SET = "668ba100fb551c8502140681" # 套纸样
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER_MODIFY_DRAWING = "66e2eb5a3c1a53053f6d234d" # 改图
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER_COLOR_SCHEME = "672adc61e72ce9924f763a0b" # 配色
|
||||
MDY_WORKSHEET_ID_PLATE_ORDER_IMAGE_DEVELOPMENT = "66e2eee45d7e45f9c5ae41b4" # 照图开发
|
||||
|
||||
|
||||
mdy_table_map = {
|
||||
"product": MDY_WORKSHEET_ID_PRODUCT,
|
||||
"customer": MDY_WORKSHEET_ID_CUSTOMER,
|
||||
"plate_order": MDY_WORKSHEET_ID_PLATE_ORDER,
|
||||
}
|
||||
|
||||
# 开版表关联数据表映射(后续做跨表查询时使用)
|
||||
plate_order_related_worksheet_map = {
|
||||
"drawing": MDY_WORKSHEET_ID_PLATE_ORDER_DRAWING,
|
||||
"coloring": MDY_WORKSHEET_ID_PLATE_ORDER_COLORING,
|
||||
"pattern_set": MDY_WORKSHEET_ID_PLATE_ORDER_PATTERN_SET,
|
||||
"modify_drawing": MDY_WORKSHEET_ID_PLATE_ORDER_MODIFY_DRAWING,
|
||||
"color_scheme": MDY_WORKSHEET_ID_PLATE_ORDER_COLOR_SCHEME,
|
||||
"image_development": MDY_WORKSHEET_ID_PLATE_ORDER_IMAGE_DEVELOPMENT,
|
||||
}
|
||||
|
||||
# 同上(中文 key,便于排查/对照)
|
||||
plate_order_related_worksheet_map_cn = {
|
||||
"画图": MDY_WORKSHEET_ID_PLATE_ORDER_DRAWING,
|
||||
"调色": MDY_WORKSHEET_ID_PLATE_ORDER_COLORING,
|
||||
"套纸样": MDY_WORKSHEET_ID_PLATE_ORDER_PATTERN_SET,
|
||||
"改图": MDY_WORKSHEET_ID_PLATE_ORDER_MODIFY_DRAWING,
|
||||
"配色": MDY_WORKSHEET_ID_PLATE_ORDER_COLOR_SCHEME,
|
||||
"照图开发": MDY_WORKSHEET_ID_PLATE_ORDER_IMAGE_DEVELOPMENT,
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 明道云:字段映射(内部字段名 -> controlId)
|
||||
# ------------------------------------------------------------------------------
|
||||
product_type_map = {
|
||||
"name": "668caa5eb80969563ecaee7d",
|
||||
"pieces": "689c5aa37c175367b29fcb87",
|
||||
"color": "66ed3b9ae01d5599bdb45f6d",
|
||||
"uid": "668caa5eb80969563ecaee7c",
|
||||
"unit": "668e2ee2ca758c8cc5d0dc1d",
|
||||
"segment_size": "668e2194790c0e04058b4f3c",
|
||||
"width": "668e2194790c0e04058b4f3c",
|
||||
"detail_str": "668caa5eb80969563ecaee7e",
|
||||
"created_at": "ctime",
|
||||
"rowid": "rowid",
|
||||
}
|
||||
|
||||
customer_type_map = {
|
||||
"name": "62d52f4b8d2972284492dcf9",
|
||||
"area": "62d52f4b8d2972284492dd09",
|
||||
"created_at": "ctime",
|
||||
"rowid": "rowid",
|
||||
"uid": "668bb9370207cf7520fe551e",
|
||||
}
|
||||
|
||||
fabric_type_map = {
|
||||
"name": "62d52f4b8d2972284492de61",
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 明道云:开版数据表(worksheetId: 668ba100fb551c8502140660)
|
||||
# 字段说明(controlId -> 元信息)。按需求保留为 dict,暂不用于拉取请求。
|
||||
# ------------------------------------------------------------------------------
|
||||
plate_order_field_definitions: Dict[str, Dict[str, Any]] = {
|
||||
# 成员 / 人员
|
||||
"62d8fffb625ac34fa91299c3": {"name": "调色", "type": "Collaborator"},
|
||||
# 关联记录(Relation)—— 展现 rowid + name + link
|
||||
"62d52f4b8d2972284492dd28": {"name": "打版面料", "type": "Relation"},
|
||||
"62d52f4b8d2972284492dd14": {"name": "客户", "type": "Relation"},
|
||||
# 下拉/文本/数值/日期
|
||||
"62d52f4b8d2972284492dd15": {"name": "做货方式", "type": "Dropdown"},
|
||||
"62d52f4b8d2972284492dd0e": {"name": "设计编号", "type": "AutoNumber"},
|
||||
"62d91d0e130624d31368bd16": {"name": "开版方式", "type": "Dropdown"},
|
||||
"64a64bc4402bb5226ee34b3c": {"name": "等级", "type": "Rating"},
|
||||
"660b79da4125de62cd904b39": {"name": "布料", "type": "Dropdown"},
|
||||
"62db8e78510fb7962439b19c": {"name": "调色设计师", "type": "Text"},
|
||||
"62d52f4b8d2972284492dd10": {"name": "开发进程", "type": "Dropdown"},
|
||||
"62d52f4b8d2972284492dd1e": {"name": "是否套唛架", "type": "Dropdown"},
|
||||
"62d52f4b8d2972284492dd2c": {"name": "款号名称", "type": "Text"},
|
||||
"62d52f4b8d2972284492dd1d": {"name": "审批结果", "type": "Dropdown"},
|
||||
"62d52f4b8d2972284492dd12": {"name": "下版时间", "type": "DateTime"},
|
||||
"62d52f4b8d2972284492dd18": {"name": "复版原因", "type": "Dropdown"},
|
||||
"62d52f4b8d2972284492dd0f": {"name": "起版情况", "type": "Dropdown"},
|
||||
"62d52f4b8d2972284492dd19": {"name": "客户要求米样米数", "type": "Number"},
|
||||
"62d52f4b8d2972284492dd11": {"name": "紧急程度", "type": "Dropdown"},
|
||||
"660b75c3422f028085086b3c": {"name": "幅宽", "type": "Dropdown"},
|
||||
"62d52f4b8d2972284492dd13": {"name": "要求完成时间", "type": "Date"},
|
||||
"62f72a26d73f8581fbe7a488": {"name": "难度评级", "type": "Dropdown"},
|
||||
# 附件(Attachment)—— 展现文件名 + 下载链接
|
||||
"62d52f4b8d2972284492dd27": {"name": "开版图", "type": "Attachment"},
|
||||
# 评级/开关/组合字段
|
||||
"6451f4ad25766313eb8cb057": {"name": "日期", "type": "DateTime"},
|
||||
"66c2ebc8666ad6264b709fa1": {"name": "画图评级", "type": "Dropdown"},
|
||||
"66c2ebc8666ad6264b709fa2": {"name": "调色评级", "type": "Dropdown"},
|
||||
"66c2ebc8666ad6264b709fa3": {"name": "套样评级", "type": "Dropdown"},
|
||||
"6718ac791f08f8ec31257ebc": {"name": "记录id(文本组合)", "type": "Concatenate"},
|
||||
"67f4d7d61c5f650e952fcee9": {"name": "客户名称", "type": "Lookup"},
|
||||
# “有这个字段,但本条记录可能为空”的 Relation(占位:[])
|
||||
"62d52f4b8d2972284492dd2d": {"name": "套纸样", "type": "Relation"},
|
||||
"62d52f4b8d2972284492dd21": {"name": "画图", "type": "Relation"},
|
||||
"62d52f4b8d2972284492dd22": {"name": "调色(关联)", "type": "Relation"},
|
||||
"62d52f4b8d2972284492dd2a": {"name": "套纸样(关联)", "type": "Relation"},
|
||||
"66e2ebb7da66655f355bf708": {"name": "改图(关联)", "type": "Relation"},
|
||||
"66e2efb0da66655f355bf964": {"name": "找图开发", "type": "Relation"},
|
||||
"672adc9b156abb9a08ab2a60": {"name": "配色", "type": "Relation"},
|
||||
}
|
||||
|
||||
# 同步代码风格:内部字段名 -> controlId(后续 pick / parse 时会用到)
|
||||
plate_order_type_map = {
|
||||
"colorist": "62d8fffb625ac34fa91299c3",
|
||||
"fabric_relation": "62d52f4b8d2972284492dd28",
|
||||
"customer_relation": "62d52f4b8d2972284492dd14",
|
||||
"production_method": "62d52f4b8d2972284492dd15",
|
||||
"design_no": "62d52f4b8d2972284492dd0e",
|
||||
"plate_method": "62d91d0e130624d31368bd16",
|
||||
"level_rating": "64a64bc4402bb5226ee34b3c",
|
||||
"fabric_source": "660b79da4125de62cd904b39",
|
||||
"color_designer": "62db8e78510fb7962439b19c",
|
||||
"dev_progress": "62d52f4b8d2972284492dd10",
|
||||
"need_marker_frame": "62d52f4b8d2972284492dd1e",
|
||||
"style_name": "62d52f4b8d2972284492dd2c",
|
||||
"approval_result": "62d52f4b8d2972284492dd1d",
|
||||
"plate_time": "62d52f4b8d2972284492dd12",
|
||||
"rework_reason": "62d52f4b8d2972284492dd18",
|
||||
"start_plate_status": "62d52f4b8d2972284492dd0f",
|
||||
"sample_meters": "62d52f4b8d2972284492dd19", # 对应样品米数(宇问云)
|
||||
"urgency": "62d52f4b8d2972284492dd11",
|
||||
"width": "660b75c3422f028085086b3c",
|
||||
"required_finish_date": "62d52f4b8d2972284492dd13",
|
||||
"difficulty_level": "62f72a26d73f8581fbe7a488",
|
||||
"plate_images": "62d52f4b8d2972284492dd27",
|
||||
"record_datetime": "6451f4ad25766313eb8cb057",
|
||||
"drawing_rating": "66c2ebc8666ad6264b709fa1",
|
||||
"color_rating": "66c2ebc8666ad6264b709fa2",
|
||||
"pattern_fit_rating": "66c2ebc8666ad6264b709fa3",
|
||||
"record_id": "6718ac791f08f8ec31257ebc",
|
||||
"customer_name": "67f4d7d61c5f650e952fcee9",
|
||||
# relations(可能为空)
|
||||
"pattern_set": "62d52f4b8d2972284492dd2d",
|
||||
"drawing_relation": "62d52f4b8d2972284492dd21",
|
||||
"color_relation": "62d52f4b8d2972284492dd22",
|
||||
"pattern_set_relation": "62d52f4b8d2972284492dd2a",
|
||||
"modify_drawing_relation": "66e2ebb7da66655f355bf708",
|
||||
"image_development_relation": "66e2efb0da66655f355bf964",
|
||||
"color_scheme_relation": "672adc9b156abb9a08ab2a60",
|
||||
# 系统字段
|
||||
"created_at": "ctime",
|
||||
"rowid": "rowid",
|
||||
}
|
||||
142
flower/utils/mingdaoyun/models.py
Normal file
142
flower/utils/mingdaoyun/models.py
Normal file
@@ -0,0 +1,142 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, List
|
||||
|
||||
from pydantic import BaseModel, Field, computed_field
|
||||
|
||||
|
||||
class Product(BaseModel):
|
||||
"""产品模型"""
|
||||
|
||||
uid: str
|
||||
rowid: str
|
||||
name: str
|
||||
pieces: int | None
|
||||
segment_size: int | None
|
||||
unit: str | None
|
||||
color: str | None
|
||||
width: str | None = None
|
||||
detail_str: str | None = Field(exclude=True) # 在序列化时排除此字段
|
||||
created_at: str
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def detail(self) -> dict[str, Any]:
|
||||
"""将 detail_str 转换为字典"""
|
||||
|
||||
try:
|
||||
return json.loads(self.detail_str) if self.detail_str else {}
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return {}
|
||||
|
||||
|
||||
class ProductListResponse(BaseModel):
|
||||
"""产品列表响应"""
|
||||
|
||||
products: List[Product]
|
||||
total: int
|
||||
|
||||
|
||||
class Customer(BaseModel):
|
||||
"""客户模型"""
|
||||
|
||||
uid: str
|
||||
rowid: str
|
||||
name: str
|
||||
area: str | None
|
||||
created_at: str
|
||||
|
||||
|
||||
class Fabric(BaseModel):
|
||||
"""面料模型"""
|
||||
|
||||
name: str
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 明道云:通用值类型(Relation / Attachment / Collaborator 等)
|
||||
# ------------------------------------------------------------------------------
|
||||
class MDYRelationItem(BaseModel):
|
||||
"""Relation 字段的单项(跨表关联)"""
|
||||
|
||||
rowid: str
|
||||
name: str
|
||||
link: str | None = None
|
||||
|
||||
|
||||
class MDYAttachmentItem(BaseModel):
|
||||
"""Attachment 字段的单项(附件)"""
|
||||
|
||||
original_file_name: str
|
||||
file_id: str
|
||||
download_url: str
|
||||
preview_url: str | None = None
|
||||
|
||||
|
||||
class MDYCollaboratorItem(BaseModel):
|
||||
"""Collaborator 字段的单项(成员/人员)
|
||||
|
||||
说明:明道云在不同场景下可能返回对象或列表;这里先按“对象字段”建模,
|
||||
后续解析时可以按实际返回结构再做兼容适配。
|
||||
"""
|
||||
|
||||
accountId: str | None = None
|
||||
fullname: str | None = None
|
||||
avatar: str | None = None
|
||||
|
||||
|
||||
class MDYPlateOrder(BaseModel):
|
||||
"""开版数据表(明道云)的一行记录:仅做类型定义"""
|
||||
|
||||
rowid: str
|
||||
created_at: str
|
||||
|
||||
# 成员 / 人员
|
||||
colorist: MDYCollaboratorItem | List[MDYCollaboratorItem] | str | None = None
|
||||
|
||||
# Relation(跨表关联)
|
||||
fabric_relation: List[MDYRelationItem] = Field(default_factory=list)
|
||||
customer_relation: List[MDYRelationItem] = Field(default_factory=list)
|
||||
|
||||
# 基础字段
|
||||
production_method: str | None = None
|
||||
design_no: str | None = None
|
||||
plate_method: str | None = None
|
||||
level_rating: int | None = None
|
||||
fabric_source: str | None = None
|
||||
color_designer: str | None = None
|
||||
dev_progress: str | None = None
|
||||
need_marker_frame: str | None = None
|
||||
style_name: str | None = None
|
||||
approval_result: str | None = None
|
||||
|
||||
plate_time: str | None = None # DateTime: 'YYYY-MM-DD HH:MM:SS'
|
||||
required_finish_date: str | None = None # Date: 'YYYY-MM-DD'
|
||||
record_datetime: str | None = None # DateTime: 'YYYY-MM-DD HH:MM:SS'
|
||||
|
||||
rework_reason: str | None = None
|
||||
start_plate_status: str | None = None
|
||||
sample_meters: int | float | None = None
|
||||
urgency: str | None = None
|
||||
width: str | None = None
|
||||
difficulty_level: str | None = None
|
||||
|
||||
# 附件
|
||||
plate_images: List[MDYAttachmentItem] = Field(default_factory=list)
|
||||
|
||||
# 评级/组合字段
|
||||
drawing_rating: str | None = None
|
||||
color_rating: str | None = None
|
||||
pattern_fit_rating: str | None = None
|
||||
record_id: str | None = None
|
||||
customer_name: str | None = None
|
||||
|
||||
# 其它 Relation(可能为空)
|
||||
pattern_set: List[MDYRelationItem] = Field(default_factory=list)
|
||||
drawing_relation: List[MDYRelationItem] = Field(default_factory=list)
|
||||
color_relation: List[MDYRelationItem] = Field(default_factory=list)
|
||||
pattern_set_relation: List[MDYRelationItem] = Field(default_factory=list)
|
||||
modify_drawing_relation: List[MDYRelationItem] = Field(default_factory=list)
|
||||
image_development_relation: List[MDYRelationItem] = Field(default_factory=list)
|
||||
color_scheme_relation: List[MDYRelationItem] = Field(default_factory=list)
|
||||
39
flower/utils/mingdaoyun/parsers.py
Normal file
39
flower/utils/mingdaoyun/parsers.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .mappings import customer_type_map, fabric_type_map, product_type_map
|
||||
from .models import Customer, Fabric, Product
|
||||
|
||||
|
||||
def pick_customer(fields: dict[str, Any]) -> Customer:
|
||||
"""从字段字典中提取客户信息"""
|
||||
|
||||
data = {k: fields.get(v, "") for k, v in customer_type_map.items()}
|
||||
return Customer(**data)
|
||||
|
||||
|
||||
def pick_product(fields: dict[str, Any]) -> Product:
|
||||
"""从字段字典中提取产品信息"""
|
||||
|
||||
data = {k: fields.get(v, "") for k, v in product_type_map.items()}
|
||||
|
||||
def _to_int(value):
|
||||
if value in ("", None):
|
||||
return None
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
data["pieces"] = _to_int(data.get("pieces"))
|
||||
data["segment_size"] = _to_int(data.get("segment_size"))
|
||||
|
||||
return Product(**data)
|
||||
|
||||
|
||||
def pick_fabric(fields: dict[str, Any]) -> Fabric:
|
||||
"""从字段字典中提取面料信息"""
|
||||
|
||||
data = {k: fields.get(v, "") for k, v in fabric_type_map.items()}
|
||||
return Fabric(**data)
|
||||
Reference in New Issue
Block a user