diff --git a/apps/reports/services/exporters.py b/apps/reports/services/exporters.py index 448f5c4..a23050b 100644 --- a/apps/reports/services/exporters.py +++ b/apps/reports/services/exporters.py @@ -26,6 +26,13 @@ BORDER = Border( top=Side(style="thin", color="D0D7DE"), bottom=Side(style="thin", color="D0D7DE"), ) +USER_SUMMARY_SEPARATOR_BORDER = Border( + left=Side(style="thin", color="D0D7DE"), + right=Side(style="thin", color="D0D7DE"), + top=Side(style="medium", color="94A3B8"), + bottom=Side(style="thin", color="D0D7DE"), +) +EMPTY_BORDER = Border() class BookmarkDocTemplate(SimpleDocTemplate): @@ -619,6 +626,8 @@ def _append_user_details_block_excel( def _merge_and_style(worksheet, *, row: int, start_col: int, end_col: int, value: str, rtl: bool) -> None: + if end_col < start_col: + end_col = start_col worksheet.merge_cells(start_row=row, start_column=start_col, end_row=row, end_column=end_col) cell = worksheet.cell(row=row, column=start_col) cell.value = value @@ -628,12 +637,19 @@ def _merge_and_style(worksheet, *, row: int, start_col: int, end_col: int, value def _append_merged_heading(worksheet, *, locale: ExportLocale, title: str, span: int) -> int: worksheet.append([title]) row = worksheet.max_row + span = max(int(span), 1) if span > 1: worksheet.merge_cells(start_row=row, start_column=1, end_row=row, end_column=span) _apply_cell_style(worksheet.cell(row=row, column=1), bold=True, fill=HEADER_FILL, rtl=locale.is_rtl) return row +def _clear_excel_cell_style(cell) -> None: + cell.value = None + cell.fill = PatternFill(fill_type=None) + cell.border = EMPTY_BORDER + + def _write_table_row( worksheet, *, @@ -740,14 +756,10 @@ def _render_all_users_overall_excel_sheet( for row_index, values in enumerate(summary_rows, start=10): _write_table_row(worksheet, row=row_index, start_col=1, values=values, rtl=locale.is_rtl) - _merge_and_style( - worksheet, - row=15, - start_col=1, - end_col=18, - value=locale.t("users_summary_sheet"), - rtl=locale.is_rtl, - ) + _merge_and_style(worksheet, row=15, start_col=1, end_col=6, value=locale.t("users_summary_sheet"), rtl=locale.is_rtl) + _merge_and_style(worksheet, row=15, start_col=8, end_col=10, value=locale.t("clients"), rtl=locale.is_rtl) + _merge_and_style(worksheet, row=15, start_col=12, end_col=14, value=locale.t("projects"), rtl=locale.is_rtl) + _merge_and_style(worksheet, row=15, start_col=16, end_col=18, value=locale.t("tags"), rtl=locale.is_rtl) summary_headers = [ locale.t("name"), locale.t("mobile"), @@ -788,6 +800,10 @@ def _render_all_users_overall_excel_sheet( values=values, rtl=locale.is_rtl, ) + if offset == 0: + for cell in worksheet[current_row]: + if cell.column not in (7, 11, 15): + cell.border = USER_SUMMARY_SEPARATOR_BORDER for column in (1, 2, 3, 6): _merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=column) rate_rows = user_summary.get("rate_periods") or [] @@ -811,12 +827,9 @@ def _render_all_users_overall_excel_sheet( _merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=18, value_present=True) current_row += span - for row_index in range(16, current_row): + for row_index in range(15, current_row): for column_index in (7, 11, 15): - cell = worksheet.cell(row=row_index, column=column_index) - cell.value = None - cell.fill = PatternFill(fill_type=None) - cell.border = Border() + _clear_excel_cell_style(worksheet.cell(row=row_index, column=column_index)) current_row += 2 for title_key, rows, hour_percentages, income_percentages in ( @@ -843,7 +856,7 @@ def _render_all_users_overall_excel_sheet( worksheet, row=current_row, start_col=1, - end_col=7, + end_col=5, value=locale.t(title_key), rtl=locale.is_rtl, ) diff --git a/apps/reports/tests/test_exporters.py b/apps/reports/tests/test_exporters.py index c74ead7..bbc3bc7 100644 --- a/apps/reports/tests/test_exporters.py +++ b/apps/reports/tests/test_exporters.py @@ -199,7 +199,16 @@ class ReportExporterTests(TestCase): self.assertEqual(summary_sheet["A1"].value, "Workspace Report") self.assertEqual(summary_sheet["B1"].value, "Exports") self.assertEqual(summary_sheet["A15"].value, "Users Summary") - self.assertIn("A15:R15", {str(item) for item in summary_sheet.merged_cells.ranges}) + merged_ranges = {str(item) for item in summary_sheet.merged_cells.ranges} + self.assertIn("A15:F15", merged_ranges) + self.assertIn("H15:J15", merged_ranges) + self.assertIn("L15:N15", merged_ranges) + self.assertIn("P15:R15", merged_ranges) + self.assertNotIn("A15:R15", merged_ranges) + self.assertIsNone(summary_sheet["G15"].fill.fill_type) + self.assertIsNone(summary_sheet["G16"].fill.fill_type) + self.assertIsNone(summary_sheet["K15"].fill.fill_type) + self.assertIsNone(summary_sheet["O15"].fill.fill_type) self.assertEqual( tuple(summary_sheet.iter_rows(min_row=16, max_row=16, values_only=True))[0][:18], ( @@ -226,6 +235,9 @@ class ReportExporterTests(TestCase): self.assertTrue(any(row and "Owner User" in row for row in summary_values)) self.assertTrue(any(row and "09129990001" in row for row in summary_values)) self.assertTrue(any(row and "Variable rate" in row for row in summary_values)) + self.assertEqual(summary_sheet["A17"].border.top.style, "medium") + self.assertEqual(summary_sheet["A18"].border.top.style, "medium") + self.assertIsNone(summary_sheet["G17"].border.top) user_sheet = workbook[workbook.sheetnames[1]] user_values = list(user_sheet.iter_rows(values_only=True))