diff --git a/backend/benefit/applications/api/v1/application_views.py b/backend/benefit/applications/api/v1/application_views.py index 264f16ea24..1079ce66b6 100755 --- a/backend/benefit/applications/api/v1/application_views.py +++ b/backend/benefit/applications/api/v1/application_views.py @@ -61,6 +61,9 @@ ApplicationAlterationCsvService, ) from applications.services.applications_csv_report import ApplicationsCsvService +from applications.services.applications_power_bi_csv_report import ( + ApplicationsPowerBiCsvService, +) from applications.services.clone_application import clone_application_based_on_other from applications.services.generate_application_summary import ( generate_application_summary_file, @@ -774,7 +777,11 @@ def with_unread_messages(self, request, *args, **kwargs): def export_csv(self, request) -> StreamingHttpResponse: queryset = self.get_queryset() filtered_queryset = self.filter_queryset(queryset) - return self._csv_response(filtered_queryset) + compact_list = ( + request.query_params.get("compact_list", "false").lower() == "true" + ) + + return self._csv_response(filtered_queryset, compact_list) APPLICATION_ORDERING = "application_number" @@ -873,12 +880,20 @@ def _csv_response( prune_data_for_talpa: bool = False, remove_quotes: bool = False, prune_sensitive_data: bool = True, + compact_list: bool = False, ) -> StreamingHttpResponse: - csv_service = ApplicationsCsvService( - queryset.order_by(self.APPLICATION_ORDERING), - prune_data_for_talpa, - prune_sensitive_data, - ) + if compact_list: + # The PowerBi service already provides a more compact list, + # so we use it here as well + csv_service = ApplicationsPowerBiCsvService( + queryset.order_by(self.APPLICATION_ORDERING), + ) + else: + csv_service = ApplicationsCsvService( + queryset.order_by(self.APPLICATION_ORDERING), + prune_data_for_talpa, + prune_sensitive_data, + ) response = StreamingHttpResponse( csv_service.get_csv_string_lines_generator(remove_quotes), content_type="text/csv", diff --git a/backend/benefit/applications/services/ahjo_payload.py b/backend/benefit/applications/services/ahjo_payload.py index 867422e2d6..6eb9b95124 100644 --- a/backend/benefit/applications/services/ahjo_payload.py +++ b/backend/benefit/applications/services/ahjo_payload.py @@ -230,12 +230,17 @@ def _prepare_top_level_dict( application: Application, case_records: List[dict], case_title: str ) -> dict: """Prepare the dictionary that is sent to Ahjo""" - application_date = application.created_at.isoformat("T", "seconds") + + application_date = ( + application.submitted_at + if hasattr(application, "submitted_at") + else application.created_at + ) handler = application.calculation.handler case_dict = { "Title": case_title, - "Acquired": application_date, + "Acquired": application_date.isoformat("T", "seconds"), "ClassificationCode": "02 05 01 00", "ClassificationTitle": "Kunnan myöntämät avustukset", "Language": resolve_payload_language(application), diff --git a/backend/benefit/applications/services/applications_csv_report.py b/backend/benefit/applications/services/applications_csv_report.py index 063ec6325a..a33fe43a29 100644 --- a/backend/benefit/applications/services/applications_csv_report.py +++ b/backend/benefit/applications/services/applications_csv_report.py @@ -1,3 +1,4 @@ +from datetime import datetime from typing import List from django.utils import translation @@ -80,6 +81,13 @@ def get_application_origin_label(application_origin: str) -> str: return str(ApplicationOrigin(application_origin).label) +def get_submitted_at_date(application) -> datetime: + if hasattr(application, "submitted_at") and application.submitted_at: + return application.submitted_at + else: + return application.created_at + + class ApplicationsCsvService(CsvExportBase): """ Export application data for further processing in Excel and other reporting software. @@ -136,6 +144,7 @@ def CSV_COLUMNS(self): columns = [ CsvColumn("Hakemusnumero", "application_number"), + CsvColumn("Hakemus saapunut", get_submitted_at_date, format_datetime), CsvColumn("Hakemusrivi", "application_row_idx"), CsvColumn("Hakemuksen tila", "status"), CsvColumn( diff --git a/backend/benefit/applications/services/applications_power_bi_csv_report.py b/backend/benefit/applications/services/applications_power_bi_csv_report.py index f174a35b9b..2ffe36319d 100644 --- a/backend/benefit/applications/services/applications_power_bi_csv_report.py +++ b/backend/benefit/applications/services/applications_power_bi_csv_report.py @@ -1,6 +1,8 @@ from datetime import datetime from typing import Union +from django.utils import translation + from applications.enums import ApplicationBatchStatus from applications.models import Application from applications.services.applications_csv_report import ( @@ -10,6 +12,7 @@ format_datetime, get_application_origin_label, get_benefit_type_label, + get_submitted_at_date, ) from applications.services.csv_export_base import CsvColumn, get_organization_type @@ -29,15 +32,13 @@ def get_completed_in_talpa_date( def get_alteration_amount(self, application: Application) -> float: sum = 0 for alteration in application.alteration_set.all(): - if alteration.recovery_amount: + # Only include alterations with recovery amount over 20€ + if alteration.recovery_amount and alteration.recovery_amount > 20: sum += alteration.recovery_amount return sum @property def CSV_COLUMNS(self): - """ - Customize the CSV columns but also return the parent class's columns. - """ calculated_benefit_amount = "calculation.calculated_benefit_amount" columns = [ @@ -52,7 +53,11 @@ def CSV_COLUMNS(self): CsvColumn( "Hakemuksen tyyppi", "application_origin", get_application_origin_label ), - CsvColumn("Hakemus saapunut", "created_at", format_datetime), + CsvColumn( + "Hakemus saapunut", + get_submitted_at_date, + format_datetime, + ), csv_default_column("Haettava lisä", "benefit_type", get_benefit_type_label), csv_default_column("Haettu alkupäivä", "start_date"), csv_default_column("Haettu päättymispäivä", "end_date"), @@ -107,3 +112,9 @@ def CSV_COLUMNS(self): ] return columns + + def get_row_items(self): + with translation.override("fi"): + for application in self.get_applications(): + application.application_row_idx = 1 + yield application diff --git a/backend/benefit/applications/tests/test_ahjo_payload.py b/backend/benefit/applications/tests/test_ahjo_payload.py index 9cc5ca26fc..f116cc597b 100644 --- a/backend/benefit/applications/tests/test_ahjo_payload.py +++ b/backend/benefit/applications/tests/test_ahjo_payload.py @@ -197,8 +197,9 @@ def test_prepare_record_title( part, total, ): - application = decided_application - formatted_date = application.created_at.strftime("%d.%m.%Y") + application = Application.objects.get(pk=decided_application.pk) + + formatted_date = application.submitted_at.strftime("%d.%m.%Y") if part and total: wanted_title = f"{record_title}{wanted_title_addition} {formatted_date},\ @@ -216,7 +217,8 @@ def test_prepare_record_title( def test_prepare_record_title_for_attachment(decided_application): - application = decided_application + application = Application.objects.get(pk=decided_application.pk) + formatted_date = application.created_at.strftime("%d.%m.%Y") wanted_title = f"{AhjoRecordTitle.APPLICATION} {formatted_date}, liite 1/3, {application.application_number}" got = f"{AhjoBaseRecordTitle(application=application, current=1, total=3)}" @@ -296,7 +298,7 @@ def test_prepare_record_document_dict(decided_application, settings): def test_prepare_case_records(decided_application, settings): settings.DEBUG = True - application = decided_application + application = Application.objects.get(pk=decided_application.pk) fake_file = ContentFile( b"fake file content", @@ -359,7 +361,7 @@ def test_prepare_case_records(decided_application, settings): def test_prepare_top_level_dict(decided_application, ahjo_open_case_top_level_dict): - application = decided_application + application = Application.objects.get(pk=decided_application.pk) got = _prepare_top_level_dict(application, [], "message title") @@ -367,7 +369,8 @@ def test_prepare_top_level_dict(decided_application, ahjo_open_case_top_level_di def test_prepare_update_application_payload(decided_application): - application = decided_application + application = Application.objects.get(pk=decided_application.pk) + handler = application.calculation.handler handler_name = f"{handler.last_name}, {handler.first_name}" handler_id = handler.ad_username @@ -390,7 +393,7 @@ def test_prepare_update_application_payload(decided_application): { "Title": f"{UpdateRecordsRecordTitle(application=application,)}", "Type": AhjoRecordType.APPLICATION, - "Acquired": application.created_at.isoformat(), + "Acquired": application.submitted_at.isoformat(), "PublicityClass": "Salassa pidettävä", "SecurityReasons": ["JulkL (621/1999) 24.1 § 25 k"], "Language": "fi", diff --git a/backend/benefit/applications/tests/test_applications_report.py b/backend/benefit/applications/tests/test_applications_report.py index e3c128bfcd..61e82075dd 100644 --- a/backend/benefit/applications/tests/test_applications_report.py +++ b/backend/benefit/applications/tests/test_applications_report.py @@ -429,7 +429,7 @@ def test_power_bi_report_csv_output(application_powerbi_csv_service): csv_row[6] == f'"{get_application_origin_label(application.application_origin)}"' ) - assert csv_row[7] == f'"{format_datetime(application.created_at)}"' + assert csv_row[7] == f'"{format_datetime(application.submitted_at)}"' assert csv_row[8] == '"Työllistämisen Helsinki-lisä"' assert csv_row[9] == f'"{str(application.start_date)}"' assert csv_row[10] == f'"{str(application.end_date)}"' @@ -624,11 +624,10 @@ def test_pruned_applications_csv_output( assert csv_lines[0][16] == '"Tarkastajan sähköposti, P2P"' assert csv_lines[0][17] == '"Hyväksyjän nimi P2P"' - # Assert that there are 15 columns in the pruned CSV + # Assert that there are 19 columns in the pruned CSV assert len(csv_lines[1]) == 18 assert int(csv_lines[1][0]) == application.application_number - assert csv_lines[1][1] == '"Yritys"' assert csv_lines[1][2] == f'"{application.company_bank_account_number}"' assert csv_lines[1][3] == f'"{application.company_name}"' @@ -764,7 +763,9 @@ def test_applications_csv_string_lines_generator(applications_csv_service): ) -def test_applications_csv_two_ahjo_rows(applications_csv_service_with_one_application): +def test_applications_csv_two_ahjo_rows( + applications_csv_service_with_one_application, tmp_path +): application = applications_csv_service_with_one_application.get_applications()[0] application.pay_subsidies.all().delete() application.pay_subsidy_granted = PaySubsidyGranted.GRANTED @@ -796,9 +797,11 @@ def test_applications_csv_two_ahjo_rows(applications_csv_service_with_one_applic assert len(application.ahjo_rows) == 2 assert csv_lines[0][0] == '\ufeff"Hakemusnumero"' assert int(csv_lines[1][0]) == application.application_number - assert int(csv_lines[1][1]) == 1 + assert csv_lines[1][1] == f'"{format_datetime(application.submitted_at)}"' + assert int(csv_lines[1][2]) == 1 assert int(csv_lines[2][0]) == application.application_number - assert int(csv_lines[2][1]) == 2 + assert csv_lines[2][1] == f'"{format_datetime(application.submitted_at)}"' + assert int(csv_lines[2][2]) == 2 # the content of columns "Siirrettävä Ahjo-rivi / xxx" and "Hakemusrivi" change, rest of the lines are equal current_ahjo_row_start = csv_lines[0].index('"Siirrettävä Ahjo-rivi / tyyppi"') @@ -806,7 +809,7 @@ def test_applications_csv_two_ahjo_rows(applications_csv_service_with_one_applic '"Siirrettävä Ahjo-rivi / päättymispäivä"' ) assert ( - csv_lines[1][2:current_ahjo_row_start] == csv_lines[2][2:current_ahjo_row_start] + csv_lines[1][3:current_ahjo_row_start] == csv_lines[2][3:current_ahjo_row_start] ) assert ( csv_lines[1][current_ahjo_row_end + 1 :] @@ -875,7 +878,7 @@ def test_applications_csv_two_ahjo_rows(applications_csv_service_with_one_applic assert csv_lines[1][start_column + 5] == f'"{ahjo_row.end_date.isoformat()}"' applications_csv_service_with_one_application.write_csv_file( - "/tmp/two_ahjo_rows.csv" + tmp_path / "two_ahjo_rows.csv" ) @@ -944,7 +947,7 @@ def test_applications_csv_non_ascii_characters( csv_lines = split_lines_at_semicolon( applications_csv_service_with_one_application.get_csv_string() ) - assert csv_lines[1][12] == '"test äöÄÖtest"' # string is quoted + assert csv_lines[1][13] == '"test äöÄÖtest"' # string is quoted def test_applications_csv_delimiter(applications_csv_service_with_one_application): diff --git a/frontend/benefit/handler/public/locales/en/common.json b/frontend/benefit/handler/public/locales/en/common.json index bc503300d1..cf1847a1bf 100644 --- a/frontend/benefit/handler/public/locales/en/common.json +++ b/frontend/benefit/handler/public/locales/en/common.json @@ -1500,8 +1500,7 @@ "main": "Raportointi", "downloadAcceptedApplications": "Hyväksytyt hakemukset joita ei ole vielä ladattu", "downloadRejectedApplications": "Hylätyt hakemukset joita ei ole vielä ladattu", - "downloadApplicationsInTimeRange": "Lataa kaikki hakemukset tietyltä ajalta" - }, + "downloadApplicationsInTimeRange": "Lataa kaikki hakemukset tietyltä ajalta" }, "fields": { "lastDownloadDateText": "Ladattu viimeksi {{date}}", "startDate": "Alkaen", @@ -1510,7 +1509,8 @@ "buttons": { "downloadAcceptedApplications": "Hyväksytyt hakemukset", "downloadRejectedApplications": "Hylätyt hakemukset", - "downloadApplicationsInTimeRange": "Kaikki hakemukset valitulta ajalta" + "downloadApplicationsInTimeRange": "Kaikki hakemukset valitulta ajalta", + "downloadCompactList": "Lataa kompaktimpi lista hakemuksista" } }, "login": { diff --git a/frontend/benefit/handler/public/locales/fi/common.json b/frontend/benefit/handler/public/locales/fi/common.json index 03a9fc0f42..291716fb9e 100644 --- a/frontend/benefit/handler/public/locales/fi/common.json +++ b/frontend/benefit/handler/public/locales/fi/common.json @@ -1509,7 +1509,8 @@ "buttons": { "downloadAcceptedApplications": "Hyväksytyt hakemukset", "downloadRejectedApplications": "Hylätyt hakemukset", - "downloadApplicationsInTimeRange": "Kaikki hakemukset valitulta ajalta" + "downloadApplicationsInTimeRange": "Kaikki hakemukset valitulta ajalta", + "downloadCompactList": "Lataa kompaktimpi lista hakemuksista" } }, "login": { diff --git a/frontend/benefit/handler/public/locales/sv/common.json b/frontend/benefit/handler/public/locales/sv/common.json index bc503300d1..cf4f925f5a 100644 --- a/frontend/benefit/handler/public/locales/sv/common.json +++ b/frontend/benefit/handler/public/locales/sv/common.json @@ -1510,7 +1510,8 @@ "buttons": { "downloadAcceptedApplications": "Hyväksytyt hakemukset", "downloadRejectedApplications": "Hylätyt hakemukset", - "downloadApplicationsInTimeRange": "Kaikki hakemukset valitulta ajalta" + "downloadApplicationsInTimeRange": "Kaikki hakemukset valitulta ajalta", + "downloadCompactList": "Lataa kompaktimpi lista hakemuksista" } }, "login": { diff --git a/frontend/benefit/handler/src/components/applicationReports/ApplicationReports.tsx b/frontend/benefit/handler/src/components/applicationReports/ApplicationReports.tsx index 0382ef67ac..6ddeace660 100644 --- a/frontend/benefit/handler/src/components/applicationReports/ApplicationReports.tsx +++ b/frontend/benefit/handler/src/components/applicationReports/ApplicationReports.tsx @@ -28,6 +28,9 @@ const ApplicationReports: React.FC = () => { buttonText={`${t( `${translationsBase}.buttons.downloadApplicationsInTimeRange` )}`} + compactButtonText={`${t( + `${translationsBase}.buttons.downloadCompactList` + )}`} > <$GridCell $colSpan={3} css="font-weight: 500;">{`${t( `${translationsBase}.fields.startDate` diff --git a/frontend/benefit/handler/src/components/applicationReports/ReportsSection.tsx b/frontend/benefit/handler/src/components/applicationReports/ReportsSection.tsx index fc8776b30b..c8c4445c2e 100644 --- a/frontend/benefit/handler/src/components/applicationReports/ReportsSection.tsx +++ b/frontend/benefit/handler/src/components/applicationReports/ReportsSection.tsx @@ -15,7 +15,8 @@ export type ReportsSectionProp = { withDivider?: boolean; header: string; buttonText: string; - onDownloadButtonClick: (type: ExportFileType) => void; + compactButtonText: string; + onDownloadButtonClick: (type: ExportFileType, isCompact:boolean) => void; }; const ReportsSection: React.FC = ({ @@ -23,6 +24,7 @@ const ReportsSection: React.FC = ({ children, header, buttonText, + compactButtonText, onDownloadButtonClick, withDivider = false, }) => { @@ -56,11 +58,23 @@ const ReportsSection: React.FC = ({ css={` margin-top: ${theme.spacing.l}; `} - onClick={() => onDownloadButtonClick(exportFileType)} + onClick={() => onDownloadButtonClick(exportFileType, false)} > {buttonText} {String(exportFileType).toUpperCase()} + <$GridCell> + + {withDivider && ( diff --git a/frontend/benefit/handler/src/components/applicationReports/useApplicationReports.ts b/frontend/benefit/handler/src/components/applicationReports/useApplicationReports.ts index 9e8d61f760..449adf706b 100644 --- a/frontend/benefit/handler/src/components/applicationReports/useApplicationReports.ts +++ b/frontend/benefit/handler/src/components/applicationReports/useApplicationReports.ts @@ -40,7 +40,7 @@ type ExtendedComponentProps = { fields: { [key in EXPORT_APPLICATIONS_IN_TIME_RANGE_FORM_KEYS]: Field; }; - exportApplicationsInTimeRange: () => void; + exportApplicationsInTimeRange: (type: ExportFileType, isCompact: boolean) => void; lastAcceptedApplicationsExportDate: string; lastRejectedApplicationsExportDate: string; }; @@ -134,7 +134,7 @@ const useApplicationReports = (): ExtendedComponentProps => { const { values } = formik; const { startDate, endDate } = values; - const exportApplicationsInTimeRange = useCallback(async () => { + const exportApplicationsInTimeRange = useCallback(async (type: ExportFileType, isCompact: boolean) => { const data = await handleResponse( axios.get( `${BackendEndpoint.HANDLER_APPLICATIONS}${EXPORT_APPLICATIONS_ROUTES.IN_TIME_RANGE}/`, @@ -142,6 +142,7 @@ const useApplicationReports = (): ExtendedComponentProps => { params: { handled_at_after: convertToBackendDateFormat(startDate), handled_at_before: convertToBackendDateFormat(endDate), + compact_list: isCompact, }, } )