diff --git a/README.md b/README.md index 72e82a93..eda49b2a 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,35 @@ npx ts-node src/scripts/import-multiple-files.ts \ --results-path="/path/to/folder/for/json_results" \ file1.xlsx file2.xlsx ``` + +### Regenerate template metadata (from DHIS2) + +You can use the script `regenerate-metadata.ts` to refresh **only** the Metadata +sheet of an existing template from fresh DHIS2 metadata, leaving every other sheet +(custom form, dropdowns, VBA) untouched: + +```bash +yarn regenerate-metadata \ + --dhis2-url="https://play.dhis2.org" \ + --auth="user:pass" \ + --input=template.xlsm \ + --output=template.regenerated.xlsm \ + --form-id=U6z7eJniNfL \ + --form-type=trackerPrograms \ + --include-codes +``` + +| Flag | Required | Description | +| ---- | -------- | ----------- | +| `--dhis2-url` / `-u` | yes | DHIS2 base URL (auth can be embedded here or via `--auth`). | +| `--auth` / `-a` | — | `"username:password"` (special characters are handled). | +| `--input` / `-i` | yes | Path to the input template (`.xlsm`/`.xlsx`). | +| `--output` / `-o` | yes | Path where the regenerated template is written. | +| `--form-id` | yes | DHIS2 program/dataSet id whose metadata is used. | +| `--form-type` | — | `trackerPrograms` \| `programs` \| `dataSets` (default `trackerPrograms`). | +| `--language` | — | Metadata name language (default `en`). | +| `--include-codes` | — | Write the Code column in the Metadata sheet (default off). | +| `--use-codes` | — | Use item codes instead of names for org units, data elements, options… (default off). | +| `--org-unit-short-name` | — | Use each org unit's short name instead of its regular name (default off). | + +The same operation is available in the app, under Settings → Maintenance → Regenerate template metadata. diff --git a/i18n/en.pot b/i18n/en.pot index 26086b4b..9df10efe 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-06-30T17:31:02.698Z\n" -"PO-Revision-Date: 2026-06-30T17:31:02.699Z\n" +"POT-Creation-Date: 2026-07-29T11:52:52.301Z\n" +"PO-Revision-Date: 2026-07-29T11:52:52.301Z\n" msgid "Events - Create/update" msgstr "" @@ -120,6 +120,11 @@ msgstr "" msgid "Found in {{locations}} of the Excel file" msgstr "" +msgid "" +"Could not identify this template. Check the file is a Bulk Load template " +"and that your session is still active." +msgstr "" + msgid "Cannot read data form id" msgstr "" @@ -369,6 +374,80 @@ msgstr "" msgid "Items older than {{period}}" msgstr "" +msgid "File cleanup completed successfully" +msgstr "" + +msgid "An error occurred during file cleanup" +msgstr "" + +msgid "History cleanup completed successfully" +msgstr "" + +msgid "An error occurred during history cleanup" +msgstr "" + +msgid "Regenerate template metadata" +msgstr "" + +msgid "Maintenance" +msgstr "" + +msgid "File cleanup" +msgstr "" + +msgid "" +"Remove files older than the selected period. History entries will be kept " +"but the files will be unaccessible" +msgstr "" + +msgid "Cleaning up files..." +msgstr "" + +msgid "Confirm File Cleanup" +msgstr "" + +msgid "" +"Are you sure you want to remove all files older than the selected period? " +"This action cannot be undone. History entries will be kept but the files " +"will be inaccessible." +msgstr "" + +msgid "Remove files older than" +msgstr "" + +msgid "Clean up files" +msgstr "" + +msgid "History cleanup" +msgstr "" + +msgid "" +"Remove history entries and their documents older than the selected period. " +"This action cannot be undone" +msgstr "" + +msgid "Cleaning up history..." +msgstr "" + +msgid "Confirm History Cleanup" +msgstr "" + +msgid "" +"Are you sure you want to remove all history entries and their documents " +"older than the selected period? This action cannot be undone." +msgstr "" + +msgid "Remove history entries older than" +msgstr "" + +msgid "Clean up history" +msgstr "" + +msgid "" +"Refresh the Metadata sheet of an existing template file with current DHIS2 " +"metadata" +msgstr "" + msgid "Access to Template Generation" msgstr "" @@ -404,16 +483,30 @@ msgstr "" msgid "Populate events for every Tracked Entity Instance (TEI)" msgstr "" -msgid "File cleanup completed successfully" +msgid "Regenerate" msgstr "" -msgid "An error occurred during file cleanup" +msgid "Drag and drop the template file to regenerate" msgstr "" -msgid "History cleanup completed successfully" +msgid "Template" msgstr "" -msgid "An error occurred during history cleanup" +msgid "Data Form Type" +msgstr "" + +msgid "Include the Code column in the Metadata sheet" +msgstr "" + +msgid "Use codes instead of names for metadata items" +msgstr "" + +msgid "Use the short name of organisation units" +msgstr "" + +msgid "" +"The Metadata sheet will be re-protected with the standard Bulk Load sheet " +"password. All other sheets and the macros are left untouched." msgstr "" msgid "Select Organisation Units on template generation" @@ -531,60 +624,6 @@ msgstr "" msgid "Permissions" msgstr "" -msgid "Maintenance" -msgstr "" - -msgid "File cleanup" -msgstr "" - -msgid "" -"Remove files older than the selected period. History entries will be kept " -"but the files will be unaccessible" -msgstr "" - -msgid "Cleaning up files..." -msgstr "" - -msgid "Confirm File Cleanup" -msgstr "" - -msgid "" -"Are you sure you want to remove all files older than the selected period? " -"This action cannot be undone. History entries will be kept but the files " -"will be inaccessible." -msgstr "" - -msgid "Remove files older than" -msgstr "" - -msgid "Clean up files" -msgstr "" - -msgid "History cleanup" -msgstr "" - -msgid "" -"Remove history entries and their documents older than the selected period. " -"This action cannot be undone" -msgstr "" - -msgid "Cleaning up history..." -msgstr "" - -msgid "Confirm History Cleanup" -msgstr "" - -msgid "" -"Are you sure you want to remove all history entries and their documents " -"older than the selected period? This action cannot be undone." -msgstr "" - -msgid "Remove history entries older than" -msgstr "" - -msgid "Clean up history" -msgstr "" - msgid "Edit custom template" msgstr "" @@ -674,9 +713,6 @@ msgstr "" msgid "Description" msgstr "" -msgid "Data Form Type" -msgstr "" - msgid "Type" msgstr "" @@ -1001,9 +1037,6 @@ msgstr "" msgid "Select available organisation units to include in the template" msgstr "" -msgid "Template" -msgstr "" - msgid "Data Model" msgstr "" @@ -1161,6 +1194,15 @@ msgstr "" msgid "Error loading import details" msgstr "" +msgid "Only .xlsx and .xlsm template files are accepted" +msgstr "" + +msgid "Regenerating metadata..." +msgstr "" + +msgid "Metadata regenerated" +msgstr "" + msgid "Error deleting data values" msgstr "" diff --git a/i18n/es.po b/i18n/es.po index 6ffe825d..9abc99f6 100644 --- a/i18n/es.po +++ b/i18n/es.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Bulk Load\n" -"POT-Creation-Date: 2026-06-30T17:31:02.698Z\n" +"POT-Creation-Date: 2026-07-29T11:52:52.301Z\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -132,6 +132,11 @@ msgstr "" msgid "Found in {{locations}} of the Excel file" msgstr "" +msgid "" +"Could not identify this template. Check the file is a Bulk Load template and " +"that your session is still active." +msgstr "" + msgid "Cannot read data form id" msgstr "No se pueden leer los datos del formulario id" @@ -410,6 +415,80 @@ msgstr "Programa" msgid "Items older than {{period}}" msgstr "" +msgid "File cleanup completed successfully" +msgstr "" + +msgid "An error occurred during file cleanup" +msgstr "" + +msgid "History cleanup completed successfully" +msgstr "" + +msgid "An error occurred during history cleanup" +msgstr "" + +msgid "Regenerate template metadata" +msgstr "" + +msgid "Maintenance" +msgstr "" + +msgid "File cleanup" +msgstr "" + +msgid "" +"Remove files older than the selected period. History entries will be kept " +"but the files will be unaccessible" +msgstr "" + +msgid "Cleaning up files..." +msgstr "" + +msgid "Confirm File Cleanup" +msgstr "" + +msgid "" +"Are you sure you want to remove all files older than the selected period? " +"This action cannot be undone. History entries will be kept but the files " +"will be inaccessible." +msgstr "" + +msgid "Remove files older than" +msgstr "" + +msgid "Clean up files" +msgstr "" + +msgid "History cleanup" +msgstr "" + +msgid "" +"Remove history entries and their documents older than the selected period. " +"This action cannot be undone" +msgstr "" + +msgid "Cleaning up history..." +msgstr "" + +msgid "Confirm History Cleanup" +msgstr "" + +msgid "" +"Are you sure you want to remove all history entries and their documents " +"older than the selected period? This action cannot be undone." +msgstr "" + +msgid "Remove history entries older than" +msgstr "" + +msgid "Clean up history" +msgstr "" + +msgid "" +"Refresh the Metadata sheet of an existing template file with current DHIS2 " +"metadata" +msgstr "" + msgid "Access to Template Generation" msgstr "Acceso a la generación de plantillas" @@ -448,19 +527,32 @@ msgstr "Buscar elementos de datos o atributos" msgid "Populate events for every Tracked Entity Instance (TEI)" msgstr "" -msgid "File cleanup completed successfully" +msgid "Regenerate" msgstr "" #, fuzzy -msgid "An error occurred during file cleanup" -msgstr "Ha ocurrido un error mientras se guardaba el tema" +msgid "Drag and drop the template file to regenerate" +msgstr "Mueva aquí los ficheros a importar" -msgid "History cleanup completed successfully" +msgid "Template" +msgstr "Plantilla" + +msgid "Data Form Type" msgstr "" -#, fuzzy -msgid "An error occurred during history cleanup" -msgstr "Ha ocurrido un error mientras se guardaba el tema" +msgid "Include the Code column in the Metadata sheet" +msgstr "" + +msgid "Use codes instead of names for metadata items" +msgstr "" + +msgid "Use the short name of organisation units" +msgstr "" + +msgid "" +"The Metadata sheet will be re-protected with the standard Bulk Load sheet " +"password. All other sheets and the macros are left untouched." +msgstr "" msgid "Select Organisation Units on template generation" msgstr "Mostrar unidades organizativas durante la generación de la plantilla" @@ -584,65 +676,6 @@ msgstr "" msgid "Permissions" msgstr "Permisos" -#, fuzzy -msgid "Maintenance" -msgstr "Migrar la instancia" - -msgid "File cleanup" -msgstr "" - -msgid "" -"Remove files older than the selected period. History entries will be kept " -"but the files will be unaccessible" -msgstr "" - -#, fuzzy -msgid "Cleaning up files..." -msgstr "Leyendo archivo..." - -msgid "Confirm File Cleanup" -msgstr "" - -msgid "" -"Are you sure you want to remove all files older than the selected period? " -"This action cannot be undone. History entries will be kept but the files " -"will be inaccessible." -msgstr "" - -msgid "Remove files older than" -msgstr "" - -#, fuzzy -msgid "Clean up files" -msgstr "No se puede leer el archivo" - -msgid "History cleanup" -msgstr "" - -msgid "" -"Remove history entries and their documents older than the selected period. " -"This action cannot be undone" -msgstr "" - -#, fuzzy -msgid "Cleaning up history..." -msgstr "Leyendo archivo..." - -msgid "Confirm History Cleanup" -msgstr "" - -msgid "" -"Are you sure you want to remove all history entries and their documents " -"older than the selected period? This action cannot be undone." -msgstr "" - -msgid "Remove history entries older than" -msgstr "" - -#, fuzzy -msgid "Clean up history" -msgstr "No se puede leer el archivo" - msgid "Edit custom template" msgstr "Editar plantilla personalizada" @@ -735,9 +768,6 @@ msgstr "Nombre" msgid "Description" msgstr "Descripción" -msgid "Data Form Type" -msgstr "" - msgid "Type" msgstr "Tipo" @@ -1064,9 +1094,6 @@ msgstr "Seleccione la unidad de organización para rellenar los datos" msgid "Select available organisation units to include in the template" msgstr "Seleccione las unidades organizativas para añadir a la plantilla" -msgid "Template" -msgstr "Plantilla" - msgid "Data Model" msgstr "Modelo de Datos" @@ -1226,6 +1253,15 @@ msgstr "" msgid "Error loading import details" msgstr "" +msgid "Only .xlsx and .xlsm template files are accepted" +msgstr "" + +msgid "Regenerating metadata..." +msgstr "" + +msgid "Metadata regenerated" +msgstr "" + msgid "Error deleting data values" msgstr "Se ha producido un error borrando valores de datos" @@ -1355,9 +1391,9 @@ msgid "" "Samaritan’s Purse, Medecins Sans Frontières (MSF), the the Norwegian Refugee " "Council (NRC) and the Clinton Health Access Initiative (CHAI) to support " "countries in strengthening the collection and use of health data by using " -"DHIS2. The application has been developed by [EyeSeeTea SL](http://" -"eyeseetea.com). Source code, documentation and release notes can be found at " -"the [EyeSeetea GitHub Project Page](https://eyeseetea.github.io/Bulk-Load-" +"DHIS2. The application has been developed by [EyeSeeTea SL](http://eyeseetea." +"com). Source code, documentation and release notes can be found at the " +"[EyeSeetea GitHub Project Page](https://eyeseetea.github.io/Bulk-Load-" "blessed/)." msgstr "" diff --git a/i18n/fr.po b/i18n/fr.po index 04efd4fc..4d03becf 100644 --- a/i18n/fr.po +++ b/i18n/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Bulk Load App\n" -"POT-Creation-Date: 2026-06-30T17:31:02.698Z\n" +"POT-Creation-Date: 2026-07-29T11:52:52.301Z\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -126,6 +126,11 @@ msgstr "" msgid "Found in {{locations}} of the Excel file" msgstr "" +msgid "" +"Could not identify this template. Check the file is a Bulk Load template and " +"that your session is still active." +msgstr "" + msgid "Cannot read data form id" msgstr "" @@ -397,6 +402,80 @@ msgstr "Programme" msgid "Items older than {{period}}" msgstr "" +msgid "File cleanup completed successfully" +msgstr "" + +msgid "An error occurred during file cleanup" +msgstr "" + +msgid "History cleanup completed successfully" +msgstr "" + +msgid "An error occurred during history cleanup" +msgstr "" + +msgid "Regenerate template metadata" +msgstr "" + +msgid "Maintenance" +msgstr "" + +msgid "File cleanup" +msgstr "" + +msgid "" +"Remove files older than the selected period. History entries will be kept " +"but the files will be unaccessible" +msgstr "" + +msgid "Cleaning up files..." +msgstr "" + +msgid "Confirm File Cleanup" +msgstr "" + +msgid "" +"Are you sure you want to remove all files older than the selected period? " +"This action cannot be undone. History entries will be kept but the files " +"will be inaccessible." +msgstr "" + +msgid "Remove files older than" +msgstr "" + +msgid "Clean up files" +msgstr "" + +msgid "History cleanup" +msgstr "" + +msgid "" +"Remove history entries and their documents older than the selected period. " +"This action cannot be undone" +msgstr "" + +msgid "Cleaning up history..." +msgstr "" + +msgid "Confirm History Cleanup" +msgstr "" + +msgid "" +"Are you sure you want to remove all history entries and their documents " +"older than the selected period? This action cannot be undone." +msgstr "" + +msgid "Remove history entries older than" +msgstr "" + +msgid "Clean up history" +msgstr "" + +msgid "" +"Refresh the Metadata sheet of an existing template file with current DHIS2 " +"metadata" +msgstr "" + msgid "Access to Template Generation" msgstr "Accès à la génération de modèles" @@ -433,19 +512,32 @@ msgstr "" msgid "Populate events for every Tracked Entity Instance (TEI)" msgstr "" -msgid "File cleanup completed successfully" +msgid "Regenerate" msgstr "" #, fuzzy -msgid "An error occurred during file cleanup" -msgstr "Une erreur s'est produite lors de l'enregistrement du thème" +msgid "Drag and drop the template file to regenerate" +msgstr "Glissez et déposez le logo ici" -msgid "History cleanup completed successfully" +msgid "Template" +msgstr "Modèle" + +msgid "Data Form Type" msgstr "" -#, fuzzy -msgid "An error occurred during history cleanup" -msgstr "Une erreur s'est produite lors de l'enregistrement du thème" +msgid "Include the Code column in the Metadata sheet" +msgstr "" + +msgid "Use codes instead of names for metadata items" +msgstr "" + +msgid "Use the short name of organisation units" +msgstr "" + +msgid "" +"The Metadata sheet will be re-protected with the standard Bulk Load sheet " +"password. All other sheets and the macros are left untouched." +msgstr "" msgid "Select Organisation Units on template generation" msgstr "Sélectionnez les unités d'organisation lors de la génération du modèle" @@ -568,64 +660,6 @@ msgstr "" msgid "Permissions" msgstr "Autorisations" -msgid "Maintenance" -msgstr "" - -msgid "File cleanup" -msgstr "" - -msgid "" -"Remove files older than the selected period. History entries will be kept " -"but the files will be unaccessible" -msgstr "" - -#, fuzzy -msgid "Cleaning up files..." -msgstr "Lecture du fichier en cours ..." - -msgid "Confirm File Cleanup" -msgstr "" - -msgid "" -"Are you sure you want to remove all files older than the selected period? " -"This action cannot be undone. History entries will be kept but the files " -"will be inaccessible." -msgstr "" - -msgid "Remove files older than" -msgstr "" - -#, fuzzy -msgid "Clean up files" -msgstr "Le fichier ne peut pas être lu" - -msgid "History cleanup" -msgstr "" - -msgid "" -"Remove history entries and their documents older than the selected period. " -"This action cannot be undone" -msgstr "" - -#, fuzzy -msgid "Cleaning up history..." -msgstr "Lecture du fichier en cours ..." - -msgid "Confirm History Cleanup" -msgstr "" - -msgid "" -"Are you sure you want to remove all history entries and their documents " -"older than the selected period? This action cannot be undone." -msgstr "" - -msgid "Remove history entries older than" -msgstr "" - -#, fuzzy -msgid "Clean up history" -msgstr "Le fichier ne peut pas être lu" - #, fuzzy msgid "Edit custom template" msgstr "Modèle" @@ -727,9 +761,6 @@ msgstr "Nom" msgid "Description" msgstr "" -msgid "Data Form Type" -msgstr "" - msgid "Type" msgstr "" @@ -1082,9 +1113,6 @@ msgstr "" msgid "Select available organisation units to include in the template" msgstr "Sélectionnez les unités d'organisation à inclure dans le modèle" -msgid "Template" -msgstr "Modèle" - msgid "Data Model" msgstr "Modèle de données" @@ -1246,6 +1274,15 @@ msgstr "" msgid "Error loading import details" msgstr "" +msgid "Only .xlsx and .xlsm template files are accepted" +msgstr "" + +msgid "Regenerating metadata..." +msgstr "" + +msgid "Metadata regenerated" +msgstr "" + msgid "Error deleting data values" msgstr "Erreur lors de la suppression des valeurs de données" @@ -1373,9 +1410,9 @@ msgid "" "Samaritan’s Purse, Medecins Sans Frontières (MSF), the the Norwegian Refugee " "Council (NRC) and the Clinton Health Access Initiative (CHAI) to support " "countries in strengthening the collection and use of health data by using " -"DHIS2. The application has been developed by [EyeSeeTea SL](http://" -"eyeseetea.com). Source code, documentation and release notes can be found at " -"the [EyeSeetea GitHub Project Page](https://eyeseetea.github.io/Bulk-Load-" +"DHIS2. The application has been developed by [EyeSeeTea SL](http://eyeseetea." +"com). Source code, documentation and release notes can be found at the " +"[EyeSeetea GitHub Project Page](https://eyeseetea.github.io/Bulk-Load-" "blessed/)." msgstr "" @@ -1474,10 +1511,10 @@ msgid "" "data that was existing for such organisation unit and periods in the system " "will be deleted first, so none will be kept before doing the import." msgstr "" -"Toutes les données de la feuille de calcul seront importées dans le " -"système,mais les données existantes pour ces unités d'organisation et ces " -"périodes seront d'abord supprimées; aucune donnée existante ne sera donc " -"conservée avant l'importation." +"Toutes les données de la feuille de calcul seront importées dans le système," +"mais les données existantes pour ces unités d'organisation et ces périodes " +"seront d'abord supprimées; aucune donnée existante ne sera donc conservée " +"avant l'importation." msgid "" "Import only new data values, without updating nor deleting any existing one. " @@ -1493,10 +1530,10 @@ msgid "" "spreadsheet will be imported to the system, but other data values present in " "the system that are not provided in the spreadsheet will be kept." msgstr "" -"Importer les nouvelles valeurs et mettre à jour les valeurs " -"existantes.Toutes les données de la feuille de calcul seront importées,mais " -"les autres valeurs présentes dans le système et absentes de la feuille de " -"calcul seront conservées." +"Importer les nouvelles valeurs et mettre à jour les valeurs existantes." +"Toutes les données de la feuille de calcul seront importées,mais les autres " +"valeurs présentes dans le système et absentes de la feuille de calcul seront " +"conservées." msgid "Warning: Your upload may result in the generation of duplicates" msgstr "" diff --git a/i18n/pt.po b/i18n/pt.po index 4eefd2be..757f562d 100644 --- a/i18n/pt.po +++ b/i18n/pt.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Bulk Load\n" -"POT-Creation-Date: 2026-06-30T17:31:02.698Z\n" +"POT-Creation-Date: 2026-07-29T11:52:52.301Z\n" "Language: pt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -134,6 +134,11 @@ msgstr "" msgid "Found in {{locations}} of the Excel file" msgstr "" +msgid "" +"Could not identify this template. Check the file is a Bulk Load template and " +"that your session is still active." +msgstr "" + msgid "Cannot read data form id" msgstr "Não é possível ler os dados do formulário de identificação" @@ -416,6 +421,80 @@ msgstr "Programa" msgid "Items older than {{period}}" msgstr "" +msgid "File cleanup completed successfully" +msgstr "" + +msgid "An error occurred during file cleanup" +msgstr "" + +msgid "History cleanup completed successfully" +msgstr "" + +msgid "An error occurred during history cleanup" +msgstr "" + +msgid "Regenerate template metadata" +msgstr "" + +msgid "Maintenance" +msgstr "" + +msgid "File cleanup" +msgstr "" + +msgid "" +"Remove files older than the selected period. History entries will be kept " +"but the files will be unaccessible" +msgstr "" + +msgid "Cleaning up files..." +msgstr "" + +msgid "Confirm File Cleanup" +msgstr "" + +msgid "" +"Are you sure you want to remove all files older than the selected period? " +"This action cannot be undone. History entries will be kept but the files " +"will be inaccessible." +msgstr "" + +msgid "Remove files older than" +msgstr "" + +msgid "Clean up files" +msgstr "" + +msgid "History cleanup" +msgstr "" + +msgid "" +"Remove history entries and their documents older than the selected period. " +"This action cannot be undone" +msgstr "" + +msgid "Cleaning up history..." +msgstr "" + +msgid "Confirm History Cleanup" +msgstr "" + +msgid "" +"Are you sure you want to remove all history entries and their documents " +"older than the selected period? This action cannot be undone." +msgstr "" + +msgid "Remove history entries older than" +msgstr "" + +msgid "Clean up history" +msgstr "" + +msgid "" +"Refresh the Metadata sheet of an existing template file with current DHIS2 " +"metadata" +msgstr "" + msgid "Access to Template Generation" msgstr "Acesso à geração de planilhas" @@ -454,19 +533,32 @@ msgstr "Pesquisar elementos de dados ou atributos" msgid "Populate events for every Tracked Entity Instance (TEI)" msgstr "" -msgid "File cleanup completed successfully" +msgid "Regenerate" msgstr "" #, fuzzy -msgid "An error occurred during file cleanup" -msgstr "Ocorreu um erro ao salvar o tema" +msgid "Drag and drop the template file to regenerate" +msgstr "Arraste e solte o logotipo aqui" -msgid "History cleanup completed successfully" +msgid "Template" +msgstr "Modelo" + +msgid "Data Form Type" msgstr "" -#, fuzzy -msgid "An error occurred during history cleanup" -msgstr "Ocorreu um erro ao salvar o tema" +msgid "Include the Code column in the Metadata sheet" +msgstr "" + +msgid "Use codes instead of names for metadata items" +msgstr "" + +msgid "Use the short name of organisation units" +msgstr "" + +msgid "" +"The Metadata sheet will be re-protected with the standard Bulk Load sheet " +"password. All other sheets and the macros are left untouched." +msgstr "" msgid "Select Organisation Units on template generation" msgstr "Selecione unidades organizacionais na geração de planilhas" @@ -589,65 +681,6 @@ msgstr "" msgid "Permissions" msgstr "Permissões" -#, fuzzy -msgid "Maintenance" -msgstr "Migrar instância" - -msgid "File cleanup" -msgstr "" - -msgid "" -"Remove files older than the selected period. History entries will be kept " -"but the files will be unaccessible" -msgstr "" - -#, fuzzy -msgid "Cleaning up files..." -msgstr "Lendo arquivo ..." - -msgid "Confirm File Cleanup" -msgstr "" - -msgid "" -"Are you sure you want to remove all files older than the selected period? " -"This action cannot be undone. History entries will be kept but the files " -"will be inaccessible." -msgstr "" - -msgid "Remove files older than" -msgstr "" - -#, fuzzy -msgid "Clean up files" -msgstr "Não é possível ler o arquivo" - -msgid "History cleanup" -msgstr "" - -msgid "" -"Remove history entries and their documents older than the selected period. " -"This action cannot be undone" -msgstr "" - -#, fuzzy -msgid "Cleaning up history..." -msgstr "Lendo arquivo ..." - -msgid "Confirm History Cleanup" -msgstr "" - -msgid "" -"Are you sure you want to remove all history entries and their documents " -"older than the selected period? This action cannot be undone." -msgstr "" - -msgid "Remove history entries older than" -msgstr "" - -#, fuzzy -msgid "Clean up history" -msgstr "Não é possível ler o arquivo" - #, fuzzy msgid "Edit custom template" msgstr "Modelo" @@ -749,9 +782,6 @@ msgstr "Nome" msgid "Description" msgstr "Descrição" -msgid "Data Form Type" -msgstr "" - msgid "Type" msgstr "Tipo" @@ -1117,9 +1147,6 @@ msgstr "Selecione a unidade da organização para preencher os dados" msgid "Select available organisation units to include in the template" msgstr "Selecione as unidades organizacionais para incluir na planilha" -msgid "Template" -msgstr "Modelo" - msgid "Data Model" msgstr "Modelo de dados" @@ -1279,6 +1306,15 @@ msgstr "" msgid "Error loading import details" msgstr "" +msgid "Only .xlsx and .xlsm template files are accepted" +msgstr "" + +msgid "Regenerating metadata..." +msgstr "" + +msgid "Metadata regenerated" +msgstr "" + msgid "Error deleting data values" msgstr "Erro ao excluir valores de dados" @@ -1408,9 +1444,9 @@ msgid "" "Samaritan’s Purse, Medecins Sans Frontières (MSF), the the Norwegian Refugee " "Council (NRC) and the Clinton Health Access Initiative (CHAI) to support " "countries in strengthening the collection and use of health data by using " -"DHIS2. The application has been developed by [EyeSeeTea SL](http://" -"eyeseetea.com). Source code, documentation and release notes can be found at " -"the [EyeSeetea GitHub Project Page](https://eyeseetea.github.io/Bulk-Load-" +"DHIS2. The application has been developed by [EyeSeeTea SL](http://eyeseetea." +"com). Source code, documentation and release notes can be found at the " +"[EyeSeetea GitHub Project Page](https://eyeseetea.github.io/Bulk-Load-" "blessed/)." msgstr "" diff --git a/i18n/ru.po b/i18n/ru.po index 059cf422..a9838372 100644 --- a/i18n/ru.po +++ b/i18n/ru.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Bulk Load\n" -"POT-Creation-Date: 2026-06-30T17:31:02.698Z\n" +"POT-Creation-Date: 2026-07-29T11:52:52.301Z\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -138,6 +138,11 @@ msgstr "" msgid "Found in {{locations}} of the Excel file" msgstr "" +msgid "" +"Could not identify this template. Check the file is a Bulk Load template and " +"that your session is still active." +msgstr "" + msgid "Cannot read data form id" msgstr "Невозможно прочитать данные формы id" @@ -419,6 +424,80 @@ msgstr "Программа" msgid "Items older than {{period}}" msgstr "" +msgid "File cleanup completed successfully" +msgstr "" + +msgid "An error occurred during file cleanup" +msgstr "" + +msgid "History cleanup completed successfully" +msgstr "" + +msgid "An error occurred during history cleanup" +msgstr "" + +msgid "Regenerate template metadata" +msgstr "" + +msgid "Maintenance" +msgstr "" + +msgid "File cleanup" +msgstr "" + +msgid "" +"Remove files older than the selected period. History entries will be kept " +"but the files will be unaccessible" +msgstr "" + +msgid "Cleaning up files..." +msgstr "" + +msgid "Confirm File Cleanup" +msgstr "" + +msgid "" +"Are you sure you want to remove all files older than the selected period? " +"This action cannot be undone. History entries will be kept but the files " +"will be inaccessible." +msgstr "" + +msgid "Remove files older than" +msgstr "" + +msgid "Clean up files" +msgstr "" + +msgid "History cleanup" +msgstr "" + +msgid "" +"Remove history entries and their documents older than the selected period. " +"This action cannot be undone" +msgstr "" + +msgid "Cleaning up history..." +msgstr "" + +msgid "Confirm History Cleanup" +msgstr "" + +msgid "" +"Are you sure you want to remove all history entries and their documents " +"older than the selected period? This action cannot be undone." +msgstr "" + +msgid "Remove history entries older than" +msgstr "" + +msgid "Clean up history" +msgstr "" + +msgid "" +"Refresh the Metadata sheet of an existing template file with current DHIS2 " +"metadata" +msgstr "" + msgid "Access to Template Generation" msgstr "Доступ к генерации шаблонов" @@ -458,19 +537,32 @@ msgstr "Поиск элементов или атрибутов данных" msgid "Populate events for every Tracked Entity Instance (TEI)" msgstr "" -msgid "File cleanup completed successfully" +msgid "Regenerate" msgstr "" #, fuzzy -msgid "An error occurred during file cleanup" -msgstr "Возникла ошибка при сохранении темы" +msgid "Drag and drop the template file to regenerate" +msgstr "Перетащите файл логотипа" -msgid "History cleanup completed successfully" +msgid "Template" +msgstr "Шаблон" + +msgid "Data Form Type" msgstr "" -#, fuzzy -msgid "An error occurred during history cleanup" -msgstr "Возникла ошибка при сохранении темы" +msgid "Include the Code column in the Metadata sheet" +msgstr "" + +msgid "Use codes instead of names for metadata items" +msgstr "" + +msgid "Use the short name of organisation units" +msgstr "" + +msgid "" +"The Metadata sheet will be re-protected with the standard Bulk Load sheet " +"password. All other sheets and the macros are left untouched." +msgstr "" msgid "Select Organisation Units on template generation" msgstr "Выберите организационные единицы при создании шаблона" @@ -592,65 +684,6 @@ msgstr "" msgid "Permissions" msgstr "Разрешения" -#, fuzzy -msgid "Maintenance" -msgstr "Мигрировать экземпляр" - -msgid "File cleanup" -msgstr "" - -msgid "" -"Remove files older than the selected period. History entries will be kept " -"but the files will be unaccessible" -msgstr "" - -#, fuzzy -msgid "Cleaning up files..." -msgstr "Чтение файла..." - -msgid "Confirm File Cleanup" -msgstr "" - -msgid "" -"Are you sure you want to remove all files older than the selected period? " -"This action cannot be undone. History entries will be kept but the files " -"will be inaccessible." -msgstr "" - -msgid "Remove files older than" -msgstr "" - -#, fuzzy -msgid "Clean up files" -msgstr "Невозможно прочитать файл" - -msgid "History cleanup" -msgstr "" - -msgid "" -"Remove history entries and their documents older than the selected period. " -"This action cannot be undone" -msgstr "" - -#, fuzzy -msgid "Cleaning up history..." -msgstr "Чтение файла..." - -msgid "Confirm History Cleanup" -msgstr "" - -msgid "" -"Are you sure you want to remove all history entries and their documents " -"older than the selected period? This action cannot be undone." -msgstr "" - -msgid "Remove history entries older than" -msgstr "" - -#, fuzzy -msgid "Clean up history" -msgstr "Невозможно прочитать файл" - #, fuzzy msgid "Edit custom template" msgstr "Шаблон" @@ -754,9 +787,6 @@ msgstr "Имя" msgid "Description" msgstr "Описание" -msgid "Data Form Type" -msgstr "" - msgid "Type" msgstr "Тип" @@ -1122,9 +1152,6 @@ msgstr "Выберите организационную единицу для з msgid "Select available organisation units to include in the template" msgstr "Выберите доступные подразделения организации для включения в шаблон" -msgid "Template" -msgstr "Шаблон" - msgid "Data Model" msgstr "Модель данных" @@ -1286,6 +1313,15 @@ msgstr "" msgid "Error loading import details" msgstr "" +msgid "Only .xlsx and .xlsm template files are accepted" +msgstr "" + +msgid "Regenerating metadata..." +msgstr "" + +msgid "Metadata regenerated" +msgstr "" + msgid "Error deleting data values" msgstr "Ошибка при удалении значений данных" @@ -1415,9 +1451,9 @@ msgid "" "Samaritan’s Purse, Medecins Sans Frontières (MSF), the the Norwegian Refugee " "Council (NRC) and the Clinton Health Access Initiative (CHAI) to support " "countries in strengthening the collection and use of health data by using " -"DHIS2. The application has been developed by [EyeSeeTea SL](http://" -"eyeseetea.com). Source code, documentation and release notes can be found at " -"the [EyeSeetea GitHub Project Page](https://eyeseetea.github.io/Bulk-Load-" +"DHIS2. The application has been developed by [EyeSeeTea SL](http://eyeseetea." +"com). Source code, documentation and release notes can be found at the " +"[EyeSeetea GitHub Project Page](https://eyeseetea.github.io/Bulk-Load-" "blessed/)." msgstr "" diff --git a/package.json b/package.json index 5fd865b3..98afe670 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@dhis2/d2-ui-core": "7.3.3", "@dhis2/ui-core": "6.24.0", "@dhis2/ui-widgets": "6.24.0", - "@eyeseetea/d2-api": "1.20.0", + "@eyeseetea/d2-api": "1.21.0", "@eyeseetea/d2-ui-components": "2.12.0", "@eyeseetea/feedback-component": "0.3.0", "@eyeseetea/xlsx-populate": "4.3.2-beta.1", @@ -55,6 +55,7 @@ "build-folder": "rm -rf build/ && d2-manifest package.json manifest.webapp && tsc && vite build --outDir=build && yarn run manifest && cp -r i18n icon.png build", "build": "VITE_DHIS2_BASE_URL='' VITE_DHIS2_AUTH='' yarn build-folder && rm -f $npm_package_name.zip && cd build && zip --quiet -r ../$npm_package_name.zip *", "test": "yarn test-unit", + "regenerate-metadata": "ts-node --transpile-only --project src/scripts/tsconfig.json src/scripts/regenerate-metadata.ts", "test-unit": "vitest run --reporter=basic", "test-unit-watch": "vitest watch", "lint": "eslint src --ext .js,.jsx,.ts,.tsx", diff --git a/src/CompositionRoot.ts b/src/CompositionRoot.ts index 3f073713..b69ea572 100644 --- a/src/CompositionRoot.ts +++ b/src/CompositionRoot.ts @@ -20,6 +20,8 @@ import { ConvertDataPackageUseCase } from "./domain/usecases/ConvertDataPackageU import { DeleteCustomTemplateUseCase } from "./domain/usecases/DeleteCustomTemplateUseCase"; import { DeleteThemeUseCase } from "./domain/usecases/DeleteThemeUseCase"; import { DownloadTemplateUseCase } from "./domain/usecases/DownloadTemplateUseCase"; +import { RegenerateTemplateMetadataUseCase } from "./domain/usecases/RegenerateTemplateMetadataUseCase"; +import { ResolveTemplateFromFileUseCase } from "./domain/usecases/ResolveTemplateFromFileUseCase"; import { GetCustomTemplatesUseCase } from "./domain/usecases/GetCustomTemplatesUseCase"; import { GetDataFormsForGenerationUseCase } from "./domain/usecases/GetDataFormsForGenerationUseCase"; import { GetDataFormsUseCase } from "./domain/usecases/GetDataFormsUseCase"; @@ -125,6 +127,8 @@ export function getCompositionRoot({ appConfig, dhisInstance, mockApi, importSou documentRepository, dataElementDisaggregationsMappingRepository ), + regenerateMetadata: new RegenerateTemplateMetadataUseCase(), + resolveFromFile: new ResolveTemplateFromFileUseCase(instance, templateManager, excelReader), list: new ListDataFormsUseCase(instance), getDataFormsForGeneration: new GetDataFormsForGenerationUseCase(instance), get: new GetDataFormsUseCase(instance), diff --git a/src/data/TemplateWebRepository.ts b/src/data/TemplateWebRepository.ts index 5da334cc..a1ac3dcd 100644 --- a/src/data/TemplateWebRepository.ts +++ b/src/data/TemplateWebRepository.ts @@ -41,6 +41,7 @@ export class TemplateWebRepository implements TemplateRepository { : {}), ...(importCustomization ? { importCustomization: importCustomization.bind(customTemplate) } : {}), generateMetadata: customTemplate.generateMetadata ?? false, + includeMetadataCodes: customTemplate.includeMetadataCodes ?? false, } : template; }); diff --git a/src/data/templates/nrc/MSFModuleMetadataD2Repository.ts b/src/data/templates/nrc/MSFModuleMetadataD2Repository.ts index 6c34a78e..87580e00 100644 --- a/src/data/templates/nrc/MSFModuleMetadataD2Repository.ts +++ b/src/data/templates/nrc/MSFModuleMetadataD2Repository.ts @@ -1,3 +1,4 @@ +import _ from "lodash"; import { D2Api, MetadataPick } from "../../../types/d2-api"; import { i18nShortCode, Id } from "../../../domain/entities/ReferenceObject"; import { CategoryOptionCombo, MSFModuleMetadata } from "../../../domain/entities/templates/MSFModuleMetadata"; diff --git a/src/domain/entities/Template.ts b/src/domain/entities/Template.ts index 6c898ed7..a6943719 100644 --- a/src/domain/entities/Template.ts +++ b/src/domain/entities/Template.ts @@ -68,6 +68,7 @@ export interface GeneratedTemplate extends BaseTemplate { type: "generated"; rowOffset: number; generateMetadata?: boolean; + includeMetadataCodes?: boolean; } export interface DownloadCustomizationOptions { @@ -87,6 +88,7 @@ export interface ImportCustomizationOptions { export interface CustomTemplateWithUrl extends BaseTemplate { type: "custom"; generateMetadata?: boolean; + includeMetadataCodes?: boolean; url: string; description: string; fixedOrgUnit?: CellRef; diff --git a/src/domain/usecases/AnalyzeTemplateUseCase.ts b/src/domain/usecases/AnalyzeTemplateUseCase.ts index 5f565f47..1bc3eca8 100644 --- a/src/domain/usecases/AnalyzeTemplateUseCase.ts +++ b/src/domain/usecases/AnalyzeTemplateUseCase.ts @@ -1,11 +1,11 @@ import { UseCase } from "../../CompositionRoot"; -import i18n from "../../utils/i18n"; import { getExcelOrThrow } from "../../utils/files"; import { ExcelReader } from "../helpers/ExcelReader"; import { ExcelRepository } from "../repositories/ExcelRepository"; import { InstanceRepository } from "../repositories/InstanceRepository"; import { TemplateRepository } from "../repositories/TemplateRepository"; import { DataElementDisaggregationsMappingRepository } from "../repositories/DataElementDisaggregationsMappingRepository"; +import { getDataFormFromTemplate, loadTemplateFromExcel } from "./utils/templates"; export class AnalyzeTemplateUseCase implements UseCase { constructor( @@ -18,24 +18,18 @@ export class AnalyzeTemplateUseCase implements UseCase { public async execute(file: File) { const excelFile = await getExcelOrThrow(file); - const templateId = await this.excelRepository.loadTemplate({ type: "file", file: excelFile }); - const template = await this.templateRepository.getTemplate(templateId); - - const dataFormId1 = await this.excelRepository.readCell(templateId, template.dataFormId, { - formula: true, - }); - const dataFormId2 = await this.excelRepository.readCell(templateId, template.dataFormId); - const dataFormId = dataFormId1 || dataFormId2; - - if (!dataFormId || typeof dataFormId !== "string") { - throw new Error(i18n.t("Cannot read data form id")); - } - - const [dataForm] = await this.instanceRepository.getDataForms({ - ids: [cleanFormula(dataFormId)], - }); + const { templateId, template } = await loadTemplateFromExcel( + this.templateRepository, + this.excelRepository, + excelFile + ); - if (!dataForm) throw new Error(i18n.t("Program or DataSet not found in instance")); + const dataForm = await getDataFormFromTemplate( + this.instanceRepository, + this.excelRepository, + templateId, + template + ); const orgUnits = await this.instanceRepository.getDataFormOrgUnits(dataForm.type, dataForm.id); @@ -59,5 +53,3 @@ export class AnalyzeTemplateUseCase implements UseCase { return { custom: true, dataForm, dataValues, orgUnits, file }; } } - -const cleanFormula = (string: string) => (string.startsWith("_") ? string.substr(1) : string); diff --git a/src/domain/usecases/DownloadTemplateUseCase.ts b/src/domain/usecases/DownloadTemplateUseCase.ts index 82a9fcb9..1df3d54c 100644 --- a/src/domain/usecases/DownloadTemplateUseCase.ts +++ b/src/domain/usecases/DownloadTemplateUseCase.ts @@ -135,6 +135,7 @@ export class DownloadTemplateUseCase implements UseCase { useCodesForMetadata, orgUnitShortName: useShortNameInOrgUnit, maxTeiRows, + includeMetadataCodes: template.includeMetadataCodes ?? false, }); const workbook = await sheetBuilder.generate(); @@ -263,7 +264,7 @@ export class DownloadTemplateUseCase implements UseCase { } } -async function getElement(api: D2Api, type: DataFormType, id: string) { +export async function getElement(api: D2Api, type: DataFormType, id: string) { const endpoint = type === dataFormTypeMap.dataSets ? "dataSets" : "programs"; const fields = [ "id", @@ -287,7 +288,7 @@ async function getElement(api: D2Api, type: DataFormType, id: string) { return { ...response, type }; } -async function getElementMetadata({ +export async function getElementMetadata({ element, api, orgUnitIds, diff --git a/src/domain/usecases/RegenerateTemplateMetadataUseCase.ts b/src/domain/usecases/RegenerateTemplateMetadataUseCase.ts new file mode 100644 index 00000000..1a582562 --- /dev/null +++ b/src/domain/usecases/RegenerateTemplateMetadataUseCase.ts @@ -0,0 +1,77 @@ +import { UseCase } from "../../CompositionRoot"; +import { D2Api } from "../../types/d2-api"; +// TODO: Settings and SheetBuilder live in webapp/logic, so this use case breaks the +// domain → presentation dependency rule. Moving them to domain requires a wider +// refactor (shared by other use cases) and is tracked as its own task. +import Settings from "../../webapp/logic/settings"; +import { SheetBuilder } from "../../webapp/logic/sheetBuilder"; +import { DataFormType } from "../entities/DataForm"; +import { GeneratedTemplate } from "../entities/Template"; +import { getElement, getElementMetadata } from "./DownloadTemplateUseCase"; + +export type RegenerateTemplateMetadataOptions = { + type: DataFormType; + id: string; + /** base64-encoded input template */ + fileContents: string; + settings: Settings; + language: string; + includeMetadataCodes: boolean; + useCodesForMetadata: boolean; + orgUnitShortName: boolean; +}; + +/** Regenerates only the Metadata sheet of a template, leaving all other sheets untouched. */ +export class RegenerateTemplateMetadataUseCase implements UseCase { + public async execute(api: D2Api, options: RegenerateTemplateMetadataOptions): Promise { + const { + type, + id, + fileContents, + settings, + language, + includeMetadataCodes, + useCodesForMetadata, + orgUnitShortName, + } = options; + + const element = await getElement(api, type, id); + const orgUnitIds = element.organisationUnits.map((orgUnit: { id: string }) => orgUnit.id); + + const result = await getElementMetadata({ + api, + element, + downloadRelationships: false, + orgUnitIds, + startDate: undefined, + endDate: undefined, + orgUnitShortName, + }); + + // Minimal template: generateMetadataOnly only needs type/id, not the form sheets. + const template: GeneratedTemplate = { + type: "generated", + id: "regenerate-metadata", + name: "Regenerate metadata", + rowOffset: 0, + styleSources: [], + dataFormId: { type: "value", id }, + dataFormType: { type: "value", id: type }, + }; + + const sheetBuilder = new SheetBuilder({ + ...result, + language, + template, + settings, + downloadRelationships: false, + splitDataEntryTabsBySection: false, + useCodesForMetadata, + orgUnitShortName, + includeMetadataCodes, + }); + + const workbook = await sheetBuilder.generateMetadataOnly(fileContents); + return workbook.writeToBase64(); + } +} diff --git a/src/domain/usecases/ResolveTemplateFromFileUseCase.ts b/src/domain/usecases/ResolveTemplateFromFileUseCase.ts new file mode 100644 index 00000000..dafc4863 --- /dev/null +++ b/src/domain/usecases/ResolveTemplateFromFileUseCase.ts @@ -0,0 +1,49 @@ +import { UseCase } from "../../CompositionRoot"; +import i18n from "../../utils/i18n"; +import { getExcelOrThrow } from "../../utils/files"; +import { DataForm } from "../entities/DataForm"; +import { Template } from "../entities/Template"; +import { ExcelRepository } from "../repositories/ExcelRepository"; +import { InstanceRepository } from "../repositories/InstanceRepository"; +import { TemplateRepository } from "../repositories/TemplateRepository"; +import { getDataFormFromTemplate, LoadedTemplate, loadTemplateFromExcel } from "./utils/templates"; + +export type ResolvedTemplate = Readonly<{ + template: Template; + dataForm: DataForm; +}>; + +export class ResolveTemplateFromFileUseCase implements UseCase { + constructor( + private instanceRepository: InstanceRepository, + private templateRepository: TemplateRepository, + private excelRepository: ExcelRepository + ) {} + + public async execute(file: File): Promise { + const excelFile = await getExcelOrThrow(file); + const { templateId, template } = await this.resolveTemplate(excelFile); + + const dataForm = await getDataFormFromTemplate( + this.instanceRepository, + this.excelRepository, + templateId, + template + ); + + return { template, dataForm }; + } + + private async resolveTemplate(excelFile: Blob): Promise { + try { + return await loadTemplateFromExcel(this.templateRepository, this.excelRepository, excelFile); + } catch (error) { + console.error(error); + throw new Error( + i18n.t( + "Could not identify this template. Check the file is a Bulk Load template and that your session is still active." + ) + ); + } + } +} diff --git a/src/domain/usecases/utils/__tests__/templates.spec.ts b/src/domain/usecases/utils/__tests__/templates.spec.ts new file mode 100644 index 00000000..3082d308 --- /dev/null +++ b/src/domain/usecases/utils/__tests__/templates.spec.ts @@ -0,0 +1,100 @@ +import { vi } from "vitest"; +import { DataForm } from "../../../entities/DataForm"; +import { Template } from "../../../entities/Template"; +import { ExcelRepository } from "../../../repositories/ExcelRepository"; +import { InstanceRepository } from "../../../repositories/InstanceRepository"; +import { TemplateRepository } from "../../../repositories/TemplateRepository"; +import { getDataFormFromTemplate, loadTemplateFromExcel } from "../templates"; + +const TEMPLATE_ID = "PHSM_CUSTOM_TEMPLATE"; +const DATA_FORM_ID = "U6z7eJniNfL"; +const CANNOT_READ_DATA_FORM_ID_MESSAGE = "Cannot read data form id"; +const DATA_FORM_NOT_FOUND_MESSAGE = "Program or DataSet not found in instance"; + +const template = { + id: TEMPLATE_ID, + type: "custom", + name: "PHSM v7", + dataFormId: { type: "cell", sheet: "Data Entry", ref: "A2" }, +} as unknown as Template; + +const dataForm = { + id: DATA_FORM_ID, + type: "programs", + name: "PHSM Program", +} as unknown as DataForm; + +const excelFile = new Blob([""]); + +function buildExcelRepository(overrides: { + readCellFormula?: unknown; + readCellValue?: unknown; + loadTemplate?: () => Promise; +}): ExcelRepository { + return { + loadTemplate: vi.fn(overrides.loadTemplate ?? (async () => TEMPLATE_ID)), + readCell: vi.fn(async (_id: string, _ref: unknown, options?: { formula?: boolean }) => + options?.formula ? overrides.readCellFormula : overrides.readCellValue + ), + } as unknown as ExcelRepository; +} + +function buildTemplateRepository(getTemplate?: () => Promise