from io import BytesIO from django.test import TestCase from openpyxl import load_workbook from apps.reports.services.export_i18n import build_export_locale from apps.reports.services.exporters import ( _pdf_summary_rate_label, _rate_label, build_excel_report, build_pdf_report, ) def make_report_data(*, user_name="Owner User", mobile="09129990001", hourly_rate=None): return { "scope": { "workspace": {"name": "Exports", "thumbnail_path": None}, "period": "this_month", "from_date": "2026-04-01", "to_date": "2026-04-30", "user": {"name": user_name, "mobile": mobile} if user_name else None, }, "summary": { "total_duration": "02:00:00", "billable_duration": "02:00:00", "non_billable_duration": "00:00:00", "income_totals": [{"amount": "30.00", "currency": "USD"}], }, "days": [ { "date": "2026-04-12", "billable_duration": "02:00:00", "non_billable_duration": "00:00:00", "total_duration": "02:00:00", "latest_hourly_rate": hourly_rate, "income_totals": [{"amount": "30.00", "currency": "USD"}], } ], "clients": [ { "name": "Acme", "billable_duration": "02:00:00", "non_billable_duration": "00:00:00", "total_duration": "02:00:00", "income_totals": [{"amount": "30.00", "currency": "USD"}], } ], "projects": [], "tags": [], } 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"}], "project_income_percentages": [{"id": "1", "name": "Website", "percentage": "100"}], "client_income_percentages": [{"id": "1", "name": "Acme", "percentage": "100"}], "tag_income_percentages": [{"id": "1", "name": "Design", "percentage": "100"}], } def make_variable_user_summary(*, name: str, mobile: str): summary = make_user_summary(name=name, mobile=mobile) summary["hourly_rates"] = [ {"amount": "15.00", "currency": "USD"}, {"amount": "18.00", "currency": "USD"}, ] summary["rate_periods"] = [ { "amount": "15.00", "currency": "USD", "from_date": "2026-04-01", "to_date": "2026-04-14", }, { "amount": "18.00", "currency": "USD", "from_date": "2026-04-15", "to_date": "2026-04-30", }, ] return summary class ReportExporterTests(TestCase): def test_export_rate_labels_trim_rial_and_toman_decimals(self): locale = build_export_locale("en") self.assertEqual( _rate_label(locale, {"amount": "1250.75", "currency": "USD"}), "1,250.75 USD", ) self.assertEqual( _rate_label(locale, {"amount": "1250.75", "currency": "IRR"}), "1,250 IRR", ) self.assertEqual( _rate_label(locale, {"amount": "9800.50", "currency": "IRT"}), "9,800 IRT", ) def test_pdf_summary_uses_multiple_rates_label(self): locale = build_export_locale("en") self.assertEqual( _pdf_summary_rate_label( locale, [ {"amount": "15.00", "currency": "USD"}, {"amount": "18.00", "currency": "USD"}, ], ), "Variable rate", ) 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_variable_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", hourly_rate={"amount": "15.00", "currency": "USD"}, ), "user_summary": make_variable_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( BytesIO( build_excel_report( report_data=report_data, locale=locale, per_user_reports=per_user_reports, ) ) ) self.assertEqual(workbook.sheetnames[0], "Overall Report") self.assertIn("Owner User", workbook.sheetnames[1]) self.assertIn("Team Mate", workbook.sheetnames[2]) summary_sheet = workbook[workbook.sheetnames[0]] summary_values = list(summary_sheet.iter_rows(values_only=True)) 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:O15", {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][:15], ( "Name", "Mobile", "Working hours", "Hourly rate", "Period", "Income", "Clients", "Hour %", "Income %", "Projects", "Hour %", "Income %", "Tags", "Hour %", "Income %", ), ) 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)) 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, ( "Date", "Billable hours", "Non-billable hours", "Total hours", "Hourly rate", "Income", ), ) 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") breakdown_header = next(row[:7] for row in user_values if row and row[0] == "Name" and row[2] == "Hour %") self.assertEqual( breakdown_header[:5], ( "Name", "Billable hours", "Hour %", "Income", "Income %", ), ) def test_pdf_export_supports_persian_locale(self): locale = build_export_locale("fa") 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, per_user_reports=per_user_reports) self.assertEqual(content[:4], b"%PDF")