254 lines
10 KiB
Python
254 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from app.clients.huobanyun_client import HuobanyunClient
|
|
from app.schemas.feishu_external import (
|
|
FeishuExternalItem,
|
|
FeishuExternalQueryRequest,
|
|
FeishuExternalQueryResponse,
|
|
)
|
|
from app.schemas.huobanyun import HuobanyunQueryResponse
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class HuobanyunService:
|
|
def __init__(self) -> None:
|
|
self.client = HuobanyunClient()
|
|
|
|
def _extract_value(self, value: Any) -> Any:
|
|
if isinstance(value, dict):
|
|
for key in ("value", "text", "name", "title", "label", "id"):
|
|
if key in value:
|
|
return value.get(key)
|
|
return value
|
|
if isinstance(value, list):
|
|
if not value:
|
|
return value
|
|
if len(value) == 1:
|
|
return self._extract_value(value[0])
|
|
return value
|
|
|
|
def _pick_field(self, fields: Dict[str, Any], keys: List[str]) -> Any:
|
|
for key in keys:
|
|
if key in fields:
|
|
return self._extract_value(fields.get(key))
|
|
return ""
|
|
|
|
def _resolve_field_key(self, key: str) -> str:
|
|
mapping = {
|
|
"项目单号": "2200000149785345",
|
|
"项目名称": "2200000150711223",
|
|
"订单状态": "2200000150497330",
|
|
"下单金额": "2200000149785349",
|
|
"是否已关联对公付款审批": "2200000589775224",
|
|
"是否已关联对私付款审批": "2200000589775228",
|
|
"平台": "2200000149785346",
|
|
"账号名称": "2200000150497326",
|
|
}
|
|
return mapping.get(key, key)
|
|
|
|
def _resolve_field_label(self, key: str) -> str:
|
|
reverse = {
|
|
"2200000149785345": "项目单号",
|
|
"2200000150711223": "项目名称",
|
|
"2200000150497330": "订单状态",
|
|
"2200000149785349": "下单金额",
|
|
"2200000589775224": "是否已关联对公付款审批",
|
|
"2200000589775228": "是否已关联对私付款审批",
|
|
"2200000149785346": "平台",
|
|
"2200000150497326": "账号名称",
|
|
}
|
|
return reverse.get(key, key)
|
|
|
|
def _to_bool(self, value: Any) -> Optional[bool]:
|
|
if isinstance(value, bool):
|
|
return value
|
|
if isinstance(value, (int, float)):
|
|
return value != 0
|
|
if isinstance(value, str):
|
|
val = value.strip().lower()
|
|
if val in {"是", "true", "1", "yes", "y"}:
|
|
return True
|
|
if val in {"否", "false", "0", "no", "n"}:
|
|
return False
|
|
if isinstance(value, list):
|
|
return len(value) > 0
|
|
return None
|
|
|
|
async def get_projects_list(
|
|
self,
|
|
project_name: str | None = None,
|
|
project_no: str | None = None,
|
|
page: int = 1,
|
|
size: int = 50,
|
|
) -> Dict[str, Any]:
|
|
table_id = "2100000015544940"
|
|
limit = 50 if size <= 0 else min(size, 100)
|
|
page = 1 if page <= 0 else page
|
|
offset = (page - 1) * limit
|
|
|
|
filter_items: List[Dict[str, Any]] = []
|
|
if project_name:
|
|
filter_items.append(
|
|
{"field": "2200000150711223", "query": {"eqm": [project_name]}}
|
|
)
|
|
if project_no:
|
|
filter_items.append(
|
|
{"field": "proj_id", "query": {"eqm": [project_no]}}
|
|
)
|
|
payload: Dict[str, Any] = {"table_id": table_id, "limit": limit, "offset": offset}
|
|
if filter_items:
|
|
payload["filter"] = {"and": filter_items}
|
|
payload["order"] = {"field_id": "created_on", "type": "desc"}
|
|
payload["with_field_config"] = 0
|
|
|
|
data = await self.client.list_items(payload)
|
|
if isinstance(data, dict):
|
|
logger.info(
|
|
"伙伴云列表响应: keys=%s code=%s message=%s",
|
|
list(data.keys()),
|
|
data.get("code"),
|
|
data.get("message"),
|
|
)
|
|
if isinstance(data, dict):
|
|
data_block = data.get("data", data)
|
|
if isinstance(data_block, list):
|
|
logger.warning("伙伴云 data 字段为列表,按 items 处理")
|
|
items = data_block
|
|
total = len(items)
|
|
elif isinstance(data_block, dict):
|
|
items = data_block.get("items", [])
|
|
if not items and isinstance(data.get("items"), list):
|
|
items = data.get("items", [])
|
|
total = data_block.get("filtered", data_block.get("total", len(items)))
|
|
else:
|
|
logger.error("伙伴云 data 字段结构异常: %s", type(data_block))
|
|
items = []
|
|
total = 0
|
|
elif isinstance(data, list):
|
|
logger.warning("伙伴云返回为列表,按 items 处理")
|
|
items = data
|
|
total = len(items)
|
|
else:
|
|
logger.error("伙伴云返回结构异常: %s", type(data))
|
|
items = []
|
|
total = 0
|
|
|
|
if items:
|
|
sample_fields = items[0].get("fields", {})
|
|
logger.info("项目字段样例 keys=%s", list(sample_fields.keys()))
|
|
|
|
mapped_items: List[Dict[str, Any]] = []
|
|
for item in items:
|
|
fields = item.get("fields", {})
|
|
project_no = self._pick_field(fields, ["proj_id", "2200000149785345"])
|
|
project_name = self._pick_field(fields, ["2200000150711223"])
|
|
if not project_name:
|
|
project_name = item.get("title", "")
|
|
order_status = self._pick_field(fields, ["2200000150497330"])
|
|
order_amount = self._pick_field(fields, ["2200000149785349"])
|
|
linked_public = self._pick_field(
|
|
fields,
|
|
[
|
|
"2200000589775224",
|
|
],
|
|
)
|
|
linked_private = self._pick_field(
|
|
fields,
|
|
[
|
|
"2200000589775228",
|
|
],
|
|
)
|
|
mapped_items.append(
|
|
{
|
|
"project_no": project_no,
|
|
"project_name": project_name,
|
|
"order_status": order_status,
|
|
"order_amount": order_amount,
|
|
"linked_public_payment": self._to_bool(linked_public),
|
|
"linked_private_payment": self._to_bool(linked_private),
|
|
}
|
|
)
|
|
|
|
return {"total": total, "items": mapped_items}
|
|
|
|
async def query(self, req: FeishuExternalQueryRequest) -> FeishuExternalQueryResponse:
|
|
raw = req.raw or {}
|
|
table_id = (
|
|
raw.get("table_id")
|
|
or raw.get("tableId")
|
|
or raw.get("table")
|
|
or "2100000015544940"
|
|
)
|
|
limit = 50
|
|
if req.page_token and str(req.page_token).isdigit():
|
|
offset = int(str(req.page_token))
|
|
else:
|
|
offset = 0
|
|
|
|
key = req.key or raw.get("key") or raw.get("field") or req.token or ""
|
|
key = self._resolve_field_key(str(key).strip()) if key else ""
|
|
query_value = req.query or req.keyword or ""
|
|
linkage_params = req.linkage_params or raw.get("linkage_params") or {}
|
|
linkage_project_no = linkage_params.get("项目单号") if isinstance(linkage_params, dict) else None
|
|
logger.info("外部选项请求: token_key=%s linkage_project_no=%s", key, linkage_project_no)
|
|
|
|
payload: Dict[str, Any] = {"table_id": table_id, "limit": limit, "offset": offset}
|
|
if linkage_project_no:
|
|
filter_field = "proj_id"
|
|
payload["filter"] = {"and": [{"field": filter_field, "query": {"eqm": [str(linkage_project_no)]}}]}
|
|
elif key and query_value:
|
|
filter_field = "proj_id" if key == "2200000149785345" else key
|
|
payload["filter"] = {"and": [{"field": filter_field, "query": {"eqm": [str(query_value)]}}]}
|
|
elif raw.get("filter"):
|
|
payload["filter"] = raw.get("filter")
|
|
elif query_value:
|
|
payload["filter"] = {"and": [{"field": "proj_id", "query": {"eqm": [query_value]}}]}
|
|
payload["order"] = {"field_id": "created_on", "type": "desc"}
|
|
payload["with_field_config"] = 0
|
|
|
|
data = await self.client.query(payload)
|
|
data_block = data.get("data", {}) if isinstance(data, dict) else {}
|
|
items = data_block.get("items", []) if isinstance(data_block, dict) else []
|
|
has_more = bool(data_block.get("has_more"))
|
|
next_token = str(offset + limit) if has_more else ""
|
|
|
|
options = []
|
|
texts = {}
|
|
for item in items:
|
|
fields = item.get("fields", {})
|
|
item_id = item.get("item_id", "")
|
|
if linkage_project_no:
|
|
if key:
|
|
desired_fields = [(self._resolve_field_label(key), key)]
|
|
else:
|
|
desired_fields = [
|
|
("平台", self._resolve_field_key("平台")),
|
|
("账号名称", self._resolve_field_key("账号名称")),
|
|
("项目名称", self._resolve_field_key("项目名称")),
|
|
]
|
|
logger.info("联动返回字段: %s", [name for name, _ in desired_fields])
|
|
for field_name, field_key in desired_fields:
|
|
value = self._extract_value(fields.get(field_key, ""))
|
|
value_str = "" if value is None else str(value)
|
|
i18n_key = f"@i18n@{item_id}:{field_name}" if item_id else f"@i18n@{field_name}:{value_str}"
|
|
options.append({"id": f"{item_id}:{field_name}", "value": i18n_key, "isDefault": False})
|
|
texts[i18n_key] = value_str
|
|
else:
|
|
project_key = key or self._resolve_field_key("项目单号")
|
|
value = self._extract_value(fields.get(project_key, ""))
|
|
value_str = "" if value is None else str(value)
|
|
i18n_key = f"@i18n@{item_id}" if item_id else f"@i18n@{value_str}"
|
|
options.append({"id": str(item_id), "value": i18n_key, "isDefault": False})
|
|
texts[i18n_key] = value_str
|
|
|
|
return {
|
|
"options": options,
|
|
"texts": texts,
|
|
"has_more": has_more,
|
|
"next_page_token": next_token,
|
|
}
|