From 787dead8c1773474f54634da2bc22b0b9bc9c022 Mon Sep 17 00:00:00 2001 From: Marsway Date: Wed, 4 Feb 2026 14:03:24 +0800 Subject: [PATCH] update --- .env | 8 ++-- app/clients/feishu_client.py | 19 ++++++++ app/config/settings.py | 14 +++--- app/main.py | 1 + app/services/approval_sync_service.py | 68 +++++++++++++++++++++++---- app/services/huobanyun_service.py | 61 +++++++++--------------- 6 files changed, 111 insertions(+), 60 deletions(-) diff --git a/.env b/.env index b7a80c1..4b35e0c 100644 --- a/.env +++ b/.env @@ -1,10 +1,8 @@ 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_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_CODE=ECD8CE34-AA80-4A4F-B4C8-8510A7126490 FEISHU_APPROVAL_EVENT_KEY=approval_instance FEISHU_PROJECT_NO_FIELD_CODE=proj_id -HUOBANYUN_ORDER_STATUS_DONE_ID=3 -HUOBANYUN_ORDER_STATUS_DONE_NAME=已完结 +FEISHU_UNSUBSCRIBE_CODES=BB944139-432F-4AC2-AD27-81C2F738E7C3,D7252659-47B6-4312-AC16-ECDE87FDB553,93F09E2D-B418-458D-A92D-10B56B53F45E,47FC32C3-5760-4547-8928-1EAB1DA6F4AF +FEISHU_UNSUBSCRIBE_ONCE=false FEISHU_APP_ID=cli_a90b035fd4799cb5 FEISHU_APP_SECRET=O729hnbQARM2DHncWUd51eFHF6TDZAc3 \ No newline at end of file diff --git a/app/clients/feishu_client.py b/app/clients/feishu_client.py index 33a48be..22b68bb 100644 --- a/app/clients/feishu_client.py +++ b/app/clients/feishu_client.py @@ -64,3 +64,22 @@ class FeishuClient: logger.error("订阅审批定义失败: status=%s body=%s", resp.status_code, resp.text) resp.raise_for_status() 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 diff --git a/app/config/settings.py b/app/config/settings.py index 0eb2a4e..1dcdd50 100644 --- a/app/config/settings.py +++ b/app/config/settings.py @@ -16,11 +16,10 @@ class Settings: feishu_encrypt_key: str feishu_ws_url: 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_project_no_field_code: str + feishu_unsubscribe_codes: List[str] + feishu_unsubscribe_once: bool huobanyun_app_id: 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()] +def _env_bool(name: str, default: str = "false") -> bool: + return _env(name, default).lower() in {"1", "true", "yes", "y", "on"} + + @lru_cache(maxsize=1) def get_settings() -> Settings: load_dotenv() @@ -59,11 +62,10 @@ def get_settings() -> Settings: feishu_encrypt_key=_env("FEISHU_ENCRYPT_KEY"), feishu_ws_url=_env("FEISHU_WS_URL"), 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_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_secret=_env("HUOBANYUN_APP_SECRET"), huobanyun_token=_env("HUOBANYUN_TOKEN"), diff --git a/app/main.py b/app/main.py index da0d91c..5658f24 100644 --- a/app/main.py +++ b/app/main.py @@ -31,6 +31,7 @@ async def on_startup() -> None: setup_logging(LOG_DIR) approval_service = ApprovalSyncService() + await approval_service.ensure_unsubscribe_once() await approval_service.ensure_approval_subscription() async def handler(payload: dict) -> None: diff --git a/app/services/approval_sync_service.py b/app/services/approval_sync_service.py index d9e1e2f..68badba 100644 --- a/app/services/approval_sync_service.py +++ b/app/services/approval_sync_service.py @@ -63,10 +63,39 @@ class ApprovalSyncService: return "" if value is None else str(value) 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: - codes = self.settings.feishu_approval_codes or ( - [self.settings.feishu_approval_code] if self.settings.feishu_approval_code else [] - ) + codes = [self.settings.feishu_approval_code] if self.settings.feishu_approval_code else [] if not codes: logger.error("未配置 FEISHU_APPROVAL_CODE,无法订阅审批定义") return @@ -82,14 +111,30 @@ class ApprovalSyncService: except Exception as 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: payload = event.get("event", event) logger.info("审批事件内容: %s", payload) approval_code = payload.get("approval_code") or payload.get("approvalCode") - codes = self.settings.feishu_approval_codes or ( - [self.settings.feishu_approval_code] if self.settings.feishu_approval_code else [] - ) - if codes and approval_code not in codes: + if self.settings.feishu_approval_code and approval_code != self.settings.feishu_approval_code: logger.info("审批定义不匹配,跳过: %s", approval_code) return @@ -137,6 +182,11 @@ class ApprovalSyncService: return 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) if not item_id: logger.error("未找到对应伙伴云项目: %s", project_no) @@ -144,9 +194,7 @@ class ApprovalSyncService: logger.info("找到伙伴云项目: %s", item_id) try: - await self.huobanyun_service.update_order_status(item_id) - if approval_code: - await self.huobanyun_service.update_linked_flags(item_id, approval_code) + await self.huobanyun_service.update_linked_flags(item_id, payment_type) logger.info("审批完成回写成功: %s", item_id) except Exception as exc: logger.error("审批完成回写失败: %s", exc) diff --git a/app/services/huobanyun_service.py b/app/services/huobanyun_service.py index fa05054..d941151 100644 --- a/app/services/huobanyun_service.py +++ b/app/services/huobanyun_service.py @@ -45,6 +45,8 @@ class HuobanyunService: "项目名称": "2200000150711223", "订单状态": "2200000150497330", "下单金额": "2200000149785349", + "对公返点金额": "2200000149785350", + "对私返点金额": "2200000149785351", "是否已关联对公付款审批": "2200000589775224", "是否已关联对私付款审批": "2200000589775228", "平台": "2200000149785346", @@ -72,24 +74,11 @@ class HuobanyunService: return "" return str(items[0].get("item_id", "")) - async def update_order_status(self, item_id: 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) + async def update_linked_flags(self, item_id: str, payment_type: str) -> None: fields: Dict[str, Any] = {} - if approval_code in public_codes: + if payment_type == "对公付款": fields[self._resolve_field_key("是否已关联对公付款审批")] = True - if approval_code in private_codes: + if payment_type == "对私付款": fields[self._resolve_field_key("是否已关联对私付款审批")] = True if not fields: return @@ -235,14 +224,6 @@ class HuobanyunService: else: 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 "" key = req.key or raw.get("key") or raw.get("field") or token_key or "" approval_hint = "" @@ -256,6 +237,9 @@ class HuobanyunService: 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 {} + 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 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": order_status_field, "query": {"ne": ["已完成"]}}, ] - if approval_code or approval_hint: - if approval_hint == "public" or approval_code in set(self.settings.feishu_approval_code_public): - base_filters.append( - { - "field": self._resolve_field_key("是否已关联对公付款审批"), - "query": {"eq": False}, - } - ) - if approval_hint == "private" or approval_code in set(self.settings.feishu_approval_code_private): - base_filters.append( - { - "field": self._resolve_field_key("是否已关联对私付款审批"), - "query": {"eq": False}, - } - ) + if payment_type == "对公付款" or approval_hint == "public": + base_filters.append( + { + "field": self._resolve_field_key("是否已关联对公付款审批"), + "query": {"eq": False}, + } + ) + if payment_type == "对私付款" or approval_hint == "private": + base_filters.append( + { + "field": self._resolve_field_key("是否已关联对私付款审批"), + "query": {"eq": False}, + } + ) if linkage_project_no: base_filters.append( {