dy-dashboard/import_inventory_0612.py
2026-10-28 11:14:17 +08:00

196 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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}")