update
This commit is contained in:
parent
606f83678a
commit
787dead8c1
8
.env
8
.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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -63,10 +63,39 @@ class ApprovalSyncService:
|
|||
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 []
|
||||
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_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)
|
||||
|
|
|
|||
|
|
@ -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,15 +250,14 @@ 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):
|
||||
if payment_type == "对公付款" or approval_hint == "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):
|
||||
if payment_type == "对私付款" or approval_hint == "private":
|
||||
base_filters.append(
|
||||
{
|
||||
"field": self._resolve_field_key("是否已关联对私付款审批"),
|
||||
|
|
|
|||
Loading…
Reference in New Issue