#!/usr/bin/env python3 """导入店铺存续时间表到 runtime/bills.sqlite。""" from __future__ import annotations import argparse import json import sqlite3 from datetime import date, datetime, time, timedelta from pathlib import Path from typing import Any def text(value: Any) -> str | None: if value is None: return None value = str(value).strip() return value or None def sqlite_time(value: Any) -> str | None: if value in (None, ""): return None if isinstance(value, datetime): return value.strftime("%Y-%m-%d %H:%M:%S") if isinstance(value, date): return datetime.combine(value, time()).strftime("%Y-%m-%d %H:%M:%S") if isinstance(value, (int, float)): value = datetime(1899, 12, 30) + timedelta(days=float(value)) return value.strftime("%Y-%m-%d %H:%M:%S") value = str(value).strip().replace("/", "-") if value in {"存续中", "至今", "长期", "无"}: return None for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d"): try: return datetime.strptime(value, fmt).strftime("%Y-%m-%d %H:%M:%S") except ValueError: pass raise ValueError(f"时间无法解析:{value!r}") def main() -> int: parser = argparse.ArgumentParser(description="导入店铺存续时间 Excel 到 SQLite") parser.add_argument("file", type=Path, default=Path("/Users/dodo/Documents/店铺存续时间.xlsx"), nargs="?") parser.add_argument("--db", type=Path, default=Path("runtime/bills.sqlite")) parser.add_argument("--reset", action="store_true", help="清空已有店铺存续时间后重新导入") args = parser.parse_args() try: import openpyxl except ImportError as exc: raise SystemExit("缺少 openpyxl,请使用项目提供的 bundled Python 运行此脚本。") from exc if not args.file.is_file(): raise SystemExit(f"文件不存在:{args.file}") args.db.parent.mkdir(parents=True, exist_ok=True) db = sqlite3.connect(args.db) db.executescript(""" CREATE TABLE IF NOT EXISTS store_lifecycles ( id INTEGER PRIMARY KEY AUTOINCREMENT, erp_store_name TEXT NOT NULL, company_name TEXT, effective_at TEXT, expires_at TEXT, source_file TEXT NOT NULL, raw_json TEXT NOT NULL, imported_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(erp_store_name, effective_at, expires_at) ); CREATE INDEX IF NOT EXISTS idx_store_lifecycles_lookup ON store_lifecycles(erp_store_name, effective_at, expires_at); """) if args.reset: db.execute("DELETE FROM store_lifecycles") workbook = openpyxl.load_workbook(args.file, read_only=True, data_only=True) inserted = 0 try: for sheet in workbook.worksheets: rows = sheet.iter_rows(values_only=True) header = next(rows, None) if not header or all(value in (None, "") for value in header): continue headers = [text(value) or "" for value in header] required = ["ERP系统店铺名称", "公司名称", "生效时间", "失效时间"] missing = [name for name in required if name not in headers] if missing: raise SystemExit(f"{args.file.name}/{sheet.title} 缺少列:{', '.join(missing)}") index = {name: headers.index(name) for name in required} for row in rows: if not row or all(value in (None, "") for value in row): continue get = lambda name: row[index[name]] if index[name] < len(row) else None store = text(get("ERP系统店铺名称")) if not store: continue raw = {headers[i]: row[i] for i in range(min(len(headers), len(row))) if row[i] not in (None, "")} db.execute("""INSERT INTO store_lifecycles (erp_store_name,company_name,effective_at,expires_at,source_file,raw_json) VALUES (?,?,?,?,?,?) ON CONFLICT(erp_store_name,effective_at,expires_at) DO UPDATE SET company_name=excluded.company_name, source_file=excluded.source_file, raw_json=excluded.raw_json, imported_at=CURRENT_TIMESTAMP""", (store, text(get("公司名称")), sqlite_time(get("生效时间")), sqlite_time(get("失效时间")), args.file.name, json.dumps(raw, ensure_ascii=False, default=str))) inserted += 1 finally: workbook.close() db.commit() total = db.execute("SELECT COUNT(*) FROM store_lifecycles").fetchone()[0] db.close() print(f"导入完成:{args.db},本次处理 {inserted} 条,表内共 {total} 条") return 0 if __name__ == "__main__": raise SystemExit(main())