import io from datetime import date, timedelta from openpyxl import Workbook from openpyxl.styles import Font, Alignment, Border, Side from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select 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.daily_note import DailyNote from app.models.customer import Customer from app.models.user import User def get_week_range(reference_date: date | None = None): """Get the Monday and Sunday of the week containing reference_date (defaults to today).""" today = reference_date or date.today() monday = today - timedelta(days=today.weekday()) sunday = monday + timedelta(days=6) return monday, sunday async def export_weekly_report(db: AsyncSession, reference_date: date | None = None) -> io.BytesIO: """Generate a 5-sheet xlsx matching the existing weekly report template.""" monday, sunday = get_week_range(reference_date) wb = Workbook() # Pre-fetch lookups customers_result = await db.execute(select(Customer.id, Customer.name)) customer_map = {str(c.id): c.name for c in customers_result.all()} users_result = await db.execute(select(User.id, User.name)) user_map = {str(u.id): u.name for u in users_result.all()} thin_border = Border( left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin') ) header_font = Font(bold=True) # ── Sheet 1: 每日拜访记录 ── ws1 = wb.active ws1.title = "每日拜访记录" headers1 = ["客户单位", "拜访日期", "拜访方式", "时间范围", "拜访人姓名", "拜访人电话", "沟通内容", "客户需求", "相关人员", "客户经理"] ws1.append(headers1) for col in range(1, len(headers1) + 1): cell = ws1.cell(row=1, column=col) cell.font = header_font cell.border = thin_border visits_result = await db.execute( select(Visit).where(Visit.visit_date >= monday, Visit.visit_date <= sunday) ) for v in visits_result.scalars(): companions_names = [user_map.get(str(cid), str(cid)) for cid in (v.companions or [])] companions_names.extend(v.companion_names or []) ws1.append([ customer_map.get(str(v.customer_id), ""), str(v.visit_date), v.visit_method, v.time_range, v.visitor_name or "", v.visitor_phone or "", v.communication_content, v.customer_demand, ", ".join(companions_names), user_map.get(str(v.manager_id), ""), ]) # ── Sheet 2: 下周工作计划 ── ws2 = wb.create_sheet("下周工作计划") headers2 = ["客户单位", "工作计划", "计划拜访时间", "客户经理", "状态"] ws2.append(headers2) for col in range(1, len(headers2) + 1): cell = ws2.cell(row=1, column=col) cell.font = header_font cell.border = thin_border plans_result = await db.execute(select(WorkPlan)) for p in plans_result.scalars(): ws2.append([ customer_map.get(str(p.customer_id), ""), p.plan_content, str(p.plan_date), user_map.get(str(p.manager_id), ""), p.status, ]) # ── Sheet 3: 小微业务商机 ── ws3 = wb.create_sheet("小微业务商机") headers3 = ["客户单位", "产品类型", "金额", "跟进内容具体情况", "跟进状态", "客户经理", "预计列收时间"] ws3.append(headers3) for col in range(1, len(headers3) + 1): cell = ws3.cell(row=1, column=col) cell.font = header_font cell.border = thin_border mb_result = await db.execute(select(MiniBusiness)) for m in mb_result.scalars(): ws3.append([ customer_map.get(str(m.customer_id), ""), m.product_type, m.amount, m.follow_up_detail, m.status, user_map.get(str(m.manager_id), ""), m.expected_revenue_date, ]) # ── Sheet 4: 要客拜访计划 ── ws4 = wb.create_sheet("要客拜访计划") headers4 = ["客户单位", "紧急重要度", "内容描述", "进展状态", "计划拜访时间", "计划拜访人", "拜访对象", "客户经理"] ws4.append(headers4) for col in range(1, len(headers4) + 1): cell = ws4.cell(row=1, column=col) cell.font = header_font cell.border = thin_border kv_result = await db.execute(select(KeyVisit)) for k in kv_result.scalars(): ws4.append([ customer_map.get(str(k.customer_id), ""), k.urgency_level, k.description, k.progress_status, k.planned_date, k.planned_visitor, k.visit_target, user_map.get(str(k.manager_id), ""), ]) # ── Sheet 5: 今日纪要 ── ws5 = wb.create_sheet("今日纪要") headers5 = ["日期", "分类", "内容", "时间范围", "填报人"] ws5.append(headers5) for col in range(1, len(headers5) + 1): cell = ws5.cell(row=1, column=col) cell.font = header_font cell.border = thin_border notes_result = await db.execute( select(DailyNote).where(DailyNote.note_date >= monday, DailyNote.note_date <= sunday) ) for n in notes_result.scalars(): ws5.append([ str(n.note_date), n.category, n.content, n.time_range, user_map.get(str(n.manager_id), ""), ]) # Adjust column widths for ws in [ws1, ws2, ws3, ws4, ws5]: for col_cells in ws.columns: max_length = max((len(str(cell.value or "")) for cell in col_cells), default=10) ws.column_dimensions[col_cells[0].column_letter].width = min(max_length + 4, 50) output = io.BytesIO() wb.save(output) output.seek(0) return output