feat(reports): improve summary rates and export formatting
Some checks failed
Backend CI/CD / test (push) Has been cancelled
Backend CI/CD / deploy (push) Has been cancelled

This commit is contained in:
2026-05-26 12:15:44 +03:30
parent af9facce7e
commit 20874b9968
4 changed files with 144 additions and 128 deletions

View File

@@ -76,7 +76,7 @@ def _money_label_excel(locale: ExportLocale, income_totals: list[dict]) -> str:
def _rates_label(locale: ExportLocale, rates: list[dict], *, ascii_digits: bool = False) -> str:
if not rates:
return "-"
return locale.format_number("0", ascii_digits=ascii_digits)
items = [
f"{locale.format_amount_for_currency(rate['amount'], rate['currency'], ascii_digits=ascii_digits)} {locale.currency_label(rate['currency'])}"
for rate in rates
@@ -103,13 +103,13 @@ def _rate_period_label(locale: ExportLocale, row: dict, *, ascii_digits: bool =
def _rate_label(locale: ExportLocale, rate: dict | None) -> str:
if not rate:
return "-"
return locale.format_number("0")
return f"{locale.format_amount_for_currency(rate['amount'], rate['currency'])} {locale.currency_label(rate['currency'])}"
def _rate_label_excel(locale: ExportLocale, rate: dict | None) -> str:
if not rate:
return "-"
return locale.format_number("0", ascii_digits=True)
value = (
f"{locale.format_amount_for_currency(rate['amount'], rate['currency'], ascii_digits=True)} "
f"{locale.currency_label(rate['currency'])}"
@@ -213,7 +213,14 @@ def _append_user_summary_block(worksheet, *, locale: ExportLocale, user_summary:
worksheet.append(row)
def _percentage_display(locale: ExportLocale, rows: list[dict], row_data: dict, *, ascii_digits: bool = False) -> str:
def _percentage_display(
locale: ExportLocale,
rows: list[dict],
row_data: dict,
*,
ascii_digits: bool = False,
default: str = "0%",
) -> str:
row_id = str(row_data.get("id")) if row_data.get("id") is not None else None
row_name = row_data.get("name")
for row in rows:
@@ -222,7 +229,7 @@ def _percentage_display(locale: ExportLocale, rows: list[dict], row_data: dict,
return value
if row_name and row["name"] == row_name:
return value
return "-"
return default
def _percentage_number(rows: list[dict] | None, row_data: dict) -> float:
@@ -277,7 +284,7 @@ def _summary_breakdown_rows(
[
row["name"],
_percentage_value(locale, row["percentage"], ascii_digits=True),
_percentage_display(locale, income_rows, row, ascii_digits=True),
_percentage_display(locale, income_rows, row, ascii_digits=True, default="-"),
]
for row in sorted(hour_rows, key=lambda row: (-_percentage_sort_value(row), row.get("name") or ""))
]
@@ -304,11 +311,11 @@ def _rate_to_label(locale: ExportLocale, to_date: str | None, *, ascii_digits: b
def _append_rate_history_table_excel(worksheet, *, locale: ExportLocale, user_summary: dict) -> None:
worksheet.append([])
_append_merged_heading(worksheet, locale=locale, title=locale.t("rate_history"), span=3)
_append_merged_heading(worksheet, locale=locale, title=locale.t("rate_history"), span=4)
header_row = worksheet.max_row + 1
worksheet.append(
_excel_table_row(
[locale.t("hourly_rate"), locale.t("from"), locale.t("to")],
[locale.t("hourly_rate"), locale.t("from"), locale.t("to"), locale.t("project")],
)
)
for cell in worksheet[header_row]:
@@ -327,6 +334,7 @@ def _append_rate_history_table_excel(worksheet, *, locale: ExportLocale, user_su
_rate_period_label(locale, row, ascii_digits=True),
locale.format_date(row["from_date"], ascii_digits=True),
_rate_to_label(locale, row.get("to_date"), ascii_digits=True),
row.get("project_name") or "-",
],
)
)
@@ -562,7 +570,7 @@ def _append_breakdown_table(
),
_money_label_excel(locale, row["income_totals"]),
*(
[_percentage_display(locale, income_percentages or [], row, ascii_digits=True)]
[_percentage_display(locale, income_percentages or [], row, ascii_digits=True, default="-")]
if hour_percentages is not None
else []
),
@@ -669,8 +677,11 @@ def _user_summary_row_payload(locale: ExportLocale, summary: dict) -> tuple[int,
locale.format_duration(summary["billable_duration"], ascii_digits=True) if index == 0 else None,
*(rate_rows[index] if index < len(rate_rows) else [None, None]),
_money_label_excel(locale, summary["income_totals"]) if index == 0 else None,
None,
*(client_rows[index] if index < len(client_rows) else [None, None, None]),
None,
*(project_rows[index] if index < len(project_rows) else [None, None, None]),
None,
*(tag_rows[index] if index < len(tag_rows) else [None, None, None]),
],
)
@@ -733,7 +744,7 @@ def _render_all_users_overall_excel_sheet(
worksheet,
row=15,
start_col=1,
end_col=15,
end_col=18,
value=locale.t("users_summary_sheet"),
rtl=locale.is_rtl,
)
@@ -744,12 +755,15 @@ def _render_all_users_overall_excel_sheet(
locale.t("hourly_rate"),
locale.t("period"),
locale.t("income"),
"",
locale.t("clients"),
locale.t("hour_percentage"),
locale.t("income_percentage"),
"",
locale.t("projects"),
locale.t("hour_percentage"),
locale.t("income_percentage"),
"",
locale.t("tags"),
locale.t("hour_percentage"),
locale.t("income_percentage"),
@@ -784,19 +798,26 @@ def _render_all_users_overall_excel_sheet(
_merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=4, value_present=True)
_merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=5, value_present=True)
if len(client_rows) == 1:
_merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=7, value_present=True)
_merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=8, value_present=True)
_merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=9, value_present=True)
if len(project_rows) == 1:
_merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=10, value_present=True)
_merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=11, value_present=True)
if len(project_rows) == 1:
_merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=12, value_present=True)
if len(tag_rows) == 1:
_merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=13, value_present=True)
_merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=14, value_present=True)
_merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=15, value_present=True)
if len(tag_rows) == 1:
_merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=16, value_present=True)
_merge_vertical_if_needed(worksheet, start_row=current_row, span=span, column=17, value_present=True)
_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 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()
current_row += 2
for title_key, rows, hour_percentages, income_percentages in (
(
@@ -855,7 +876,7 @@ def _render_all_users_overall_excel_sheet(
locale.format_duration(row["billable_duration"], ascii_digits=True),
_percentage_display(locale, hour_percentages or [], row, ascii_digits=True),
_money_label_excel(locale, row["income_totals"]),
_percentage_display(locale, income_percentages or [], row, ascii_digits=True),
_percentage_display(locale, income_percentages or [], row, ascii_digits=True, default="-"),
],
rtl=locale.is_rtl,
)
@@ -1121,7 +1142,7 @@ def _build_pdf_user_summary_table(locale: ExportLocale, summary: dict, doc_width
def _build_pdf_rate_history_table(locale: ExportLocale, summary: dict, doc_width: float) -> Table:
rows = summary.get("rate_periods") or []
data = [
_rtl_row(locale, [locale.t("hourly_rate"), locale.t("from"), locale.t("to")]),
_rtl_row(locale, [locale.t("hourly_rate"), locale.t("from"), locale.t("to"), locale.t("project")]),
*(
_rtl_row(
locale,
@@ -1129,14 +1150,15 @@ def _build_pdf_rate_history_table(locale: ExportLocale, summary: dict, doc_width
_rate_period_label(locale, row),
locale.format_date(row["from_date"]),
_rate_to_label(locale, row.get("to_date")),
row.get("project_name") or "-",
],
)
for row in rows
),
]
if not rows:
data.append(_rtl_row(locale, [locale.t("no_data"), "", ""]))
fixed_widths = [doc_width * 0.18, doc_width * 0.18]
data.append(_rtl_row(locale, [locale.t("no_data"), "", "", ""]))
fixed_widths = [doc_width * 0.18, doc_width * 0.18, doc_width * 0.24]
column_widths = [doc_width - sum(fixed_widths), *fixed_widths]
if locale.is_rtl:
column_widths = list(reversed(column_widths))
@@ -1227,7 +1249,7 @@ def _append_pdf_report_sections(
]
),
_money_label(locale, row["income_totals"]),
_percentage_display(locale, income_percentage_rows or [], row),
_percentage_display(locale, income_percentage_rows or [], row, default="-"),
],
)
for row in sorted_rows