from __future__ import annotations from typing import Any from .feishu import FeishuApiError, FeishuClient from .postgres import query_postgres from .settings import AppConfig from .specs import BRAND_SYNC_TARGETS, FIELD_SPECS, PRIMARY_FIELD_NAME, PRIMARY_FIELD_SOURCE, BrandSyncTarget def build_feishu_record(row: dict[str, Any]) -> dict[str, Any]: if PRIMARY_FIELD_SOURCE not in row: raise KeyError(f"映射缺少主字段来源列: {PRIMARY_FIELD_SOURCE}") fields: dict[str, Any] = { PRIMARY_FIELD_NAME: "" if row.get(PRIMARY_FIELD_SOURCE) is None else str(row.get(PRIMARY_FIELD_SOURCE)), } for spec in FIELD_SPECS: if spec.source_name not in row: raise KeyError(f"映射缺少 SQL 列: {spec.source_name} -> {spec.feishu_name}") value = row.get(spec.source_name) if value is None or value == "": fields[spec.feishu_name] = None elif spec.field_type == 2: number = float(value) fields[spec.feishu_name] = int(round(number)) if spec.scale == 0 else round(number, spec.scale or 0) else: fields[spec.feishu_name] = str(value) return {"fields": fields} def run_feishu_summary(client: FeishuClient) -> dict[str, Any]: access_token = client.get_tenant_access_token() base_info = client.parse_base_info(client.build_base_url(), access_token) advanced_permission_enabled = client.enable_advanced_permission(access_token, base_info["app_token"] or "") tables = client.list_tables(access_token, base_info["app_token"] or "") return { "base_url": base_info["base_url"], "app_token": base_info["app_token"], "auth_mode": "tenant_access_token", "selected_table_id": base_info["selected_table_id"], "selected_view_id": base_info["selected_view_id"], "advanced_permission_enabled": advanced_permission_enabled, "folder": { "name": base_info["app_token"], "type": "base", "tables": [ {"table_id": table.get("table_id"), "name": table.get("name"), "revision": table.get("revision")} for table in tables ], }, "table_count": len(tables), } def sync_brand_target( client: FeishuClient, access_token: str, app_token: str, tables: list[dict[str, Any]], postgres_rows: list[dict[str, Any]], target: BrandSyncTarget, ) -> dict[str, Any]: target_table = next( (table for table in tables if table.get("name") == target.table_name), None, ) if not target_table: raise FeishuApiError(f"未找到目标飞书表: {target.table_name}") table_id = target_table.get("table_id") if not table_id: raise FeishuApiError(f"目标飞书表缺少 table_id: {target.table_name}") filtered_rows = [row for row in (postgres_rows or []) if row.get("品牌名称") == target.brand_name] available_fields = client.ensure_target_fields(access_token, app_token, table_id) existing_records = client.list_records(access_token, app_token, table_id) existing_record_ids = [record.get("record_id") for record in existing_records if record.get("record_id")] deleted_count = client.batch_delete_records(access_token, app_token, table_id, existing_record_ids) if existing_record_ids else 0 created_count = client.batch_create_records( access_token, app_token, table_id, [build_feishu_record(row) for row in filtered_rows], ) if filtered_rows else 0 return { "brand_name": target.brand_name, "target_table_name": target.table_name, "target_table_id": table_id, "available_fields": available_fields, "source_row_count": len(postgres_rows or []), "filtered_row_count": len(filtered_rows), "deleted_record_count": deleted_count, "created_record_count": created_count, } def run_sync_brand(config: AppConfig, client: FeishuClient, postgres_result: dict[str, Any] | None = None) -> dict[str, Any]: postgres_payload = postgres_result or query_postgres(config) postgres_rows = postgres_payload["rows"] access_token = client.get_tenant_access_token() base_info = client.parse_base_info(client.build_base_url(), access_token) app_token = base_info["app_token"] or "" client.enable_advanced_permission(access_token, app_token) tables = client.list_tables(access_token, app_token) results = [ sync_brand_target(client, access_token, app_token, tables, postgres_rows, target) for target in BRAND_SYNC_TARGETS ] return { "target_count": len(BRAND_SYNC_TARGETS), "source_row_count": len(postgres_rows or []), "targets": results, }