diff --git a/config/do_dashboard_extended_template.xlsx b/config/do_dashboard_extended_template.xlsx index 794fad8..42c09ea 100644 Binary files a/config/do_dashboard_extended_template.xlsx and b/config/do_dashboard_extended_template.xlsx differ diff --git a/config/do_dashboard_monitoring_template.xlsx b/config/do_dashboard_monitoring_template.xlsx index e529a1c..3faab2f 100644 Binary files a/config/do_dashboard_monitoring_template.xlsx and b/config/do_dashboard_monitoring_template.xlsx differ diff --git a/do_dashboard.py b/do_dashboard.py index c44df78..cf25ad4 100644 --- a/do_dashboard.py +++ b/do_dashboard.py @@ -1525,9 +1525,9 @@ def main(): console.print("[green]✓ Data saved to JSON files[/green]") print() - # === EXCEL EXPORT === (temporarily disabled for JSON generation testing) - # run_normal_mode_export(excel_export_enabled, excel_export_config, - # requests_mapping_config, organizations_mapping_config) + # === EXCEL EXPORT === + run_normal_mode_export(excel_export_enabled, excel_export_config, + requests_mapping_config, organizations_mapping_config) except IOError as io_err: logging.critical(f"Error while writing JSON file : {io_err}") diff --git a/do_dashboard_constants.py b/do_dashboard_constants.py index dac705c..48f37d9 100644 --- a/do_dashboard_constants.py +++ b/do_dashboard_constants.py @@ -75,7 +75,7 @@ API_AUTH_CONFIG_TOKEN_ENDPOINT = "/api/auth/config-token" API_AUTH_REFRESH_TOKEN_ENDPOINT = "/api/auth/refreshToken" # GDD (Diagnostic Order) endpoints -API_DO_WORKLIST_ENDPOINT = "/api/requests/worklist-filter" +API_DO_WORKLIST_ENDPOINT = "/api/requests/worklist-filter?role=admin" API_DO_REQUEST_DETAIL_ENDPOINT = "/api/requests" # + /{id}/validation API_DO_PROFESSIONALS_ENDPOINT = "/api/entity-manager/meta/modele_fr/data/nodes/pro/nodes" diff --git a/do_dashboard_excel_export.py b/do_dashboard_excel_export.py index 3e76578..a87c62d 100644 --- a/do_dashboard_excel_export.py +++ b/do_dashboard_excel_export.py @@ -1172,6 +1172,60 @@ def _get_column_mapping(mapping_config, mapping_column_name, source_type): return column_mapping if column_mapping else None +def _strftime_to_excel_format(strftime_fmt): + """Convert a Python strftime format string to an Excel number format string.""" + mapping = [ + ("%Y", "YYYY"), ("%y", "YY"), + ("%m", "MM"), ("%d", "DD"), + ("%H", "HH"), ("%M", "MM"), + ("%S", "SS"), + ] + result = strftime_fmt + for strftime_token, excel_token in mapping: + result = result.replace(strftime_token, excel_token) + return result + + +def _get_date_format_mapping(mapping_config, mapping_column_name, source_type): + """ + Build a date format mapping for columns whose field has a strftime-style field_template. + + Args: + mapping_config: List of mapping config rows + mapping_column_name: Name of the mapping column (e.g., "MainReport_PatientsList") + source_type: "Requests" or "Organizations" + + Returns: + Dict: {excel_col_index: {"strftime": "%d/%m/%Y", "excel": "DD/MM/YYYY"}} + Only includes columns with a date field_template (contains '%'). + """ + if not mapping_config: + return {} + + result = {} + for row in mapping_config: + field_template = row.get("field_template") + if not field_template or "%" not in str(field_template): + continue + + mapping_value = row.get(mapping_column_name) + if mapping_value is None or mapping_value == "": + continue + + try: + excel_col_index = int(mapping_value) - 1 + except (ValueError, TypeError): + continue + + if excel_col_index >= 0: + result[excel_col_index] = { + "strftime": str(field_template), + "excel": _strftime_to_excel_format(str(field_template)), + } + + return result + + def _parse_range_dimensions(start_row, start_col, end_row, end_col, header_row_count=0): """ Shared utility: Calculate dimensions from cell coordinates. @@ -1485,9 +1539,11 @@ def _prepare_table_data(source_type, source, sheet_config, requests_data, organi column_mapping = _get_column_mapping(mapping_config, source, source_type) if not column_mapping: logging.warning(f"Column mapping '{source}' not found or empty for {target_name}") - return None, None + return None, None, {} - return sorted_data, column_mapping + date_format_mapping = _get_date_format_mapping(mapping_config, source, source_type) + + return sorted_data, column_mapping, date_format_mapping def _resize_table_range(workbook_xw, sheet_xw, target_name, start_cell, max_col, start_row, num_data_rows, header_row_count=0, target_type=TARGET_TYPE_TABLE): @@ -1627,7 +1683,7 @@ def _duplicate_template_row(sheet_xw, start_cell, max_col, start_row, num_data_r def _fill_table_with_data(sheet_xw, start_cell, start_row, start_col, sorted_data, column_mapping, - value_replacement, target_name, sheet_name): + value_replacement, target_name, sheet_name, date_format_mapping=None): """ Fill table with data: group contiguous columns and transfer via bulk 2D arrays. @@ -1641,6 +1697,8 @@ def _fill_table_with_data(sheet_xw, start_cell, start_row, start_col, sorted_dat value_replacement: Value replacement configuration (or None) target_name: Target range name (for logging) sheet_name: Sheet name (for logging) + date_format_mapping: Optional dict {col_idx: {"strftime": ..., "excel": ...}} + Columns listed here will be written as native datetime objects. Returns: None (logging handles errors and success) @@ -1676,6 +1734,15 @@ def _fill_table_with_data(sheet_xw, start_cell, start_row, start_col, sorted_dat if value_replacement: value = _apply_value_replacement(value, value_replacement) + # Convert date strings to native datetime objects + if (date_format_mapping and excel_col_index in date_format_mapping + and isinstance(value, str)): + strftime_fmt = date_format_mapping[excel_col_index]["strftime"] + try: + value = datetime.strptime(value, strftime_fmt) + except (ValueError, TypeError): + pass # keep as string if parsing fails + row_values.append(value) data_2d.append(row_values) @@ -1687,6 +1754,18 @@ def _fill_table_with_data(sheet_xw, start_cell, start_row, start_col, sorted_dat # Write 2D array at once (xlwings automatically maps rows × columns) sheet_xw.range(target_range_start).value = data_2d + # Apply Excel number format to date columns in this group + if date_format_mapping and len(sorted_data) > 0: + last_data_row = start_row + len(sorted_data) - 1 + for col_idx in col_group: + if col_idx in date_format_mapping: + col_abs = start_col + col_idx + col_letter = get_column_letter(col_abs) + excel_fmt = date_format_mapping[col_idx]["excel"] + sheet_xw.range( + f"{col_letter}{start_row}:{col_letter}{last_data_row}" + ).number_format = excel_fmt + # Logging num_data_rows = len(sorted_data) logging.info(f"Filled table {target_name} with {num_data_rows} rows " @@ -1732,7 +1811,7 @@ def _process_sheet_xlwings(workbook_xw, sheet_config, requests_data, organizatio return False # Prepare data: filter, sort, get column mapping - sorted_data, column_mapping = _prepare_table_data( + sorted_data, column_mapping, date_format_mapping = _prepare_table_data( source_type, source, sheet_config, requests_data, organizations_data, requests_mapping_config, organizations_mapping_config, target_name ) @@ -1763,7 +1842,8 @@ def _process_sheet_xlwings(workbook_xw, sheet_config, requests_data, organizatio # STEP 2-3: Fill with data (grouped contiguous columns) _fill_table_with_data(sheet_xw, start_cell, start_row, start_col, sorted_data, - column_mapping, value_replacement, target_name, sheet_name) + column_mapping, value_replacement, target_name, sheet_name, + date_format_mapping) else: # No data - template row stays empty logging.info(f"No data for target '{target_name}' ({target_type}), leaving template row empty")