update
This commit is contained in:
parent
606f83678a
commit
787dead8c1
8
.env
8
.env
|
|
@ -1,10 +1,8 @@
|
||||||
HUOBANYUN_API_KEY=emdYCszTIUrczBf2wOPGQ553J3OO9NCKKnLGJEK9
|
HUOBANYUN_API_KEY=emdYCszTIUrczBf2wOPGQ553J3OO9NCKKnLGJEK9
|
||||||
FEISHU_APPROVAL_CODES=ECD8CE34-AA80-4A4F-B4C8-8510A7126490,BB944139-432F-4AC2-AD27-81C2F738E7C3,D7252659-47B6-4312-AC16-ECDE87FDB553,93F09E2D-B418-458D-A92D-10B56B53F45E,47FC32C3-5760-4547-8928-1EAB1DA6F4AF
|
FEISHU_APPROVAL_CODE=ECD8CE34-AA80-4A4F-B4C8-8510A7126490
|
||||||
FEISHU_APPROVAL_CODE_PUBLIC=ECD8CE34-AA80-4A4F-B4C8-8510A7126490,BB944139-432F-4AC2-AD27-81C2F738E7C3
|
|
||||||
FEISHU_APPROVAL_CODE_PRIVATE=D7252659-47B6-4312-AC16-ECDE87FDB553,93F09E2D-B418-458D-A92D-10B56B53F45E,47FC32C3-5760-4547-8928-1EAB1DA6F4AF
|
|
||||||
FEISHU_APPROVAL_EVENT_KEY=approval_instance
|
FEISHU_APPROVAL_EVENT_KEY=approval_instance
|
||||||
FEISHU_PROJECT_NO_FIELD_CODE=proj_id
|
FEISHU_PROJECT_NO_FIELD_CODE=proj_id
|
||||||
HUOBANYUN_ORDER_STATUS_DONE_ID=3
|
FEISHU_UNSUBSCRIBE_CODES=BB944139-432F-4AC2-AD27-81C2F738E7C3,D7252659-47B6-4312-AC16-ECDE87FDB553,93F09E2D-B418-458D-A92D-10B56B53F45E,47FC32C3-5760-4547-8928-1EAB1DA6F4AF
|
||||||
HUOBANYUN_ORDER_STATUS_DONE_NAME=已完结
|
FEISHU_UNSUBSCRIBE_ONCE=false
|
||||||
FEISHU_APP_ID=cli_a90b035fd4799cb5
|
FEISHU_APP_ID=cli_a90b035fd4799cb5
|
||||||
FEISHU_APP_SECRET=O729hnbQARM2DHncWUd51eFHF6TDZAc3
|
FEISHU_APP_SECRET=O729hnbQARM2DHncWUd51eFHF6TDZAc3
|
||||||
|
|
@ -64,3 +64,22 @@ class FeishuClient:
|
||||||
logger.error("订阅审批定义失败: status=%s body=%s", resp.status_code, resp.text)
|
logger.error("订阅审批定义失败: status=%s body=%s", resp.status_code, resp.text)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
async def unsubscribe_approval(self, approval_code: str) -> Dict[str, Any]:
|
||||||
|
token = await self._get_tenant_token()
|
||||||
|
url = (
|
||||||
|
"https://open.feishu.cn/open-apis/approval/v4/approvals/"
|
||||||
|
f"{approval_code}/unsubscribe"
|
||||||
|
)
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
async with httpx.AsyncClient(timeout=self.settings.request_timeout) as client:
|
||||||
|
resp = await client.post(url, headers=headers)
|
||||||
|
data = {}
|
||||||
|
try:
|
||||||
|
data = resp.json()
|
||||||
|
except Exception:
|
||||||
|
data = {}
|
||||||
|
if resp.status_code != 200 and data.get("code") not in (0, 1390007):
|
||||||
|
logger.error("取消订阅审批失败: status=%s body=%s", resp.status_code, resp.text)
|
||||||
|
resp.raise_for_status()
|
||||||
|
return data
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,10 @@ class Settings:
|
||||||
feishu_encrypt_key: str
|
feishu_encrypt_key: str
|
||||||
feishu_ws_url: str
|
feishu_ws_url: str
|
||||||
feishu_approval_code: str
|
feishu_approval_code: str
|
||||||
feishu_approval_codes: List[str]
|
|
||||||
feishu_approval_code_public: List[str]
|
|
||||||
feishu_approval_code_private: List[str]
|
|
||||||
feishu_approval_event_key: str
|
feishu_approval_event_key: str
|
||||||
feishu_project_no_field_code: str
|
feishu_project_no_field_code: str
|
||||||
|
feishu_unsubscribe_codes: List[str]
|
||||||
|
feishu_unsubscribe_once: bool
|
||||||
|
|
||||||
huobanyun_app_id: str
|
huobanyun_app_id: str
|
||||||
huobanyun_app_secret: str
|
huobanyun_app_secret: str
|
||||||
|
|
@ -49,6 +48,10 @@ def _env_list(name: str) -> List[str]:
|
||||||
return [item.strip() for item in raw.split(",") if item.strip()]
|
return [item.strip() for item in raw.split(",") if item.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
def _env_bool(name: str, default: str = "false") -> bool:
|
||||||
|
return _env(name, default).lower() in {"1", "true", "yes", "y", "on"}
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1)
|
@lru_cache(maxsize=1)
|
||||||
def get_settings() -> Settings:
|
def get_settings() -> Settings:
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
@ -59,11 +62,10 @@ def get_settings() -> Settings:
|
||||||
feishu_encrypt_key=_env("FEISHU_ENCRYPT_KEY"),
|
feishu_encrypt_key=_env("FEISHU_ENCRYPT_KEY"),
|
||||||
feishu_ws_url=_env("FEISHU_WS_URL"),
|
feishu_ws_url=_env("FEISHU_WS_URL"),
|
||||||
feishu_approval_code=_env("FEISHU_APPROVAL_CODE"),
|
feishu_approval_code=_env("FEISHU_APPROVAL_CODE"),
|
||||||
feishu_approval_codes=_env_list("FEISHU_APPROVAL_CODES"),
|
|
||||||
feishu_approval_code_public=_env_list("FEISHU_APPROVAL_CODE_PUBLIC"),
|
|
||||||
feishu_approval_code_private=_env_list("FEISHU_APPROVAL_CODE_PRIVATE"),
|
|
||||||
feishu_approval_event_key=_env("FEISHU_APPROVAL_EVENT_KEY"),
|
feishu_approval_event_key=_env("FEISHU_APPROVAL_EVENT_KEY"),
|
||||||
feishu_project_no_field_code=_env("FEISHU_PROJECT_NO_FIELD_CODE"),
|
feishu_project_no_field_code=_env("FEISHU_PROJECT_NO_FIELD_CODE"),
|
||||||
|
feishu_unsubscribe_codes=_env_list("FEISHU_UNSUBSCRIBE_CODES"),
|
||||||
|
feishu_unsubscribe_once=_env_bool("FEISHU_UNSUBSCRIBE_ONCE", "false"),
|
||||||
huobanyun_app_id=_env("HUOBANYUN_APP_ID"),
|
huobanyun_app_id=_env("HUOBANYUN_APP_ID"),
|
||||||
huobanyun_app_secret=_env("HUOBANYUN_APP_SECRET"),
|
huobanyun_app_secret=_env("HUOBANYUN_APP_SECRET"),
|
||||||
huobanyun_token=_env("HUOBANYUN_TOKEN"),
|
huobanyun_token=_env("HUOBANYUN_TOKEN"),
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ async def on_startup() -> None:
|
||||||
setup_logging(LOG_DIR)
|
setup_logging(LOG_DIR)
|
||||||
|
|
||||||
approval_service = ApprovalSyncService()
|
approval_service = ApprovalSyncService()
|
||||||
|
await approval_service.ensure_unsubscribe_once()
|
||||||
await approval_service.ensure_approval_subscription()
|
await approval_service.ensure_approval_subscription()
|
||||||
|
|
||||||
async def handler(payload: dict) -> None:
|
async def handler(payload: dict) -> None:
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,39 @@ class ApprovalSyncService:
|
||||||
return "" if value is None else str(value)
|
return "" if value is None else str(value)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def _extract_payment_type_from_detail(self, detail: Dict[str, Any]) -> str:
|
||||||
|
data = detail.get("data", {})
|
||||||
|
form = data.get("form")
|
||||||
|
if isinstance(form, str):
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
form = json.loads(form)
|
||||||
|
except Exception:
|
||||||
|
form = None
|
||||||
|
if isinstance(form, list):
|
||||||
|
for item in form:
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
continue
|
||||||
|
code = (
|
||||||
|
item.get("custom_id")
|
||||||
|
or item.get("id")
|
||||||
|
or item.get("field_code")
|
||||||
|
or item.get("code")
|
||||||
|
)
|
||||||
|
if code == "type":
|
||||||
|
value = item.get("value") or item.get("text") or item.get("name")
|
||||||
|
if isinstance(value, list) and value:
|
||||||
|
return str(value[0])
|
||||||
|
return "" if value is None else str(value)
|
||||||
|
if isinstance(form, dict):
|
||||||
|
value = form.get("type")
|
||||||
|
if isinstance(value, list) and value:
|
||||||
|
return str(value[0])
|
||||||
|
return "" if value is None else str(value)
|
||||||
|
return ""
|
||||||
|
|
||||||
async def ensure_approval_subscription(self) -> None:
|
async def ensure_approval_subscription(self) -> None:
|
||||||
codes = self.settings.feishu_approval_codes or (
|
codes = [self.settings.feishu_approval_code] if self.settings.feishu_approval_code else []
|
||||||
[self.settings.feishu_approval_code] if self.settings.feishu_approval_code else []
|
|
||||||
)
|
|
||||||
if not codes:
|
if not codes:
|
||||||
logger.error("未配置 FEISHU_APPROVAL_CODE,无法订阅审批定义")
|
logger.error("未配置 FEISHU_APPROVAL_CODE,无法订阅审批定义")
|
||||||
return
|
return
|
||||||
|
|
@ -82,14 +111,30 @@ class ApprovalSyncService:
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("订阅审批定义异常: %s", exc)
|
logger.error("订阅审批定义异常: %s", exc)
|
||||||
|
|
||||||
|
async def ensure_unsubscribe_once(self) -> None:
|
||||||
|
if not self.settings.feishu_unsubscribe_once:
|
||||||
|
return
|
||||||
|
codes = self.settings.feishu_unsubscribe_codes
|
||||||
|
if not codes:
|
||||||
|
logger.error("未配置 FEISHU_UNSUBSCRIBE_CODES,无法取消订阅")
|
||||||
|
return
|
||||||
|
for approval_code in codes:
|
||||||
|
try:
|
||||||
|
resp = await self.feishu_client.unsubscribe_approval(approval_code)
|
||||||
|
code = resp.get("code")
|
||||||
|
msg = resp.get("msg")
|
||||||
|
if code in (0, 1390007):
|
||||||
|
logger.info("取消订阅成功或不存在: %s", msg)
|
||||||
|
else:
|
||||||
|
logger.error("取消订阅失败: code=%s msg=%s", code, msg)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error("取消订阅异常: %s", exc)
|
||||||
|
|
||||||
async def handle_approval_event(self, event: Dict[str, Any]) -> None:
|
async def handle_approval_event(self, event: Dict[str, Any]) -> None:
|
||||||
payload = event.get("event", event)
|
payload = event.get("event", event)
|
||||||
logger.info("审批事件内容: %s", payload)
|
logger.info("审批事件内容: %s", payload)
|
||||||
approval_code = payload.get("approval_code") or payload.get("approvalCode")
|
approval_code = payload.get("approval_code") or payload.get("approvalCode")
|
||||||
codes = self.settings.feishu_approval_codes or (
|
if self.settings.feishu_approval_code and approval_code != self.settings.feishu_approval_code:
|
||||||
[self.settings.feishu_approval_code] if self.settings.feishu_approval_code else []
|
|
||||||
)
|
|
||||||
if codes and approval_code not in codes:
|
|
||||||
logger.info("审批定义不匹配,跳过: %s", approval_code)
|
logger.info("审批定义不匹配,跳过: %s", approval_code)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -137,6 +182,11 @@ class ApprovalSyncService:
|
||||||
return
|
return
|
||||||
logger.info("提取项目单号: %s", project_no)
|
logger.info("提取项目单号: %s", project_no)
|
||||||
|
|
||||||
|
payment_type = self._extract_payment_type_from_detail(detail)
|
||||||
|
if not payment_type:
|
||||||
|
logger.error("未能从审批详情提取付款类型")
|
||||||
|
return
|
||||||
|
|
||||||
item_id = await self.huobanyun_service.find_item_by_project_no(project_no)
|
item_id = await self.huobanyun_service.find_item_by_project_no(project_no)
|
||||||
if not item_id:
|
if not item_id:
|
||||||
logger.error("未找到对应伙伴云项目: %s", project_no)
|
logger.error("未找到对应伙伴云项目: %s", project_no)
|
||||||
|
|
@ -144,9 +194,7 @@ class ApprovalSyncService:
|
||||||
logger.info("找到伙伴云项目: %s", item_id)
|
logger.info("找到伙伴云项目: %s", item_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.huobanyun_service.update_order_status(item_id)
|
await self.huobanyun_service.update_linked_flags(item_id, payment_type)
|
||||||
if approval_code:
|
|
||||||
await self.huobanyun_service.update_linked_flags(item_id, approval_code)
|
|
||||||
logger.info("审批完成回写成功: %s", item_id)
|
logger.info("审批完成回写成功: %s", item_id)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("审批完成回写失败: %s", exc)
|
logger.error("审批完成回写失败: %s", exc)
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ class HuobanyunService:
|
||||||
"项目名称": "2200000150711223",
|
"项目名称": "2200000150711223",
|
||||||
"订单状态": "2200000150497330",
|
"订单状态": "2200000150497330",
|
||||||
"下单金额": "2200000149785349",
|
"下单金额": "2200000149785349",
|
||||||
|
"对公返点金额": "2200000149785350",
|
||||||
|
"对私返点金额": "2200000149785351",
|
||||||
"是否已关联对公付款审批": "2200000589775224",
|
"是否已关联对公付款审批": "2200000589775224",
|
||||||
"是否已关联对私付款审批": "2200000589775228",
|
"是否已关联对私付款审批": "2200000589775228",
|
||||||
"平台": "2200000149785346",
|
"平台": "2200000149785346",
|
||||||
|
|
@ -72,24 +74,11 @@ class HuobanyunService:
|
||||||
return ""
|
return ""
|
||||||
return str(items[0].get("item_id", ""))
|
return str(items[0].get("item_id", ""))
|
||||||
|
|
||||||
async def update_order_status(self, item_id: str) -> None:
|
async def update_linked_flags(self, item_id: str, payment_type: str) -> None:
|
||||||
status_field_id = self._resolve_field_key("订单状态")
|
|
||||||
status_id = self.settings.huobanyun_order_status_done_id
|
|
||||||
status_name = self.settings.huobanyun_order_status_done_name or "已完成"
|
|
||||||
status_value = status_id or status_name
|
|
||||||
if not status_value:
|
|
||||||
logger.error("未配置订单状态已完成选项值,跳过更新")
|
|
||||||
return
|
|
||||||
payload = {"fields": {status_field_id: [str(status_value)]}}
|
|
||||||
await self.client.update_item(item_id, payload)
|
|
||||||
|
|
||||||
async def update_linked_flags(self, item_id: str, approval_code: str) -> None:
|
|
||||||
public_codes = set(self.settings.feishu_approval_code_public)
|
|
||||||
private_codes = set(self.settings.feishu_approval_code_private)
|
|
||||||
fields: Dict[str, Any] = {}
|
fields: Dict[str, Any] = {}
|
||||||
if approval_code in public_codes:
|
if payment_type == "对公付款":
|
||||||
fields[self._resolve_field_key("是否已关联对公付款审批")] = True
|
fields[self._resolve_field_key("是否已关联对公付款审批")] = True
|
||||||
if approval_code in private_codes:
|
if payment_type == "对私付款":
|
||||||
fields[self._resolve_field_key("是否已关联对私付款审批")] = True
|
fields[self._resolve_field_key("是否已关联对私付款审批")] = True
|
||||||
if not fields:
|
if not fields:
|
||||||
return
|
return
|
||||||
|
|
@ -235,14 +224,6 @@ class HuobanyunService:
|
||||||
else:
|
else:
|
||||||
offset = 0
|
offset = 0
|
||||||
|
|
||||||
approval_code = (
|
|
||||||
req.approval_code
|
|
||||||
or raw.get("approval_code")
|
|
||||||
or raw.get("approvalCode")
|
|
||||||
or (req.linkage_params or {}).get("approval_code")
|
|
||||||
or (req.linkage_params or {}).get("approvalCode")
|
|
||||||
or ""
|
|
||||||
)
|
|
||||||
token_key = req.token or ""
|
token_key = req.token or ""
|
||||||
key = req.key or raw.get("key") or raw.get("field") or token_key or ""
|
key = req.key or raw.get("key") or raw.get("field") or token_key or ""
|
||||||
approval_hint = ""
|
approval_hint = ""
|
||||||
|
|
@ -256,6 +237,9 @@ class HuobanyunService:
|
||||||
key = self._resolve_field_key(str(key).strip()) if key else ""
|
key = self._resolve_field_key(str(key).strip()) if key else ""
|
||||||
query_value = req.query or req.keyword or ""
|
query_value = req.query or req.keyword or ""
|
||||||
linkage_params = req.linkage_params or raw.get("linkage_params") or {}
|
linkage_params = req.linkage_params or raw.get("linkage_params") or {}
|
||||||
|
payment_type = ""
|
||||||
|
if isinstance(linkage_params, dict):
|
||||||
|
payment_type = linkage_params.get("type") or linkage_params.get("付款类型") or ""
|
||||||
linkage_project_no = linkage_params.get("项目单号") if isinstance(linkage_params, dict) else None
|
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)
|
logger.info("外部选项请求: token_key=%s linkage_project_no=%s", key, linkage_project_no)
|
||||||
|
|
||||||
|
|
@ -266,21 +250,20 @@ class HuobanyunService:
|
||||||
{"field": project_no_field_id, "query": {"em": False}},
|
{"field": project_no_field_id, "query": {"em": False}},
|
||||||
{"field": order_status_field, "query": {"ne": ["已完成"]}},
|
{"field": order_status_field, "query": {"ne": ["已完成"]}},
|
||||||
]
|
]
|
||||||
if approval_code or approval_hint:
|
if payment_type == "对公付款" or approval_hint == "public":
|
||||||
if approval_hint == "public" or approval_code in set(self.settings.feishu_approval_code_public):
|
base_filters.append(
|
||||||
base_filters.append(
|
{
|
||||||
{
|
"field": self._resolve_field_key("是否已关联对公付款审批"),
|
||||||
"field": self._resolve_field_key("是否已关联对公付款审批"),
|
"query": {"eq": False},
|
||||||
"query": {"eq": False},
|
}
|
||||||
}
|
)
|
||||||
)
|
if payment_type == "对私付款" or approval_hint == "private":
|
||||||
if approval_hint == "private" or approval_code in set(self.settings.feishu_approval_code_private):
|
base_filters.append(
|
||||||
base_filters.append(
|
{
|
||||||
{
|
"field": self._resolve_field_key("是否已关联对私付款审批"),
|
||||||
"field": self._resolve_field_key("是否已关联对私付款审批"),
|
"query": {"eq": False},
|
||||||
"query": {"eq": False},
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
if linkage_project_no:
|
if linkage_project_no:
|
||||||
base_filters.append(
|
base_filters.append(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue