feat(reports): refine exports and restore project access

This commit is contained in:
2026-05-14 17:06:35 +03:30
parent 77c07adec8
commit d4a52d6f3b
16 changed files with 1594 additions and 136 deletions

View File

@@ -46,15 +46,58 @@ def make_report_data(*, user_name="Owner User", mobile="09129990001", hourly_rat
}
def make_user_summary(*, name: str, mobile: str):
return {
"user": {"id": mobile, "name": name, "mobile": mobile},
"hourly_rates": [{"amount": "15.00", "currency": "USD"}],
"rate_periods": [
{
"amount": "15.00",
"currency": "USD",
"from_date": "2026-04-01",
"to_date": "2026-04-30",
}
],
"total_seconds": 7200,
"total_duration": "02:00:00",
"billable_seconds": 7200,
"billable_duration": "02:00:00",
"non_billable_seconds": 0,
"non_billable_duration": "00:00:00",
"income_totals": [{"amount": "30.00", "currency": "USD"}],
"project_percentages": [{"id": "1", "name": "Website", "percentage": "100"}],
"client_percentages": [{"id": "1", "name": "Acme", "percentage": "100"}],
"tag_percentages": [{"id": "1", "name": "Design", "percentage": "100"}],
}
class ReportExporterTests(TestCase):
def test_excel_export_adds_per_user_sheets_and_daily_rate_column(self):
locale = build_export_locale("en")
report_data = make_report_data(
hourly_rate={"amount": "15.00", "currency": "USD"},
)
report_data["user_summaries"] = [
make_user_summary(name="Owner User", mobile="09129990001"),
make_user_summary(name="Team Mate", mobile="09129990002"),
]
per_user_reports = [
make_report_data(user_name="Owner User", mobile="09129990001"),
make_report_data(user_name="Team Mate", mobile="09129990002"),
{
**make_report_data(
user_name="Owner User",
mobile="09129990001",
hourly_rate={"amount": "15.00", "currency": "USD"},
),
"user_summary": make_user_summary(name="Owner User", mobile="09129990001"),
},
{
**make_report_data(
user_name="Team Mate",
mobile="09129990002",
hourly_rate={"amount": "15.00", "currency": "USD"},
),
"user_summary": make_user_summary(name="Team Mate", mobile="09129990002"),
},
]
workbook = load_workbook(
@@ -71,13 +114,38 @@ class ReportExporterTests(TestCase):
self.assertIn("Owner User", workbook.sheetnames[1])
self.assertIn("Team Mate", workbook.sheetnames[2])
worksheet = workbook.active
values = list(worksheet.iter_rows(values_only=True))
summary_sheet = workbook[workbook.sheetnames[0]]
summary_values = list(summary_sheet.iter_rows(values_only=True))
self.assertTrue(any(row[:2] == ("User", "Owner User") for row in values if row))
self.assertTrue(any(row[:2] == ("Mobile", "09129990001") for row in values if row))
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:M15", {str(item) for item in summary_sheet.merged_cells.ranges})
self.assertEqual(
tuple(summary_sheet.iter_rows(min_row=16, max_row=16, values_only=True))[0][:13],
(
"Name",
"Mobile",
"Working hours",
"Non-working hours",
"Income",
"Hourly rate",
"Period",
"Clients",
"Percentage",
"Projects",
"Percentage",
"Tags",
"Percentage",
),
)
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))
daily_header = next(row[:6] for row in values if row and row[0] == "Date")
user_sheet = workbook[workbook.sheetnames[1]]
user_values = list(user_sheet.iter_rows(values_only=True))
daily_header = next(row[:6] for row in user_values if row and "Date" in row)
self.assertEqual(
daily_header,
(
@@ -90,7 +158,7 @@ class ReportExporterTests(TestCase):
),
)
daily_row = next(row[:6] for row in values if row and row[0] == "2026/04/12")
daily_row = next(row[:6] for row in user_values if row and "2026/04/12" in row)
self.assertEqual(daily_row[4], "15 USD")
def test_pdf_export_supports_persian_locale(self):
@@ -98,7 +166,11 @@ class ReportExporterTests(TestCase):
report_data = make_report_data(
hourly_rate={"amount": "15.00", "currency": "USD"},
)
report_data["user_summaries"] = [make_user_summary(name="Owner User", mobile="09129990001")]
per_user_reports = [
{**make_report_data(user_name="Owner User", mobile="09129990001"), "user_summary": make_user_summary(name="Owner User", mobile="09129990001")}
]
content = build_pdf_report(report_data=report_data, locale=locale)
content = build_pdf_report(report_data=report_data, locale=locale, per_user_reports=per_user_reports)
self.assertEqual(content[:4], b"%PDF")

View File

@@ -138,8 +138,18 @@ class ReportViewTests(APITestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["summary"]["total_duration"], "03:00:00")
self.assertEqual(len(response.data["days"]), 2)
self.assertEqual(len(response.data["user_summaries"]), 2)
self.assertIsNone(response.data["days"][0]["latest_hourly_rate"])
self.assertIsNone(response.data["days"][1]["latest_hourly_rate"])
summaries = {item["user"]["id"]: item for item in response.data["user_summaries"]}
owner_summary = summaries[str(self.owner.id)]
member_summary = summaries[str(self.member.id)]
self.assertEqual(owner_summary["project_percentages"][0]["percentage"], "100")
self.assertEqual(owner_summary["client_percentages"][0]["percentage"], "100")
self.assertEqual(owner_summary["tag_percentages"][0]["percentage"], "100")
self.assertEqual(member_summary["project_percentages"], [])
self.assertEqual(member_summary["client_percentages"], [])
self.assertEqual(member_summary["tag_percentages"], [])
def test_daily_rate_uses_latest_billable_entry_snapshot(self):
self.client.force_authenticate(user=self.owner)