combined_sales_exporter.py 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. from openpyxl import Workbook
  2. from openpyxl.utils import get_column_letter
  3. from openpyxl.utils.dataframe import dataframe_to_rows
  4. from data_processor.daily_sales_exporter import DailySalesExporter
  5. from data_processor.daily_zones_exporter import DailyZonesSalesExporter
  6. class CombinedSalesExporter:
  7. def __init__(self, daily_sales_exporter: DailySalesExporter, daily_zones_sales_exporter: DailyZonesSalesExporter):
  8. self.daily_sales_exporter = daily_sales_exporter
  9. self.daily_zones_sales_exporter = daily_zones_sales_exporter
  10. def export(self, output_file='combined_output.xlsx'):
  11. sales_df = self.daily_sales_exporter.export_to_dataframe()
  12. zones_df = self.daily_zones_sales_exporter.export_to_dataframe()
  13. wb = Workbook()
  14. ws = wb.active
  15. start_row_sales = 1
  16. # 写入第一个 DataFrame(DailySalesExporter)
  17. for r in dataframe_to_rows(sales_df, index=False, header=True):
  18. ws.append(r)
  19. end_row_sales = ws.max_row
  20. # 添加 3 行空行
  21. for _ in range(3):
  22. ws.append([])
  23. # 记录当前行号,用于格式化设置
  24. start_row_for_zones_header = end_row_sales + 1 + 3 # 第二个表头行号
  25. start_row_for_zones_data = start_row_for_zones_header + 1 # 第二个表数据起始行
  26. # 写入第二个 DataFrame(DailyZonesSalesExporter),包含表头
  27. for r in dataframe_to_rows(zones_df, index=False, header=True):
  28. ws.append(r)
  29. # 设置格式化(分别应用到两个区域)
  30. self._apply_formatting(ws, sales_df, start_row=start_row_sales + 1, header_row=start_row_sales)
  31. self._apply_formatting(ws, zones_df, start_row=start_row_for_zones_data, header_row=start_row_for_zones_header)
  32. # 保存文件
  33. wb.save(output_file)
  34. print(f"✅ 数据已成功导出到 {output_file}")
  35. def _apply_formatting(self, ws, df, start_row=2, header_row=1):
  36. """
  37. :param ws: worksheet 对象
  38. :param df: 数据 DataFrame
  39. :param start_row: 数据开始的行号(数据从该行开始应用格式)
  40. :param header_row: 表头所在行号(用于匹配列名)
  41. """
  42. header = {cell.value: cell.column for cell in ws[header_row]}
  43. numeric_columns = [
  44. '999及以内单数', '1000-1999单数', '2000-2899单数', '2900-3999单数',
  45. '4000-4999单数', '5000-7999单数', '8000以上单数', '满足奖励条件大单数',
  46. '最大单件数', '最大单金额', '总单数', '2000+单数'
  47. ]
  48. for col_name in numeric_columns:
  49. if col_name in header:
  50. col_letter = get_column_letter(header[col_name])
  51. for row in range(start_row, ws.max_row + 1):
  52. cell = ws[f"{col_letter}{row}"]
  53. cell.number_format = '0'
  54. if '2000+单数占比' in header:
  55. col_letter = get_column_letter(header['2000+单数占比'])
  56. for row in range(start_row, ws.max_row + 1):
  57. cell = ws[f"{col_letter}{row}"]
  58. cell.number_format = '0.00%'