Files
qiji/backend/app/api/import_data.py
T
v6ole 3285e22142 feat: 导入增强 + 客户合并 + 同伴自定义 + 性能优化
=== 导入系统全面增强 ===
- 5 Sheet 完整导入:拜访/计划/商机/要客/纪要
- 同访人智能解析:中英文逗号/顿号/分号 → 系统用户UUID + 外部人员TEXT
- 客户自动创建:Excel中不存在的客户自动入库
- 模糊名称匹配:去空格 + 包含关系纠错
- manager_id 修正:导入时用客户分配的经理(非Excel列/非导入人)
- 模板更新:Sheet5「客户经理」→「填报人」,同访人示例含逗号分隔

=== 客户合并功能 ===
- PUT 改名碰撞检测 → 409 + 合并预览
- GET merge-preview / POST merge 端点
- 事务级迁移:Visit/WorkPlan/MiniBusiness/KeyVisit/联系人/分配
- 去重逻辑:联系人(name+phone)、分配(manager+role)
- last_visit_date 取最大值

=== 拜访记录完善 ===
- visits 新增 companion_names TEXT[] 列
- 同访人支持自定义输入(外部人员),el-select allow-create
- 移动端 VisitForm + PC端 ManagerWorkspace 统一
- 周报「客户经理」→「相关人员」(创建人+同访人)
- 纪要「客户经理」→「填报人」
- 删除拜访后重新计算 customer.last_visit_date

=== 客户选择放开 ===
- 客户经理可看到全部客户(不再限自己分配的)
- 拜访时客户下拉返回全量

=== 前端性能优化 ===
- Element Plus 按需加载 (unplugin-vue-components + unplugin-element-plus)
- 周报照片URL并行请求 (Promise.all)
- onMounted 三路并行 (loadReport + dropdowns + AI)
- nginx Cache-Control: immutable for /assets/
- Google Fonts preconnect hints
- 图片上传前 Canvas 压缩 (max 1920px, quality 0.8)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-29 22:25:04 +08:00

96 lines
4.4 KiB
Python

import io
import uuid
from fastapi import APIRouter, Depends, File, UploadFile, HTTPException
from fastapi.responses import StreamingResponse
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.middleware.auth import get_current_user, require_director
from app.services.excel_import import import_from_excel
router = APIRouter(prefix="/import", tags=["Import"])
@router.get("/template")
async def download_weekly_report_template():
"""Download a 5-sheet weekly report import template."""
from openpyxl import Workbook
from openpyxl.styles import Font
wb = Workbook()
header_font = Font(bold=True)
# Sheet 1: 每日拜访记录
ws1 = wb.active
ws1.title = "每日拜访记录"
ws1.append(["客户单位", "拜访日期", "拜访方式", "时间范围", "拜访人姓名", "拜访人电话", "沟通内容", "客户需求", "同访人员", "客户经理"])
for c in ws1[1]: c.font = header_font
ws1.append(["XX科技有限公司", "2026-06-23", "上门", "9:00-10:00", "韦柳柏", "13800000000", "沟通了解云桌面需求", "希望扩容", "韦柳柏, 张科长", "韦矍森"])
ws1.column_dimensions['A'].width = 20; ws1.column_dimensions['E'].width = 30; ws1.column_dimensions['F'].width = 20
# Sheet 2: 下周工作计划
ws2 = wb.create_sheet("下周工作计划")
ws2.append(["客户单位", "工作计划", "计划拜访时间", "客户经理", "状态"])
for c in ws2[1]: c.font = header_font
ws2.append(["XX科技有限公司", "跟进云桌面扩容方案", "2026-06-30", "韦柳柏", "计划中"])
ws2.column_dimensions['A'].width = 20; ws2.column_dimensions['B'].width = 35
# Sheet 3: 小微业务商机
ws3 = wb.create_sheet("小微业务商机")
ws3.append(["客户单位", "产品类型", "金额", "跟进内容具体情况", "跟进状态", "客户经理", "预计列收时间"])
for c in ws3[1]: c.font = header_font
ws3.append(["XX科技有限公司", "云桌面", "5000元/月", "确认技术方案中", "跟进中", "韦柳柏", "2026Q3"])
ws3.column_dimensions['A'].width = 20; ws3.column_dimensions['D'].width = 30
# Sheet 4: 要客拜访计划
ws4 = wb.create_sheet("要客拜访计划")
ws4.append(["客户单位", "紧急重要度", "内容描述", "进展状态", "计划拜访时间", "计划拜访人", "拜访对象", "客户经理"])
for c in ws4[1]: c.font = header_font
ws4.append(["XX科技有限公司", "重要", "拜访技术负责人确认方案", "未开始", "2026-07-01", "韦柳柏", "王局长", "韦矍森"])
ws4.column_dimensions['A'].width = 20; ws4.column_dimensions['C'].width = 30
# Sheet 5: 今日纪要
ws5 = wb.create_sheet("今日纪要")
ws5.append(["日期", "分类", "内容", "时间范围", "填报人"])
for c in ws5[1]: c.font = header_font
ws5.append(["2026-06-23", "内部会议", "参加云桌面项目方案讨论会", "14:00-16:00", "韦矍森"])
ws5.column_dimensions['A'].width = 15; ws5.column_dimensions['B'].width = 12; ws5.column_dimensions['C'].width = 40
output = io.BytesIO()
wb.save(output)
output.seek(0)
return StreamingResponse(
output,
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={"Content-Disposition": "attachment; filename=weekly_report_template.xlsx"},
)
@router.post("/weekly-report")
async def import_weekly_report(
file: UploadFile = File(...),
current_user: dict = Depends(require_director),
db: AsyncSession = Depends(get_db),
):
"""Upload old weekly report Excel and import data. Only director can do this."""
if not file.filename or not file.filename.endswith(('.xlsx', '.xls')):
raise HTTPException(status_code=400, detail="Only .xlsx and .xls files are supported")
content = await file.read()
try:
import openpyxl
wb = openpyxl.load_workbook(io.BytesIO(content), data_only=True)
preview = {}
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
headers = [str(cell.value) for cell in ws[1]]
row_count = ws.max_row - 1
preview[sheet_name] = {"headers": headers, "row_count": row_count}
except Exception as e:
raise HTTPException(status_code=400, detail=f"Failed to parse Excel: {str(e)}")
stats = await import_from_excel(db, content, uuid.UUID(current_user["user_id"]))
stats["preview"] = preview
return stats