1
0
forked from erp-dev/erp

feat: tasks for sync products and customers

This commit is contained in:
2025-12-08 22:22:18 +08:00
parent fc18f71615
commit a36a7e5265
14 changed files with 717 additions and 248 deletions

View File

@@ -1,227 +1,283 @@
# import aiohttp
# from typing import Optional, Dict, Any, List
# from pydantic import BaseModel, Field, computed_field
# from enum import Enum
# import json
import aiohttp
from typing import Optional, Dict, Any, List
from pydantic import BaseModel, Field, computed_field
from enum import Enum
import json
# class Product(BaseModel):
# """产品模型 - 请在此填入你的字段"""
# uid: str
# name: str
# unit: str | None
# color: str | None
# detail_str: str | None = Field(exclude=True) # 在序列化时排除此字段
# created_at: str
class Product(BaseModel):
"""产品模型 - 请在此填入你的字段"""
uid: str
rowid: str
name: str
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 {}
@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 ProductListResponse(BaseModel):
"""产品列表响应"""
products: List[Product]
total: int
# mdy_table_map = {
# 'product': 'spmx',
# 'customer': 'quanbu',
mdy_table_map = {
'product': 'spmx',
'customer': 'quanbu',
}
class HTTPMethod(Enum):
"""HTTP 请求方法枚举"""
GET = "GET"
POST = "POST"
class MingDaoYunClient:
"""明道云 API 客户端"""
def __init__(self, app_key: str, sign: str, base_url: str = ""):
"""
初始化客户端
Args:
app_key: 应用密钥
sign: 签名
base_url: API 基础地址
"""
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:
"""
通用异步请求函数
Args:
method: 请求方法 (GET/POST)
endpoint: API 端点路径
data: POST 请求体数据JSON
params: URL 查询参数
headers: 自定义请求头
Returns:
响应 JSON 数据
Raises:
aiohttp.ClientError: 请求失败时抛出
"""
url = f"{self.base_url}{endpoint}"
# 构建默认请求头
default_headers = {
"Content-Type": "application/json",
}
if headers:
default_headers.update(headers)
# 添加认证参数
auth_params = {
"appKey": self.app_key,
"sign": self.sign
}
async with aiohttp.ClientSession() 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()
elif method == HTTPMethod.POST:
if data is None:
data = {}
data.update(auth_params)
async with session.post(url, json=data) as response:
response.raise_for_status()
return await response.json()
else:
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 请求
Args:
endpoint: API 端点路径
params: URL 查询参数
headers: 自定义请求头
Returns:
响应 JSON 数据
"""
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 请求
Args:
endpoint: API 端点路径
data: POST 请求体数据JSON
params: URL 查询参数
headers: 自定义请求头
Returns:
响应 JSON 数据
"""
return await self.request(
method=HTTPMethod.POST,
endpoint=endpoint,
data=data,
params=params,
headers=headers,
)
product_type_map = {
'name': '668caa5eb80969563ecaee7d',
'color': '66ed3b9ae01d5599bdb45f6d',
'uid': '668caa5eb80969563ecaee7c',
'unit': '668e2ee2ca758c8cc5d0dc1d',
'width': '668e2194790c0e04058b4f3c',
'detail_str': '668caa5eb80969563ecaee7e',
'created_at': 'ctime',
'rowid': 'rowid',
}
customer_type_map = {
'name': '62d52f4b8d2972284492dcf9',
'area': '62d52f4b8d2972284492dd09',
'created_at': 'ctime',
'rowid': 'rowid',
'uid': '668bb9370207cf7520fe551e',
}
class Customer(BaseModel):
"""客户模型 - 请在此填入你的字段"""
uid: str
rowid: str
name: str
area: str | None
created_at: str
def pick_customer(fields: dict) -> Customer:
"""从字段字典中提取客户信息"""
data = {k: fields.get(v, '') for k, v in customer_type_map.items()}
return Customer(**data)
# json param example:
# {
# "appKey": "208e55fea5cea59f",
# "sign": "MWU0YmViYjkwZmM1ZDIzYzRiN2U3ZGQ4MmE4ZGNkMjc0MWM1ZmQ2ZjkwMjljODE4YmNkZTBhMzA0OTU2YzE2NA==",
# "worksheetId": "quanbu",
# "listType": 1,
# "sortId": "ctime",
# "isAsc": false,
# "notGetTotal": true
# }
# class HTTPMethod(Enum):
# """HTTP 请求方法枚举"""
# GET = "GET"
# POST = "POST"
def pick_product(fields: dict) -> Product:
"""
从字段字典中提取产品信息
"""
data = {k: fields.get(v, '') for k, v in product_type_map.items()}
return Product(**data)
# class MingDaoYunClient:
# """明道云 API 客户端"""
async def fetch_products_from_mingdaoyun(page: int = 1, page_size: int = 100) -> tuple[list[Product], int]:
"""
从明道云获取产品列表
# def __init__(self, app_key: str, sign: str, base_url: str = ""):
# """
# 初始化客户端
Returns:
tuple: (产品列表, 总数量)
"""
client = MingDaoYunClient(
app_key='208e55fea5cea59f',
sign='MWU0YmViYjkwZmM1ZDIzYzRiN2U3ZGQ4MmE4ZGNkMjc0MWM1ZmQ2ZjkwMjljODE4YmNkZTBhMzA0OTU2YzE2NA==',
base_url="https://api.mingdao.com"
)
response = await client.post(
endpoint='/v2/open/worksheet/getFilterRows',
data={
'worksheetId': 'spmx',
'pageIndex': page,
'pageSize': page_size,
'sortId': 'ctime',
'isAsc': False,
}
)
data = response.get('data')
if not data:
return [], 0
# Args:
# app_key: 应用密钥
# sign: 签名
# base_url: API 基础地址
# """
# 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:
# """
# 通用异步请求函数
# Args:
# method: 请求方法 (GET/POST)
# endpoint: API 端点路径
# data: POST 请求体数据JSON
# params: URL 查询参数
# headers: 自定义请求头
# Returns:
# 响应 JSON 数据
# Raises:
# aiohttp.ClientError: 请求失败时抛出
# """
# url = f"{self.base_url}{endpoint}"
# # 构建默认请求头
# default_headers = {
# "Content-Type": "application/json",
# }
# if headers:
# default_headers.update(headers)
# # 添加认证参数
# auth_params = {
# "appKey": self.app_key,
# "sign": self.sign
# }
# async with aiohttp.ClientSession() 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()
# elif method == HTTPMethod.POST:
# if data is None:
# data = {}
# data.update(auth_params)
# async with session.post(url, json=data) as response:
# response.raise_for_status()
# return await response.json()
# else:
# 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 请求
# Args:
# endpoint: API 端点路径
# params: URL 查询参数
# headers: 自定义请求头
# Returns:
# 响应 JSON 数据
# """
# 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 请求
# Args:
# endpoint: API 端点路径
# data: POST 请求体数据JSON
# params: URL 查询参数
# headers: 自定义请求头
# Returns:
# 响应 JSON 数据
# """
# return await self.request(
# method=HTTPMethod.POST,
# endpoint=endpoint,
# data=data,
# params=params,
# headers=headers,
# )
products = [pick_product(item) for item in data.get('rows', [])]
total_count = data.get('total', 0)
return products, total_count
# product_type_map = {
# 'name': '668caa5eb80969563ecaee7d',
# 'color': '66ed3b9ae01d5599bdb45f6d',
# 'uid': '668caa5eb80969563ecaee7c',
# 'unit': '668e2ee2ca758c8cc5d0dc1d',
# 'detail_str': '668caa5eb80969563ecaee7e',
# 'created_at': 'ctime',
# }
async def fetch_customers_from_mingdaoyun(page: int = 1, page_size: int = 100) -> tuple[list[Customer], int]:
"""
从明道云获取客户列表
"""
client = MingDaoYunClient(
app_key='208e55fea5cea59f',
sign='MWU0YmViYjkwZmM1ZDIzYzRiN2U3ZGQ4MmE4ZGNkMjc0MWM1ZmQ2ZjkwMjljODE4YmNkZTBhMzA0OTU2YzE2NA==',
base_url="https://api.mingdao.com"
)
response = await client.post(
endpoint='/v2/open/worksheet/getFilterRows',
data={
'worksheetId': 'quanbu',
'pageIndex': page,
'pageSize': page_size,
'sortId': 'ctime',
'isAsc': False,
}
)
data = response.get('data')
if not data:
return [], 0
# # json param example:
# # {
# # "appKey": "208e55fea5cea59f",
# # "sign": "MWU0YmViYjkwZmM1ZDIzYzRiN2U3ZGQ4MmE4ZGNkMjc0MWM1ZmQ2ZjkwMjljODE4YmNkZTBhMzA0OTU2YzE2NA==",
# # "worksheetId": "quanbu",
# # "listType": 1,
# # "sortId": "ctime",
# # "isAsc": false,
# # "notGetTotal": true
# # }
# def pick_product(fields: dict) -> Product:
# """
# 从字段字典中提取产品信息
# """
# data = {k: fields.get(v, '') for k, v in product_type_map.items()}
# return Product(**data)
# async def fetch_products_from_mingdaoyun(page: int = 1, page_size: int = 100) -> tuple[list[Product], int]:
# """
# 从明道云获取产品列表
# Returns:
# tuple: (产品列表, 总数量)
# """
# client = MingDaoYunClient(
# app_key='208e55fea5cea59f',
# sign='MWU0YmViYjkwZmM1ZDIzYzRiN2U3ZGQ4MmE4ZGNkMjc0MWM1ZmQ2ZjkwMjljODE4YmNkZTBhMzA0OTU2YzE2NA==',
# base_url="https://api.mingdao.com"
# )
# response = await client.post(
# endpoint='/v2/open/worksheet/getFilterRows',
# data={
# 'worksheetId': 'spmx',
# 'pageIndex': page,
# 'pageSize': page_size,
# }
# )
# data = response.get('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
customers = [pick_customer(item) for item in data.get('rows', [])]
total_count = data.get('total', 0)
return customers, total_count