a1886074dd
后端: FastAPI + SQLAlchemy 2.0 (async) + Alembic + MinIO + Casdoor + 企微 前端: Vue 3 + Vite + TypeScript + Element Plus + Pinia 功能清单: - 8 张数据表自动建表 / Casdoor OIDC 登录 / 企微静默登录 - 双布局: 移动端(填报) + PC端(汇总管理) - 拜访记录 CRUD + MinIO 照片直传 + 缩略图预览 + 同访人草稿 - 今日纪要 (6 分类) / 工作计划 / 小微商机 / 要客拜访 CRUD - 客户档案: 备注/收支费用/联系人/归属分配/批量转移 - 客户导入导出 + 模板下载 + 搜索/分页/筛选 - 仪表盘: 四卡统计 + 填报进度 (拜访+纪要双维度) - 周报详情: 五 Tab + 按人/客户筛选 + 时间轴 - 用户管理 / 客户经理 PC 端工作台 - 企微: 催办/公告/定时提醒 / 时区修正 - Docker 部署配置 Co-Authored-By: Claude <noreply@anthropic.com>
79 lines
3.1 KiB
Python
79 lines
3.1 KiB
Python
import uuid
|
|
from datetime import datetime, date
|
|
from typing import Any
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
import openpyxl
|
|
from app.models.customer import Customer
|
|
from app.models.visit import Visit
|
|
from app.models.work_plan import WorkPlan
|
|
from app.models.mini_business import MiniBusiness
|
|
from app.models.key_visit import KeyVisit
|
|
from app.models.user import User
|
|
|
|
|
|
async def import_from_excel(db: AsyncSession, file_bytes: bytes, manager_id: uuid.UUID) -> dict:
|
|
"""Parse old weekly report Excel and import data. Returns summary stats."""
|
|
wb = openpyxl.load_workbook(io.BytesIO(file_bytes), data_only=True)
|
|
stats = {"visits": 0, "work_plans": 0, "mini_business": 0, "key_visits": 0, "skipped": 0}
|
|
|
|
# Resolve customer name -> id cache
|
|
customers_result = await db.execute(select(Customer.id, Customer.name))
|
|
customer_map = {c.name: c.id for c in customers_result.all()}
|
|
|
|
# ── Parse Sheet 1: 每日拜访记录 ──
|
|
if "每日拜访记录" in wb.sheetnames:
|
|
ws = wb["每日拜访记录"]
|
|
for row in ws.iter_rows(min_row=2, values_only=True):
|
|
if not row[0]:
|
|
continue
|
|
try:
|
|
cust_name, visit_date_str, visit_method, time_range, content, demand, companions_str, mgr_str = \
|
|
row[0], str(row[1]) if row[1] else str(date.today()), \
|
|
str(row[2]) if row[2] else "上门", str(row[3]) if row[3] else "", \
|
|
str(row[4]) if row[4] else "", str(row[5]) if row[5] else "", \
|
|
str(row[6]) if row[6] else "", str(row[7]) if row[7] else ""
|
|
|
|
customer_id = customer_map.get(cust_name)
|
|
if not customer_id:
|
|
stats["skipped"] += 1
|
|
continue
|
|
|
|
# Parse date
|
|
try:
|
|
visit_date = datetime.strptime(visit_date_str[:10], "%Y-%m-%d").date()
|
|
except ValueError:
|
|
visit_date = date.today()
|
|
|
|
# Check for duplicates (same day, same person, same customer)
|
|
existing = await db.execute(
|
|
select(Visit).where(
|
|
Visit.visit_date == visit_date,
|
|
Visit.manager_id == manager_id,
|
|
Visit.customer_id == customer_id,
|
|
)
|
|
)
|
|
if existing.scalar_one_or_none():
|
|
stats["skipped"] += 1
|
|
continue
|
|
|
|
visit = Visit(
|
|
customer_id=customer_id,
|
|
visit_date=visit_date,
|
|
visit_method=visit_method if visit_method in ["上门", "电话", "微信", "出差"] else "上门",
|
|
time_range=time_range,
|
|
communication_content=content,
|
|
customer_demand=demand,
|
|
manager_id=manager_id,
|
|
)
|
|
db.add(visit)
|
|
stats["visits"] += 1
|
|
except Exception:
|
|
stats["skipped"] += 1
|
|
|
|
await db.commit()
|
|
return stats
|
|
|
|
|
|
import io
|