feat: 历史周报+图片预览+照片管理+导入优化+用户合并

新增:
- 历史周报: Dashboard/周报详情支持周选择器翻看往周,归档只读
- 图片预览: 全屏大图(移动端+PC端),点击遮罩关闭
- PC端照片管理: 编辑时可上传/删除照片
- 客户导入改为更新模式: 重名自动更新信息,显示操作明细
- 导入跳过原因: 旧周报导入也显示每条跳过原因

优化:
- 填报进度权限: 经理只看到自己,支局长/领导看全员
- 客户经理暖灰色hash标签列
- 用户去重合并(4组),数据完整迁移
- CLAUDE.md 更新到最新状态

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-23 16:59:31 +08:00
parent 8926475b20
commit aa3fbca710
15 changed files with 323 additions and 74 deletions
+37 -29
View File
@@ -194,11 +194,11 @@ async def import_customers(
raise HTTPException(status_code=400, detail=f"Excel 解析失败: {str(e)}")
ws = wb.active
created, skipped = 0, 0
errors = []
created, updated, skipped = 0, 0, 0
reasons = []
# Build user name → id lookup
user_rows = await db.execute(select(User.name, User.id).where(User.role == "manager"))
# Build user name → id lookup (all users, not just managers)
user_rows = await db.execute(select(User.name, User.id))
user_map = {name: uid for name, uid in user_rows.all()}
default_user_id = uuid_mod.UUID(current_user["user_id"])
@@ -220,37 +220,45 @@ async def import_customers(
contact_phone = str(row[9]).strip() if len(row) > 9 and row[9] else ""
contact_role = str(row[10]).strip() if len(row) > 10 and row[10] else ""
existing = await db.execute(select(Customer).where(Customer.name == name))
if existing.scalar_one_or_none():
skipped += 1
continue
# Resolve manager: by name from template, or fallback to current user
assignee_id = user_map.get(mgr_name, default_user_id)
existing_result = await db.execute(select(Customer).where(Customer.name == name))
existing = existing_result.scalar_one_or_none()
try:
customer = Customer(
name=name, industry=industry, address=address,
in_use_services=services, monthly_fee=fee,
remarks=remarks,
created_by=default_user_id,
)
db.add(customer)
await db.flush()
if contact_name:
db.add(CustomerContact(customer_id=customer.id, name=contact_name, phone=contact_phone, role_desc=contact_role))
db.add(CustomerAssignment(
customer_id=customer.id, manager_id=assignee_id,
role="primary", assigned_by=default_user_id,
))
created += 1
if existing:
# Update existing customer
existing.industry = industry or existing.industry
existing.address = address or existing.address
existing.in_use_services = services or existing.in_use_services
existing.monthly_fee = fee or existing.monthly_fee
existing.remarks = remarks or existing.remarks
# Update or create primary assignment if manager changed
if mgr_name:
assign_rows = await db.execute(
select(CustomerAssignment).where(CustomerAssignment.customer_id == existing.id, CustomerAssignment.role == "primary")
)
first_assign = assign_rows.first()
if first_assign:
first_assign[0].manager_id = assignee_id
else:
db.add(CustomerAssignment(customer_id=existing.id, manager_id=assignee_id, role="primary", assigned_by=default_user_id))
updated += 1
reasons.append(f"更新「{name}」的信息")
else:
customer = Customer(name=name, industry=industry, address=address, in_use_services=services, monthly_fee=fee, remarks=remarks, created_by=default_user_id)
db.add(customer)
await db.flush()
if contact_name:
db.add(CustomerContact(customer_id=customer.id, name=contact_name, phone=contact_phone, role_desc=contact_role))
db.add(CustomerAssignment(customer_id=customer.id, manager_id=assignee_id, role="primary", assigned_by=default_user_id))
created += 1
reasons.append(f"新建「{name}")
except Exception as e:
errors.append(f"{row_idx}行: {str(e)}")
errors.append(f"{row_idx}({name}): {str(e)}")
await db.commit()
return {"created": created, "skipped": skipped, "errors": errors}
return {"created": created, "updated": updated, "skipped": skipped, "reasons": reasons, "errors": errors}
@router.get("/check-duplicate/{name}")