From 8ba49be41b68e1c7568c2071a4fa53d8cf1b0e26 Mon Sep 17 00:00:00 2001 From: BXY0414 <1079823339@qq.com> Date: Fri, 10 Apr 2026 18:35:01 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=A1=A8=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project_bitable_common.cpython-312.pyc | Bin 79965 -> 87061 bytes project_bitable_common.py | 186 +++++++++++++++--- 2 files changed, 159 insertions(+), 27 deletions(-) diff --git a/__pycache__/project_bitable_common.cpython-312.pyc b/__pycache__/project_bitable_common.cpython-312.pyc index 695cb010eaf50eea4fefb0f567bd90a6b16f5757..47c207784db39732fff6ba35c4719741c9060559 100644 GIT binary patch delta 24186 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 X2qp$5T@!LftHqv@X>1OC5#av<(Lb8L delta 19363 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 LzJg>Za4`M{wHe(4 diff --git a/project_bitable_common.py b/project_bitable_common.py index 4fc8197..f1bff57 100644 --- a/project_bitable_common.py +++ b/project_bitable_common.py @@ -3,7 +3,7 @@ import os import re from collections import defaultdict from dataclasses import dataclass -from datetime import date, datetime, timedelta +from datetime import date, datetime, timedelta, timezone from decimal import Decimal from pathlib import Path from typing import Any @@ -19,7 +19,7 @@ from logging_config import get_logger, setup_logging BASE_DIR = Path(__file__).resolve().parent ENV_FILE = BASE_DIR / ".env" DEFAULT_MAPPING_FILE = BASE_DIR / "店铺映射表.xlsx" -DEFAULT_START_DATE = "2026-02-25" +DEFAULT_START_DATE = "2026-04-01" DEFAULT_STORE_FIELD_NAME = "店铺名称" STORE_FIELD_CANDIDATES = ["业务店铺名称", "店铺名称", "门店名称", "店铺", "名称"] SUPPORTED_PROJECTS = ["大眼睛", "羽心堂", "MMMJ", "EM", "春回盏", "BROKEN ZOO"] @@ -717,6 +717,42 @@ class FeishuBitableClient: log_step(f"字段创建完成: table_id={table_id}, field={field_name}") return True + def ensure_text_field(self, table_id: str, field_name: str) -> bool: + log_debug(f"检查文本字段是否存在: table_id={table_id}, field={field_name}") + fields = self.list_fields(table_id) + if any(item.get("field_name") == field_name for item in fields): + log_debug(f"文本字段已存在,跳过创建: table_id={table_id}, field={field_name}") + return False + log_step(f"开始创建文本字段: table_id={table_id}, field={field_name}") + self.request( + "POST", + f"/open-apis/bitable/v1/apps/{self.app_token}/tables/{table_id}/fields", + json={"field_name": field_name, "type": 1}, + ) + self._fields_cache.pop(table_id, None) + log_step(f"文本字段创建完成: table_id={table_id}, field={field_name}") + return True + + def ensure_date_field(self, table_id: str, field_name: str) -> bool: + log_debug(f"检查日期字段是否存在: table_id={table_id}, field={field_name}") + fields = self.list_fields(table_id) + if any(item.get("field_name") == field_name for item in fields): + log_debug(f"日期字段已存在,跳过创建: table_id={table_id}, field={field_name}") + return False + log_step(f"开始创建日期字段: table_id={table_id}, field={field_name}") + self.request( + "POST", + f"/open-apis/bitable/v1/apps/{self.app_token}/tables/{table_id}/fields", + json={ + "field_name": field_name, + "type": 5, + "property": {"date_formatter": "yyyy-MM-dd", "auto_fill": False}, + }, + ) + self._fields_cache.pop(table_id, None) + log_step(f"日期字段创建完成: table_id={table_id}, field={field_name}") + return True + def create_field_groups(self, table_id: str, field_groups: list[dict[str, Any]]) -> dict[str, Any]: log_step( f"开始创建字段编组: table_id={table_id}, " @@ -775,6 +811,18 @@ class FeishuBitableClient: item.setdefault("fields", {}).update(fields) break + def create_record(self, table_id: str, fields: dict[str, Any]) -> dict[str, Any]: + log_debug(f"新增记录: table_id={table_id}, fields={list(fields.keys())}") + data = self.request( + "POST", + f"/open-apis/bitable/v1/apps/{self.app_token}/tables/{table_id}/records", + json={"fields": fields}, + ) + record = data.get("record") or {} + if table_id in self._records_cache and record: + self._records_cache[table_id].append(record) + return record + def load_table_name_overrides(path_text: str) -> dict[str, str]: if not path_text: @@ -1531,33 +1579,117 @@ def run_daily_data_sync_for_date(context: DailySyncContext, stat_date: date) -> def run_daily_sync_for_date(context: DailySyncContext, stat_date: date) -> None: - log_step(f"开始处理日期: {stat_date.isoformat()}") - created_fields: set[str] = set() - for metric_name in ("sales", "refund"): - artifacts = run_metric_sync_with_context(context, metric_name, stat_date, stat_date) - created_fields.update(artifacts.created_fields) - gmv_artifacts = run_gmv_sync_with_context(context, stat_date, stat_date) - created_fields.update(gmv_artifacts.created_fields) + log_step(f"开始处理日期(旧流程+新表结构): {stat_date.isoformat()}") + sales_rows = fetch_metric_rows(stat_date, stat_date, METRIC_CONFIGS["sales"]) + refund_rows = fetch_metric_rows(stat_date, stat_date, METRIC_CONFIGS["refund"]) + grouped_sales = build_project_metrics(sales_rows, context.mapping, METRIC_CONFIGS["sales"]) + grouped_refund = build_project_metrics(refund_rows, context.mapping, METRIC_CONFIGS["refund"]) - should_sync_field_groups = stat_date.day == 1 or bool(created_fields) - if should_sync_field_groups: - if stat_date.day == 1: - sync_reason = "每月1号完整编组" - else: - sync_reason = f"新增字段={sorted(created_fields)}" - log_step( - f"触发字段编组同步: 日期={stat_date.isoformat()}, " - f"原因={sync_reason}" + date_text = stat_date.isoformat() + shanghai_tz = timezone(timedelta(hours=8)) + date_value_ms = int(datetime(stat_date.year, stat_date.month, stat_date.day, tzinfo=shanghai_tz).timestamp() * 1000) + header_date = "日期" + header_metric = "指标" + metric_labels = ( + ("销售金额", "sales"), + ("退款金额", "refund"), + ("GMV", "gmv"), + ("汇总GMV", "summary_gmv"), + ) + + for project in SUPPORTED_PROJECTS: + month_key = (stat_date.year, stat_date.month) + sales_by_store = grouped_sales.get(project, {}).get(month_key, {}).get(stat_date, {}) + refund_by_store = grouped_refund.get(project, {}).get(month_key, {}).get(stat_date, {}) + mapped_project_stores = sorted( + store_name for store_name in set(context.mapping.values()) if classify_project(store_name) == project ) - sync_field_groups_for_day_with_context( - context=context, - stat_date=stat_date, - force_full_rebuild=stat_date.day == 1, - allowed_field_names_override=None if stat_date.day == 1 else created_fields, - ) - else: - log_step(f"跳过字段编组同步: 日期={stat_date.isoformat()}, 原因=当天无新增字段") - log_step(f"日期处理完成: {stat_date.isoformat()}") + all_stores = sorted(set(mapped_project_stores) | set(sales_by_store.keys()) | set(refund_by_store.keys())) + gmv_by_store = { + store_name: sales_by_store.get(store_name, Decimal("0")) - refund_by_store.get(store_name, Decimal("0")) + for store_name in all_stores + } + metric_store_map: dict[str, dict[str, float]] = { + "sales": {store_name: decimal_to_number(amount) for store_name, amount in sales_by_store.items()}, + "refund": {store_name: decimal_to_number(amount) for store_name, amount in refund_by_store.items()}, + "gmv": {store_name: decimal_to_number(amount) for store_name, amount in gmv_by_store.items()}, + "summary_gmv": {}, + } + try: + table = find_target_table(context.tables, project, stat_date.year, stat_date.month, context.table_overrides) + except ValueError as exc: + if context.config.skip_missing_tables: + log_step(f"跳过: {exc}") + continue + raise + if context.config.dry_run: + for metric_label, metric_key in metric_labels: + print( + f"[DRY-RUN] 表={table.name} 日期={date_text} 指标={metric_label} " + f"店铺数={len(metric_store_map[metric_key])}" + ) + continue + + context.client.ensure_date_field(table.table_id, header_date) + context.client.ensure_text_field(table.table_id, header_metric) + for store_name in all_stores: + context.client.ensure_number_field(table.table_id, store_name) + records = context.client.list_records(table.table_id) + record_index: dict[tuple[str, str], str] = {} + summary_record_id: str | None = None + for item in records: + fields = item.get("fields", {}) + row_date_value = fields.get(header_date) + row_date = "" + if isinstance(row_date_value, (int, float)): + row_date = datetime.fromtimestamp(float(row_date_value) / 1000).date().isoformat() + elif isinstance(row_date_value, str): + row_date = row_date_value.strip() + row_metric = str(fields.get(header_metric) or "").strip() + if row_metric == "汇总GMV" and not summary_record_id: + summary_record_id = item["record_id"] + if row_date and row_metric: + record_index[(row_date, row_metric)] = item["record_id"] + + for metric_label, metric_key in metric_labels: + row_fields = { + header_date: date_value_ms, + header_metric: metric_label, + } + row_fields.update(metric_store_map[metric_key]) + if metric_label == "汇总GMV": + summary_gmv_values: dict[str, float] = {} + for item in records: + fields = item.get("fields", {}) + row_metric = str(fields.get(header_metric) or "").strip() + if row_metric != "GMV": + continue + row_date_value = fields.get(header_date) + row_date: date | None = None + if isinstance(row_date_value, (int, float)): + row_date = datetime.fromtimestamp(float(row_date_value) / 1000).date() + elif isinstance(row_date_value, str): + try: + row_date = validate_date(row_date_value.strip()) + except ValueError: + row_date = None + if row_date is None: + continue + if row_date.year != stat_date.year or row_date.month != stat_date.month or row_date > stat_date: + continue + for store_name in all_stores: + store_amount = parse_number(fields.get(store_name)) or 0.0 + summary_gmv_values[store_name] = round(summary_gmv_values.get(store_name, 0.0) + store_amount, 2) + row_fields.update(summary_gmv_values) + record_id = summary_record_id + else: + record_id = record_index.get((date_text, metric_label)) + if record_id: + context.client.update_record(table.table_id, record_id, row_fields) + else: + context.client.create_record(table.table_id, row_fields) + log_step(f"表处理完成: {table.name}, 日期={date_text}, 指标行=销售金额/退款金额/GMV,汇总GMV=月内GMV累加") + log_step(f"日期处理完成(旧流程+新表结构): {stat_date.isoformat()}") def send_sync_notification(