#!/usr/bin/env python3 """下载并导入 2026-06-12 库存明细 CSV 文件""" import sys, os, csv, json, subprocess, pymysql, re from datetime import datetime sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import importlib.util spec = importlib.util.spec_from_file_location("config", "config.py") config = importlib.util.module_from_spec(spec) spec.loader.exec_module(config) DB_CONFIG = config.DB_CONFIG TMP_DIR = "tmp_csv" os.makedirs(TMP_DIR, exist_ok=True) DATA_DATE = "2026-06-12" SOURCE = "wdt" SPACE_ID = "28814565790" # File list from 钉盘: (name, fileId) FILES = [ ("20260612-wdt-kcmx-01", "ndMj49yWjXkyODyehwNYo0L0J3pmz5aA"), ("20260612-wdt-kcmx-02", "G53mjyd80pGevAeNtgN6Epjx86zbX04v"), ("20260612-wdt-kcmx-03", "4lgGw3P8vRrBDmBeUp5kDkQR85daZ90D"), ("20260612-wdt-kcmx-04", "1R7q3QmWeeDRbPROCZyQO423WxkXOEP2"), ("20260612-wdt-kcmx-05", "MyQA2dXW7eKDkAD2C5mG2gD2JzlwrZgb"), ("20260612-wdt-kcmx-06", "GZLxjv9VGqm0xA04u12yODm686EDybno"), ("20260612-wdt-kcmx-07", "1DKw2zgV2PdeDAe0HPb2on2O8B5r9YAn"), ("20260612-wdt-kcmx-08", "wva2dxOW4Y14M642H0wpqOlwVbkz3BRL"), ("20260612-wdt-kcmx-09", "y20BglGWO2ywEgwkh04KEQ2g8A7depqY"), ("20260612-wdt-kcmx-10", "QBnd5ExVEvZ73d7xC2mDEdAdJyeZqMmz"), ("20260612-wdt-kcmx-11", "NkDwLng8ZLGrXmrnFx4y0A1rVKMEvZBY"), ("20260612-wdt-kcmx-12", "4lgGw3P8vRrBDmBeUp5kDePa85daZ90D"), ("20260612-wdt-kcmx-13", "gpG2NdyVX3EMNZMACGz4OQqzWMwvDqPk"), ("20260612-wdt-kcmx-15", "7QG4Yx2JpLaqBXqeFgN9opxdJ9dEq3XD"), ("20260612-wdt-kcmx-16", "vNG4YZ7JnPzwqNwLH9dan7QkW2LD0oRE"), ("20260612-wdt-kcmx-20", "NDoBb60VLQbxqvxMtBELN3o2JlemrZQ3"), ("20260612-wdt-kcmx-22", "NDoBb60VLQbxqvxMtBELN9kDJlemrZQ3"), ("20260612-wdt-kcmx-23", "NkDwLng8ZLGrXmrnFx4yYyMlVKMEvZBY"), ("20260612-wdt-kcmx-24", "QBnd5ExVEvZ73d7xC2mDj2y7JyeZqMmz"), ] def parse_int(val): if val is None or val == '' or str(val).upper() in ('NA', '#N/A', 'N/A'): return 0 try: return int(float(val)) except (ValueError, TypeError): return 0 def parse_float(val): if val is None or val == '' or str(val).upper() in ('NA', '#N/A', 'N/A'): return 0.0 try: return float(val) except (ValueError, TypeError): return 0.0 def parse_datetime(val): if not val or val == '' or str(val).upper() in ('NA', '#N/A', 'N/A'): return None try: return datetime.strptime(val.strip(), '%Y-%m-%d %H:%M:%S') except ValueError: try: return datetime.strptime(val.strip(), '%Y-%m-%d') except ValueError: return None def extract_warehouse_code(name): """从文件名提取仓库代码。如 20260612-wdt-kcmx-01 → 01""" m = re.search(r'kcmx-(\d+)$', name) return m.group(1) if m else '00' def download_file(name, file_id): """用 dws drive download 下载 CSV""" local_path = os.path.join(TMP_DIR, f"{name}.csv") result = subprocess.run( ["dws", "drive", "download", "--node", file_id, "--space-id", SPACE_ID, "--output", local_path, "--format", "json"], capture_output=True, text=True, timeout=60 ) if result.returncode != 0: print(f" ❌ 下载失败: {result.stderr[:300]}") return None if os.path.exists(local_path) and os.path.getsize(local_path) > 50: print(f" ✅ 下载完成: {name}.csv ({os.path.getsize(local_path)} bytes)") return local_path print(f" ❌ 下载后文件过小或不存在") return None def count_existing(conn, wh_code): """查询该仓库已有记录数""" with conn.cursor() as cur: cur.execute("SELECT COUNT(*) as cnt FROM inventory_detail WHERE data_date=%s AND warehouse_code=%s", (DATA_DATE, wh_code)) return cur.fetchone()['cnt'] print(f"📋 导入 {DATA_DATE} 库存明细数据(来源: {SOURCE})") print(f"共 {len(FILES)} 个仓库文件\n") # 第一步:删除该日期已有数据 print("🧹 清理已有数据...") conn = pymysql.connect(**DB_CONFIG, cursorclass=pymysql.cursors.DictCursor) with conn.cursor() as cur: cur.execute("DELETE FROM inventory_detail WHERE data_date=%s", (DATA_DATE,)) deleted = cur.rowcount conn.commit() conn.close() print(f" 已删除 {deleted} 条记录\n") # 第二步:逐个下载并导入 total_imported = 0 total_files = 0 errors = [] for name, file_id in FILES: wh_code = extract_warehouse_code(name) total_files += 1 print(f"[{total_files}/{len(FILES)}] {name} (仓库:{wh_code})") # 下载 local_path = download_file(name, file_id) if not local_path: errors.append((name, "下载失败")) continue # 检查编码 file_result = subprocess.run(["file", local_path], capture_output=True, text=True, timeout=5) if 'ISO' in file_result.stdout or '8859' in file_result.stdout: utf8_path = local_path.replace('.csv', '_utf8.csv') subprocess.run(["iconv", "-f", "GBK", "-t", "UTF-8", local_path, "-o", utf8_path], capture_output=True, timeout=10) os.replace(utf8_path, local_path) # 导入 conn = pymysql.connect(**DB_CONFIG, cursorclass=pymysql.cursors.DictCursor) inserted = 0 try: with open(local_path, 'r', encoding='utf-8-sig') as f: reader = csv.DictReader(f) rows = list(reader) columns = reader.fieldnames print(f" 读取到 {len(rows)} 条记录, 列: {columns[:6]}...") with conn.cursor() as cur: for row in rows: try: cur.execute(""" INSERT INTO inventory_detail (data_date, source, warehouse_code, goods_code, product_code, product_name, brand, cost_price, available_qty, production_in_transit, purchase_in_transit, category1, category2, category3, created_time) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """, ( DATA_DATE, SOURCE, wh_code, row.get('商家编码', '').strip(), row.get('货品编号', '').strip(), row.get('货品名称', '').strip(), row.get('品牌', '').strip(), parse_float(row.get('成本价', 0)), parse_int(row.get('可用库存', 0)), parse_int(row.get('生产在途量', 0)), parse_int(row.get('采购在途', 0)), row.get('一级类目', '').strip(), row.get('二级类目', '').strip(), row.get('三级类目', '').strip(), parse_datetime(row.get('单品创建时间', '')) )) inserted += 1 except Exception as e: if inserted < 3: print(f" 插入异常: {e}") conn.commit() print(f" ✅ 导入 {inserted} 条记录") total_imported += inserted except Exception as e: conn.rollback() print(f" ❌ 导入失败: {e}") errors.append((name, str(e))) finally: conn.close() # 汇总 print(f"\n{'='*50}") print(f"库存导入完成!") print(f"处理 {total_files} 个文件, 导入 {total_imported} 条记录") if errors: print(f"\n⚠️ 失败:") for name, reason in errors: print(f" - {name}: {reason}") print(f"{'='*50}")